Decompose chase.go (refactor step 7)
chase splits into chaseBestSpot/chaseTry/scareScrollAt with a chaseSearch state struct; chaseStep gains chaseRooms, chaseGoal, dragonBreath/dragonShoots, and chaseTakeObject; runners gains runnerTurn; findDest gains objectClaimed. chase.go is complexity-clean. Behavior and RNG call order unchanged.
This commit is contained in:
293
game/chase.go
293
game/chase.go
@@ -9,17 +9,32 @@ const dragonShot = 5
|
|||||||
func (g *RogueGame) runners(int) {
|
func (g *RogueGame) runners(int) {
|
||||||
list := append([]*Monster(nil), g.Level.Monsters...)
|
list := append([]*Monster(nil), g.Level.Monsters...)
|
||||||
for _, tp := range list {
|
for _, tp := range list {
|
||||||
if !tp.On(Held) && tp.On(Awake) {
|
g.runnerTurn(tp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.HasHit {
|
||||||
|
g.endmsg()
|
||||||
|
g.HasHit = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// runnerTurn gives one monster its motion for the turn; flying monsters
|
||||||
|
// far from the hero move twice (the loop body of chase.c runners).
|
||||||
|
func (g *RogueGame) runnerTurn(tp *Monster) {
|
||||||
|
if tp.On(Held) || !tp.On(Awake) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
origPos := tp.Pos
|
origPos := tp.Pos
|
||||||
|
|
||||||
wastarget := tp.On(Targeted)
|
wastarget := tp.On(Targeted)
|
||||||
if removed := g.moveMonster(tp); removed {
|
if removed := g.moveMonster(tp); removed {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if tp.On(Flying) && distCp(g.Player.Pos, tp.Pos) >= 3 {
|
if tp.On(Flying) && distCp(g.Player.Pos, tp.Pos) >= 3 {
|
||||||
if removed := g.moveMonster(tp); removed {
|
if removed := g.moveMonster(tp); removed {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,13 +44,6 @@ func (g *RogueGame) runners(int) {
|
|||||||
g.ToDeath = false
|
g.ToDeath = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if g.HasHit {
|
|
||||||
g.endmsg()
|
|
||||||
g.HasHit = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// moveMonster executes a single turn of running for a monster (chase.c
|
// moveMonster executes a single turn of running for a monster (chase.c
|
||||||
// move_monst). The result reports that the monster died or left the
|
// move_monst). The result reports that the monster died or left the
|
||||||
@@ -87,13 +95,48 @@ func (g *RogueGame) relocate(th *Monster, newLoc Coord) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// chaseStep makes one thing chase another (chase.c do_chase). removed
|
// chaseStep makes one thing chase another (chase.c do_chase). The
|
||||||
// reports that the chaser died or left the level in the attempt (the C
|
// result reports that the chaser died or left the level in the attempt
|
||||||
// -1 return).
|
// (the C -1 return).
|
||||||
func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
|
func (g *RogueGame) chaseStep(th *Monster) bool {
|
||||||
p := &g.Player
|
|
||||||
stoprun := false // true means we are there
|
stoprun := false // true means we are there
|
||||||
mindist := 32767
|
|
||||||
|
rer, ree, door := g.chaseRooms(th)
|
||||||
|
|
||||||
|
this, shot := g.chaseGoal(th, rer, ree, door, 32767)
|
||||||
|
if shot {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// This now contains what we want to run to this time so we run to it.
|
||||||
|
// If we hit it we either want to fight it or stop running
|
||||||
|
if g.chase(th, this) {
|
||||||
|
if th.Type == 'F' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch this {
|
||||||
|
case g.Player.Pos:
|
||||||
|
return g.attack(th)
|
||||||
|
case *th.Dest:
|
||||||
|
g.chaseTakeObject(th)
|
||||||
|
|
||||||
|
stoprun = th.Type != 'F'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.relocate(th, g.chRet)
|
||||||
|
// And stop running if need be
|
||||||
|
if stoprun && th.Pos == *th.Dest {
|
||||||
|
th.Flags.Clear(Awake)
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// chaseRooms finds the rooms of the chaser and its desire; doors do not
|
||||||
|
// count as inside rooms here (the setup of chase.c do_chase).
|
||||||
|
func (g *RogueGame) chaseRooms(th *Monster) (*Room, *Room, bool) {
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
rer := th.Room // find room of chaser
|
rer := th.Room // find room of chaser
|
||||||
if th.On(Greedy) && rer.GoldVal == 0 {
|
if th.On(Greedy) && rer.GoldVal == 0 {
|
||||||
@@ -106,15 +149,26 @@ func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
|
|||||||
} else {
|
} else {
|
||||||
ree = g.roomIn(*th.Dest)
|
ree = g.roomIn(*th.Dest)
|
||||||
}
|
}
|
||||||
// We don't count doors as inside rooms for this routine
|
|
||||||
door := g.Level.Char(th.Pos.Y, th.Pos.X) == Door
|
|
||||||
|
|
||||||
|
return rer, ree, g.Level.Char(th.Pos.Y, th.Pos.X) == Door
|
||||||
|
}
|
||||||
|
|
||||||
|
// chaseGoal picks the spot the chaser runs toward this turn: the
|
||||||
|
// nearest exit toward its desire when it is in a different room, or the
|
||||||
|
// desire itself. shot means a dragon breathed flame instead of moving
|
||||||
|
// (the goal loop of chase.c do_chase).
|
||||||
|
func (g *RogueGame) chaseGoal(th *Monster, rer, ree *Room, door bool, mindist int) (Coord, bool) {
|
||||||
var this Coord
|
var this Coord
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// If the object of our desire is in a different room, and we are
|
// If the object of our desire is in a different room, and we are
|
||||||
// not in a corridor, run to the door nearest to our goal.
|
// not in a corridor, run to the door nearest to our goal.
|
||||||
if rer != ree {
|
if rer == ree {
|
||||||
|
this = *th.Dest
|
||||||
|
|
||||||
|
return this, g.dragonBreath(th)
|
||||||
|
}
|
||||||
|
|
||||||
for i := range rer.Exits {
|
for i := range rer.Exits {
|
||||||
curdist := distCp(*th.Dest, rer.Exits[i])
|
curdist := distCp(*th.Dest, rer.Exits[i])
|
||||||
if curdist < mindist {
|
if curdist < mindist {
|
||||||
@@ -123,21 +177,25 @@ func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if door {
|
if !door {
|
||||||
|
return this, false
|
||||||
|
}
|
||||||
|
|
||||||
rer = &g.Level.Passages[*g.Level.FlagsAt(th.Pos.Y, th.Pos.X)&FPassNum]
|
rer = &g.Level.Passages[*g.Level.FlagsAt(th.Pos.Y, th.Pos.X)&FPassNum]
|
||||||
door = false
|
door = false
|
||||||
|
// the C goto over: redo with the passage as room
|
||||||
continue // the C goto over: redo with the passage as room
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
this = *th.Dest
|
|
||||||
// For dragons check and see if (a) the hero is on a straight
|
// dragonBreath checks whether a dragon shoots flame at the hero instead
|
||||||
// line from it, and (b) that it is within shooting distance,
|
// of moving, and shoots it (the D block of chase.c do_chase).
|
||||||
// but outside of striking range.
|
func (g *RogueGame) dragonBreath(th *Monster) bool {
|
||||||
if th.Type == 'D' && (th.Pos.Y == p.Pos.Y || th.Pos.X == p.Pos.X ||
|
if th.Type != 'D' || !g.dragonShoots(th) {
|
||||||
abs(th.Pos.Y-p.Pos.Y) == abs(th.Pos.X-p.Pos.X)) &&
|
return false
|
||||||
distCp(th.Pos, p.Pos) <= BoltLength*BoltLength &&
|
}
|
||||||
!th.On(Cancelled) && g.rnd(dragonShot) == 0 {
|
|
||||||
|
p := &g.Player
|
||||||
|
|
||||||
g.Delta.Y = sign(p.Pos.Y - th.Pos.Y)
|
g.Delta.Y = sign(p.Pos.Y - th.Pos.Y)
|
||||||
|
|
||||||
g.Delta.X = sign(p.Pos.X - th.Pos.X)
|
g.Delta.X = sign(p.Pos.X - th.Pos.X)
|
||||||
@@ -155,18 +213,27 @@ func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
|
|||||||
g.Kamikaze = false
|
g.Kamikaze = false
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
// dragonShoots decides whether the dragon takes the shot: the hero is
|
||||||
|
// on a straight line from it, within shooting distance but outside
|
||||||
|
// striking range, it is not cancelled, and the shot roll comes up
|
||||||
|
// (chase.c do_chase).
|
||||||
|
func (g *RogueGame) dragonShoots(th *Monster) bool {
|
||||||
|
p := &g.Player
|
||||||
|
if th.Pos.Y != p.Pos.Y && th.Pos.X != p.Pos.X &&
|
||||||
|
abs(th.Pos.Y-p.Pos.Y) != abs(th.Pos.X-p.Pos.X) {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
// This now contains what we want to run to this time so we run to it.
|
|
||||||
// If we hit it we either want to fight it or stop running
|
return distCp(th.Pos, p.Pos) <= BoltLength*BoltLength &&
|
||||||
if !g.chase(th, this) {
|
!th.On(Cancelled) && g.rnd(dragonShot) == 0
|
||||||
if this == p.Pos {
|
}
|
||||||
return g.attack(th)
|
|
||||||
} else if this == *th.Dest {
|
// chaseTakeObject has the monster pick up the object it was running to
|
||||||
|
// (the dest arm of chase.c do_chase).
|
||||||
|
func (g *RogueGame) chaseTakeObject(th *Monster) {
|
||||||
for _, obj := range g.Level.Objects {
|
for _, obj := range g.Level.Objects {
|
||||||
if th.Dest == &obj.Pos {
|
if th.Dest == &obj.Pos {
|
||||||
g.Level.RemoveObject(obj)
|
g.Level.RemoveObject(obj)
|
||||||
@@ -183,34 +250,12 @@ func (g *RogueGame) chaseStep(th *Monster) (removed bool) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if th.Type != 'F' {
|
|
||||||
stoprun = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if th.Type == 'F' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g.relocate(th, g.chRet)
|
|
||||||
// And stop running if need be
|
|
||||||
if stoprun && th.Pos == *th.Dest {
|
|
||||||
th.Flags.Clear(Awake)
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// chase finds the spot for the chaser to move closer to the chasee
|
// chase finds the spot for the chaser to move closer to the chasee
|
||||||
// (chase.c chase). Returns true if we want to keep on chasing later, false
|
// (chase.c chase). Returns true if we want to keep on chasing later, false
|
||||||
// if we reach the goal. The chosen spot lands in g.chRet.
|
// if we reach the goal. The chosen spot lands in g.chRet.
|
||||||
func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
|
func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
|
||||||
p := &g.Player
|
|
||||||
er := tp.Pos
|
|
||||||
plcnt := 1
|
|
||||||
|
|
||||||
var curdist int
|
var curdist int
|
||||||
|
|
||||||
// If the thing is confused, let it move randomly. Invisible Stalkers
|
// If the thing is confused, let it move randomly. Invisible Stalkers
|
||||||
@@ -226,11 +271,27 @@ func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
|
|||||||
tp.Flags.Clear(Confused)
|
tp.Flags.Clear(Confused)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, find the empty spot next to the chaser that is
|
curdist = g.chaseBestSpot(tp, ee)
|
||||||
// closest to the chasee. This will eventually hold where we move
|
}
|
||||||
// to get closer. If we can't find an empty spot, we stay where we
|
|
||||||
// are.
|
return curdist != 0 && g.chRet != g.Player.Pos
|
||||||
curdist = distCp(er, ee)
|
}
|
||||||
|
|
||||||
|
// chaseSearch is the scan state while chase looks for the step that
|
||||||
|
// gets a monster closest to its chasee.
|
||||||
|
type chaseSearch struct {
|
||||||
|
er Coord // where the chaser is
|
||||||
|
ee Coord // where it wants to go
|
||||||
|
curdist int
|
||||||
|
plcnt int
|
||||||
|
}
|
||||||
|
|
||||||
|
// chaseBestSpot finds the empty spot next to the chaser that is closest
|
||||||
|
// to the chasee, leaving it in g.chRet; if there is none, the chaser
|
||||||
|
// stays where it is (the search half of chase.c chase).
|
||||||
|
func (g *RogueGame) chaseBestSpot(tp *Monster, ee Coord) int {
|
||||||
|
er := tp.Pos
|
||||||
|
s := chaseSearch{er: er, ee: ee, curdist: distCp(er, ee), plcnt: 1}
|
||||||
g.chRet = er
|
g.chRet = er
|
||||||
|
|
||||||
ey := er.Y + 1
|
ey := er.Y + 1
|
||||||
@@ -249,54 +310,59 @@ func (g *RogueGame) chase(tp *Monster, ee Coord) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for y := er.Y - 1; y <= ey; y++ {
|
for y := er.Y - 1; y <= ey; y++ {
|
||||||
|
g.chaseTry(&s, y, x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.curdist
|
||||||
|
}
|
||||||
|
|
||||||
|
// chaseTry scores one candidate square, reservoir-sampling among ties
|
||||||
|
// (the scan body of chase.c chase).
|
||||||
|
func (g *RogueGame) chaseTry(s *chaseSearch, y, x int) {
|
||||||
tryp := Coord{X: x, Y: y}
|
tryp := Coord{X: x, Y: y}
|
||||||
if !g.diagOk(er, tryp) {
|
if !g.diagOk(s.er, tryp) {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := g.Level.VisibleChar(y, x)
|
ch := g.Level.VisibleChar(y, x)
|
||||||
if stepOk(ch) {
|
if !stepOk(ch) {
|
||||||
// If it is a scroll, it might be a scare monster
|
return
|
||||||
// scroll so we need to look it up to see what type
|
|
||||||
// it is.
|
|
||||||
if ch == Scroll {
|
|
||||||
var found *Object
|
|
||||||
|
|
||||||
for _, obj := range g.Level.Objects {
|
|
||||||
if y == obj.Pos.Y && x == obj.Pos.X {
|
|
||||||
found = obj
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if found != nil && found.ScrollKind() == ScrollScareMonster {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
// If it is a scroll, it might be a scare monster scroll so we need
|
||||||
|
// to look it up to see what type it is.
|
||||||
|
if ch == Scroll && g.scareScrollAt(y, x) {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// It can also be a Xeroc, which we shouldn't step on
|
// It can also be a Xeroc, which we shouldn't step on
|
||||||
if m := g.Level.MonsterAt(y, x); m != nil && m.Type == 'X' {
|
if m := g.Level.MonsterAt(y, x); m != nil && m.Type == 'X' {
|
||||||
continue
|
return
|
||||||
}
|
}
|
||||||
// If we didn't find any scrolls at this place or it
|
// If we didn't find any scrolls at this place or it wasn't a scare
|
||||||
// wasn't a scare scroll, then this place counts
|
// scroll, then this place counts
|
||||||
thisdist := distance(y, x, ee.Y, ee.X)
|
thisdist := distance(y, x, s.ee.Y, s.ee.X)
|
||||||
if thisdist < curdist {
|
if thisdist < s.curdist {
|
||||||
plcnt = 1
|
s.plcnt = 1
|
||||||
g.chRet = tryp
|
g.chRet = tryp
|
||||||
curdist = thisdist
|
s.curdist = thisdist
|
||||||
} else if thisdist == curdist {
|
} else if thisdist == s.curdist {
|
||||||
if plcnt++; g.rnd(plcnt) == 0 {
|
if s.plcnt++; g.rnd(s.plcnt) == 0 {
|
||||||
g.chRet = tryp
|
g.chRet = tryp
|
||||||
curdist = thisdist
|
s.curdist = thisdist
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return curdist != 0 && g.chRet != p.Pos
|
// scareScrollAt reports whether the object lying at (y, x) is a scare
|
||||||
|
// monster scroll (chase.c chase).
|
||||||
|
func (g *RogueGame) scareScrollAt(y, x int) bool {
|
||||||
|
for _, obj := range g.Level.Objects {
|
||||||
|
if y == obj.Pos.Y && x == obj.Pos.X {
|
||||||
|
return obj.ScrollKind() == ScrollScareMonster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// setOldChar sets the oldch character for the monster (chase.c set_oldch).
|
// setOldChar sets the oldch character for the monster (chase.c set_oldch).
|
||||||
@@ -431,22 +497,23 @@ func (g *RogueGame) findDest(tp *Monster) *Coord {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.roomIn(obj.Pos) == tp.Room && g.rnd(100) < prob {
|
if g.roomIn(obj.Pos) == tp.Room && g.rnd(100) < prob &&
|
||||||
claimed := false
|
!g.objectClaimed(obj) {
|
||||||
|
|
||||||
for _, other := range g.Level.Monsters {
|
|
||||||
if other.Dest == &obj.Pos {
|
|
||||||
claimed = true
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !claimed {
|
|
||||||
return &obj.Pos
|
return &obj.Pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return &g.Player.Pos
|
return &g.Player.Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// objectClaimed reports whether some monster already runs toward this
|
||||||
|
// object (chase.c find_dest).
|
||||||
|
func (g *RogueGame) objectClaimed(obj *Object) bool {
|
||||||
|
for _, other := range g.Level.Monsters {
|
||||||
|
if other.Dest == &obj.Pos {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user