diff --git a/game/passages.go b/game/passages.go index a49682f..b9cd56e 100644 --- a/game/passages.go +++ b/game/passages.go @@ -15,204 +15,234 @@ func (g *RogueGame) digPassages() { r1 := g.rnd(MaxRooms) ingraph[r1] = true - for { + for roomcount < MaxRooms { // find a room to connect with - j := 0 - r2 := -1 - - for i := range MaxRooms { - if g.data.rdesConn[r1][i] && !ingraph[i] { - if j++; g.rnd(j) == 0 { - r2 = i - } - } - } - - if j == 0 { - // if no adjacent rooms are outside the graph, pick a new room - // to look from + r2 := g.pickNeighbor(r1, func(i int) bool { return !ingraph[i] }) + if r2 < 0 { + // if no adjacent rooms are outside the graph, pick a new + // room to look from for { r1 = g.rnd(MaxRooms) if ingraph[r1] { break } } - } else { - // otherwise, connect new room to the graph, and draw a tunnel - // to it - ingraph[r2] = true //nolint:gosec // G602: rnd(MaxRooms) bounded - g.connectRooms(r1, r2) - isconn[r1][r2] = true - isconn[r2][r1] = true //nolint:gosec // G602: rnd(MaxRooms) bounded - roomcount++ - } - if roomcount >= MaxRooms { - break + continue } + // otherwise, connect new room to the graph, and draw a tunnel + // to it + ingraph[r2] = true + g.connectRooms(r1, r2) + isconn[r1][r2] = true + isconn[r2][r1] = true + roomcount++ } // attempt to add passages to the graph a random number of times so that // there isn't always just one unique passage through it. for roomcount = g.rnd(5); roomcount > 0; roomcount-- { r1 = g.rnd(MaxRooms) // a random room to look from - // find an adjacent room not already connected - j := 0 - r2 := -1 - - for i := range MaxRooms { - if g.data.rdesConn[r1][i] && !isconn[r1][i] { - if j++; g.rnd(j) == 0 { - r2 = i - } - } - } - // if there is one, connect it and look for the next added passage - if j != 0 { + // find an adjacent room not already connected; if there is one, + // connect it and look for the next added passage + r2 := g.pickNeighbor(r1, func(i int) bool { return !isconn[r1][i] }) + if r2 >= 0 { g.connectRooms(r1, r2) isconn[r1][r2] = true - isconn[r2][r1] = true //nolint:gosec // G602: rnd(MaxRooms) bounded + isconn[r2][r1] = true } } g.numberPassages() } +// pickNeighbor reservoir-picks an adjacent room for which ok holds, or +// -1 when there is none (passages.c do_passages). +func (g *RogueGame) pickNeighbor(r1 int, ok func(int) bool) int { + j := 0 + r2 := -1 + + for i := range MaxRooms { + if g.data.rdesConn[r1][i] && ok(i) { + if j++; g.rnd(j) == 0 { + r2 = i + } + } + } + + return r2 +} + +// corridorPlan is the movement setup connectRooms computes before it +// digs (the local variables of passages.c conn). +type corridorPlan struct { + rpf, rpt *Room // the rooms being joined + del Coord // direction of move + turnDelta Coord // direction to turn + spos, epos Coord // start and end of move + distance, turnDistance int // how far to move and to turn +} + // connectRooms draws a corridor from a room in a certain direction // (passages.c conn). func (g *RogueGame) connectRooms(r1, r2 int) { - var ( - rm int - direc byte - ) - - if r1 < r2 { - rm = r1 - if r1+1 == r2 { - direc = 'r' - } else { - direc = 'd' - } - } else { - rm = r2 - if r2+1 == r1 { - direc = 'r' - } else { - direc = 'd' - } - } - - rpf := &g.Level.Rooms[rm] - - // Set up the movement variables, in two cases: first drawing one down. - var ( - rpt *Room - del, turnDelta, spos, epos Coord - distance, turnDistance int - ) + rm, direc := connOrient(r1, r2) + var plan corridorPlan if direc == 'd' { - rmt := rm + 3 // room # of dest - rpt = &g.Level.Rooms[rmt] // room pointer of dest - del = Coord{X: 0, Y: 1} // direction of move - spos = rpf.Pos // start of move - epos = rpt.Pos // end of move - - if !rpf.Flags.Has(Gone) { // if not gone pick door pos - for { - spos.X = rpf.Pos.X + g.rnd(rpf.Max.X-2) + 1 - - spos.Y = rpf.Pos.Y + rpf.Max.Y - 1 - if !rpf.Flags.Has(Maze) || g.Level.FlagsAt(spos.Y, spos.X).Has(FPassage) { - break - } - } - } - - if !rpt.Flags.Has(Gone) { - for { - epos.X = rpt.Pos.X + g.rnd(rpt.Max.X-2) + 1 - if !rpt.Flags.Has(Maze) || g.Level.FlagsAt(epos.Y, epos.X).Has(FPassage) { - break - } - } - } - - distance = abs(spos.Y-epos.Y) - 1 // distance to move - - turnDelta.Y = 0 // direction to turn - if spos.X < epos.X { - turnDelta.X = 1 - } else { - turnDelta.X = -1 - } - - turnDistance = abs(spos.X - epos.X) // how far to turn - } else { // setup for moving right - rmt := rm + 1 - rpt = &g.Level.Rooms[rmt] - del = Coord{X: 1, Y: 0} - spos = rpf.Pos - epos = rpt.Pos - - if !rpf.Flags.Has(Gone) { - for { - spos.X = rpf.Pos.X + rpf.Max.X - 1 - - spos.Y = rpf.Pos.Y + g.rnd(rpf.Max.Y-2) + 1 - if !rpf.Flags.Has(Maze) || g.Level.FlagsAt(spos.Y, spos.X).Has(FPassage) { - break - } - } - } - - if !rpt.Flags.Has(Gone) { - for { - epos.Y = rpt.Pos.Y + g.rnd(rpt.Max.Y-2) + 1 - if !rpt.Flags.Has(Maze) || g.Level.FlagsAt(epos.Y, epos.X).Has(FPassage) { - break - } - } - } - - distance = abs(spos.X-epos.X) - 1 - if spos.Y < epos.Y { - turnDelta.Y = 1 - } else { - turnDelta.Y = -1 - } - - turnDelta.X = 0 - turnDistance = abs(spos.Y - epos.Y) + plan = g.connPlanDown(rm) + } else { + plan = g.connPlanRight(rm) } - turnSpot := g.rnd(distance-1) + 1 // where turn starts + turnSpot := g.rnd(plan.distance-1) + 1 // where turn starts // Draw in the doors on either side of the passage or just put #'s if // the rooms are gone. - if !rpf.Flags.Has(Gone) { - g.door(rpf, spos) - } else { - g.putPassage(spos) + g.connEnd(plan.rpf, plan.spos) + g.connEnd(plan.rpt, plan.epos) + + g.digCorridor(plan, turnSpot) +} + +// connOrient picks the upper-left room of the pair and the digging +// direction: right for horizontal neighbors, down otherwise (passages.c +// conn). +func connOrient(r1, r2 int) (int, byte) { + rm := min(r1, r2) + if abs(r1-r2) == 1 { + return rm, 'r' + } + + return rm, 'd' +} + +// connPlanDown sets up the movement variables for a corridor drawn +// downward (passages.c conn). +func (g *RogueGame) connPlanDown(rm int) corridorPlan { + rpf := &g.Level.Rooms[rm] + rpt := &g.Level.Rooms[rm+3] // room pointer of dest + + plan := corridorPlan{ + rpf: rpf, + rpt: rpt, + del: Coord{X: 0, Y: 1}, // direction of move + spos: rpf.Pos, // start of move + epos: rpt.Pos, // end of move + } + + if !rpf.Flags.Has(Gone) { // if not gone pick door pos + for { + plan.spos.X = rpf.Pos.X + g.rnd(rpf.Max.X-2) + 1 + + plan.spos.Y = rpf.Pos.Y + rpf.Max.Y - 1 + if !rpf.Flags.Has(Maze) || + g.Level.FlagsAt(plan.spos.Y, plan.spos.X).Has(FPassage) { + break + } + } } if !rpt.Flags.Has(Gone) { - g.door(rpt, epos) - } else { - g.putPassage(epos) + for { + plan.epos.X = rpt.Pos.X + g.rnd(rpt.Max.X-2) + 1 + if !rpt.Flags.Has(Maze) || + g.Level.FlagsAt(plan.epos.Y, plan.epos.X).Has(FPassage) { + break + } + } } - // Get ready to move... - curr := spos + + plan.distance = abs(plan.spos.Y-plan.epos.Y) - 1 // distance to move + + plan.turnDelta.Y = 0 // direction to turn + if plan.spos.X < plan.epos.X { + plan.turnDelta.X = 1 + } else { + plan.turnDelta.X = -1 + } + + plan.turnDistance = abs(plan.spos.X - plan.epos.X) // how far to turn + + return plan +} + +// connPlanRight sets up the movement variables for a corridor drawn to +// the right (passages.c conn). +func (g *RogueGame) connPlanRight(rm int) corridorPlan { + rpf := &g.Level.Rooms[rm] + rpt := &g.Level.Rooms[rm+1] + + plan := corridorPlan{ + rpf: rpf, + rpt: rpt, + del: Coord{X: 1, Y: 0}, + spos: rpf.Pos, + epos: rpt.Pos, + } + + if !rpf.Flags.Has(Gone) { + for { + plan.spos.X = rpf.Pos.X + rpf.Max.X - 1 + + plan.spos.Y = rpf.Pos.Y + g.rnd(rpf.Max.Y-2) + 1 + if !rpf.Flags.Has(Maze) || + g.Level.FlagsAt(plan.spos.Y, plan.spos.X).Has(FPassage) { + break + } + } + } + + if !rpt.Flags.Has(Gone) { + for { + plan.epos.Y = rpt.Pos.Y + g.rnd(rpt.Max.Y-2) + 1 + if !rpt.Flags.Has(Maze) || + g.Level.FlagsAt(plan.epos.Y, plan.epos.X).Has(FPassage) { + break + } + } + } + + plan.distance = abs(plan.spos.X-plan.epos.X) - 1 + if plan.spos.Y < plan.epos.Y { + plan.turnDelta.Y = 1 + } else { + plan.turnDelta.Y = -1 + } + + plan.turnDelta.X = 0 + plan.turnDistance = abs(plan.spos.Y - plan.epos.Y) + + return plan +} + +// connEnd draws a corridor end: a door on a real room, a passage square +// on a gone one (passages.c conn). +func (g *RogueGame) connEnd(rp *Room, pos Coord) { + if !rp.Flags.Has(Gone) { + g.door(rp, pos) + } else { + g.putPassage(pos) + } +} + +// digCorridor digs from spos to epos, turning at turnSpot (the digging +// loop of passages.c conn). +func (g *RogueGame) digCorridor(plan corridorPlan, turnSpot int) { + curr := plan.spos + distance := plan.distance + turnDistance := plan.turnDistance + for distance > 0 { // Move to new position - curr.X += del.X - curr.Y += del.Y + curr.X += plan.del.X + curr.Y += plan.del.Y // Check if we are at the turn place, if so do the turn if distance == turnSpot { for ; turnDistance > 0; turnDistance-- { g.putPassage(curr) - curr.X += turnDelta.X - curr.Y += turnDelta.Y + curr.X += plan.turnDelta.X + curr.Y += plan.turnDelta.Y } } // Continue digging along @@ -221,10 +251,10 @@ func (g *RogueGame) connectRooms(r1, r2 int) { distance-- } - curr.X += del.X + curr.X += plan.del.X - curr.Y += del.Y - if curr != epos { + curr.Y += plan.del.Y + if curr != plan.epos { g.msg("warning, connectivity problem on this level") } } @@ -270,38 +300,50 @@ func (g *RogueGame) door(rm *Room, cp Coord) { func (g *RogueGame) addPass() { for y := 1; y < NumLines-1; y++ { for x := range NumCols { - pp := g.Level.At(y, x) - if pp.Flags.Has(FPassage) || pp.Ch == Door || - (!pp.Flags.Has(FReal) && (pp.Ch == '|' || pp.Ch == '-')) { - ch := pp.Ch - if pp.Flags.Has(FPassage) { - ch = Passage - } - - pp.Flags.Set(FSeen) - g.move(y, x) - - switch { - case pp.Monst != nil: - pp.Monst.OldCh = pp.Ch - case pp.Flags.Has(FReal): - g.addch(ch) - default: - g.standout() - - if pp.Flags.Has(FPassage) { - g.addch(Passage) - } else { - g.addch(Door) - } - - g.standend() - } - } + g.addPassSpot(g.Level.At(y, x), y, x) } } } +// addPassSpot shows one passage or door square for the wizard (the loop +// body of passages.c add_pass). +func (g *RogueGame) addPassSpot(pp *Place, y, x int) { + if !pp.Flags.Has(FPassage) && !hiddenExit(pp.Flags, pp.Ch) { + return + } + + ch := pp.Ch + if pp.Flags.Has(FPassage) { + ch = Passage + } + + pp.Flags.Set(FSeen) + g.move(y, x) + + switch { + case pp.Monst != nil: + pp.Monst.OldCh = pp.Ch + case pp.Flags.Has(FReal): + g.addch(ch) + default: + g.standout() + + if pp.Flags.Has(FPassage) { + g.addch(Passage) + } else { + g.addch(Door) + } + + g.standend() + } +} + +// hiddenExit reports a door, or a secret door still drawn as a wall +// (passages.c add_pass / numpass). +func hiddenExit(fp PlaceFlags, ch byte) bool { + return ch == Door || (!fp.Has(FReal) && (ch == '|' || ch == '-')) +} + // numberPassages assigns a number to each passageway (passages.c passnum). func (g *RogueGame) numberPassages() { g.pnum = 0 @@ -338,8 +380,7 @@ func (g *RogueGame) numberPassage(y, x int) { } // check to see if it is a door or secret door, i.e., a new exit, or a // numerable type of place - if ch := g.Level.Char(y, x); ch == Door || - (!fp.Has(FReal) && (ch == '|' || ch == '-')) { + if hiddenExit(*fp, g.Level.Char(y, x)) { rp := &g.Level.Passages[g.pnum] rp.Exits = append(rp.Exits, Coord{Y: y, X: x}) } else if !fp.Has(FPassage) {