package game // passages.c — draw the connecting passages. // doPassages draws all the passages on a level (passages.c do_passages). func (g *RogueGame) doPassages() { var ( isconn [MaxRooms][MaxRooms]bool ingraph [MaxRooms]bool ) // starting with one room, connect it to a random adjacent room and // then pick a new room to start with. roomcount := 1 r1 := g.rnd(MaxRooms) ingraph[r1] = true for { // 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 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.conn(r1, r2) isconn[r1][r2] = true isconn[r2][r1] = true //nolint:gosec // G602: rnd(MaxRooms) bounded roomcount++ } if roomcount >= MaxRooms { break } } // 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 { g.conn(r1, r2) isconn[r1][r2] = true isconn[r2][r1] = true //nolint:gosec // G602: rnd(MaxRooms) bounded } } g.passnum() } // conn draws a corridor from a room in a certain direction (passages.c // conn). func (g *RogueGame) conn(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 ) 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) } turnSpot := g.rnd(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.putpass(spos) } if !rpt.Flags.Has(Gone) { g.door(rpt, epos) } else { g.putpass(epos) } // Get ready to move... curr := spos for distance > 0 { // Move to new position curr.X += del.X curr.Y += del.Y // Check if we are at the turn place, if so do the turn if distance == turnSpot { for ; turnDistance > 0; turnDistance-- { g.putpass(curr) curr.X += turnDelta.X curr.Y += turnDelta.Y } } // Continue digging along g.putpass(curr) distance-- } curr.X += del.X curr.Y += del.Y if curr != epos { g.msg("warning, connectivity problem on this level") } } // putpass adds a passage character or secret passage here (passages.c // putpass). func (g *RogueGame) putpass(cp Coord) { pp := g.Level.At(cp.Y, cp.X) pp.Flags.Set(FPassage) if g.rnd(10)+1 < g.Depth && g.rnd(40) == 0 { pp.Flags.Clear(FReal) } else { pp.Ch = Passage } } // door adds a door or possibly a secret door; also enters the door in the // exits array of the room (passages.c door). func (g *RogueGame) door(rm *Room, cp Coord) { rm.Exits = append(rm.Exits, cp) if rm.Flags.Has(Maze) { return } pp := g.Level.At(cp.Y, cp.X) if g.rnd(10)+1 < g.Depth && g.rnd(5) == 0 { if cp.Y == rm.Pos.Y || cp.Y == rm.Pos.Y+rm.Max.Y-1 { pp.Ch = '-' } else { pp.Ch = '|' } pp.Flags.Clear(FReal) } else { pp.Ch = Door } } // addPass adds the passages to the current window — wizard command // (passages.c add_pass). 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() } } } } } // passnum assigns a number to each passageway (passages.c passnum). func (g *RogueGame) passnum() { g.pnum = 0 g.newpnum = false for i := range g.Level.Passages { g.Level.Passages[i].Exits = g.Level.Passages[i].Exits[:0] } for i := range g.Level.Rooms { rp := &g.Level.Rooms[i] for j := range rp.Exits { g.newpnum = true g.numpass(rp.Exits[j].Y, rp.Exits[j].X) } } } // numpass numbers a passageway square and its brethren (passages.c // numpass). func (g *RogueGame) numpass(y, x int) { if x >= NumCols || x < 0 || y >= NumLines || y <= 0 { return } fp := g.Level.FlagsAt(y, x) if fp.Has(FPassNum) { return } if g.newpnum { g.pnum++ g.newpnum = false } // 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 == '-')) { rp := &g.Level.Passages[g.pnum] rp.Exits = append(rp.Exits, Coord{Y: y, X: x}) } else if !fp.Has(FPassage) { return } *fp |= PlaceFlags(g.pnum) //nolint:gosec // G115: pnum < MaxPass=13 // recurse on the surrounding places g.numpass(y+1, x) g.numpass(y-1, x) g.numpass(y, x+1) g.numpass(y, x-1) } // abs is C abs() for ints. func abs(n int) int { if n < 0 { return -n } return n }