From ea68df32f06d9eb729a13cc137cae130586ede3c Mon Sep 17 00:00:00 2001 From: sneak Date: Tue, 7 Jul 2026 03:22:54 +0200 Subject: [PATCH] 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. --- game/move.go | 488 +++++++++++++++++++++++++++++-------------------- game/tables.go | 15 ++ 2 files changed, 305 insertions(+), 198 deletions(-) diff --git a/game/move.go b/game/move.go index f59ace6..e1c15cf 100644 --- a/game/move.go +++ b/game/move.go @@ -38,108 +38,145 @@ func (g *RogueGame) moveHero(dy, dx int) { } for { - // Check if he tried to move off the screen or make an illegal - // diagonal move, and stop him if he did. - hitBound := nh.X < 0 || nh.X >= NumCols || nh.Y <= 0 || nh.Y >= NumLines-1 - - var ( - ch byte - fl PlaceFlags - ) - - if !hitBound { - if !g.diagOk(p.Pos, nh) { - g.After = false - g.Running = false - - return - } - - if g.Running && p.Pos == nh { - g.After = false - g.Running = false - } - - fl = *g.Level.FlagsAt(nh.Y, nh.X) - - ch = g.Level.VisibleChar(nh.Y, nh.X) - if !fl.Has(FReal) && ch == Floor { - if !p.On(Levitating) { - ch = Trap - g.Level.SetChar(nh.Y, nh.X, Trap) - g.Level.FlagsAt(nh.Y, nh.X).Set(FReal) - } - } else if p.On(Held) && ch != 'F' { - g.msg("you are being held") - - return - } + ch, fl, stop := g.moveTarget(nh) + if stop { + return } - if hitBound { - ch = ' ' // fall into the wall case below + turned, ndy, ndx := g.moveResolve(nh, ch, fl, dy, dx) + if !turned { + return } - switch ch { - case ' ', '|', '-': - if turn, ndy, ndx := g.passageTurn(dy, dx); turn { - dy, dx = ndy, ndx + // the C goto over: re-check the turned move + dy, dx = ndy, ndx - g.turnRefresh() + g.turnRefresh() - nh = Coord{Y: p.Pos.Y + dy, X: p.Pos.X + dx} + 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 { - g.SeenStairs = true - } - - g.Running = false - if isUpper(ch) || g.Level.MonsterAt(nh.Y, nh.X) != nil { - g.fight(nh, p.CurWeapon, false) - } else { - if ch != Stairs { - g.Take = ch - } - - g.finishMove(nh, fl) - } +// 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 + // diagonal move, and stop him if he did. + if offMap(nh) { + return ' ', 0, false // fall into the wall case + } + + if !g.diagOk(p.Pos, nh) { + g.After = false + g.Running = false + + return 0, 0, true + } + + if g.Running && p.Pos == nh { + g.After = false + g.Running = false + } + + fl := *g.Level.FlagsAt(nh.Y, nh.X) + + ch := g.Level.VisibleChar(nh.Y, nh.X) + if !fl.Has(FReal) && ch == Floor { + if !p.On(Levitating) { + ch = Trap + g.Level.SetChar(nh.Y, nh.X, Trap) + g.Level.FlagsAt(nh.Y, nh.X).Set(FReal) + } + } else if p.On(Held) && ch != 'F' { + g.msg("you are being held") + + return 0, 0, true + } + + return ch, fl, false +} + +// moveOnto handles stepping at a monster or onto an item (the default +// arm of the move.c do_move switch). +func (g *RogueGame) moveOnto(nh Coord, fl PlaceFlags, ch byte) { + p := &g.Player + if ch == Stairs { + g.SeenStairs = true + } + + g.Running = false + if isUpper(ch) || g.Level.MonsterAt(nh.Y, nh.X) != nil { + g.fight(nh, p.CurWeapon, false) + } else { + if ch != Stairs { + g.Take = ch + } + + g.finishMove(nh, fl) + } } // passageTurn checks whether a runner in a gone-room passage should turn @@ -152,44 +189,66 @@ func (g *RogueGame) passageTurn(dy, dx int) (bool, int, int) { return false, dy, dx } - var b1, b2 bool - switch g.RunCh { case 'h', 'l': - 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 { - if b1 { - g.RunCh = 'k' - dy = -1 - } else { - g.RunCh = 'j' - dy = 1 - } - - return true, dy, 0 + if turn, ndy := g.passageTurnVertical(); turn { + return true, ndy, 0 } case 'j', 'k': - 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 { - if b1 { - g.RunCh = 'h' - dx = -1 - } else { - g.RunCh = 'l' - dx = 1 - } - - return true, 0, dx + if turn, ndx := g.passageTurnHorizontal(); turn { + return true, 0, ndx } } 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. func (g *RogueGame) finishMove(nh Coord, fl PlaceFlags) { p := &g.Player @@ -255,85 +314,8 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind { tr := TrapKind(pp.Flags & FTrapMask) pp.Flags.Set(FSeen) - switch tr { - case TrapDoor: - g.Depth++ - g.NewLevel() - g.msg("you fell into a trap!") - case TrapBear: - g.NoMove += g.spread(3) // BEARTIME - g.msg("you are caught in a bear trap") - case TrapMystery: - switch g.rnd(11) { - case 0: - g.msg("you are suddenly in a parallel dimension") - case 1: - g.msg("the light in here suddenly seems %s", g.data.rainbow[g.rnd(len(g.data.rainbow))]) - case 2: - g.msg("you feel a sting in the side of your neck") - case 3: - g.msg("multi-colored lines swirl around you, then fade") - case 4: - g.msg("a %s light flashes in your eyes", g.data.rainbow[g.rnd(len(g.data.rainbow))]) - case 5: - g.msg("a spike shoots past your ear!") - case 6: - g.msg("%s sparks dance across your armor", g.data.rainbow[g.rnd(len(g.data.rainbow))]) - case 7: - g.msg("you suddenly feel very thirsty") - case 8: - g.msg("you feel time speed up suddenly") - case 9: - g.msg("time now seems to be going slower") - case 10: - g.msg("you pack turns %s!", g.data.rainbow[g.rnd(len(g.data.rainbow))]) - } - case TrapSleep: - g.NoCommand += g.spread(5) // SLEEPTIME - - p.Flags.Clear(Awake) - g.msg("a strange white mist envelops you and you fall asleep") - case TrapArrow: - if g.swing(p.Stats.Lvl-1, p.Stats.ArmorClass, 1) { - p.Stats.HP -= g.roll(1, 6) - if p.Stats.HP <= 0 { - g.msg("an arrow killed you") - g.death('a') - } else { - g.msg("oh no! An arrow shot you") - } - } else { - arrow := newObject() - g.initWeapon(arrow, WeaponArrow) - arrow.Count = 1 - arrow.Pos = p.Pos - g.fall(arrow, false) - g.msg("an arrow shoots past you") - } - case TrapTeleport: - // since the hero's leaving, look() won't put a TRAP down for us, - // so we have to do it ourself - g.teleport() - g.mvaddch(tc.Y, tc.X, Trap) - case TrapDart: - if !g.swing(p.Stats.Lvl+1, p.Stats.ArmorClass, 1) { - g.msg("a small dart whizzes by your ear and vanishes") - } else { - p.Stats.HP -= g.roll(1, 4) - if p.Stats.HP <= 0 { - g.msg("a poisoned dart killed you") - g.death('d') - } - - if !p.IsWearing(RingSustainStrength) && !g.save(VsPoison) { - g.changeStrength(-1) - } - - g.msg("a small dart just hit you in the shoulder") - } - case TrapRust: - g.msg("a gush of water hits you on the head") - g.rustArmor(p.CurArmor) + if h := g.data.trapHandlers[tr]; h != nil { + h(g, tc) } g.flushType() @@ -341,6 +323,116 @@ func (g *RogueGame) springTrap(tc Coord) TrapKind { 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.NewLevel() + g.msg("you fell into a trap!") +} + +func (g *RogueGame) trapBear(Coord) { + g.NoMove += g.spread(3) // BEARTIME + g.msg("you are caught in a bear trap") +} + +func (g *RogueGame) trapMystery(Coord) { + which := g.rnd(11) + switch which { + case 0: + g.msg("you are suddenly in a parallel dimension") + case 1: + g.msg("the light in here suddenly seems %s", g.data.rainbow[g.rnd(len(g.data.rainbow))]) + case 2: + g.msg("you feel a sting in the side of your neck") + case 3: + g.msg("multi-colored lines swirl around you, then fade") + case 4: + g.msg("a %s light flashes in your eyes", g.data.rainbow[g.rnd(len(g.data.rainbow))]) + case 5: + 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: + g.msg("%s sparks dance across your armor", g.data.rainbow[g.rnd(len(g.data.rainbow))]) + case 7: + g.msg("you suddenly feel very thirsty") + case 8: + g.msg("you feel time speed up suddenly") + case 9: + g.msg("time now seems to be going slower") + case 10: + g.msg("you pack turns %s!", g.data.rainbow[g.rnd(len(g.data.rainbow))]) + } +} + +func (g *RogueGame) trapSleep(Coord) { + g.NoCommand += g.spread(5) // SLEEPTIME + + g.Player.Flags.Clear(Awake) + g.msg("a strange white mist envelops you and you fall asleep") +} + +func (g *RogueGame) trapArrow(Coord) { + p := &g.Player + if g.swing(p.Stats.Lvl-1, p.Stats.ArmorClass, 1) { + p.Stats.HP -= g.roll(1, 6) + if p.Stats.HP <= 0 { + g.msg("an arrow killed you") + g.death('a') + } else { + g.msg("oh no! An arrow shot you") + } + } else { + arrow := newObject() + g.initWeapon(arrow, WeaponArrow) + arrow.Count = 1 + arrow.Pos = p.Pos + g.fall(arrow, false) + g.msg("an arrow shoots past you") + } +} + +func (g *RogueGame) trapTeleport(tc Coord) { + // since the hero's leaving, look() won't put a TRAP down for us, + // so we have to do it ourself + g.teleport() + g.mvaddch(tc.Y, tc.X, Trap) +} + +func (g *RogueGame) trapDart(Coord) { + p := &g.Player + if !g.swing(p.Stats.Lvl+1, p.Stats.ArmorClass, 1) { + g.msg("a small dart whizzes by your ear and vanishes") + + return + } + + p.Stats.HP -= g.roll(1, 4) + if p.Stats.HP <= 0 { + g.msg("a poisoned dart killed you") + g.death('d') + } + + if !p.IsWearing(RingSustainStrength) && !g.save(VsPoison) { + g.changeStrength(-1) + } + + g.msg("a small dart just hit you in the shoulder") +} + +func (g *RogueGame) trapRust(Coord) { + g.msg("a gush of water hits you on the head") + g.rustArmor(g.Player.CurArmor) +} + // randomStep moves in a random direction if the monster/person is // confused (move.c rndmove). func (g *RogueGame) randomStep(who *Creature) Coord { diff --git a/game/tables.go b/game/tables.go index 05044f4..7455d7e 100644 --- a/game/tables.go +++ b/game/tables.go @@ -147,6 +147,10 @@ type gameData struct { // cases of the big command.c switch. Keys that re-dispatch (runs, // fight, repeat, move-on) and wizard keys stay in dispatchKey. 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. @@ -811,6 +815,17 @@ func newGameData() *gameData { 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, + }, } }