diff --git a/compiler/test/TestCompilerOnExamples.kt b/compiler/test/TestCompilerOnExamples.kt index 7ad828452..c0577c67f 100644 --- a/compiler/test/TestCompilerOnExamples.kt +++ b/compiler/test/TestCompilerOnExamples.kt @@ -91,12 +91,14 @@ class TestCompilerOnExamplesCx16: FunSpec({ listOf( "vtui/testvtui", "amiga", + "bdmusic", "bobs", "cobramk3-gfx", "colorbars", "datetime", "highresbitmap", "kefrenbars", + "mandelbrot", "mandelbrot-gfx-colors", "multipalette", "rasterbars", @@ -131,6 +133,7 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({ "fibonacci", "line-circle-gfx", "line-circle-txt", + "maze", "mandelbrot", "mandelbrot-gfx", "numbergame", diff --git a/docs/source/todo.rst b/docs/source/todo.rst index ed04f210d..d1eb0d41c 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,9 +3,13 @@ TODO For next release ^^^^^^^^^^^^^^^^ +- have a proper option to move the evalstack rather than just assembly symbol redefine +- then make the cx16 virtual registers in syslib.p8 use that definition to be able to shift them around on non-cx16 targets + ... + Need help with ^^^^^^^^^^^^^^ - c128 target: various machine specific things (free zp locations, how banking works, getting the floating point routines working, ...) @@ -19,6 +23,9 @@ Compiler: - bool data type? see below - add some more optimizations in vmPeepholeOptimizer +- on non-cx16 targets: have an option that if zeropage=FULL, moves the cx16 virtual registers to ZP (same location as on x16?) + needs the dynamic base address for the symbols in syslib.p8 + also needs a trick to allocate them in ZP like Cx16Zeropage already does - vm Instruction needs to know what the read-registers/memory are, and what the write-register/memory is. this info is needed for more advanced optimizations and later code generation steps. - vm: implement remaining sin/cos functions in math.p8 diff --git a/examples/maze.p8 b/examples/maze.p8 new file mode 100644 index 000000000..55ef39ab9 --- /dev/null +++ b/examples/maze.p8 @@ -0,0 +1,293 @@ +%import textio + +; Even though prog8 only has support for extremely limited recursion, +; you can write recursive algorithms with a bit of extra work by building your own explicit stack structure. +; This program shows a depth-first maze generation algorithm (1 possible path from start to finish), +; and a depth-first maze solver algorithm, both using a stack to store the path taken. + +; Note: this program is compatible with C64 and CX16. + +main { + sub start() { + repeat { + maze.initialize() + maze.drawStartFinish() + maze.generate() + maze.drawStartFinish() + maze.solve() + maze.drawStartFinish() + + txt.print(" enter=new maze") + c64.CHRIN() + } + } + +} + +maze { + const uword screenwidth = txt.DEFAULT_WIDTH + const uword screenheight = txt.DEFAULT_HEIGHT + + const ubyte numCellsHoriz = (screenwidth-1) / 2 + const ubyte numCellsVert = (screenheight-1) / 2 + + ; maze start and finish cells + const ubyte startCx = 0 + const ubyte startCy = 0 + const ubyte finishCx = numCellsHoriz-1 + const ubyte finishCy = numCellsVert-1 + + ; cell properties + const ubyte STONE = 128 + const ubyte WALKED = 64 + const ubyte BACKTRACKED = 32 + const ubyte UP = 1 + const ubyte RIGHT = 2 + const ubyte DOWN = 4 + const ubyte LEFT = 8 + const ubyte WALLCOLOR = 12 + const ubyte EMPTYCOLOR = 0 + + ; unfortunately on larger screens (cx16), the number of cells exceeds 256 and doesn't fit in a regular array anymore. + uword cells = memory("cells", numCellsHoriz*numCellsVert, 0) + + ubyte[256] cx_stack + ubyte[256] cy_stack + ubyte stackptr + + sub draw() { + ubyte cx + ubyte cy + for cx in 0 to numCellsHoriz-1 { + for cy in 0 to numCellsVert-1 { + drawCell(cx, cy) + } + } + } + + sub generate() { + ubyte cx = startCx + ubyte cy = startCy + + stackptr = 0 + @(celladdr(cx,cy)) &= ~STONE + drawCell(cx, cy) + uword cells_to_carve = numCellsHoriz * numCellsVert - 1 + + repeat { +carve_restart_after_repath: + ubyte direction = choose_uncarved_direction() + if direction==0 { + ;backtrack + stackptr-- + if stackptr==255 { + ; stack empty. + ; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit) + if cells_to_carve { + if repath() + goto carve_restart_after_repath + } + return + } + cx = cx_stack[stackptr] + cy = cy_stack[stackptr] + } else { + cx_stack[stackptr] = cx + cy_stack[stackptr] = cy + stackptr++ + if stackptr==0 { + ; stack overflow, we can't track our path any longer. + ; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit) + if cells_to_carve { + if repath() + goto carve_restart_after_repath + } + return + } + @(celladdr(cx,cy)) |= direction + when direction { + UP -> { + cy-- + @(celladdr(cx,cy)) |= DOWN + } + RIGHT -> { + cx++ + @(celladdr(cx,cy)) |= LEFT + } + DOWN -> { + cy++ + @(celladdr(cx,cy)) |= UP + } + LEFT -> { + cx-- + @(celladdr(cx,cy)) |= RIGHT + } + } + @(celladdr(cx,cy)) &= ~STONE + cells_to_carve-- + drawCell(cx, cy) + } + } + + sub repath() -> ubyte { ; TODO BOOL + ; repath: try to find a new start cell with possible directions. + ; we limit our number of searches so that the algorith doesn't get stuck + ; for too long on bad rng... just accept a few unused cells in that case. + repeat 255 { + do { + cx = rnd() % numCellsHoriz + cy = rnd() % numCellsVert + } until not @(celladdr(cx, cy)) & STONE + if available_uncarved() + return true + } + return false + } + + sub available_uncarved() -> ubyte { + ubyte candidates = 0 + if cx>0 and @(celladdr(cx-1, cy)) & STONE + candidates |= LEFT + if cx0 and @(celladdr(cx, cy-1)) & STONE + candidates |= UP + if cy ubyte { + ubyte candidates = available_uncarved() + if not candidates + return 0 + + ubyte[4] bitflags = [LEFT,RIGHT,UP,DOWN] + repeat { + ubyte choice = candidates & bitflags[rnd() & 3] + if choice + return choice + } + } + } + + sub solve() { + ubyte cx = startCx + ubyte cy = startCy + const uword max_path_length = 1024 + + ; the path through the maze can be longer than 256 so doesn't fit in a regular array.... :( + uword pathstack = memory("pathstack", max_path_length, 0) + uword pathstackptr = 0 + + @(celladdr(cx,cy)) |= WALKED + txt.setcc(cx*2+1, cy*2+1, 81, 1) + + repeat { +solve_loop: + sys.waitvsync() + if cx==finishCx and cy==finishCy { + txt.home() + txt.print("found! path length: ") + txt.print_uw(pathstackptr) + return + } + + ubyte cell = @(celladdr(cx,cy)) + if cell & UP and not @(celladdr(cx,cy-1)) & (WALKED|BACKTRACKED) { + @(pathstack + pathstackptr) = UP + txt.setcc(cx*2+1, cy*2, 81, 3) + cy-- + } + else if cell & DOWN and not @(celladdr(cx,cy+1)) & (WALKED|BACKTRACKED) { + @(pathstack + pathstackptr) = DOWN + txt.setcc(cx*2+1, cy*2+2, 81, 3) + cy++ + } + else if cell & LEFT and not @(celladdr(cx-1,cy)) & (WALKED|BACKTRACKED) { + @(pathstack + pathstackptr) = LEFT + txt.setcc(cx*2, cy*2+1, 81, 3) + cx-- + } + else if cell & RIGHT and not @(celladdr(cx+1,cy)) & (WALKED|BACKTRACKED) { + @(pathstack + pathstackptr) = RIGHT + txt.setcc(cx*2+2, cy*2+1, 81, 3) + cx++ + } + else { + ; dead end, pop stack + pathstackptr-- + if stackptr==65535 { + txt.print("no solution?!") + return + } + @(celladdr(cx,cy)) |= BACKTRACKED + txt.setcc(cx*2+1, cy*2+1, 81, 2) + when @(pathstack + pathstackptr) { + UP -> { + txt.setcc(cx*2+1, cy*2+2, 81, 9) + cy++ + } + DOWN -> { + txt.setcc(cx*2+1, cy*2, 81, 9) + cy-- + } + LEFT -> { + txt.setcc(cx*2+2, cy*2+1, 81, 9) + cx++ + } + RIGHT -> { + txt.setcc(cx*2, cy*2+1, 81, 9) + cx-- + } + } + goto solve_loop + } + pathstackptr++ + if pathstackptr==max_path_length { + txt.print("stack overflow, path too long") + return + } + @(celladdr(cx,cy)) |= WALKED + txt.setcc(cx*2+1, cy*2+1, 81, 1) + } + } + + sub celladdr(ubyte cx, ubyte cy) -> uword { + return cells+(numCellsHoriz as uword)*cy+cx + } + + sub drawCell(ubyte cx, ubyte cy) { + ubyte x = cx * 2 + 1 + ubyte y = cy * 2 + 1 + ubyte doors = @(celladdr(cx,cy)) + if doors & UP + txt.setcc(x, y-1, ' ', EMPTYCOLOR) + if doors & RIGHT + txt.setcc(x+1, y, ' ', EMPTYCOLOR) + if doors & DOWN + txt.setcc(x, y+1, ' ', EMPTYCOLOR) + if doors & LEFT + txt.setcc(x-1, y, ' ', EMPTYCOLOR) + if doors & STONE + txt.setcc(x, y, 160, WALLCOLOR) + else + txt.setcc(x, y, 32, EMPTYCOLOR) + + if doors & WALKED + txt.setcc(x, y, 81, 1) + if doors & BACKTRACKED + txt.setcc(x, y, 81, 2) + } + + sub initialize() { + sys.memset(cells, numCellsHoriz*numCellsVert, STONE) + txt.fill_screen(160, WALLCOLOR) + drawStartFinish() + } + + sub drawStartFinish() { + txt.setcc(startCx*2+1,startCy*2+1,sc:'s',5) + txt.setcc(finishCx*2+1, finishCy*2+1, sc:'f', 13) + } +}