go: port dungeon generation, items base, pack, and monster creation
rooms.c, passages.c, new_level.c ported in full: room/maze layout, corridor spanning tree with extra connections, passage numbering flood fill, trap/stairs/object placement, treasure rooms. Careful RNG-call ordering throughout keeps generation seed-faithful to C. Supporting subsystems this depends on, also ported: - screen.go: curses replaced by in-memory Window cell buffers behind a Terminal interface (tcell arrives with the UI phase; tests run headless) - io.go: msg/addmsg/endmsg message-line protocol, status line, step_ok - pack.c in full (add_pack sorting/stacking walk, get_item, inventory) - things.c in full (inv_name, new_thing, discovery lists, pagination) - monsters.c in full; chase.c navigation half (roomin/cansee/see_monst/ find_dest/runto/set_oldch); rings.c, armor.c in full - weapons.c/sticks.c creation half (init_weapon, fix_stick, num) - misc.c look() display update, eat, level-up, direction input - daemons.c callbacks except stomach (needs death()) and the runners chase driver (combat phase) - init.c init_player with the starting kit Tests: level invariants across seeds, determinism, 30-depth sweep.
This commit is contained in:
302
game/passages.go
Normal file
302
game/passages.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package game
|
||||
|
||||
// passages.c — draw the connecting passages.
|
||||
|
||||
// rdesConn is the hardcoded 3x3 room adjacency matrix from do_passages.
|
||||
var rdesConn = [MaxRooms][MaxRooms]bool{
|
||||
{false, true, false, true, false, false, false, false, false},
|
||||
{true, false, true, false, true, false, false, false, false},
|
||||
{false, true, false, false, false, true, false, false, false},
|
||||
{true, false, false, false, true, false, true, false, false},
|
||||
{false, true, false, true, false, true, false, true, false},
|
||||
{false, false, true, false, true, false, false, false, true},
|
||||
{false, false, false, true, false, false, false, true, false},
|
||||
{false, false, false, false, true, false, true, false, true},
|
||||
{false, false, false, false, false, true, false, true, false},
|
||||
}
|
||||
|
||||
// doPassages draws all the passages on a level (passages.c do_passages).
|
||||
func (g *RogueGame) doPassages() {
|
||||
var isconn [MaxRooms][MaxRooms]bool
|
||||
var 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 := 0; i < MaxRooms; i++ {
|
||||
if 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
|
||||
g.conn(r1, r2)
|
||||
isconn[r1][r2] = true
|
||||
isconn[r2][r1] = true
|
||||
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 := 0; i < MaxRooms; i++ {
|
||||
if 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
|
||||
}
|
||||
}
|
||||
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
|
||||
var 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
|
||||
var del, turnDelta, spos, epos Coord
|
||||
var 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(IsGone) { // 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(IsMaze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPass)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !rpt.Flags.Has(IsGone) {
|
||||
for {
|
||||
epos.X = rpt.Pos.X + g.rnd(rpt.Max.X-2) + 1
|
||||
if !(rpt.Flags.Has(IsMaze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPass)) {
|
||||
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(IsGone) {
|
||||
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(IsMaze) && !g.Level.FlagsAt(spos.Y, spos.X).Has(FPass)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !rpt.Flags.Has(IsGone) {
|
||||
for {
|
||||
epos.Y = rpt.Pos.Y + g.rnd(rpt.Max.Y-2) + 1
|
||||
if !(rpt.Flags.Has(IsMaze) && !g.Level.FlagsAt(epos.Y, epos.X).Has(FPass)) {
|
||||
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(IsGone) {
|
||||
g.door(rpf, spos)
|
||||
} else {
|
||||
g.putpass(spos)
|
||||
}
|
||||
if !rpt.Flags.Has(IsGone) {
|
||||
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(FPass)
|
||||
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(IsMaze) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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(FPNum) {
|
||||
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(FPass) {
|
||||
return
|
||||
}
|
||||
*fp |= PlaceFlags(g.pnum)
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user