package game // rooms.c — create the layout for the new level. // spot is the rooms.c SPOT position matrix for maze positions. type spot struct { nexits int exits [4]Coord used bool } // mazeState is the rooms.c file-scope maze generation state. type mazeState struct { maxy, maxx, starty, startx int maze [NumLines/3 + 1][NumCols/3 + 1]spot } const goldGrp = 1 // digRooms creates rooms and corridors with a connectivity graph (rooms.c // do_rooms). func (g *RogueGame) digRooms() { var bsze Coord // maximum room size bsze.X = NumCols / 3 bsze.Y = NumLines / 3 // Clear things for a new level for i := range g.Level.Rooms { rp := &g.Level.Rooms[i] rp.GoldVal = 0 rp.Exits = rp.Exits[:0] rp.Flags = 0 } // Put the gone rooms, if any, on the level leftOut := g.rnd(4) for range leftOut { g.Level.Rooms[g.randomRoom()].Flags.Set(Gone) } // dig and populate all the rooms on the level for i := range g.Level.Rooms { rp := &g.Level.Rooms[i] // Find upper left corner of box that this room goes in top := Coord{X: (i%3)*bsze.X + 1, Y: (i / 3) * bsze.Y} if rp.Flags.Has(Gone) { // Place a gone room. Make certain that there is a blank line // for passage drawing. for { rp.Pos.X = top.X + g.rnd(bsze.X-2) + 1 rp.Pos.Y = top.Y + g.rnd(bsze.Y-2) + 1 rp.Max.X = -NumCols rp.Max.Y = -NumLines if rp.Pos.Y > 0 && rp.Pos.Y < NumLines-1 { break } } continue } // set room type if g.rnd(10) < g.Depth-1 { rp.Flags.Set(Dark) // dark room if g.rnd(15) == 0 { rp.Flags = Maze // maze room } } // Find a place and size for a random room if rp.Flags.Has(Maze) { rp.Max.X = bsze.X - 1 rp.Max.Y = bsze.Y - 1 if rp.Pos.X = top.X; rp.Pos.X == 1 { rp.Pos.X = 0 } if rp.Pos.Y = top.Y; rp.Pos.Y == 0 { rp.Pos.Y++ rp.Max.Y-- } } else { for { rp.Max.X = g.rnd(bsze.X-4) + 4 rp.Max.Y = g.rnd(bsze.Y-4) + 4 rp.Pos.X = top.X + g.rnd(bsze.X-rp.Max.X) rp.Pos.Y = top.Y + g.rnd(bsze.Y-rp.Max.Y) if rp.Pos.Y != 0 { break } } } g.drawRoom(rp) // Put the gold in if g.rnd(2) == 0 && (!g.HasAmulet || g.Depth >= g.MaxDepth) { gold := newObject() rp.GoldVal = g.goldCalc() gold.GoldValue = rp.GoldVal rp.Gold, _ = g.findFloorIn(rp, 0, false) gold.Pos = rp.Gold g.Level.SetChar(rp.Gold.Y, rp.Gold.X, Gold) gold.Flags = Stackable gold.Group = goldGrp gold.Kind = KindGold g.Level.AddObject(gold) } // Put the monster in prob := 25 if rp.GoldVal > 0 { prob = 80 } if g.rnd(100) < prob { tp := &Monster{} mp, _ := g.findFloorIn(rp, 0, true) g.newMonster(tp, g.randMonster(false), mp) g.givePack(tp) } } } // drawRoom draws a box around a room and lays down the floor for normal // rooms; for maze rooms, draws the maze (rooms.c draw_room). func (g *RogueGame) drawRoom(rp *Room) { if rp.Flags.Has(Maze) { g.digMaze(rp) return } g.vert(rp, rp.Pos.X) // Draw left side g.vert(rp, rp.Pos.X+rp.Max.X-1) // Draw right side g.horiz(rp, rp.Pos.Y) // Draw top g.horiz(rp, rp.Pos.Y+rp.Max.Y-1) // Draw bottom // Put the floor down for y := rp.Pos.Y + 1; y < rp.Pos.Y+rp.Max.Y-1; y++ { for x := rp.Pos.X + 1; x < rp.Pos.X+rp.Max.X-1; x++ { g.Level.SetChar(y, x, Floor) } } } // vert draws a vertical line (rooms.c vert). func (g *RogueGame) vert(rp *Room, startx int) { for y := rp.Pos.Y + 1; y <= rp.Max.Y+rp.Pos.Y-1; y++ { g.Level.SetChar(y, startx, '|') } } // horiz draws a horizontal line (rooms.c horiz). func (g *RogueGame) horiz(rp *Room, starty int) { for x := rp.Pos.X; x <= rp.Pos.X+rp.Max.X-1; x++ { g.Level.SetChar(starty, x, '-') } } // digMaze digs a maze (rooms.c do_maze). func (g *RogueGame) digMaze(rp *Room) { m := &g.maze for y := range m.maze { for x := range m.maze[y] { m.maze[y][x].used = false m.maze[y][x].nexits = 0 } } m.maxy = rp.Max.Y m.maxx = rp.Max.X m.starty = rp.Pos.Y m.startx = rp.Pos.X starty := (g.rnd(rp.Max.Y) / 2) * 2 startx := (g.rnd(rp.Max.X) / 2) * 2 pos := Coord{Y: starty + m.starty, X: startx + m.startx} g.putPassage(pos) g.dig(starty, startx) } // dig digs out from around where we are now, if possible (rooms.c dig). func (g *RogueGame) dig(y, x int) { m := &g.maze del := [4]Coord{{X: 2, Y: 0}, {X: -2, Y: 0}, {X: 0, Y: 2}, {X: 0, Y: -2}} for { cnt := 0 var nexty, nextx int for _, cp := range del { newy := y + cp.Y newx := x + cp.X if newy < 0 || newy > m.maxy || newx < 0 || newx > m.maxx { continue } if g.Level.FlagsAt(newy+m.starty, newx+m.startx).Has(FPassage) { continue } if cnt++; g.rnd(cnt) == 0 { nexty = newy nextx = newx } } if cnt == 0 { return } g.accountMaze(y, x, nexty, nextx) g.accountMaze(nexty, nextx, y, x) var pos Coord if nexty == y { pos.Y = y + m.starty if nextx-x < 0 { pos.X = nextx + m.startx + 1 } else { pos.X = nextx + m.startx - 1 } } else { pos.X = x + m.startx if nexty-y < 0 { pos.Y = nexty + m.starty + 1 } else { pos.Y = nexty + m.starty - 1 } } g.putPassage(pos) pos.Y = nexty + m.starty pos.X = nextx + m.startx g.putPassage(pos) g.dig(nexty, nextx) } } // accountMaze accounts for maze exits (rooms.c accnt_maze). func (g *RogueGame) accountMaze(y, x, ny, nx int) { sp := &g.maze.maze[y][x] for i := range sp.nexits { if sp.exits[i].Y == ny && sp.exits[i].X == nx { return } } // Note: the C original never increments nexits here (a latent bug it // inherits); it stores the exit in the next slot and relies on the // slot staying zero-counted. We reproduce the store-without-count. if sp.nexits < len(sp.exits) { sp.exits[sp.nexits] = Coord{Y: ny, X: nx} } } // randomPos picks a random spot in a room (rooms.c rnd_pos). func (g *RogueGame) randomPos(rp *Room) Coord { var cp Coord cp.X = rp.Pos.X + g.rnd(rp.Max.X-2) + 1 cp.Y = rp.Pos.Y + g.rnd(rp.Max.Y-2) + 1 return cp } // findFloor finds a valid floor spot, picking a new random room each time // around the loop; it retries forever (rooms.c find_floor with rp == NULL // — every such C call site passed FALSE for the limit). func (g *RogueGame) findFloor(monst bool) (Coord, bool) { return g.findFloorImpl(nil, 0, monst, true) } // findFloorIn finds a valid floor spot in this room (rooms.c find_floor // with a specific room). func (g *RogueGame) findFloorIn(rp *Room, limit int, monst bool) (Coord, bool) { return g.findFloorImpl(rp, limit, monst, false) } func (g *RogueGame) findFloorImpl(rp *Room, limit int, monst, pickroom bool) (Coord, bool) { var compchar byte if !pickroom { compchar = Floor if rp.Flags.Has(Maze) { compchar = Passage } } cnt := limit for { if limit != 0 { if cnt--; cnt == -1 { return Coord{}, false } } if pickroom { rp = &g.Level.Rooms[g.randomRoom()] compchar = Floor if rp.Flags.Has(Maze) { compchar = Passage } } cp := g.randomPos(rp) pp := g.Level.At(cp.Y, cp.X) if monst { if pp.Monst == nil && stepOk(pp.Ch) { return cp, true } } else if pp.Ch == compchar { return cp, true } } } // enterRoom is the code executed whenever you appear in a room (rooms.c // enter_room). func (g *RogueGame) enterRoom(cp Coord) { p := &g.Player rp := g.roomIn(cp) p.Room = rp g.doorOpen(rp) if !rp.Flags.Has(Dark) && !p.On(Blind) { for y := rp.Pos.Y; y < rp.Max.Y+rp.Pos.Y; y++ { g.move(y, rp.Pos.X) for x := rp.Pos.X; x < rp.Max.X+rp.Pos.X; x++ { tp := g.Level.MonsterAt(y, x) ch := g.Level.Char(y, x) if tp == nil { if g.inch() != ch { g.addch(ch) } else { g.move(y, x+1) } } else { tp.OldCh = ch if !g.seeMonst(tp) { if p.On(SenseMonsters) { g.standout() g.addch(tp.Disguise) g.standend() } else { g.addch(ch) } } else { g.addch(tp.Disguise) } } } } } } // leaveRoom is the code for when we exit a room (rooms.c leave_room). func (g *RogueGame) leaveRoom(cp Coord) { p := &g.Player rp := p.Room if rp.Flags.Has(Maze) { return } var floor byte switch { case rp.Flags.Has(Gone): floor = Passage case !rp.Flags.Has(Dark) || p.On(Blind): floor = Floor default: floor = ' ' } p.Room = &g.Level.Passages[*g.Level.FlagsAt(cp.Y, cp.X)&FPassNum] for y := rp.Pos.Y; y < rp.Max.Y+rp.Pos.Y; y++ { for x := rp.Pos.X; x < rp.Max.X+rp.Pos.X; x++ { g.move(y, x) switch ch := g.inch(); ch { case Floor: if floor == ' ' { g.addch(' ') } default: // to check for monster, we have to strip out the standout // bit (our Window returns the bare character already) if isUpper(ch) { if p.On(SenseMonsters) { g.standout() g.addch(ch) g.standend() break } pp := g.Level.At(y, x) if pp.Ch == Door { g.addch(Door) } else { g.addch(floor) } } } } } g.doorOpen(rp) }