Decompose move.go; traps become a handler table (refactor step 7)
The be_trapped switch becomes gameData.trapHandlers with one trap* method per trap kind (mystery messages split in two); moveHero splits into moveTarget/moveResolve/moveEnter/moveOnto/offMap; passageTurn gains per-axis passageTurnVertical/Horizontal. move.go is complexity-clean. Behavior and RNG call order unchanged.
This commit is contained in:
304
game/move.go
304
game/move.go
@@ -38,21 +38,102 @@ func (g *RogueGame) moveHero(dy, dx int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
ch, fl, stop := g.moveTarget(nh)
|
||||||
|
if stop {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
turned, ndy, ndx := g.moveResolve(nh, ch, fl, dy, dx)
|
||||||
|
if !turned {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// the C goto over: re-check the turned move
|
||||||
|
dy, dx = ndy, ndx
|
||||||
|
|
||||||
|
g.turnRefresh()
|
||||||
|
|
||||||
|
nh = Coord{Y: p.Pos.Y + dy, X: p.Pos.X + dx}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveResolve acts on the square the hero stepped at: a wall may turn a
|
||||||
|
// passage runner (reported with the new deltas); anything else completes
|
||||||
|
// or refuses the move (the switch of move.c do_move).
|
||||||
|
func (g *RogueGame) moveResolve(nh Coord, ch byte, fl PlaceFlags, dy, dx int) (bool, int, int) {
|
||||||
|
switch ch {
|
||||||
|
case ' ', '|', '-':
|
||||||
|
if turn, ndy, ndx := g.passageTurn(dy, dx); turn {
|
||||||
|
return true, ndy, ndx
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Running = false
|
||||||
|
g.After = false
|
||||||
|
default:
|
||||||
|
g.moveEnter(nh, fl, ch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveEnter completes a step onto a walkable square: doors, traps,
|
||||||
|
// passages, floor, and things (the entry arms of the move.c do_move
|
||||||
|
// switch).
|
||||||
|
func (g *RogueGame) moveEnter(nh Coord, fl PlaceFlags, ch byte) {
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
|
switch ch {
|
||||||
|
case Door:
|
||||||
|
g.Running = false
|
||||||
|
if g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Has(FPassage) {
|
||||||
|
g.enterRoom(nh)
|
||||||
|
}
|
||||||
|
case Trap:
|
||||||
|
tr := g.springTrap(nh)
|
||||||
|
if tr == TrapDoor || tr == TrapTeleport {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case Passage:
|
||||||
|
// when you're in a corridor, you don't know if you're in a maze
|
||||||
|
// room or not, and there ain't no way to find out if you're
|
||||||
|
// leaving a maze room, so it is necessary to always recalculate
|
||||||
|
// proom.
|
||||||
|
p.Room = g.roomIn(p.Pos)
|
||||||
|
case Floor:
|
||||||
|
if !fl.Has(FReal) {
|
||||||
|
g.springTrap(p.Pos)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
g.moveOnto(nh, fl, ch)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
g.finishMove(nh, fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// offMap reports coordinates outside the walkable map (move.c do_move).
|
||||||
|
func offMap(nh Coord) bool {
|
||||||
|
return nh.X < 0 || nh.X >= NumCols || nh.Y <= 0 || nh.Y >= NumLines-1
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveTarget inspects the square the hero is stepping onto: bounds and
|
||||||
|
// diagonal legality, hidden traps underfoot, and being held. stop means
|
||||||
|
// the move is refused (the checks of move.c do_move).
|
||||||
|
func (g *RogueGame) moveTarget(nh Coord) (byte, PlaceFlags, bool) {
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
// Check if he tried to move off the screen or make an illegal
|
// Check if he tried to move off the screen or make an illegal
|
||||||
// diagonal move, and stop him if he did.
|
// diagonal move, and stop him if he did.
|
||||||
hitBound := nh.X < 0 || nh.X >= NumCols || nh.Y <= 0 || nh.Y >= NumLines-1
|
if offMap(nh) {
|
||||||
|
return ' ', 0, false // fall into the wall case
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
ch byte
|
|
||||||
fl PlaceFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
if !hitBound {
|
|
||||||
if !g.diagOk(p.Pos, nh) {
|
if !g.diagOk(p.Pos, nh) {
|
||||||
g.After = false
|
g.After = false
|
||||||
g.Running = false
|
g.Running = false
|
||||||
|
|
||||||
return
|
return 0, 0, true
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.Running && p.Pos == nh {
|
if g.Running && p.Pos == nh {
|
||||||
@@ -60,9 +141,9 @@ func (g *RogueGame) moveHero(dy, dx int) {
|
|||||||
g.Running = false
|
g.Running = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fl = *g.Level.FlagsAt(nh.Y, nh.X)
|
fl := *g.Level.FlagsAt(nh.Y, nh.X)
|
||||||
|
|
||||||
ch = g.Level.VisibleChar(nh.Y, nh.X)
|
ch := g.Level.VisibleChar(nh.Y, nh.X)
|
||||||
if !fl.Has(FReal) && ch == Floor {
|
if !fl.Has(FReal) && ch == Floor {
|
||||||
if !p.On(Levitating) {
|
if !p.On(Levitating) {
|
||||||
ch = Trap
|
ch = Trap
|
||||||
@@ -72,56 +153,16 @@ func (g *RogueGame) moveHero(dy, dx int) {
|
|||||||
} else if p.On(Held) && ch != 'F' {
|
} else if p.On(Held) && ch != 'F' {
|
||||||
g.msg("you are being held")
|
g.msg("you are being held")
|
||||||
|
|
||||||
return
|
return 0, 0, true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if hitBound {
|
return ch, fl, false
|
||||||
ch = ' ' // fall into the wall case below
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ch {
|
// moveOnto handles stepping at a monster or onto an item (the default
|
||||||
case ' ', '|', '-':
|
// arm of the move.c do_move switch).
|
||||||
if turn, ndy, ndx := g.passageTurn(dy, dx); turn {
|
func (g *RogueGame) moveOnto(nh Coord, fl PlaceFlags, ch byte) {
|
||||||
dy, dx = ndy, ndx
|
p := &g.Player
|
||||||
|
|
||||||
g.turnRefresh()
|
|
||||||
|
|
||||||
nh = Coord{Y: p.Pos.Y + dy, X: p.Pos.X + dx}
|
|
||||||
|
|
||||||
continue // the C goto over: re-check the turned move
|
|
||||||
}
|
|
||||||
|
|
||||||
g.Running = false
|
|
||||||
g.After = false
|
|
||||||
case Door:
|
|
||||||
g.Running = false
|
|
||||||
if g.Level.FlagsAt(p.Pos.Y, p.Pos.X).Has(FPassage) {
|
|
||||||
g.enterRoom(nh)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.finishMove(nh, fl)
|
|
||||||
case Trap:
|
|
||||||
tr := g.springTrap(nh)
|
|
||||||
if tr == TrapDoor || tr == TrapTeleport {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
g.finishMove(nh, fl)
|
|
||||||
case Passage:
|
|
||||||
// when you're in a corridor, you don't know if you're in a maze
|
|
||||||
// room or not, and there ain't no way to find out if you're
|
|
||||||
// leaving a maze room, so it is necessary to always recalculate
|
|
||||||
// proom.
|
|
||||||
p.Room = g.roomIn(p.Pos)
|
|
||||||
g.finishMove(nh, fl)
|
|
||||||
case Floor:
|
|
||||||
if !fl.Has(FReal) {
|
|
||||||
g.springTrap(p.Pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.finishMove(nh, fl)
|
|
||||||
default:
|
|
||||||
if ch == Stairs {
|
if ch == Stairs {
|
||||||
g.SeenStairs = true
|
g.SeenStairs = true
|
||||||
}
|
}
|
||||||
@@ -138,10 +179,6 @@ func (g *RogueGame) moveHero(dy, dx int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// passageTurn checks whether a runner in a gone-room passage should turn
|
// passageTurn checks whether a runner in a gone-room passage should turn
|
||||||
// the corner instead of stopping at a wall (the PASSGO block of move.c
|
// the corner instead of stopping at a wall (the PASSGO block of move.c
|
||||||
// do_move). It reports whether to turn and the new deltas, updating RunCh.
|
// do_move). It reports whether to turn and the new deltas, updating RunCh.
|
||||||
@@ -152,44 +189,66 @@ func (g *RogueGame) passageTurn(dy, dx int) (bool, int, int) {
|
|||||||
return false, dy, dx
|
return false, dy, dx
|
||||||
}
|
}
|
||||||
|
|
||||||
var b1, b2 bool
|
|
||||||
|
|
||||||
switch g.RunCh {
|
switch g.RunCh {
|
||||||
case 'h', 'l':
|
case 'h', 'l':
|
||||||
b1 = p.Pos.Y != 1 && g.turnOk(p.Pos.Y-1, p.Pos.X)
|
if turn, ndy := g.passageTurnVertical(); turn {
|
||||||
|
return true, ndy, 0
|
||||||
b2 = p.Pos.Y != NumLines-2 && g.turnOk(p.Pos.Y+1, p.Pos.X)
|
|
||||||
if b1 != b2 {
|
|
||||||
if b1 {
|
|
||||||
g.RunCh = 'k'
|
|
||||||
dy = -1
|
|
||||||
} else {
|
|
||||||
g.RunCh = 'j'
|
|
||||||
dy = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, dy, 0
|
|
||||||
}
|
}
|
||||||
case 'j', 'k':
|
case 'j', 'k':
|
||||||
b1 = p.Pos.X != 0 && g.turnOk(p.Pos.Y, p.Pos.X-1)
|
if turn, ndx := g.passageTurnHorizontal(); turn {
|
||||||
|
return true, 0, ndx
|
||||||
b2 = p.Pos.X != NumCols-1 && g.turnOk(p.Pos.Y, p.Pos.X+1)
|
|
||||||
if b1 != b2 {
|
|
||||||
if b1 {
|
|
||||||
g.RunCh = 'h'
|
|
||||||
dx = -1
|
|
||||||
} else {
|
|
||||||
g.RunCh = 'l'
|
|
||||||
dx = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, 0, dx
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, dy, dx
|
return false, dy, dx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// passageTurnVertical decides whether a horizontal runner turns up or
|
||||||
|
// down at a corner (move.c do_move).
|
||||||
|
func (g *RogueGame) passageTurnVertical() (bool, int) {
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
|
b1 := p.Pos.Y != 1 && g.turnOk(p.Pos.Y-1, p.Pos.X)
|
||||||
|
|
||||||
|
b2 := p.Pos.Y != NumLines-2 && g.turnOk(p.Pos.Y+1, p.Pos.X)
|
||||||
|
if b1 == b2 {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b1 {
|
||||||
|
g.RunCh = 'k'
|
||||||
|
|
||||||
|
return true, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
g.RunCh = 'j'
|
||||||
|
|
||||||
|
return true, 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// passageTurnHorizontal decides whether a vertical runner turns left or
|
||||||
|
// right at a corner (move.c do_move).
|
||||||
|
func (g *RogueGame) passageTurnHorizontal() (bool, int) {
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
|
b1 := p.Pos.X != 0 && g.turnOk(p.Pos.Y, p.Pos.X-1)
|
||||||
|
|
||||||
|
b2 := p.Pos.X != NumCols-1 && g.turnOk(p.Pos.Y, p.Pos.X+1)
|
||||||
|
if b1 == b2 {
|
||||||
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if b1 {
|
||||||
|
g.RunCh = 'h'
|
||||||
|
|
||||||
|
return true, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
g.RunCh = 'l'
|
||||||
|
|
||||||
|
return true, 1
|
||||||
|
}
|
||||||
|
|
||||||
// finishMove is the move_stuff label in do_move: complete the step.
|
// finishMove is the move_stuff label in do_move: complete the step.
|
||||||
func (g *RogueGame) finishMove(nh Coord, fl PlaceFlags) {
|
func (g *RogueGame) finishMove(nh Coord, fl PlaceFlags) {
|
||||||
p := &g.Player
|
p := &g.Player
|
||||||
@@ -255,16 +314,32 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind {
|
|||||||
tr := TrapKind(pp.Flags & FTrapMask)
|
tr := TrapKind(pp.Flags & FTrapMask)
|
||||||
pp.Flags.Set(FSeen)
|
pp.Flags.Set(FSeen)
|
||||||
|
|
||||||
switch tr {
|
if h := g.data.trapHandlers[tr]; h != nil {
|
||||||
case TrapDoor:
|
h(g, tc)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.flushType()
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// The per-trap effect handlers, dispatched through
|
||||||
|
// gameData.trapHandlers. Each is one case of the C be_trapped switch.
|
||||||
|
|
||||||
|
func (g *RogueGame) trapFall(Coord) {
|
||||||
g.Depth++
|
g.Depth++
|
||||||
g.NewLevel()
|
g.NewLevel()
|
||||||
g.msg("you fell into a trap!")
|
g.msg("you fell into a trap!")
|
||||||
case TrapBear:
|
}
|
||||||
|
|
||||||
|
func (g *RogueGame) trapBear(Coord) {
|
||||||
g.NoMove += g.spread(3) // BEARTIME
|
g.NoMove += g.spread(3) // BEARTIME
|
||||||
g.msg("you are caught in a bear trap")
|
g.msg("you are caught in a bear trap")
|
||||||
case TrapMystery:
|
}
|
||||||
switch g.rnd(11) {
|
|
||||||
|
func (g *RogueGame) trapMystery(Coord) {
|
||||||
|
which := g.rnd(11)
|
||||||
|
switch which {
|
||||||
case 0:
|
case 0:
|
||||||
g.msg("you are suddenly in a parallel dimension")
|
g.msg("you are suddenly in a parallel dimension")
|
||||||
case 1:
|
case 1:
|
||||||
@@ -277,6 +352,14 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind {
|
|||||||
g.msg("a %s light flashes in your eyes", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
g.msg("a %s light flashes in your eyes", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
||||||
case 5:
|
case 5:
|
||||||
g.msg("a spike shoots past your ear!")
|
g.msg("a spike shoots past your ear!")
|
||||||
|
default:
|
||||||
|
g.trapMysteryMore(which)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// trapMysteryMore holds the back half of the mystery-trap messages.
|
||||||
|
func (g *RogueGame) trapMysteryMore(which int) {
|
||||||
|
switch which {
|
||||||
case 6:
|
case 6:
|
||||||
g.msg("%s sparks dance across your armor", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
g.msg("%s sparks dance across your armor", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
||||||
case 7:
|
case 7:
|
||||||
@@ -288,12 +371,17 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind {
|
|||||||
case 10:
|
case 10:
|
||||||
g.msg("you pack turns %s!", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
g.msg("you pack turns %s!", g.data.rainbow[g.rnd(len(g.data.rainbow))])
|
||||||
}
|
}
|
||||||
case TrapSleep:
|
}
|
||||||
|
|
||||||
|
func (g *RogueGame) trapSleep(Coord) {
|
||||||
g.NoCommand += g.spread(5) // SLEEPTIME
|
g.NoCommand += g.spread(5) // SLEEPTIME
|
||||||
|
|
||||||
p.Flags.Clear(Awake)
|
g.Player.Flags.Clear(Awake)
|
||||||
g.msg("a strange white mist envelops you and you fall asleep")
|
g.msg("a strange white mist envelops you and you fall asleep")
|
||||||
case TrapArrow:
|
}
|
||||||
|
|
||||||
|
func (g *RogueGame) trapArrow(Coord) {
|
||||||
|
p := &g.Player
|
||||||
if g.swing(p.Stats.Lvl-1, p.Stats.ArmorClass, 1) {
|
if g.swing(p.Stats.Lvl-1, p.Stats.ArmorClass, 1) {
|
||||||
p.Stats.HP -= g.roll(1, 6)
|
p.Stats.HP -= g.roll(1, 6)
|
||||||
if p.Stats.HP <= 0 {
|
if p.Stats.HP <= 0 {
|
||||||
@@ -310,15 +398,23 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind {
|
|||||||
g.fall(arrow, false)
|
g.fall(arrow, false)
|
||||||
g.msg("an arrow shoots past you")
|
g.msg("an arrow shoots past you")
|
||||||
}
|
}
|
||||||
case TrapTeleport:
|
}
|
||||||
|
|
||||||
|
func (g *RogueGame) trapTeleport(tc Coord) {
|
||||||
// since the hero's leaving, look() won't put a TRAP down for us,
|
// since the hero's leaving, look() won't put a TRAP down for us,
|
||||||
// so we have to do it ourself
|
// so we have to do it ourself
|
||||||
g.teleport()
|
g.teleport()
|
||||||
g.mvaddch(tc.Y, tc.X, Trap)
|
g.mvaddch(tc.Y, tc.X, Trap)
|
||||||
case TrapDart:
|
}
|
||||||
|
|
||||||
|
func (g *RogueGame) trapDart(Coord) {
|
||||||
|
p := &g.Player
|
||||||
if !g.swing(p.Stats.Lvl+1, p.Stats.ArmorClass, 1) {
|
if !g.swing(p.Stats.Lvl+1, p.Stats.ArmorClass, 1) {
|
||||||
g.msg("a small dart whizzes by your ear and vanishes")
|
g.msg("a small dart whizzes by your ear and vanishes")
|
||||||
} else {
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p.Stats.HP -= g.roll(1, 4)
|
p.Stats.HP -= g.roll(1, 4)
|
||||||
if p.Stats.HP <= 0 {
|
if p.Stats.HP <= 0 {
|
||||||
g.msg("a poisoned dart killed you")
|
g.msg("a poisoned dart killed you")
|
||||||
@@ -331,14 +427,10 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind {
|
|||||||
|
|
||||||
g.msg("a small dart just hit you in the shoulder")
|
g.msg("a small dart just hit you in the shoulder")
|
||||||
}
|
}
|
||||||
case TrapRust:
|
|
||||||
|
func (g *RogueGame) trapRust(Coord) {
|
||||||
g.msg("a gush of water hits you on the head")
|
g.msg("a gush of water hits you on the head")
|
||||||
g.rustArmor(p.CurArmor)
|
g.rustArmor(g.Player.CurArmor)
|
||||||
}
|
|
||||||
|
|
||||||
g.flushType()
|
|
||||||
|
|
||||||
return tr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomStep moves in a random direction if the monster/person is
|
// randomStep moves in a random direction if the monster/person is
|
||||||
|
|||||||
@@ -147,6 +147,10 @@ type gameData struct {
|
|||||||
// cases of the big command.c switch. Keys that re-dispatch (runs,
|
// cases of the big command.c switch. Keys that re-dispatch (runs,
|
||||||
// fight, repeat, move-on) and wizard keys stay in dispatchKey.
|
// fight, repeat, move-on) and wizard keys stay in dispatchKey.
|
||||||
commandHandlers map[byte]func(g *RogueGame)
|
commandHandlers map[byte]func(g *RogueGame)
|
||||||
|
|
||||||
|
// trapHandlers dispatches each sprung trap to its effect method:
|
||||||
|
// the cases of the move.c be_trapped switch.
|
||||||
|
trapHandlers [NumTrapTypes]func(g *RogueGame, tc Coord)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ripWall is the repeated blank wall line of the tombstone art.
|
// ripWall is the repeated blank wall line of the tombstone art.
|
||||||
@@ -811,6 +815,17 @@ func newGameData() *gameData {
|
|||||||
g.After = false
|
g.After = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
trapHandlers: [NumTrapTypes]func(*RogueGame, Coord){
|
||||||
|
TrapDoor: (*RogueGame).trapFall,
|
||||||
|
TrapArrow: (*RogueGame).trapArrow,
|
||||||
|
TrapSleep: (*RogueGame).trapSleep,
|
||||||
|
TrapBear: (*RogueGame).trapBear,
|
||||||
|
TrapTeleport: (*RogueGame).trapTeleport,
|
||||||
|
TrapDart: (*RogueGame).trapDart,
|
||||||
|
TrapRust: (*RogueGame).trapRust,
|
||||||
|
TrapMystery: (*RogueGame).trapMystery,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user