Random level generation (post #3: https://www.xtof.info/blog/?p=1186)

This commit is contained in:
Christophe Meneboeuf 2020-01-15 23:56:47 +01:00
parent 7fcdb9d800
commit eff3827d20
35 changed files with 3503 additions and 1018 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.vscode/
.DS_Store
*.out
*.a2
*.o

1348
LICENSE

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,8 @@
APPLE2_CL := $(CC65_HOME)/bin/cl65
APPLE2_SRC := src/main.asm src/math.asm src/random.asm \
APPLE2_SRC := src/main.asm src/math.asm src/memory.asm src/random.asm \
src/game_loop.asm src/display.asm src/tiles.asm src/world.asm src/player.asm \
src/debug.asm
src/debug.asm src/builder/builder.asm src/builder/rooms.asm src/builder/maze.asm src/display_map.asm \
src/io/title.asm src/io/textio.asm src/io/gr.asm
APPLE2_MAP := escape.map
APPLE2_CFLAGS := -Oirs -v -t apple2 -vm --cpu 6502
APPLE2_OUT := bin/escape.a2
@ -15,4 +16,4 @@ apple2: $(APPLE2_SRC)
$(APPLE2_CL) -m $(APPLE2_MAP) -o $(APPLE2_OUT) $? $(APPLE2_CFLAGS) -C src/escape.cfg
clean: $(SRC)
rm -f $(APPLE2_MAP) src/*.o src/*.s gmon.out & rm -r bin/
rm -f $(APPLE2_MAP) src/*.o src/*.s src/builder/*.o src/builder/*.s src/io/*.s src/io/*.s gmon.out & rm -r bin/

Binary file not shown.

View File

@ -10,7 +10,7 @@ if (( $# != 3 )); then
exit
fi
echo " . revoving previous instance of ESCAPE form the disk"
echo " . removing previous instance of ESCAPE form the disk"
java -jar ${1} -d ${3} ESCAPE
echo " .. adding ESCAPE to the disk"

View File

@ -38,21 +38,52 @@ def fill_rect(x,y,color):
outline = (0,0,128),
fill = color)
# returns rays from (x0, y0) to (x1, y1)
# output in rays
def compute_rays(x0, y0, x1, y1, rays):
ray = list(bresenham(x0, y0, x1, y1))
# Duplicate the ray so that x and y are not incremented at the same time
duplicated = False
ray_x = [] # x incremented before y
ray_y = [] # y incremented before x
x = ray[0][0]
y = ray[0][1]
for tile in ray[1:]:
if tile[0] != x and tile[1] != y:
duplicated = True
ray_x.append((tile[0], y))
ray_x.append((tile[0], tile[1]))
ray_y.append((x, tile[1]))
ray_y.append((tile[0], tile[1]))
else:
ray_x.append((tile[0], tile[1]))
ray_y.append((tile[0], tile[1]))
x = tile[0]
y = tile[1]
rays.append(ray_x)
if duplicated:
rays.append(ray_y)
return rays
if __name__=="__main__":
rays = []
y = 0
for x in range(0,SIZE_GRID-1):
rays.append(list(bresenham(x_player,y_player,x,y)))
rays = compute_rays(x_player,y_player,x,y, rays)
x = SIZE_GRID-1
for y in range(0,SIZE_GRID-1):
rays.append(list(bresenham(x_player,y_player,x,y)))
rays = compute_rays(x_player,y_player,x,y, rays)
y = SIZE_GRID-1
for x in range(SIZE_GRID-1,0,-1):
rays.append(list(bresenham(x_player,y_player,x,y)))
rays = compute_rays(x_player,y_player,x,y, rays)
x = 0
for y in range(SIZE_GRID-1,0,-1):
rays.append(list(bresenham(x_player,y_player,x,y)))
rays = compute_rays(x_player,y_player,x,y, rays)
# create the grid
@ -67,7 +98,7 @@ if __name__=="__main__":
nb_cells = 0
rgb = 0
for ray in rays:
for tile in ray[1:]:
for tile in ray:
fill_rect(tile[0], tile[1], (rgb,rgb,rgb))
nb_cells += 1
rgb += int(200 / len(rays))
@ -78,8 +109,8 @@ if __name__=="__main__":
str_ray = "; Nb rays: {}\n".format(len(rays))
str_ray += "; A ray: length (nb_tiles), offset_from_view_in_world_low, offset_from_view_in_world_high, offset_view\nRays:\n"
for ray in rays:
str_ray += ".byte " + str(len(ray)-1)
for tile in ray[1:]:
str_ray += ".byte " + str(len(ray))
for tile in ray:
offset_view = tile[0] + SIZE_GRID*tile[1]
offset_world = (tile[0] + WIDTH_WORLD*tile[1])
offset_world_low = offset_world & 0xFF
@ -90,4 +121,4 @@ if __name__=="__main__":
print(str_ray)
Im.show()

247
src/builder/builder.asm Normal file
View File

@ -0,0 +1,247 @@
; Copyright (C) 2019 Christophe Meneboeuf <christophe@xtof.info>
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
; All the dungeon builder is based on this article: http://journal.stuffwithstuff.com/2014/12/21/rooms-and-mazes/
.include "rooms.inc"
.include "maze.inc"
.include "../io/textio.inc"
.include "../monitor.inc"
.include "../world.inc"
.include "../memory.inc"
.import World
.import Random8
.import Grow_Maze ; to patch
.export Get_Size_Maze
.export Init_Dimensions_Maze
.export Build_Level
.export Rooms
.export WIDTH_MAZE
.export HEIGHT_MAZE
.define MAX_NB_ROOMS 64 ; MAX_NB_ROOMS *MUST BE* <= 64
.BSS
; Describes a room to be built
; typedef struct {
; uint8_t height;
; uint8_t width;
; uint8_t x;
; uint8_t y;
; } room_t;
.define SIZEOF_ROOM_T 4
.align 256
Rooms: .res SIZEOF_ROOM_T*MAX_NB_ROOMS ; MAX 1 page of data!
WIDTH_MAZE: .res 1
HEIGHT_MAZE: .res 1
.DATA
STR_SIZE_MAZE_1: ASCIIZ "PLEASE ENTER THE SIZE OF THE LEVEL."
STR_SIZE_MAZE_2: ASCIIZ "A:TINY B:SMALL C:NORMAL D:BIG E:HUGE"
STR_SIZE_MAZE_3: ASCIIZ "PLEASE ENTER A VALID CHOICE."
STR_ROOMS: ASCIIZ "CARVING ROOMS..."
STR_MAZE: ASCIIZ "GROWING THE MAZE..."
STR_DOORS: ASCIIZ "OPENING DOORS..."
STR_DEADENDS: ASCIIZ "FILLING DEAD ENDS..."
.CODE
; @brief Asks for the size of the maze
; Returns Width in X and Height in Y
Get_Size_Maze:
; User input
PRINT STR_SIZE_MAZE_1
choice_size_maze:
PRINT STR_SIZE_MAZE_2
jsr Cin_Char
; switch case over the input
tst_tiny:
cmp #$C1
bne tst_small
ldx #16
ldy #16
rts
tst_small:
cmp #$C2
bne tst_medium
ldx #24
ldy #24
rts
tst_medium:
cmp #$C3
bne tst_big
ldx #32
ldy #32
rts
tst_big:
cmp #$C4
bne tst_huge
ldx #48
ldy #48
rts
tst_huge:
cmp #$C5
bne bad_size
ldx #64
ldy #64
rts
bad_size:
PRINT STR_SIZE_MAZE_3
jmp choice_size_maze
; @brief Fills border walls
; @param type of the "wall" in A
; destroys ZERO_2_1, ZERO_2_2
.define ADDR_WORLD ZERO_2_1
.macro WORLD_NEXT_LINE
clc
lda ADDR_WORLD
adc #WIDTH_WORLD
sta ADDR_WORLD
lda ADDR_WORLD+1
adc #0
sta ADDR_WORLD+1
.endmacro
; DO NOT MESS WITH THIS FUNCTION: IT IS PATCHED!!
.define PATCH_WIDTH_MAZE_1 0
.define PATCH_HEIGHT_MAZE_2 0
_build_fences:
ldx #<World
stx ADDR_WORLD
ldx #>World
stx ADDR_WORLD+1
ldy #PATCH_WIDTH_MAZE_1
loop_wall_top:
sta (ADDR_WORLD), Y
dey
bne loop_wall_top
sta (ADDR_WORLD), Y
ldx #PATCH_HEIGHT_MAZE_2
loop_wall_left_right:
pha
WORLD_NEXT_LINE
pla
ldy #PATCH_WIDTH_MAZE_1
sta (ADDR_WORLD), Y
ldy #0
sta (ADDR_WORLD), Y
dex
bne loop_wall_left_right
pha
WORLD_NEXT_LINE
pla
ldy #PATCH_WIDTH_MAZE_1
loop_wall_bottom:
sta (ADDR_WORLD), Y
dey
bne loop_wall_bottom
sta (ADDR_WORLD), Y
rts
.undefine ADDR_WORLD
; @brief Sets the Maze's dimentions
; @param width in X
; @param height in Y
Init_Dimensions_Maze:
stx WIDTH_MAZE
; patch WIDTH_MAZE usage NO MORE PATCH: comment to be removed
dex
stx _build_fences + $9
stx _build_fences + $23
stx _build_fences + $3D
; dex
; dex
; dex
; stx Grow_Maze + $C
; patch HEIGHT_MAZE usage NO MORE PATCH: comment to be removed
sty HEIGHT_MAZE
dey
dey
sty _build_fences + $12
; dey
; dey
; sty Grow_Maze + $19
rts
; @brief Builds a whole level
.define DST_WORLD World
.define ADDR_TO_PATCH init_world_line + 3
.define NB_ROOMS ZERO_8_2
Build_Level:
; Filling World with ACTORS::WALL_1
ldy #HEIGHT_WORLD
init_world:
ldx #0
init_world_line:
lda #ACTORS::WALL_1
sta DST_WORLD, x
inx
cpx #WIDTH_WORLD
bne init_world_line
; patching DST_WORLD
lda ADDR_TO_PATCH
clc
adc #WIDTH_WORLD
sta ADDR_TO_PATCH
lda ADDR_TO_PATCH + 1
adc #0
sta ADDR_TO_PATCH + 1
dey
bne init_world
PRINT STR_ROOMS
lda #MAX_NB_ROOMS+1
jsr Carve_Rooms
sta NB_ROOMS
lda #ACTORS::FLOOR_1
jsr _build_fences
PRINT STR_MAZE
jsr Grow_Maze
lda #ACTORS::WALL_1
jsr _build_fences
PRINT STR_DOORS
.define PERCENT_7 #17
ldx PERCENT_7
lda NB_ROOMS
jsr Connect_Rooms
PRINT STR_DEADENDS
jsr Remove_Dead_Ends
rts

19
src/builder/builder.inc Normal file
View File

@ -0,0 +1,19 @@
; Copyright (C) 2019 Christophe Meneboeuf <christophe@xtof.info>
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
.import Get_Size_Maze
.import Init_Dimensions_Maze
.import Build_Level
.import Rooms

534
src/builder/maze.asm Normal file
View File

@ -0,0 +1,534 @@
; Copyright (C) 2019 Christophe Meneboeuf <christophe@xtof.info>
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be usefuELEFT,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
.include "../memory.inc"
.include "../world.inc"
.include "../random.inc"
.include "../math.inc"
.export Grow_Maze
.export Remove_Dead_Ends
.import Compute_Maze_Addr
.import World
.import WIDTH_MAZE
.import HEIGHT_MAZE
; using HGR2 space as the stack
; Stack contains pointers to tiles (2 byte long)
.define STACK_ADDR $3FFE ; Will be 2 byte incremented before 1st push
.define FALSE #0
.define TRUE #1
.define Y_TILE ZERO_2_4
.define X_TILE ZERO_2_5
.define PTR_STACK ZERO_4_1 ; 2 bytes
.define PTR_NEW_TILE ZERO_4_3 ; 2 bytes
; increments the stack pointer then pushes address A:X (little endian)
; *ptr_stack = x
; *(ptr_stack+1) = a
; ptr_stack += 2
.macro PUSHAX
pha
; increment stack pointer
clc
lda PTR_STACK
adc #2
sta PTR_STACK
lda PTR_STACK+1
adc #0
sta PTR_STACK+1
; push A:X to the stack
pla
ldy #1
sta (PTR_STACK),Y
dey
txa
sta (PTR_STACK),Y
.endmacro
; ptr_newtile is offseted by -WIDTH_WORLD so we can access all its neighbors
; with positive offsets using Y indirect addressing
.macro PTR_UP_TILE
sec
ldy #0
lda (PTR_STACK),Y
sbc #(2*WIDTH_WORLD)
sta PTR_NEW_TILE
iny
lda (PTR_STACK),Y
sbc #0
sta PTR_NEW_TILE+1
.endmacro
.macro PTR_LEFT_TILE
sec
ldy #0
lda (PTR_STACK),Y
sbc #(WIDTH_WORLD+1)
sta PTR_NEW_TILE
iny
lda (PTR_STACK),Y
sbc #0
sta PTR_NEW_TILE+1
.endmacro
.macro PTR_RIGHT_TILE
sec
ldy #0
lda (PTR_STACK),Y
sbc #(WIDTH_WORLD-1)
sta PTR_NEW_TILE
iny
lda (PTR_STACK),Y
sbc #0
sta PTR_NEW_TILE+1
.endmacro
.macro PTR_DOWN_TILE
ldy #0
lda (PTR_STACK),Y
sta PTR_NEW_TILE
iny
lda (PTR_STACK),Y
sta PTR_NEW_TILE+1
.endmacro
.macro PTR_CURR_TILE
sec
ldy #0
lda (PTR_STACK),Y
sbc #(WIDTH_WORLD)
sta PTR_NEW_TILE
iny
lda (PTR_STACK),Y
sbc #0
sta PTR_NEW_TILE+1
.endmacro
; test if the tile offseted from PTR_NEW_TILE is walkable
.macro ISWALKABLE offset
ldy offset
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE+1
bcc cannot_carve
.endmacro
.define OFFSET_NIL #WIDTH_WORLD
.define OFFSET_UP #0
.define OFFSET_RIGHT #(WIDTH_WORLD+1)
.define OFFSET_DOWN #(2*WIDTH_WORLD)
.define OFFSET_LEFT #(WIDTH_WORLD-1)
.macro IS_TILE_WALLED
PTR_CURR_TILE
ldy OFFSET_NIL
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE
bcc end_loop_stack
ldy OFFSET_UP
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE
bcc end_loop_stack
ldy OFFSET_RIGHT
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE
bcc end_loop_stack
ldy OFFSET_DOWN
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE
bcc end_loop_stack
ldy OFFSET_LEFT
lda (PTR_NEW_TILE),Y
cmp #ACTORS::WALKABLE
bcc end_loop_stack
.endmacro
; @brief Fills the empty space with a perfect maze
.define PATCH_WIDTH_MAZE_4 0
.define PATCH_HEIGHT_MAZE_4 0
Grow_Maze:
; Groth start location
ldx #2
stx X_TILE
stx Y_TILE
loop_grow_maze:
; init the stack
lda #<STACK_ADDR
sta PTR_STACK
lda #>STACK_ADDR
sta PTR_STACK+1
; Test if the tile is suitable
ldy Y_TILE
ldx X_TILE
jsr Compute_Maze_Addr ; result addr in A:X
; test if the tile is walled
PUSHAX
IS_TILE_WALLED
; carve
ldy #WIDTH_WORLD
lda #ACTORS::FLOOR_1
sta (PTR_NEW_TILE),Y
.define IDX ZERO_2_6
; while the stack is not empty: carve
loop_stack:
jsr _random_directions
tax
stx IDX
loop_find_dir: ; find a direction suitable to carvingm
jsr _can_carve
cmp #1 ; cannot carve -> test other directions
beq carve_the_tile
lda IDX
and #3 ; 4th direction?
cmp #3
beq test_stack
inc IDX
ldx IDX
jmp loop_find_dir
test_stack:
lda PTR_STACK+1
cmp #>(STACK_ADDR+2)
bne unstack
lda PTR_STACK
cmp #<(STACK_ADDR+2)
beq end_loop_stack ; stack is empty -> break
unstack:
sec
lda PTR_STACK
sbc #2
sta PTR_STACK
lda PTR_STACK+1
sbc #0
sta PTR_STACK+1
jmp loop_stack
carve_the_tile:
; carve the tile
ldy #0
lda #ACTORS::FLOOR_1
sta (PTR_NEW_TILE),Y
jmp loop_stack
end_loop_stack:
inc X_TILE
ldx WIDTH_MAZE
dex
dex
cpx X_TILE
beq incr_y_tile
jmp loop_grow_maze
incr_y_tile:
ldx #2
stx X_TILE
inc Y_TILE
ldy HEIGHT_MAZE
dey
cpy Y_TILE
beq end_loop_grow_maze
jmp loop_grow_maze
end_loop_grow_maze:
rts
.enum
EUP = 0
ERIGHT
EDOWN
ELEFT
.endenum
; 24 direction quartets
RandomDirection:
.byte EUP,ERIGHT,EDOWN,ELEFT,EUP,ERIGHT,ELEFT,EDOWN,EUP,EDOWN,ERIGHT,ELEFT,EUP,EDOWN,ELEFT,ERIGHT,EUP,ELEFT,ERIGHT,EDOWN,EUP,ELEFT,EDOWN,ERIGHT
.byte ERIGHT,EUP,EDOWN,ELEFT,ERIGHT,EUP,ELEFT,EDOWN,ERIGHT,EDOWN,EUP,ELEFT,ERIGHT,EDOWN,ELEFT,EUP,ERIGHT,ELEFT,EUP,EDOWN,ERIGHT,ELEFT,EDOWN,EUP
.byte EDOWN,ERIGHT,EUP,ELEFT,EDOWN,ERIGHT,ELEFT,EUP,EDOWN,ELEFT,EUP,ERIGHT,EDOWN,ELEFT,ERIGHT,EUP,EDOWN,EUP,ELEFT,ERIGHT,EDOWN,EUP,ERIGHT,ELEFT
.byte ELEFT,ERIGHT,EDOWN,EUP,ELEFT,ERIGHT,EDOWN,EUP,ELEFT,EDOWN,ERIGHT,EUP,ELEFT,EDOWN,EUP,ERIGHT,ELEFT,EUP,ERIGHT,EDOWN,ELEFT,EUP,EDOWN,ERIGHT
; Uses a precomputed table to quickly return an offset to one of the direction quartets
_random_directions:
jsr Random8
and #31
ldx #24
jsr Modulus
asl
asl ; offset to a direction quartet
rts
; @brief Returns A=1 if the tile can be carved, A=0 otherwise.
; some difficulties for the branches to be in range to end_can_carve.
; thus the jsr and jmp and the breakdown of the routine
_can_carve:
lda RandomDirection,X
can_carve_up:
cmp #EUP
bne can_carve_right
jmp _can_carve_up
can_carve_right:
cmp #ERIGHT
bne can_carve_down
jmp _can_carve_right
can_carve_down:
cmp #EDOWN
bne can_carve_left
PTR_DOWN_TILE ; ptr_newtile = down - width_world
ISWALKABLE OFFSET_NIL ; the new tile
ISWALKABLE OFFSET_RIGHT ; new tile's right neighbor
ISWALKABLE OFFSET_DOWN ; new tile's bottom neighbor
ISWALKABLE OFFSET_LEFT ; new tile's left neighbor
jmp _save_ptr_newtile
can_carve_left:
cmp #ELEFT
bne end_can_carve
PTR_LEFT_TILE ; ptr_newtile = left - width_world
ISWALKABLE OFFSET_NIL ; the new tile
ISWALKABLE OFFSET_UP ; new tile's upper neighbor
ISWALKABLE OFFSET_DOWN ; new tile's bottom neighbor
ISWALKABLE OFFSET_LEFT ; new tile's left neighbor
jmp _save_ptr_newtile
cannot_carve:
lda #0
end_can_carve:
rts
_can_carve_up:
PTR_UP_TILE ; ptr_newtile = up - width_world
ISWALKABLE OFFSET_NIL ; the new tile
ISWALKABLE OFFSET_UP ; new tile's upper neighbor
ISWALKABLE OFFSET_RIGHT ; new tile's right neighbor
ISWALKABLE OFFSET_LEFT ; new tile's left neighbor
jmp _save_ptr_newtile
_can_carve_right:
PTR_RIGHT_TILE ; ptr_newtile = rigth - width_world
ISWALKABLE OFFSET_NIL ; the new tile
ISWALKABLE OFFSET_RIGHT ; new tile's right neighbor
ISWALKABLE OFFSET_DOWN ; new tile's bottom neighbor
ISWALKABLE OFFSET_UP ; new tile's upper neighbor
; jmp _save_ptr_newtile
; save new tile on the stack
_save_ptr_newtile:
clc
lda PTR_NEW_TILE
adc #WIDTH_WORLD
sta PTR_NEW_TILE
tax
lda PTR_NEW_TILE+1
adc #0
sta PTR_NEW_TILE+1
PUSHAX
;CREUSER???
lda #1
jmp end_can_carve
.undefine Y_TILE
.undefine X_TILE
.undefine PTR_STACK
.undefine PTR_NEW_TILE
.define PTR_TILE ZERO_2_1 ; 2 bytes
.define PTR_NEXT_TILE ZERO_2_3 ; 2 bytes
.define NB_WALLS ZERO_2_5
.define ADDR_END ZERO_4_1 ; 2 bytes
.define HEIGHTxWIDTH WIDTH_WORLD*HEIGHT_WORLD
; @brief Removes all the dead ends
Remove_Dead_Ends:
; Compute addr_end as the preprocessor cannot handle 16bit multiplications (???)
lda #WIDTH_WORLD
sta FAC1
lda #HEIGHT_WORLD
sta FAC2
jsr mul8
sta ADDR_END+1
stx ADDR_END
lda #<World
clc
adc ADDR_END
sta ADDR_END
lda #>World
adc ADDR_END+1
sta ADDR_END+1
; starting tile: offsetted by - width_world
lda #<(World + 1) ; &World[1][1] - width_world
sta PTR_TILE
lda #>(World + 1)
sta PTR_TILE+1
loop_tiles:
jsr _is_tile_dead_end
lda NB_WALLS
cmp #3
bcc next_tile
jsr _follow_dead_end
next_tile:
clc
lda PTR_TILE
adc #1
sta PTR_TILE
lda PTR_TILE+1
adc #0
sta PTR_TILE+1
; end?
lda PTR_TILE+1
cmp ADDR_END+1
bne loop_tiles
lda PTR_TILE
cmp ADDR_END
bne loop_tiles
end_loop_tiles:
rts
.undefine ADDR_END
_follow_dead_end:
; saving ptr_tile
lda PTR_TILE
pha
lda PTR_TILE+1
pha
loop_follow:
ldy #WIDTH_WORLD
lda #ACTORS::WALL_1
sta (PTR_TILE), Y
lda PTR_NEXT_TILE
sta PTR_TILE
lda PTR_NEXT_TILE+1
sta PTR_TILE+1
jsr _is_tile_dead_end
lda NB_WALLS
cmp #3
bcs loop_follow
end_loop_follow:
; restoring ptr_tile
pla
sta PTR_TILE+1
pla
sta PTR_TILE
rts
.define ADD_FACTOR ZERO_4_1
; REM: PTR_TILE is already offsetted by -WIDTH_WORLD
; for easy access to adjacent tiles by indirect indexing
; Returns : NB_WALLS >= 3 if it is a dead end
_is_tile_dead_end:
lda #0
sta NB_WALLS
ldy #WIDTH_WORLD
sty ADD_FACTOR
; Returns if the tile is a wall
lda #ACTORS::WALKABLE
cmp (PTR_TILE), Y
bcc end_tst_up_tile
tst_up_tile:
ldy #0
cmp (PTR_TILE), Y
bcc up_non_walkable
sty ADD_FACTOR
bcs tst_right_tile
up_non_walkable:
inc NB_WALLS
tst_right_tile:
ldy #(WIDTH_WORLD + 1)
cmp (PTR_TILE), Y
bcc right_non_walkable
sty ADD_FACTOR
bcs tst_down_tile
right_non_walkable:
inc NB_WALLS
tst_down_tile:
ldy #(2*WIDTH_WORLD)
cmp (PTR_TILE), Y
bcc down_non_walkable
sty ADD_FACTOR
bcs tst_left_tile
down_non_walkable:
inc NB_WALLS
tst_left_tile:
ldy #(WIDTH_WORLD - 1)
cmp (PTR_TILE), Y
bcc left_non_walkable
sty ADD_FACTOR
bcs end_tests
left_non_walkable:
inc NB_WALLS
end_tests:
; computing ptr_next_tile
clc
lda PTR_TILE
adc ADD_FACTOR
sta PTR_NEXT_TILE
lda PTR_TILE+1
adc #0
sta PTR_NEXT_TILE+1
; offseting ptr_next_tile
sec
lda PTR_NEXT_TILE
sbc #WIDTH_WORLD
sta PTR_NEXT_TILE
lda PTR_NEXT_TILE+1
sbc #0
sta PTR_NEXT_TILE+1
end_tst_up_tile:
rts

2
src/builder/maze.inc Normal file
View File

@ -0,0 +1,2 @@
.import Grow_Maze
.import Remove_Dead_Ends

664
src/builder/rooms.asm Normal file
View File

@ -0,0 +1,664 @@
; Copyright (C) 2019 Christophe Meneboeuf <christophe@xtof.info>
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
.include "../memory.inc"
.include "../random.inc"
.include "../world.inc"
.include "../math.inc"
.export Carve_Rooms
.export Connect_Rooms
.import Rooms
.import Compute_Maze_Addr
.import World
.import WIDTH_MAZE
.import HEIGHT_MAZE
.define TRUE #1
.define FALSE #0
.BSS
; Configration to build rooms
.struct Config_Room
width_min .byte
width_max .byte
height_min .byte
height_max .byte
.endstruct
.CODE
.define NB_ATTEMPTS ZERO_2_1
.define NB_ROOMS_OK ZERO_2_2
.define NB_ROOMS_TO_DRAW NB_ATTEMPTS
.define IDX_ROOMS ZERO_3
; @params A nb_attempts
; @returns NB_ROOMS_OK in A
Carve_Rooms:
sta NB_ATTEMPTS
; height_max + width_max shall be < 64
lda #9
sta Config_Room::height_max
lda #3
sta Config_Room::height_min
lda #11
sta Config_Room::width_max
lda #3
sta Config_Room::width_min
ldx #0
stx NB_ROOMS_OK
loop_rooms:
dec NB_ATTEMPTS
beq end_loop_rooms
lda NB_ROOMS_OK ; NB_ROOMS_OK*sizeof(room_t) -> X
asl
asl
tax
jsr _Build_Room
lda NB_ROOMS_OK
jsr _Is_intersecting
cmp TRUE ; not intersecting with another room?
beq loop_rooms
inc NB_ROOMS_OK
clc
bcc loop_rooms
end_loop_rooms:
lda NB_ROOMS_OK
sta NB_ROOMS_TO_DRAW
ldx #0
loop_draw_rooms:
jsr _Draw_Room
inx
inx
inx
inx
dec NB_ROOMS_TO_DRAW
bne loop_draw_rooms
end_loop_draw_rooms:
lda NB_ROOMS_OK
rts
.undefine NB_ATTEMPTS
.undefine NB_ROOMS_OK
.undefine NB_ROOMS_TO_DRAW
.undefine IDX_ROOMS
.define ADDR_WORLD ZERO_2_3 ; 2 bytes
.define NB_LINE ZERO_2_5
.define LINE_LENGTH ZERO_2_6
.define IDX_ROOMS ZERO_3
_Draw_Room:
stx IDX_ROOMS
ldy Rooms+3, X ; room->y
lda Rooms+2, X ; rooms->x
tax
PUSH ZERO_2_1
PUSH ZERO_2_2
jsr Compute_Maze_Addr
stx ADDR_WORLD
sta ADDR_WORLD+1
POP ZERO_2_2
POP ZERO_2_1
ldx IDX_ROOMS
ldy Rooms, X ; room->height
sty NB_LINE
lda Rooms+1, X ; room->width
sta LINE_LENGTH
loop_draw_line:
lda #ACTORS::FLOOR_1
ldy #0
loop_draw_tile:
sta (ADDR_WORLD), Y
iny
cpy LINE_LENGTH
bne loop_draw_tile
end_loop_draw_tile:
clc
lda ADDR_WORLD
adc #WIDTH_WORLD
sta ADDR_WORLD
lda ADDR_WORLD+1
adc #0
sta ADDR_WORLD+1
dec NB_LINE
bne loop_draw_line
end_loop_draw_line:
ldx IDX_ROOMS
rts
.undefine ADDR_WORLD
.undefine NB_LINE
.undefine LINE_LENGTH
.undefine IDX_ROOMS
.define MODULUS ZERO_2_3
.define WIDTH_ROOM ZERO_2_5
.define HEIGHT_ROOM ZERO_2_6
.define OFFSET_ROOMS ZERO_5_1
; @param X offset to the room to be built from Rooms
; @return offset room in X
_Build_Room:
stx OFFSET_ROOMS
; room.height = config->height_min + Random8() % (config->height_max - config->height_min - 1);
sec
lda Config_Room::height_max
sbc Config_Room::height_min
sbc #1
sta MODULUS
jsr Random8
and #7
ldx MODULUS
jsr Modulus
ldx OFFSET_ROOMS
clc
adc Config_Room::height_min
ora #1
sta Rooms, x ; height
sta HEIGHT_ROOM
inx
stx OFFSET_ROOMS
; room.width = config->width_min + Random8() % (config->width_max - config->width_min - 1);
sec
lda Config_Room::width_max
sbc Config_Room::width_min
sbc #1
sta MODULUS
jsr Random8
and #$F ; room's height shall be < 16
ldx MODULUS
jsr Modulus
ldx OFFSET_ROOMS
clc
adc Config_Room::width_min
ora #1
sta Rooms, x ; width
sta WIDTH_ROOM
inx
stx OFFSET_ROOMS
; room.x = 3 + Random8() % (WIDTH_MAZE - room.width - 5);
sec
lda WIDTH_MAZE
sbc WIDTH_ROOM
sbc #5
sta MODULUS
jsr Random8
and #$7F
ldx MODULUS
jsr Modulus
ldx OFFSET_ROOMS
clc
adc #3
ora #1
sta Rooms, x ; x
inx
stx OFFSET_ROOMS
; room.y = 3 + Random8() % (HEIGHT_MAZE - room.height - 5);
sec
lda HEIGHT_MAZE
sbc HEIGHT_ROOM
sbc #5
sta MODULUS
jsr Random8
and #$7F
ldx MODULUS
jsr Modulus
ldx OFFSET_ROOMS
clc
adc #3
ora #1
sta Rooms, x ; y
inx
stx OFFSET_ROOMS
rts
.undefine MODULUS
.undefine WIDTH_ROOM
.undefine HEIGHT_ROOM
.undefine OFFSET_ROOMS
; @brief test ifthe room is intersecting with other rooms
; @param A : nb rooms already carved
; @return 0 if no intersection, 1 otherwise
.define NB_ROOMS ZERO_3
.define OFFSET_Y ZERO_2_4
.define OFFSET_X ZERO_2_5
_Is_intersecting:
cmp #0
bne compare
; first room
lda FALSE
clc
bcc end_intersecting ; branch always
; previous rooms were carved
compare:
sta NB_ROOMS
asl ; * sizeof(room_t) to get offset to the last room randomized
asl
tax ; offset to new room in X
stx OFFSET_X
ldy #0 ; offset to carved rooms in Y
loop_intersecting: ; each test must be true
sty OFFSET_Y
clc
lda Rooms+2, Y ; room->x
adc Rooms+1, Y ; room->width
cmp Rooms+2, X
bcc false ; branch if room->x + room->width < new_room->x
clc
lda Rooms+2, X ; new_room->x
adc Rooms+1, X ; new_room->width
cmp Rooms+2, Y ; room->x
bcc false ; branch if new_room->x + new_room->width < room->x
clc
lda Rooms+3, Y ; room->y
adc Rooms, Y ; room->height
cmp Rooms+3, X ; new_room->y
bcc false ; branch if room->y + room->height < new_room->y
clc
lda Rooms+3, X ; new_room->y
adc Rooms, X ; new_room->height
cmp Rooms+3, Y ; room->y
bcc false ; branch if new_room->y + new_room->height < room->y
; all test are true: rooms are intersecting
lda TRUE ; return value
clc
bcc end_intersecting
false:
ldx OFFSET_X
lda OFFSET_Y
adc #4
tay
dec NB_ROOMS
bne loop_intersecting
lda FALSE ; no room intersects
end_intersecting:
rts
; using HGR2 space as the stack
; Stack contains pointers to the tiles encompassing a room (except the corners)
.define STACK_ADDR $4000
.define PTR_TILE_TOP ZERO_2_3 ; 2 bytes
.define PTR_TILE_BOT ZERO_4_4 ; 2 bytes
.define PTR_TILE_LEFT ZERO_2_3 ; 2 bytes
.define PTR_TILE_RIGHT ZERO_4_4 ; 2 bytes
.define PTR_TILE ZERO_2_1
.define PTR_STACK ZERO_4_1 ; 2 bytes
.define PROBABILITY_OPENING ZERO_4_3
.define NB_DOORS ZERO_5_1
.define NB_WALKABLE ZERO_5_2
.define SIZE_STACK ZERO_5_3 ; 2 bytes
.define WIDTH_ROOM ZERO_2_5
.define HEIGHT_ROOM ZERO_2_6
.define OFFSET_ROOMS ZERO_5_5
; @brief Connects the rooms to the maze's galleries
; @param A number of rooms
; @param X probability to make an opening in a wall tile
; @detail One opening is made, then all remaning encompassing wall tiles
; can be opened. Depending on the provided probability
Connect_Rooms:
sta NB_ROOMS
stx PROBABILITY_OPENING
lda #0
sta OFFSET_ROOMS
; for each room
loop_connect_rooms:
; # Build a stack of encompassing tiles. Except corners
lda #<STACK_ADDR
sta PTR_STACK
lda #>STACK_ADDR
sta PTR_STACK+1
; ## stacking horizontal walls
; ### init ptr_top
ldx OFFSET_ROOMS
inx
inx
lda Rooms, X ; room->x
sta PTR_TILE_TOP
clc
lda #<World
adc PTR_TILE_TOP
sta PTR_TILE_TOP
lda #>World
adc #0
sta PTR_TILE_TOP+1
inx
lda Rooms, X ; room->y
tax
dex
stx FAC1
lda #WIDTH_WORLD
sta FAC2
jsr mul8
tay
txa
clc
adc PTR_TILE_TOP
sta PTR_TILE_TOP
tya
adc PTR_TILE_TOP+1
sta PTR_TILE_TOP+1
; ### init ptr_bottom
ldx OFFSET_ROOMS
lda Rooms, X ; room->height
sta HEIGHT_ROOM
tax
inx
stx FAC1
lda #WIDTH_WORLD
sta FAC2
jsr mul8
tay
txa
clc
adc PTR_TILE_TOP
sta PTR_TILE_BOT
tya
adc PTR_TILE_TOP+1
sta PTR_TILE_BOT+1
; ## stacking
ldx OFFSET_ROOMS
inx
lda Rooms, X ; room->width
sta WIDTH_ROOM
; for x = 0; x < room->width; x++
ldx #0
loop_stack_horiz:
ldy #0
clc
txa
adc PTR_TILE_TOP
sta (PTR_STACK), Y
iny
lda #0
adc PTR_TILE_TOP+1
sta (PTR_STACK), Y
iny
txa
adc PTR_TILE_BOT
sta (PTR_STACK), Y
iny
lda #0
adc PTR_TILE_BOT+1
sta (PTR_STACK), Y
iny
; incr ptr_stack
tya
clc
adc PTR_STACK
sta PTR_STACK
lda #0
adc PTR_STACK+1
sta PTR_STACK+1
; next x
inx
cpx WIDTH_ROOM
bne loop_stack_horiz
; ## stacking vertical walls
; ### init ptr_left
clc
lda #(WIDTH_WORLD-1)
adc PTR_TILE_TOP
sta PTR_TILE_LEFT
lda #0
adc PTR_TILE_TOP+1
sta PTR_TILE_LEFT+1
; ### init ptr_right
clc
lda WIDTH_ROOM
adc #1
adc PTR_TILE_LEFT
sta PTR_TILE_RIGHT
lda #0
adc PTR_TILE_LEFT+1
sta PTR_TILE_RIGHT+1
; ### stacking
; for y = 0; y < room->height; y++
ldx #0
loop_stack_vertical:
ldy #1
lda PTR_TILE_LEFT+1
sta (PTR_STACK), Y
dey
lda PTR_TILE_LEFT
sta (PTR_STACK), Y
clc
adc #WIDTH_WORLD
sta PTR_TILE_LEFT
lda PTR_TILE_LEFT+1
adc #0
sta PTR_TILE_LEFT+1
iny
iny
iny
lda PTR_TILE_RIGHT+1
sta (PTR_STACK), Y
dey
lda PTR_TILE_RIGHT
sta (PTR_STACK), Y
clc
adc #WIDTH_WORLD
sta PTR_TILE_RIGHT
lda PTR_TILE_RIGHT+1
adc #0
sta PTR_TILE_RIGHT+1
; incr ptr_stack
clc
lda #4
adc PTR_STACK
sta PTR_STACK
lda #0
adc PTR_STACK+1
sta PTR_STACK+1
; next y
inx
cpx HEIGHT_ROOM
bne loop_stack_vertical
; ## Compute stack's size
; UTILISER DIRECTEMENT L ADRESSE DE FIN ET BREAKER QUAND ON L ATTEINT
sec
lda PTR_STACK
sbc #<STACK_ADDR
pha
lda PTR_STACK+1
sbc #>STACK_ADDR
lsr
sta SIZE_STACK+1
pla
ror
sta SIZE_STACK
; # Opening the first door
lda #0
sta NB_DOORS
lda #<STACK_ADDR
sta PTR_STACK ; here stack size < 128, no need for hsb of the address
loop_first_door:
jsr Random8
ldx SIZE_STACK
jsr Modulus
asl
tay
lda (PTR_STACK), Y
;PTR_TILE = *PTR_STACK - WIDTH_WORLD
sec
sbc #WIDTH_WORLD
sta PTR_TILE
iny
lda (PTR_STACK), Y
sbc #0
sta PTR_TILE+1
jsr _nb_walkable
lda NB_WALKABLE
cmp #2
bcc loop_first_door ; nb_walkable < 2
inc NB_DOORS
ldy #WIDTH_WORLD
lda #ACTORS::FLOOR_1
sta (PTR_TILE), Y
; # Opening the other doors
.define IDX ZERO_2_3
lda #<STACK_ADDR
sta PTR_STACK ; here stack size < 128, no need for hsb of the address
lda #$FF
sta IDX
loop_other_doors:
inc IDX
; test if end
lda SIZE_STACK
asl
cmp IDX
beq end_loop_other_doors
; random number to be compare to the probability of a door
jsr Random8
cmp PROBABILITY_OPENING
bcc test_door
beq test_door
inc IDX
bcs loop_other_doors ; always jump as the previous bcc failed
; test if the tile can be linked to the maze
test_door:
ldy IDX
lda (PTR_STACK), Y
;PTR_TILE = *PTR_STACK - WIDTH_WORLD
sec
sbc #WIDTH_WORLD
sta PTR_TILE
iny
lda (PTR_STACK), Y
sbc #0
sta PTR_TILE+1
sty IDX
jsr _nb_walkable
lda NB_WALKABLE
cmp #2
bcs carve_a_door
bcc loop_other_doors ; always jump as the previous bcs failed
carve_a_door:
ldy #WIDTH_WORLD
lda #ACTORS::FLOOR_1
sta (PTR_TILE), Y
inc NB_DOORS
jmp loop_other_doors
end_loop_other_doors:
dec NB_ROOMS
beq end_loop_connect_rooms
; next room
lda OFFSET_ROOMS
clc
adc #.sizeof(Config_Room)
sta OFFSET_ROOMS
jmp loop_connect_rooms
end_loop_connect_rooms:
rts
; @brief returns the number of walkable neighbours. >= 2 if it can be carved
; @detailed PTR_TILE is offsetted by -WIDTH_WORLD
_nb_walkable:
lda #0
sta NB_WALKABLE
lda #ACTORS::FLOOR_1
tst_up:
ldy #0
cmp (PTR_TILE), Y
bne tst_left
inc NB_WALKABLE
tst_left:
ldy #(WIDTH_WORLD-1)
cmp (PTR_TILE), Y
bne tst_right
inc NB_WALKABLE
tst_right:
ldy #(WIDTH_WORLD+1)
cmp (PTR_TILE), Y
bne tst_down
inc NB_WALKABLE
tst_down:
ldy #(2*WIDTH_WORLD)
cmp (PTR_TILE), Y
bne end_is_door_possible
inc NB_WALKABLE
end_is_door_possible:
rts
.undefine STACK_ADDR
.undefine PTR_TILE_TOP
.undefine PTR_TILE_BOT
.undefine PTR_TILE_LEFT
.undefine PTR_TILE_RIGHT
.undefine PTR_TILE
.undefine PTR_STACK
.undefine PROBABILITY_OPENING
.undefine NB_DOORS
.undefine NB_WALKABLE
.undefine SIZE_STACK
.undefine WIDTH_ROOM
.undefine HEIGHT_ROOM
.undefine OFFSET_ROOMS

18
src/builder/rooms.inc Normal file
View File

@ -0,0 +1,18 @@
; Copyright (C) 2019 Christophe Meneboeuf <christophe@xtof.info>
;
; This program is free software: you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation, either version 3 of the License, or
; (at your option) any later version.
;
; This program is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with this program. If not, see <http://www.gnu.org/licenses/>.
.import Carve_Rooms
.import Connect_Rooms

View File

@ -1,8 +1,11 @@
.include "memory.inc"
; nb of bytes to be displayed in DBG_TRACES[0]
.export DBG_TRACE
; bytes to be displayed
.export DBG_TRACES
.CODE
.define STROUT $DB3A ; Applesoft: OUTPUTS AY-POINTED NULL TERMINATED STRING
.define LINPTR $ED24 ; Applesoft: Displays the number A(high)X(low) in decimal
@ -39,3 +42,6 @@ str_space: .byte " ", 0
.BSS
DBG_TRACES: .res 7 ; bytes to be displayed by TRACE