eris2010

Documentation: http://frombelow.net/projects/eris2010/
Clone: git clone https://git.frombelow.net/eris2010.git
Log | Files | Refs | Submodules | README | LICENSE

commit 11ac3d7a1e65ab5a0222b935c78bc3da2775cf13
parent 09fbd26bda3ce80c009d5f1706f8d82c63f5ebbe
Author: Gerd Beuster <gerd@frombelow.net>
Date:   Fri,  5 Nov 2021 21:21:13 +0100

Tali Forth 2 and Snake added

Diffstat:
A.gitmodules | 3+++
MREADME.md | 84++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mcontrib/asm/wozmon/wozmon.asm | 9++++++++-
Mcontrib/c/mmind/mmind.c | 4++--
Acontrib/forth | 1+
Mcontrib/write_default_files.sh | 54+++++++++++++++++++++++++++++++++---------------------
Mroms/os/os.asm | 22++++++++++++++++++++--
Mroms/os/os.inc | 5+----
Msw/asm/load_from_card/load_from_card.asm | 10++++------
Msw/asm/ttt/README.txt | 16++++++++--------
Msw/asm/ttt/board.asm | 12+-----------
Msw/asm/ttt/board_test.asm | 3++-
Msw/asm/ttt/computer_player.asm | 22+++++++++++++---------
Msw/asm/ttt/computer_player_test.asm | 561+++++--------------------------------------------------------------------------
Msw/asm/ttt/ttt.asm | 29+++++++++++------------------
Msw/c/blink/blink.c | 6++++++
Msw/c/cc65_eris/os.h | 6++++++
Msw/c/cc65_eris/os.inc | 6++++++
Msw/c/cc65_eris/os.s | 6++++++
Msw/c/conio_test/conio_test.c | 6++++++
Msw/c/dio_test/dio_test.c | 6++++++
Msw/c/fibonacci/fibonacci.c | 6++++++
Asw/c/snake/Makefile | 3+++
Asw/c/snake/snake.c | 306+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
24 files changed, 549 insertions(+), 637 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sw/TaliForth2"] + path = contrib/forth + url = https://git.frombelow.net/TaliForth2.git diff --git a/README.md b/README.md @@ -47,17 +47,19 @@ repository. ## Software -Eris 2010 can be programmed in assembler and C. Directory `sw/` -contains the game of Tic-Tac-Toe (no fan of WarGames should go without -one), the famous 10 PRINT program (check out [this +Eris 2010 can be programmed in assembler, C, and Forth. Directory +`sw/` contains the games Snake and Tic-Tac-Toe (no fan of WarGames +should go without one), the famous 10 PRINT program (check out [this book](https://10print.org/ "10 PRINT") on the cultural significance of 10 PRINT if you do not know it already), and some test programs. The `contrib/` directory contains additional third party software ported -to Eris 2010. Besides Wozmon, Steve Wozniak's "operating system" for -the Apple I, this repository contains a port of Microchess, the first -commercial game for microcomputers, some other games, and a port of -Eliza. The contrib directory also contains a port of Tiny BASIC, so -you can even program the system in BASIC, just like in the old days. +to Eris 2010. Most notably, it contains a port of Tali Forth 2, a +very capable Forth system for 65C02 MPUs. Other software includes +Wozmon, Steve Wozniak's "operating system" for the Apple I, +Microchess, the first commercial game for microcomputers, some other +games, and a port of Eliza. The contrib directory also contains a port +of Tiny BASIC, so you can even program the system in BASIC, just like +in the old days. All components of the system (except for the third party software in `contrib/`) are free soft- and hardware. You can find all source code @@ -80,10 +82,11 @@ need a terminal program. ### Linux -Run a terminal program like tio. tio should be available in the -package store of your distribution. If not try minicom or picocom. The -interface is probably available as `/dev/ttyUSB0`. Connect to Eris 2010 -by +Run a terminal program like `tio`. `tio` should be available in the +package store of your distribution. If not try `minicom` or +`picocom`. The interface is probably available as `/dev/ttyUSB0`. Make +sure your user account has access rights to this device! Connect to +Eris 2010 by `tio -b 19200 /dev/ttyUSB0` @@ -107,7 +110,7 @@ windows, type any character or push the reset button on Eris 2010. You should now be connected to the computer. If this does not work, try "COM1", "COM2", ... for "Serial Line". -The default font of PuTTY does not support the unicode characters ued +The default font of PuTTY does not support the Unicode characters used by the 10 PRINT program. I order to run this program, choose "Window/Appearance" and change the font to e.g. @NSimSun. @@ -272,11 +275,11 @@ The boot ROM includes `sw/load_from_card/load_from_card.asm`, the program to load programs from SD card. This program is executed when no program is transmitted via serial line. -SD cards can store up to 10 programs. The filesystem format is as +SD cards can store up to 10 programs. The file system format is as follows: Block $00000000 is loaded on initialization. The first two bytes -should be $E215. This indicates the filesystem type. The third byte +should be $E215. This indicates the file system type. The third byte should be $00. This is the version of the file system. The fourth byte is the number of the program to start automatically, or $ff. In case of $ff, a menu of the programs on the card is presented for the user @@ -310,9 +313,13 @@ devices. See section Memory Map above. ## Interrupts The I/O interfaces are wired to IRQ. NMI can be triggered by pushing a -physical button. While user programs can write their own ISRs for -IRQs, the standard ROM fixes NMI servicing to an ISR that dumps the -RAM. +physical button. User programs can write their own ISRs for IRQs. The +ISR for NMIs is set in the ROM. The default ISR dumps the +RAM. Alternatively NMIs may jump into Wozmon. For this, set flag +`WOZMON_ON_NMI` in `roms/os/os.asm`. Beware that compiling Wozmon into +the kernel does not conform to the GNU GPL v3 license, because Wozmon +is not offically free software. However I, Gerd Beuster, the author of +the kernel, grant you the right to compile Wozmon into it. ## Toolchain @@ -390,12 +397,12 @@ parameters. Refer to `sw/stack_test/stack_test.bin` and You can program Eris 2010 in C using the CC65 C compiler, available at <https://cc65.github.io/>. Suitable configuration and Makefiles, as -well as ports of the conio and dio libraries, are located in directory -`sw/c/cc65_eris`. You have to run `make` once in this directory in -order to generate `eris2010.lib`. After that, you can compile the -programs in the other subdirectories. The programs here are simple -test programs. Check out `contrib/c/` for ports of (more or less) -useful programs. +well as implementations of the conio and dio libraries, are +located in directory `sw/c/cc65_eris`. You have to run `make` once in +this directory in order to generate `eris2010.lib`. After that, you +can compile the programs in the other subdirectories. The programs +here are simple test programs. Check out `contrib/c/` for ports of +(more or less) useful programs. #### The conio Library @@ -431,6 +438,29 @@ application number of the application whose data you are accessing. The application number is a (the) parameter of `main`. Use it to make sure that your program only writes to its own storage space. +### Forth + +Tali Forth 2 (<https://github.com/scotws/TaliForth2>) has been ported +to Eris 2010. It resides in directory `contrib/forth`. Note that Tali +Forth 2 is included as a submodule. Either checkout the Eris 2010 +repository with option `--recurse-submodules` or run `git submodule +init && git submodule update` in the Eris 2010 directory. If you want +to use Tali Forth 2 for any other purpose than running it on Eris +2010, I recommend to use the version from the original repository, not +the one adapted to Eris 2010 used here. + +Due to memory constraints, `ed` has been removed. Even after removing +`ed`, only approximately 7 KB are free. + +Following the SD card structure of Eris 2010, the two upper bytes of +the block number are fixed to $00 (MSB) and the application number +(second MSB). Since SD card blocks are 512B, but Forth blocks are +1024B, a total of $7fff blocks are available from within the Forth +system. These blocks represent the SD card memory available to the +Forth application, i.e. the Forth application itself resides in the +blocks 0 to 31. If you write to any of these blocks, you overwrite the +Forth system! Blocks above 31 are free to use. + ## Tools ### kfs.py @@ -448,7 +478,7 @@ Triggers a reset by toggling DTR. ## Case -The main purpose of the case is to expose the main board. :-) +The main purpose of the case is to expose the mainboard. :-) Therefore it just consists of a top and bottom acrylic plates, separated by spacer bolts. Laser cutter cutout files are located in directory `case/` @@ -460,8 +490,10 @@ implementation for Eris 2010 worked with 5 out of 6 cards tested. If it does not work with your card, try a different card (or fix the protocol implementation). :-) -## Changelog +## ChangeLog +- v1.2 +Tali Forth 2 and Snake added. - v1.1 Support for C programming language. - v1.0 diff --git a/contrib/asm/wozmon/wozmon.asm b/contrib/asm/wozmon/wozmon.asm @@ -5,8 +5,15 @@ ;;; ;;; This version supports an additional command: S prints ;;; the stack pointer. - + + +;;; Don't have to include OS stuff if we are part of the OS. +.weak + WOZMON_EMBEDDED = false +.endweak +.if !WOZMON_EMBEDDED .include "os.inc" +.endif ACIA = io.acia_base ACIA_CTRL = ACIA+3 diff --git a/contrib/c/mmind/mmind.c b/contrib/c/mmind/mmind.c @@ -74,8 +74,8 @@ void main() my[3] = z4+48; my[4] = 0; /* terminate string */ - // cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n","****"); /* insert my to see code */ - cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n",my); /* insert my to see code */ + cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n","****"); /* insert my to see code */ + // cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n",my); /* insert my to see code */ // gb: Additional explanation cputs("Hint:\r\n"); cputs("+ for each correct digit\r\n"); diff --git a/contrib/forth b/contrib/forth @@ -0,0 +1 @@ +Subproject commit fc32086582df82b1ab49cc1743e57b9232d09cb9 diff --git a/contrib/write_default_files.sh b/contrib/write_default_files.sh @@ -5,42 +5,54 @@ KFS_CMD="sudo ../tools/kfs.py" DEVICE=$1 -SW_MAIN=../sw/asm -SW_CONTRIB=./asm -SW_CC65=./c +SW_ASM=../sw/asm +SW_C=../sw/c +SW_CONTRIB_ASM=./asm +SW_CONTRIB_C=./c +SW_FORTH=./forth for i in 10print serial_line_echo ttt via_test do - pushd ${SW_MAIN}/$i + pushd ${SW_ASM}/$i make clean all popd done -pushd ${SW_MAIN}/ttt -make test -popd -for i in wozmon microchess Tiny-BASIC + +for i in snake +do + pushd ${SW_C}/$i + make clean all + popd +done + + +for i in microchess Tiny-BASIC do - pushd ${SW_CONTRIB}/$i + pushd ${SW_CONTRIB_ASM}/$i make clean all popd done -for i in eliza mmind wumpus +for i in eliza mmind wumpus abandoned_farmhouse do - pushd ${SW_CC65}/$i + pushd ${SW_CONTRIB_C}/$i make clean all popd done +pushd ${SW_FORTH} +make clean all +popd + # ${KFS_CMD} ${DEVICE} format -${KFS_CMD} ${DEVICE} store 0 "Tic Tac Toe" ${SW_MAIN}/ttt/ttt.bin -${KFS_CMD} ${DEVICE} store 1 "Tic Tac Toe (Computer vs. Computer)" ${SW_MAIN}/ttt/ttt_test.bin -${KFS_CMD} ${DEVICE} store 2 "10PRINT" ${SW_MAIN}/10print/10print.bin -${KFS_CMD} ${DEVICE} store 3 "Microchess" ${SW_CONTRIB}/microchess/microchess.bin -${KFS_CMD} ${DEVICE} store 4 "Eliza" ${SW_CC65}/eliza/eliza.bin -${KFS_CMD} ${DEVICE} store 5 "Wumpus" ${SW_CC65}/wumpus/wumpus.bin -${KFS_CMD} ${DEVICE} store 6 "Master Mind" ${SW_CC65}/mmind/mmind.bin -${KFS_CMD} ${DEVICE} store 7 "The Abandoned Farmhouse Adventure" ${SW_CC65}/abandoned_farmhouse/adventure.bin -${KFS_CMD} ${DEVICE} store 8 "Tiny-BASIC" ${SW_CONTRIB}/Tiny-BASIC/tinybasic.bin -${KFS_CMD} ${DEVICE} store 9 "Wozmon" ${SW_CONTRIB}/wozmon/wozmon.bin +${KFS_CMD} ${DEVICE} store 0 "Tic Tac Toe" ${SW_ASM}/ttt/ttt.bin +${KFS_CMD} ${DEVICE} store 1 "Snake" ${SW_C}/snake/snake.bin +${KFS_CMD} ${DEVICE} store 2 "10PRINT" ${SW_ASM}/10print/10print.bin +${KFS_CMD} ${DEVICE} store 3 "Microchess" ${SW_CONTRIB_ASM}/microchess/microchess.bin +${KFS_CMD} ${DEVICE} store 4 "Eliza" ${SW_CONTRIB_C}/eliza/eliza.bin +${KFS_CMD} ${DEVICE} store 5 "Wumpus" ${SW_CONTRIB_C}/wumpus/wumpus.bin +${KFS_CMD} ${DEVICE} store 6 "Master Mind" ${SW_CONTRIB_C}/mmind/mmind.bin +${KFS_CMD} ${DEVICE} store 7 "The Abandoned Farmhouse Adventure" ${SW_CONTRIB_C}/abandoned_farmhouse/adventure.bin +${KFS_CMD} ${DEVICE} store 8 "Tiny-BASIC" ${SW_CONTRIB_ASM}/Tiny-BASIC/tinybasic.bin +${KFS_CMD} ${DEVICE} store 9 "Forth" ${SW_FORTH}/taliforth-eris2010-ram.bin ${KFS_CMD} ${DEVICE} ls diff --git a/roms/os/os.asm b/roms/os/os.asm @@ -7,6 +7,15 @@ CLOCK_SPEED = 4 ;4 Mhz Clock +;;; An NMI may either dump the ROM (default behavior) or jump into Wozmon (in case +;;; WOZMON_ON_NMI is set to true). The default behavior compiles a ROM +;;; fully compliant with the GPL v3. Since Wozmon is not officially +;;; free software, compiling the kernel into Wozmon does not comply to +;;; the GPL v3. However I, Gerd Beuster, the author of the kernel, +;;; grant you the right to compile Wozmon into the kernel. + WOZMON_ON_NMI = false + ; WOZMON_ON_NMI = true + .if SYMON rom_start = $c000 .else @@ -234,13 +243,22 @@ derefer_ram_irq: jmp (os.irq_vector) default_program: -;; .include "../../sw/asm/10print/10print.asm" -;; .include "../../sw/asm/ttt/ttt.asm" +LOAD_FROM_CARD_EMBEDDED = true .include "../../sw/asm/load_from_card/load_from_card.asm" +.if WOZMON_ON_NMI +wozmon: +WOZMON_EMBEDDED = true +.include "../../contrib/asm/wozmon/wozmon.asm" +.endif + ;;; Vectors .fill $fffa-*, $ff +.if WOZMON_ON_NMI + .word wozmon ; nmi +.else .word memdump ; nmi +.fi .word boot ; reset .word derefer_ram_irq ; irq diff --git a/roms/os/os.inc b/roms/os/os.inc @@ -6,7 +6,7 @@ ;;; This file may be included from os.asm or from ;;; some userland program. -;;; We use the definition of CLOCK_SPPED in os.asm to +;;; We use the definition of CLOCK_SPEED in os.asm to ;;; check if we are compiling in the context of a userland ;;; program. .weak @@ -27,14 +27,11 @@ sd: .binclude "sd" .. filename_addition .. ".l" lfsr: .binclude "lfsr" .. filename_addition .. ".l" via: .binclude "via" .. filename_addition .. ".l" term: .binclude "term" .. filename_addition .. ".l" - BOOT_EMBEDDED = false ;; Set start addresses for program and ;; zero page variables * = os.rom_zero_page_end .dsection zero_page * = start_address -.else - BOOT_EMBEDDED = true .endif .include "io.inc" diff --git a/sw/asm/load_from_card/load_from_card.asm b/sw/asm/load_from_card/load_from_card.asm @@ -2,13 +2,11 @@ ;;; software under the GNU GPL v3 license or any later version. See ;;; COPYING in the root directory for details. -;;; Start program form SD card. -;;; If we are included in boot.asm, we -;;; should not set the start address +;;; Don't have to include OS stuff if we are part of the OS. .weak - BOOT_EMBEDDED = false + LOAD_FROM_CARD_EMBEDDED = false .endweak -.if !BOOT_EMBEDDED +.if !LOAD_FROM_CARD_EMBEDDED .include "os.inc" .endif @@ -36,7 +34,7 @@ init: jsr io.init_acia jsr via.init .endif -.if !BOOT_EMBEDDED +.if !LOAD_FROM_CARD_EMBEDDED ;; Wait for key to start jsr io.getc .endif diff --git a/sw/asm/ttt/README.txt b/sw/asm/ttt/README.txt @@ -9,16 +9,16 @@ ttt_symon.bin * Source Code -ttt.s +ttt.asm Contains the main routine -board.s +board.asm Subroutines for managing the board -board_test.s - Tests for board.s -computer_player.s +board_test.asm + Tests for board.asm +computer_player.asm Implementations of random and perfect computer players -computer_player_test.s - Tests for computer_player.s -human_player.s +computer_player_test.asm + Tests for computer_player.asm +human_player.asm CLI for human players diff --git a/sw/asm/ttt/board.asm b/sw/asm/ttt/board.asm @@ -20,7 +20,7 @@ play_game: .block jsr init_board ;; jsr does not allow indirect addressing, - ;; threfore we have to wrap the indirect + ;; therefore we have to wrap the indirect ;; calls this way. jsr player_x_init jsr player_o_init @@ -309,12 +309,10 @@ print_board: .block ldx #$ff phx -.if !RUN_TESTS jsr term.clear_screen #term.SET_CURSOR #$01, #$01 #io.PRINTSNL '** Tic-Tac-Toe **' #io.PRINTNL -.endif #io.PRINTSNL '+---+---+---+' print_field: lda #$03 @@ -570,8 +568,6 @@ no_draw: rts .bend - - ;;; This subroutine can be called ;;; called after get_game_state ;;; in order to print the state @@ -586,9 +582,6 @@ print_game_state: cmp #piece_none bne no_draw #io.PRINTSNL "Draw!" -.if !RUN_TESTS - jsr io.getc -.endif rts no_draw: #io.PRINTSNL "Let the game continue!" @@ -601,8 +594,5 @@ somebody_won: adc #'0' jsr io.putc #io.PRINTSNL " wins!" -.if !RUN_TESTS - jsr io.getc -.endif rts .bend diff --git a/sw/asm/ttt/board_test.asm b/sw/asm/ttt/board_test.asm @@ -142,5 +142,6 @@ game_board_test: #PLAYER_WIN_SECOND_DIAGONAL piece_o #PLAYER_WIN_FIRST_DIAGONAL piece_o #io.PRINTSNL "Board tests completed. No errors found!" - rts +loop: + jmp loop .bend diff --git a/sw/asm/ttt/computer_player.asm b/sw/asm/ttt/computer_player.asm @@ -10,8 +10,15 @@ computer_random_init: rts computer_random_ply: - .block pha ; Remember who we are + jsr print_board + pla +computer_random_ply_no_printing: + ;; Second entry point in case we just want + ;; to make a random move without printing + ;; the board first + .block + pha ;; We need a random number ;; in the range 0..8. For ;; this, we update the lfsr @@ -33,9 +40,6 @@ get_random: ;; Occupy field ldx tmp pla -.if RUN_TESTS - jsr print_random_ply -.endif jsr set_field clc rts @@ -52,10 +56,13 @@ computer_perfect_init: rts computer_perfect_ply: .block + pha + jsr print_board + pla + pha ;; Random First ;; Added by gb: Start with a random move on an empty field in ;; order to create different games - pha jsr random_first pla bcc done @@ -135,9 +142,6 @@ computer_perfect_ply: error: jmp error execute_ply: -.if RUN_TESTS - jsr print_ply -.endif jsr set_field_x_y done: rts @@ -156,7 +160,7 @@ row_loop: cpy #$03 ; are bne row_loop ; empty lda tmp - jmp computer_random_ply + jmp computer_random_ply_no_printing not_first: sec rts diff --git a/sw/asm/ttt/computer_player_test.asm b/sw/asm/ttt/computer_player_test.asm @@ -4,25 +4,25 @@ computer_player_test: .block - jsr clear_memory - ;; jsr mirror_board_test - ;; jsr win_by_column - ;; jsr win_by_diagonal - ;; jsr block_by_column - ;; jsr fork_row_column - ;; jsr test_block_fork - ;; jsr test_play_opposite_corner - ;; jsr test_play_empty_corner - ;; jsr test_perfect_vs_random_computer_player - ;; jsr test_random_vs_perfect_computer_player - jsr test_perfect_vs_perfect_computer_player -loop: - jmp loop + jsr clear_memory_and_check_for_quit + jsr test_perfect_vs_random_computer_player + jsr clear_memory_and_check_for_quit + jsr test_random_vs_perfect_computer_player + jsr clear_memory_and_check_for_quit + jsr test_perfect_vs_perfect_computer_player + jmp computer_player_test .bend -clear_memory: - ;; Clear RAM for debugging purposes +clear_memory_and_check_for_quit: .block + ;; Check if it is time to quit + jsr io.getc_nonblocking + tax + cmp #'q' ; Quit? + bne cont + jmp ($fffc) ; Reset +cont: + ;; Clear RAM for debugging purposes lda #<end_of_code sta tmp lda #>end_of_code @@ -38,8 +38,6 @@ clear_memory_loop: bne clear_memory_loop rts .bend - - SET_BOARD: .macro lda #\1 @@ -65,452 +63,8 @@ check_board_err: check_board_cont: .endm -mirror_board_test: - .block - jsr init_board - #mem.STORE_WORD board_mirrored,other_board_ptr - ;; Test rotation of board - ;; One piece on field 0. - ;; Rotation should change nothing - #SET_BOARD %00000011,%00000000,%00000000 - jsr mirror_board - #CHECK_BOARD %00000011,%00000000,%00000000 - ;; First row to first column - #SET_BOARD %00111001,%00000000,%00000000 - jsr mirror_board - #CHECK_BOARD %00000001,%00000010,%00000011 - ;; Second row to Second column - #SET_BOARD %00000000,%00111001,%00000000 - jsr mirror_board - #CHECK_BOARD %00000100,%00001000,%00001100 - ;; Third row to third column - #SET_BOARD %00000000,%00000000,%00111001 - jsr mirror_board - #CHECK_BOARD %00010000,%00100000,%00110000 - ;; Complex board pattern - #SET_BOARD %00100100,%00110011,%00011001 - jsr mirror_board - #CHECK_BOARD %011100,%100001,%011110 -no_error: - #io.PRINTSNL "OK" - rts -error: - #io.PRINTSNL "ERROR" - rts - .bend - - ;; Computer finishes game - ;; by completing column -win_by_column: - ;; First column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 6 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 3 - #SET_FIELD piece_x, 6 - jsr play_win_by_column - ;; Second column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 4 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 7 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 7 - jsr play_win_by_column - ;; Third column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 5 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 8 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 5 - #SET_FIELD piece_x, 8 - jsr play_win_by_column - rts -play_win_by_column: - jsr print_board - jsr io.putnl - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_column_error - #io.PRINTSNL "OK" - rts -win_by_column_error: - #io.PRINTSNL "ERROR" - rts - -win_by_diagonal: - ;; - ;; First diagonal - ;; - ;; Lower right field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 4 - jsr play_win_by_diagonal - ;; Middle field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 8 - jsr play_win_by_diagonal - ;; Upper left field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 8 - jsr play_win_by_diagonal - ;; - ;; Second diagonal - ;; - ;; Lower left field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 4 - jsr play_win_by_diagonal - ;; Middle field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 6 - jsr play_win_by_diagonal - ;; Upper right field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 6 - jsr play_win_by_diagonal - rts -play_win_by_diagonal: - jsr print_board - jsr io.putnl - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_diagonal_error - #io.PRINTSNL "OK" - rts -win_by_diagonal_error: - #io.PRINTSNL "ERROR" - rts - -block_by_column: - ;; First column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 6 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 3 - #SET_FIELD piece_x, 6 - jsr play_block_by_column - ;; Second column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 4 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 7 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 7 - jsr play_block_by_column - ;; Third column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 5 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 8 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 5 - #SET_FIELD piece_x, 8 - jsr play_block_by_column - rts -play_block_by_column: - jsr print_board - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr io.getc - rts - -fork_row_column: - .block - ;; Row/column fork - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 1 - #SET_FIELD piece_o, 2 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 6 - pha - jsr print_board - pla - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,5 - #CHECK_ERROR piece_o - jsr io.getc - ;; Row/diagonal fork - jsr init_board - ;; lda #$ac ; Test runs perfectly with this initialization of the lfsr - ;; sta lfsr.state - ;; sta lfsr.state+1 - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 6 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_x - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_x - jsr io.getc - ;; Another Row/diagonal fork - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - #SET_FIELD piece_o, 6 - #SET_FIELD piece_o, 7 - #SET_FIELD piece_x, 8 - pha - jsr print_board - pla - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,4 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_block_fork: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,1 - #CHECK_ERROR piece_o - jsr io.getc - ;; Check if computer creates - ;; two-in-a-row/column/diagonal - ;; when when a fork by the opponet is - ;; possible. - ;; The following scenario - ;; can be created by the plys - ;; X -> 5 - ;; O -> 1 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_o, 0 - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,1 - #CHECK_ERROR piece_o - jsr io.getc - ;; The following scenario - ;; can be created by the plys - ;; X -> 1 - ;; O -> 5 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 4 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,3 - #CHECK_ERROR piece_o - jsr io.getc - rts - ;; The following scenario - ;; can be created by the plys - ;; X -> 5 - ;; O -> 1 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 0 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_play_opposite_corner: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 4 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,8 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_play_empty_corner: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 0 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - test_perfect_vs_random_computer_player: .block -game_loop: ;; Set player X #mem.STORE_WORD computer_perfect_init, player_x_init_ptr #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr @@ -520,14 +74,16 @@ game_loop: jsr play_game jsr get_game_state cmp #piece_o ; Random player - bne game_loop ; Should never win - #io.PRINTSNL 'Error' + beq error ; Should never win + rts error: - jmp error + #io.PRINTSNL 'Error in game perfect vs. random' +error_loop: + jmp error_loop .bend + test_random_vs_perfect_computer_player: .block -game_loop: ;; Set player X #mem.STORE_WORD computer_random_init, player_x_init_ptr #mem.STORE_WORD computer_random_ply, player_x_ply_ptr @@ -537,21 +93,16 @@ game_loop: jsr play_game jsr get_game_state cmp #piece_x ; Random player - bne game_loop ; Should never win - #io.PRINTSNL 'Error' + beq error ; Should never win + rts error: - jmp error + #io.PRINTSNL 'Error in game random vs. perfect' +error_loop: + jmp error_loop .bend + test_perfect_vs_perfect_computer_player: .block -game_loop: - ;; Check if it is time to quit - jsr io.getc_nonblocking - tax - cmp #'q' ; Quit? - bne start - jmp ($fffc) ; Reset -start: ;; Set player X #mem.STORE_WORD computer_perfect_init, player_x_init_ptr #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr @@ -561,57 +112,11 @@ start: jsr play_game jsr get_game_state cmp #piece_none ; All games should end in - beq game_loop ; a draw. - #io.PRINTSNL 'Error' + bne error ; a draw. + rts error: - jmp error + #io.PRINTSNL 'Error in game perfect vs. perfect' +error_loop: + jmp error_loop .bend -print_random_ply: - .block - ;; Debug output - Computer ply - pha - phx - clc - adc #'0' - jsr io.putc - #io.PRINTS ' plays ' - pla - pha - adc #'0' - jsr io.putc - jsr io.putnl - plx - pla - rts - .bend - -print_ply: - .block - ;; Debug output - Computer ply - pha - phy - phx - clc - adc #'0' - jsr io.putc - #io.PRINTS ' plays ' - pla - pha - adc #'0' - jsr io.putc - lda #'/' - jsr io.putc - plx - pla - pha - phx - adc #'0' - jsr io.putc - jsr io.putnl - plx - ply - pla - rts - .bend - diff --git a/sw/asm/ttt/ttt.asm b/sw/asm/ttt/ttt.asm @@ -47,17 +47,18 @@ board_ascii: .byte ?, ?, ?, ?, ?, ?, ?, ?, ? .section ttt_game +.weak + RUN_TESTS = false +.endweak + start_ttt: .block cld jsr lfsr.init .if RUN_TESTS - jsr run_tests + jmp game_board_test .endif game_loop: -.if !RUN_TESTS - #io.SETUP -.endif #io.PRINTSNL '** Tic-Tac-Toe **' jsr io.putnl lda #piece_x @@ -65,6 +66,7 @@ game_loop: lda #piece_o jsr choose_player jsr play_game + jsr io.getc jmp game_loop .bend @@ -82,6 +84,7 @@ choose_player: #io.PRINTSNL '1 - Human' #io.PRINTSNL '2 - Computer (perfect)' #io.PRINTSNL '3 - Computer (random)' + #io.PRINTSNL '4 - Computer vs. Computer Stress Test' jsr io.getc_seed_rng cmp #'q' bne not_quit @@ -93,6 +96,10 @@ not_quit: beq set_computer_perfect_player cmp #'3' beq set_computer_random_player + cmp #'4' + bne cont + jmp computer_player_test +cont: pla jmp choose_player ;; Board is not initialized at @@ -139,22 +146,8 @@ set_second_player: .include "human_player.asm" .include "computer_player.asm" -.weak - RUN_TESTS = false -.endweak -.if RUN_TESTS .include "board_test.asm" .include "computer_player_test.asm" -run_tests: - ;; jsr game_board_test - ;; jsr io.putnl - ;; jsr getc - ;; jsr mirror_board_test - jsr computer_player_test - jsr io.putnl - jsr io.getc - jmp run_tests -.endif end_of_code: .send ttt_game diff --git a/sw/c/blink/blink.c b/sw/c/blink/blink.c @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + #include <conio.h> #include "../cc65_eris/os.h" diff --git a/sw/c/cc65_eris/os.h b/sw/c/cc65_eris/os.h @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + #ifndef OS_H #define OS_H diff --git a/sw/c/cc65_eris/os.inc b/sw/c/cc65_eris/os.inc @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + .setcpu "65C02" .IFDEF SYMON diff --git a/sw/c/cc65_eris/os.s b/sw/c/cc65_eris/os.s @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + .setcpu "65C02" .include "os.inc" diff --git a/sw/c/conio_test/conio_test.c b/sw/c/conio_test/conio_test.c @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + #include <stdio.h> #include <conio.h> #include "../cc65_eris/os.h" diff --git a/sw/c/dio_test/dio_test.c b/sw/c/dio_test/dio_test.c @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + #include <stdio.h> #include <stdlib.h> #include <conio.h> diff --git a/sw/c/fibonacci/fibonacci.c b/sw/c/fibonacci/fibonacci.c @@ -1,3 +1,9 @@ +/* + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + #include <conio.h> #include <limits.h> diff --git a/sw/c/snake/Makefile b/sw/c/snake/Makefile @@ -0,0 +1,3 @@ +TARGET=snake + +include ../cc65_eris/Makefile.common diff --git a/sw/c/snake/snake.c b/sw/c/snake/snake.c @@ -0,0 +1,306 @@ +/* Snake + * + * Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free + * software under the GNU GPL v3 license or any later version. See + * COPYING in the root directory for details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <conio.h> +#include "../cc65_eris/os.h" + +#define FOOD_DENSITY (0x100) // Food on every nth field +#define SNAKE_LENGTH_MAX (0x1000) +#define DELAY (0x800) // Delay between moves + + +#define FALSE 0 +#define TRUE !FALSE + +/* Generic data structures */ + +typedef struct screen_pos { + int x; + int y; +} screen_pos_t; + +/* Data structures and functions for snake */ + +enum direction {north, south, west, east}; + +typedef struct snake { + // List of snake elements. start_element points to the tail of the + // snake and end_element to the head. When the snake moves, + // end_element is increased, elements[end_element] becomes the new + // head. Unless the snake grows in size, start_element is increased + // as well, removing the previous tail element. + screen_pos_t elements[SNAKE_LENGTH_MAX]; + unsigned start_element; + unsigned end_element; + // snake_length is the target length of the snake. On every move, + // the snake grows by one element until it has reached length + // snake_length + unsigned snake_length; + unsigned score; + enum direction direction; + unsigned char screen_w; + unsigned char screen_h; + int (*move)(struct snake *self, enum direction direction); + int (*check_collision)(struct snake *self); + int (*occupied)(struct snake *self, screen_pos_t *p); +} snake_t; + +static int snake_move(snake_t *self, enum direction direction) { + screen_pos_t *prev, *new; + unsigned current_snake_length; + // Check if the snake grows. If it does not grow, we have to + // overwrite the current tail element with a space. + if (self->start_element <= self->end_element) + current_snake_length = self->end_element - self->start_element + 1; + else + current_snake_length = (self->end_element + + (SNAKE_LENGTH_MAX - self->start_element)) + 1; + // Snake can not grow beyond its maximal length. + current_snake_length = (current_snake_length > SNAKE_LENGTH_MAX ? + SNAKE_LENGTH_MAX : current_snake_length); + // Snake did not grow, we have to clear the previous start element + if (current_snake_length == self->snake_length) { + gotoxy(self->elements[self->start_element].x, + self->elements[self->start_element].y); + cputc(' '); + self->start_element = ((self->start_element) + 1) % SNAKE_LENGTH_MAX; + } + // The new element is added after the previous end_element. + // We copy the position of the previous end_element over + // and adjust the position according to the direction of the + // move. + prev = &(self->elements[self->end_element]); + self->end_element = ((self->end_element) + 1) % SNAKE_LENGTH_MAX; + new = &(self->elements[self->end_element]); + (new->y) = (prev->y); + (new->x) = (prev->x); + switch (direction) { + case north: + (new->y)--; + if (new->y == -1) + return -1; + break; + case south: + (new->y)++; + if (new->y == self->screen_h) + return -1; + break; + case west: + (new->x)--; + if (new->x == -1) + return -1; + break; + case east: + (new->x)++; + if (new->x == self->screen_w) + return -1; + break; + } + // Draw new end element + gotoxy(self->elements[self->end_element].x, + self->elements[self->end_element].y); + cputc('#'); + // Move cursor to neutral position + gotoxy(0, 0); + return 0; +} + +static int snake_check_collision(snake_t *self) { + unsigned current; + int collision = FALSE; + if (self->start_element != self->end_element) { + for (current = self->start_element; + (collision == FALSE) && (current != self->end_element); + current = (current + 1) % SNAKE_LENGTH_MAX) { + if ((self->elements[self->end_element].x == + self->elements[current].x) && + (self->elements[self->end_element].y == + self->elements[current].y)) + collision = TRUE; + } + } + return collision; +}; + +static int snake_occupied(snake_t *self, screen_pos_t *p) { + unsigned current = self->start_element; + int occupied = FALSE; + for (current = self->start_element; + (occupied == FALSE) && (current != self->end_element); + current = (current + 1) % SNAKE_LENGTH_MAX) { + if ((self->elements[current].x == p->x) && + (self->elements[current].y == p->y)) + occupied = TRUE; + } + if ((self->elements[self->end_element].x == p->x) && + (self->elements[self->end_element].y == p->y)) + occupied = TRUE; + return occupied; +}; + + +snake_t *snake_new(unsigned char screen_w, unsigned char screen_h, + unsigned start_x, unsigned start_y) { + snake_t *snake = malloc(sizeof(snake_t)); + snake->move = &snake_move; + snake->check_collision = &snake_check_collision; + snake->occupied = &snake_occupied; + snake->direction = east; + snake->start_element = 0; + snake->end_element = 0; + snake->elements[0].x = start_x; + snake->elements[0].y = start_y; + snake->snake_length = 1; + snake->score = 0; + snake->direction = east; + snake->screen_w = screen_w; + snake->screen_h = screen_h; + return snake; +}; + +/* Data structures and functions for food */ + +typedef struct food { + unsigned char screen_w; + unsigned char screen_h; + unsigned food_items; // Number of food items + screen_pos_t *elements; + snake_t *snake; + void (*check_eat)(struct food *self); +} food_t; + +static void check_eat(struct food *self) { + unsigned i; + for (i = 0; i < self->food_items; i++) { + // Check if current element has been eaten + if ((self->snake->elements[self->snake->end_element].x == + self->elements[i].x) && + (self->snake->elements[self->snake->end_element].y == + self->elements[i].y)) { + self->snake->score++; + gotoxy(7, self->screen_h+1); + cprintf("%d", self->snake->score); + // Food item has been eaten. + self->elements[i].x = 0xFF; + self->elements[i].y = 0xFF; + // Grow snake + if (self->snake->snake_length < SNAKE_LENGTH_MAX) { + self->snake->snake_length += 1; + } + } + // Check if current element is empty + if ((self->elements[i].x == 0xFF) && + (self->elements[i].y == 0xFF)) { + // Current element is not placed. Try to place it. + // Biased way to draw random numbers + self->elements[i].x = rand() % self->screen_w; + self->elements[i].y = rand() % self->screen_h; + // Make sure we did not place the new food element on a + // snake element + if (snake_occupied(self->snake, &(self->elements[i]))) { + self->elements[i].x = 0xFF; + self->elements[i].y = 0xFF; + } + else { + // Draw new food element + gotoxy(self->elements[i].x, + self->elements[i].y); + cputc('*'); + gotoxy(0, 0); + } + } + } +} + +food_t *food_new(unsigned char screen_w, unsigned char screen_h, + unsigned food_items, struct snake *snake) { + unsigned i; + food_t *food = malloc(sizeof(food_t)); + food->screen_w = screen_w; + food->screen_h = screen_h; + food->food_items = food_items; + food->elements = calloc(food_items, sizeof(screen_pos_t)); + food->snake = snake; + food->check_eat = &check_eat; + for (i = 0; i < food->food_items; i++) { + // Initial placement of food items + // Biased way to draw random numbers + food->elements[i].x = rand() % food->screen_w; + food->elements[i].y = rand() % food->screen_h; + // Draw new food element + gotoxy(food->elements[i].x, + food->elements[i].y); + cputc('*'); + } + return food; +}; + + +void main(void) { + unsigned char screen_w, screen_h; + snake_t *snake; + food_t *food; + unsigned delay; + unsigned char key; + enum direction heading; + unsigned char counter; + + // TODO: Initialize RNG + + do { // Loop forever + cprintf("q to quit, any other key to play."); + if (cgetc() == 'q') { + return; + } + clrscr(); + screensize(&screen_w, &screen_h); + screen_h--; // Last line reserved for status information + snake = snake_new(screen_w, screen_h, screen_w/2, screen_h/2); + food = food_new(screen_w, screen_h, screen_w * screen_h / FOOD_DENSITY + 5, + snake); + gotoxy(0, screen_h+1); + cputs("Score: 0 Move: wasd Quit: q"); + heading = east; + counter = 0; + while (!snake->check_collision(snake)) { + if (kbhit()) { + key = cgetc(); + switch (key) { + case 'a': + heading = west; + break; + case 's': + heading = south; + break; + case 'd': + heading = east; + break; + case 'w': + heading = north; + break; + case 'q': + return; + } + } + if (snake->move(snake, heading) == -1) { + // crashed into border of screen + break; + }; + food->check_eat(food); + for (delay = 0; delay < DELAY; delay++) { + }; + }; + free(snake); + gotoxy(0,0); + cprintf("CRASH!\r\n"); + // Delay loop after crash + for (delay = 0; delay < 0xFFFF; delay++) { + }; + } while (TRUE); +}