eris2010

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

commit 822e76c00ceadee13fd533320aa460a502c57169
parent a33ba6bc619440ee0f9b704d519255cabdc5dda3
Author: Gerd Beuster <gerd@frombelow.net>
Date:   Sun, 29 Nov 2020 13:16:29 +0100

Switched to 64tass

Diffstat:
M.gitignore | 3++-
Aroms/Makefile.common | 15+++++++++++++++
Mroms/boot/Makefile | 22++--------------------
Aroms/boot/boot.asm | 520+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Droms/boot/boot.h | 262-------------------------------------------------------------------------------
Droms/boot/boot.s | 361-------------------------------------------------------------------------------
Aroms/boot/boot_macros.inc | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Droms/boot/export_symbols.py | 46----------------------------------------------
Mroms/serial_char_out/Makefile | 12++----------
Aroms/serial_char_out/serial_char_out.asm | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Droms/serial_char_out/serial_char_out.s | 51---------------------------------------------------
Mroms/simple_loop/Makefile | 12++----------
Aroms/simple_loop/loop.asm | 23+++++++++++++++++++++++
Droms/simple_loop/loop.s | 16----------------
Asw/10print/10print.asm | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/10print/10print.s | 40----------------------------------------
Msw/10print/Makefile | 17++---------------
Asw/Makefile.common | 15+++++++++++++++
Msw/aaa/Makefile | 17++---------------
Asw/aaa/aaa.asm | 44++++++++++++++++++++++++++++++++++++++++++++
Dsw/aaa/aaa.s | 44--------------------------------------------
Msw/interrupts/Makefile | 17++---------------
Asw/interrupts/interrupts.asm | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/interrupts/interrupts.s | 77-----------------------------------------------------------------------------
Msw/mem_test/Makefile | 17++---------------
Asw/mem_test/mem_test.asm | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/mem_test/mem_test.s | 56--------------------------------------------------------
Msw/serial_line_echo/Makefile | 17++---------------
Asw/serial_line_echo/serial_line_echo.asm | 17+++++++++++++++++
Dsw/serial_line_echo/serial_line_echo.s | 14--------------
Msw/ttt/Makefile | 37+++++++++----------------------------
Asw/ttt/board.asm | 592+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/board.s | 589-------------------------------------------------------------------------------
Asw/ttt/board_test.asm | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/board_test.s | 143-------------------------------------------------------------------------------
Asw/ttt/computer_player.asm | 756+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/computer_player.s | 755-------------------------------------------------------------------------------
Asw/ttt/computer_player_test.asm | 584+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/computer_player_test.s | 582------------------------------------------------------------------------------
Asw/ttt/human_player.asm | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/human_player.s | 53-----------------------------------------------------
Asw/ttt/ttt.asm | 163+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsw/ttt/ttt.s | 159-------------------------------------------------------------------------------
43 files changed, 3338 insertions(+), 3392 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -18,6 +18,7 @@ misc/clock_reset_attiny45/clock_and_reset.hex misc/clock_reset_attiny45/clock_and_reset.map sw/**/*.bin sw/**/*.l -roms/boot/liba.h +sw/**/*.lst roms/**/*.bin roms/**/*.l +roms/**/*.lst diff --git a/roms/Makefile.common b/roms/Makefile.common @@ -0,0 +1,15 @@ +CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b --labels-root=export + +all: $(TARGET).bin $(TARGET)_symon.bin + +%.bin: %.asm + 64tass $(CFLAGS) -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" + +%_symon.bin: %.asm + 64tass $(CFLAGS) -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" + +flash: $(TARGET).bin + sudo ~/opt/minipro-0.3/minipro -p AT28C64B -w $< + +clean: + rm -f *.bin *.l *.lst diff --git a/roms/boot/Makefile b/roms/boot/Makefile @@ -1,21 +1,3 @@ -TARGET=boot.bin -TARGET_BASENAME=$(basename $(TARGET)) -SYMON=java -jar ../../emulator/symon-1.3.1.jar -cpu 65c02 -INC=-I../../sw/ttt/ +TARGET=boot -all: $(TARGET) $(TARGET_BASENAME)_symon.bin liba.h - -liba.h: $(TARGET_BASENAME).l $(TARGET_BASENAME)_symon.l $(TARGET_BASENAME).s - ./export_symbols.py - -%.bin: %.s - xa $(INC) -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -flash: $(TARGET) - sudo ~/opt/minipro-0.3/minipro -p AT28C64B -w $< - -clean: - rm -f *.bin *.l liba.h +include ../Makefile.common diff --git a/roms/boot/boot.asm b/roms/boot/boot.asm @@ -0,0 +1,520 @@ +CLOCK_SPEED = 4 ;4 Mhz Clock + +.include "boot_macros.inc" + +;;; Global zero page variables +export .namespace +lfsr_state = $20 ; 16 bit +.endn + +;;; Temporary zero page variables; +;;; only used in subroutine +.namespace export +puts_str = $10 ; 16 bit address +gets_len = $10 ; 8 bit +gets_str = $11 ; 16 bit address +.endn + +.if SYMON + * = $c000 +.else + * = $e000 +.endif + +;;; The actual (hardware) interrupt vectors are located in the ROM +;;; at $ fffa/fffb (nmi) and $fffe/$ffff (irq). Since we want to be +;;; able to handle interrupts in RAM, the ROM isr routines jump to the +;;; addresses given here. After a reset, this RAM address is set to +;;; default_irq_handler. +.namespace export +nmi_vector = $7ffc +irq_vector = $7ffe +.endn + +;;; ---------------------------------------------------------- +;;; RESET +;;; +;;; Initialize serial interface, read code from serial +;;; interface into RAM, execute it. +;;; ---------------------------------------------------------- + +boot: .block + sei + #STORE_WORD export.default_irq_handler, export.irq_vector + #STORE_WORD export.default_nmi_handler, export.nmi_vector + cld + cli + jsr export.init_acia + jsr check_for_program_download + bcs no_program_download + jmp download_program +no_program_download: + ;; No program download? + ;; Start embedded program. + jmp default_program + .bend + +;;; If we receive a byte right after a reset, this +;;; triggers a program download via serial line. +;;; The value of the byte is the number of $100 byte +;;; blocks to receive. +check_for_program_download: .block + #PRINTSNL "READY!" + ldy #$20 ; Outer loop counter + ldx #$00 ; Inner counter +loop: + ;; Try to get number of 0x100 byte blocks to be read + jsr export.getc_nonblocking + bcc got_byte + dex + bne loop + dey + bne loop + #PRINTSNL "Nothing to download." + sec +got_byte: + rts + .bend + +download_program: .block + ;; Program is loaded to this memory address + start_addr = $0300 + ;; Location of temporary variables + addr_pointer = $12 ; addr_pointer+1 stores MSB + msb_last_addr = $14 + add_checksum = $15 + xor_checksum = $16 + ;; We expect the number of $100 blocks to be + ;; downloaded in A. + ;; The msb of the last block to be written is the msb + ;; of the start address plus the number of blocks. + clc + adc #>start_addr + sta msb_last_addr + ;; Read n * 0x100 bytes from acia + lda #<start_addr + sta addr_pointer + lda #>start_addr + sta addr_pointer+1 + ;; We calculate a two checksums over the data: + ;; The first byte is the addition of all bytes + ;; received. The second byte is the xor of all + ;; bytes received. + lda #$00 + sta add_checksum + sta xor_checksum +transmit_block: + jsr export.getc + ;; Store data + ldy #$00 + sta (addr_pointer), y + ;; Update checksums + pha + clc + adc add_checksum + sta add_checksum + pla + eor xor_checksum + sta xor_checksum + ;; Update LSB of target address and check if + ;; transmission of block is completed. + inc addr_pointer + bne transmit_block + ;; Transmission of one block completed. + ;; If we are not done, increase + ;; MSB of target address and continue + inc addr_pointer+1 + lda addr_pointer+1 + cmp msb_last_addr + bne transmit_block + ;; Transmission completed. + ;; Transmit result of checksum calculation + lda add_checksum + jsr export.putc + lda xor_checksum + jsr export.putc + ;; Start program + jmp $0300 + .bend + + +.namespace export +.if SYMON + acia_base = $8800 ; Symon +.else + acia_base = $dc00 +.endif + acia_data_reg = acia_base + acia_status_reg = acia_base + 1 + acia_cmd_reg = acia_base + 2 + acia_ctrl_reg = acia_base + 3 +.endn + +;;; init_acia +;;; Initialize acai for communication +;;; Input: +;;; - +;;; Output: +;;; - +;;; Changes: +;;; a, acai registers +.namespace export +init_acia: +.endn + .block + ;; Reset acai + sta export.acia_status_reg + lda #%00011111 ; 19200 bps, 8 data bits, 1 stop bit + sta export.acia_ctrl_reg + ;; No parity, no echo, no interrupts, DTR ready + lda #%11001011 + sta export.acia_cmd_reg + rts + .bend + +;;; ---------------------------------------------------------- +;;; STANDARD LIBRARY FUNCTIONS +;;; +;;; These functions may be used by user programs. Labels +;;; marked with 'EXPORT' are exported to liba.h. When +;;; the user program imports this header file, the functions +;;; are available. +;;; ---------------------------------------------------------- + +;;; puts +;;; Send up to 256 characters terminated by null via acia +;;; Input: +;;; puts_str, put_str+1: +;;; 2 bytes on zero page containing string address +;;; Output: +;;; - +;;; Changes: +;;; a, x, y, puts_str, put_str+1 +.namespace export +puts: +.endn + .block + ;; Send string terminated by '\0' + ldy #$00 +_puts_loop: + lda (export.puts_str), y + beq _puts_end + phy + jsr export.putc + ply + iny + jmp _puts_loop +_puts_end: + rts + .bend + +;;; putc +;;; Send character via acia +;;; Input: +;;; a: +;;; Character to be printed +;;; Output: +;;; - +;;; Changes: +;;; x, acai registers +.namespace export +putc: +.endn + .block + ;; Send character. + ;; Due to bugs in the register and interrupt + ;; handling of the WDC 65C02, we have to use + ;; a manual delay. + sta export.acia_data_reg +.switch CLOCK_SPEED +.case 4 ; 4 Mhz Clock + SERIAL_SEND_DELAY_X = $c3 + SERIAL_SEND_DELAY_Y = $02 +.case 2 ; 4 Mhz Clock + SERIAL_SEND_DELAY_X = $c2 + SERIAL_SEND_DELAY_Y = $01 +.default ; 1 Mhz Clock + SERIAL_SEND_DELAY_X = $88 + SERIAL_SEND_DELAY_Y = $01 +.endswitch + ldy #SERIAL_SEND_DELAY_Y +outer_loop: + ldx #SERIAL_SEND_DELAY_X +inner_loop: + dex + bne inner_loop + dey + bne outer_loop + rts + .bend + +;;; puth +;;; Convert byte to hex and send via acia +;;; Input: +;;; a: +;;; Byte to be printed +;;; Output: +;;; - +;;; Changes: +;;; a, x, acai registers, c-flag +.namespace export +puth: +.endn + .block + ;; Send byte a as hex number + pha + lsr a + lsr a + lsr a + lsr a + jsr export.puth_nibble + pla + jsr export.puth_nibble + rts + .bend + +;;; puth_nibble +;;; Convert lower half of byte to hex and send via acia +;;; Input: +;;; a: +;;; Nibble to be printed +;;; Output: +;;; - +;;; Changes: +;;; a, x, acai registers, c-flag +.namespace export +puth_nibble: +.endn + .block + ;; Print hex digit + clc + and #$0F + adc #$30 ; Decimal number + cmp #$3A ; >10 ? + bmi _puth_putc + adc #$26 +_puth_putc: + jsr export.putc + rts + .bend + +;;; putnl +;;; Send newline via acia +;;; Input: +;;; - +;;; Output: +;;; - +;;; Changes: +;;; a +.namespace export +putnl: +.endn + .block + lda #$0d + jsr export.putc + lda #$0a + jsr export.putc + rts + .bend + +;;; getc +;;; Read character from acia. Blocks until character is read +;;; Input: +;;; - +;;; Output: +;;; a: +;;; Character read +;;; Changes: +;;; a, acai registers +.namespace export +getc: +.endn + .block + ;; Read character from acia + lda export.acia_status_reg + and #%00001000 + beq export.getc + lda export.acia_data_reg + rts + .bend + +;;; getc_seed_rng +;;; Like getc, but also updates lsfr +;;; This is our only source of randomness right now: +;;; While we are busy waiting for a character, lsfr is +;;; updated. +;;; Input: +;;; - +;;; Output: +;;; a: +;;; Character read +;;; Changes: +;;; a, acai registers, lsfr_state +.namespace export +getc_seed_rng: +.endn + .block + ;; Read character from acia + ;; We also use the time between keystrokes + ;; as entropy source for the RNG + jsr export.lfsr_step + lda export.acia_status_reg + and #%00001000 + beq export.getc_seed_rng + lda export.acia_data_reg + rts + .bend + +.namespace export +getc_nonblocking: +.endn + .block + ;; Non-blocking read: If + ;; character has been read, + ;; it is returned in A and + ;; C is cleared. If not, + ;; C is set + sec + lda export.acia_status_reg + and #%00001000 + beq nothing_read + clc + lda export.acia_data_reg +nothing_read: + rts + .bend + +;;; gets +;;; Read up to 255 characters terminated by CR via acia +;;; Input: +;;; gets_len: +;;; Max. length of input. +;;; gets_str, gets_str+1: +;;; 2 bytes on zero page containing destination address +;;; +-------------------------------------------------------+ +;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! | +;;; +-------------------------------------------------------+ +;;; Note that a terminating zero is added to the string. +;;; Therefore gets_len+1 bytes may be written! +;;; Output: +;;; Zero terminated string at gets_str, gets_str+1 +;;; Changes: +;;; a, y, ACAI registers +.namespace export +gets: +.endn + .block + ;; Read string terminated by CR + ldy #$00 +loop: + phy + jsr export.getc + jsr export.putc + ply + cmp #$0d ; GOT CR? + beq terminate_string ; if not + sta (export.gets_str), y ; store character + iny ; if max length + cpy export.gets_len ; not reached, + bne loop ; continue loop. +terminate_string: + lda #$00 + sta (export.gets_str), y + rts + .bend + +;;; 16 Bit Galouis LFSR +;;; If you use the whole state as RNG, the quality of the +;;; random numbers is rather poor. Just extracting one +;;; bit from the state yields random numbers with reasonable +;;; statistical properties. + +;;; lfsr_init +;;; Initialize lfsr +;;; Input: +;;; - +;;; Output: +;;; - +;;; Changes: +;;; lfsr +.namespace export +lfsr_init: +.endn + .block + #STORE_WORD $beef, export.lfsr_state + .bend + +;;; lfsr_step +;;; update lfsr +;;; Input: +;;; - +;;; Output: +;;; - +;;; Changes: +;;; lfsr +.namespace export +lfsr_step: +.endn + .block + asl export.lfsr_state + lda export.lfsr_state+1 + rol a + bcc cont + eor #$0b +cont: + sta export.lfsr_state+1 + lda export.lfsr_state + adc #$00 + sta export.lfsr_state + rts + .bend + +.if false +;;; Test function for LFSR +test_lfsr: .block +loop: + jsr export.lfsr_step + lda export.lfsr_state+1 + jsr puth + lda export.lfsr_state + jsr export.puth + PRINTSNL("") + jsr export.getc + jmp loop + .bend +.endif ; false + + +;;; Default IRQ handler. Unless the user program changes +;;; the irq_vector, IRQs are handled here. +.namespace export +default_irq_handler: +.endn + rti + +derefer_ram_irq: + ;; Jump to the address given in the IRQ vector + ;; in RAM + jmp (export.irq_vector) + +;;; Default NMI handler. Unless the user program changes +;;; the nmi_vector, NMIs are handled here. +.namespace export +default_nmi_handler: +.endn + rti + +derefer_ram_nmi: + ;; Jump to the address given in the NMI vector + ;; in RAM + jmp (export.nmi_vector) + +default_program: +;; .include "../../sw/10print/10print.asm" +.include "../../sw/ttt/ttt.asm" + +;;; Vectors + .fill $fffa-*, $ff + .word derefer_ram_nmi ; nmi + .word boot ; reset + .word derefer_ram_irq ; irq diff --git a/roms/boot/boot.h b/roms/boot/boot.h @@ -1,262 +0,0 @@ -;;; Function signatures and local variables - -;;; Global zero page variables -lfsr_state = $20 ; 16 bit - -;;; Temporary zero page variables; -;;; only used in subroutine -puts_str = $10 ; 16 bit address -gets_len = $10 ; 8 bit -gets_str = $11 ; 16 bit address - -;;; init_acia -;;; Initialize acai for communication -;;; Input: -;;; - -;;; Output: -;;; - -;;; Changes: -;;; a, acai registers - -#ifdef SYMON - acia_base = $8800 ; Symon -#else - acia_base = $dc00 -#endif - acia_data_reg = acia_base - acia_status_reg = acia_base + 1 - acia_cmd_reg = acia_base + 2 - acia_ctrl_reg = acia_base + 3 - -;;; getc -;;; Read character from acia. Blocks until character is read -;;; Input: -;;; - -;;; Output: -;;; a: -;;; Character read -;;; Changes: -;;; a, acai registers - -;;; getc_seed_rng -;;; Like getc, but also updates lsfr -;;; This is our only source of randomness right now: -;;; While we are busy waiting for a character, lsfr is -;;; updated. -;;; Input: -;;; - -;;; Output: -;;; a: -;;; Character read -;;; Changes: -;;; a, acai registers, lsfr_state - -;;; puts -;;; Send up to 256 characters terminated by null via acia -;;; Input: -;;; puts_str, put_str+1: -;;; 2 bytes on zero page containing string address -;;; Output: -;;; - -;;; Changes: -;;; a, x, y, puts_str, put_str+1 - - -;;; putc -;;; Send character via acia -;;; Input: -;;; a: -;;; Character to be printed -;;; Output: -;;; - -;;; Changes: -;;; x, acai registers - -;;; puth -;;; Convert byte to hex and send via acia -;;; Input: -;;; a: -;;; Byte to be printed -;;; Output: -;;; - -;;; Changes: -;;; a, x, acai registers, c-flag - -;;; puth_nibble -;;; Convert lower half of byte to hex and send via acia -;;; Input: -;;; a: -;;; Nibble to be printed -;;; Output: -;;; - -;;; Changes: -;;; a, x, acai registers, c-flag - -;;; putnl -;;; Send newline via acia -;;; Input: -;;; - -;;; Output: -;;; - -;;; Changes: -;;; a - - - -;;; gets -;;; Read up to 255 characters terminated by CR via acia -;;; Input: -;;; gets_len: -;;; Max. length of input. -;;; gets_str, gets_str+1: -;;; 2 bytes on zero page containing destination address -;;; +-------------------------------------------------------+ -;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! | -;;; +-------------------------------------------------------+ -;;; Note that a terminating zero is added to the string. -;;; Therefore gets_len+1 bytes may be written! -;;; Output: -;;; Zero terminated string at gets_str, gets_str+1 -;;; Changes: -;;; a, y, ACAI registers - -;;; lfsr_init -;;; Initialize lfsr -;;; Input: -;;; - -;;; Output: -;;; - -;;; Changes: -;;; lfsr - -;;; lfsr_step -;;; update lfsr -;;; Input: -;;; - -;;; Output: -;;; - -;;; Changes: -;;; lfsr - - -;;; Macros - -;;; PRINT(addr) -;;; Send zero terminated string at addr via acia. -;;; Input: -;;; addr: -;;; Address of zero terminated string (<= 256 characters) -;;; Output: -;;; - -;;; Changes: -;;; a, x, y, puts_str, put_str+1 - -;;; PRINTS(string) (<= 255 characters) -;;; Send string via acia -;;; Input: -;;; string: -;;; String to be send. -;;; Output: -;;; - -;;; Changes: -;;; a, x, y, puts_str, put_str+1 - -;;; PRINTSNL(string) (<= 253 characters) -;;; Send string and newline via acia -;;; Input: -;;; string: -;;; String to be send. -;;; Output: -;;; - -;;; Changes: -;;; a, x, y, puts_str, put_str+1 - -;;; INPUTS(addr, len) -;;; Read up to len characters from acai and store them at addr -;;; This is a wrapper for gets -;;; Input: -;;; addr: -;;; Target address for zero-terminated string -;;; len: -;;; Maximal length of string to be read (not terminating zero!) -;;; +-------------------------------------------------------+ -;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! | -;;; +-------------------------------------------------------+ -;;; Note that a terminating zero is added to the string. -;;; Therefore len+1 bytes may be written! -;;; Output: -;;; - -;;; Changes: -;;; a, y, ACAI registers - - -;;; PRINTNL -;;; Send newline via acia -;;; Input: -;;; - -;;; Output: -;;; - -;;; Changes: -;;; a, x, acai registers - -;;; STORE_WORD(word,dst) -;;; Store word at dst -;;; Input: -;;; word -;;; Output: -;;; - -;;; Changes: -;;; a, dst - -#define PRINT(addr) \ - .(: \ - lda #<addr: \ - sta puts_str: \ - lda #>addr: \ - sta puts_str+1: \ - jsr puts: \ - .) - -#define PRINTS(string) \ - .(: \ - PRINT(saddr): \ - jmp cont: \ -saddr: \ - .asc string, $00:\ -cont: \ - .) - -#define PRINTSNL(string) \ - .(: \ - PRINT(saddr): \ - jmp cont: \ -saddr: \ - .asc string, $0d, $0a, $00:\ -cont: \ - .) - -#define INPUTS(addr,len) \ - lda #<addr: \ - sta gets_str: \ - lda #>addr: \ - sta gets_str+1: \ - lda len: \ - sta gets_len: \ - jsr gets - -#define PRINTNL \ - lda #$0d: \ - jsr putc: \ - lda #$0a: \ - jsr putc - -#define STORE_WORD(word,dst) \ - .(: \ - lda #<word: \ - sta dst: \ - lda #>word: \ - sta dst+1: \ - .) - -;;; The lines below are automatically generated by export_symbols.py - diff --git a/roms/boot/boot.s b/roms/boot/boot.s @@ -1,361 +0,0 @@ -;;; Receive data from terminal. Store it in RAM & execute it. - -#include "boot.h" - -#define CLOCK_4_MHZ - -#ifdef SYMON - * = $c000 -#else - * = $e000 -#endif - -;;; The actual (hardware) interrupt vectors are located in the ROM -;;; at $ fffa/fffb (nmi) and $fffe/$ffff (irq). Since we want to be -;;; able to handle interrupts in RAM, the ROM isr routines jump to the -;;; addresses given here. After a reset, this RAM address is set to -;;; default_irq_handler. -nmi_vector = $7ffc ; EXPORT -irq_vector = $7ffe ; EXPORT - -;;; We have enough space left to -;;; fit in a Tic-Tac-Toe engine! - start_ttt = * + $0c00 - -;;; ---------------------------------------------------------- -;;; RESET -;;; -;;; Initialize serial interface, read code from serial -;;; interface into RAM, execute it. -;;; ---------------------------------------------------------- - -init: - sei - STORE_WORD(default_irq_handler, irq_vector) - STORE_WORD(default_nmi_handler, nmi_vector) - cld - cli - jsr init_acia - jsr check_for_program_download - bcs no_program_download - jmp download_program -no_program_download: - ;; No program download? - ;; Then let's play some - ;; Tic-Tac-Toe! - jmp start_ttt - -;;; If we receive a byte right after a reset, this -;;; triggers a program download via serial line. -;;; The value of the byte is the number of $100 byte -;;; blocks to receive. -check_for_program_download: - .( - PRINTSNL("READY!") - ldy #$20 ; Outer loop counter - ldx #$00 ; Inner counter -loop: - ;; Try to get number of 0x100 byte blocks to be read - jsr getc_nonblocking - bcc got_byte - dex - bne loop - dey - bne loop - PRINTSNL("Nothing to download.") - sec -got_byte: - rts - .) - - - ;; Program is loaded to this memory address - start_addr = $0300 - ;; Location of temporary variables - addr_pointer = $12 ; addr_pointer+1 stores MSB - msb_last_addr = $14 - add_checksum = $15 - xor_checksum = $16 -download_program: - ;; We expect the number of $100 blocks to be - ;; downloaded in A. - ;; The msb of the last block to be written is the msb - ;; of the start address plus the number of blocks. - clc - adc #>start_addr - sta msb_last_addr - ;; Read n * 0x100 bytes from acia - lda #<start_addr - sta addr_pointer - lda #>start_addr - sta addr_pointer+1 - ;; We calculate a two checksums over the data: - ;; The first byte is the addition of all bytes - ;; received. The second byte is the xor of all - ;; bytes received. - lda #$00 - sta add_checksum - sta xor_checksum -transmit_block: - jsr getc - ;; Store data - ldy #$00 - sta (addr_pointer), y - ;; Update checksums - pha - clc - adc add_checksum - sta add_checksum - pla - eor xor_checksum - sta xor_checksum - ;; Update LSB of target address and check if - ;; transmission of block is completed. - inc addr_pointer - bne transmit_block - ;; Transmission of one block completed. - ;; If we are not done, increase - ;; MSB of target address and continue - inc addr_pointer+1 - lda addr_pointer+1 - cmp msb_last_addr - bne transmit_block - ;; Transmission completed. - ;; Transmit result of checksum calculation - lda add_checksum - jsr putc - lda xor_checksum - jsr putc - ;; Start program - jmp $0300 - -init_acia: ; EXPORT - ;; Reset acai - sta acia_status_reg - lda #%00011111 ; 19200 bps, 8 data bits, 1 stop bit - sta acia_ctrl_reg - ;; No parity, no echo, no interrupts, DTR ready - lda #%11001011 - sta acia_cmd_reg - rts - -;;; ---------------------------------------------------------- -;;; STANDARD LIBRARY FUNCTIONS -;;; -;;; These functions may be used by user programs. Labels -;;; marked with 'EXPORT' are exported to liba.h. When -;;; the user program imports this header file, the functions -;;; are available. -;;; ---------------------------------------------------------- - -puts: ; EXPORT - ;; Send string terminated by '\0' - ldy #$00 -_puts_loop: - lda (puts_str), y - beq _puts_end - phy - jsr putc - ply - iny - jmp _puts_loop -_puts_end: - rts - -#ifdef CLOCK_4_MHZ - SERIAL_SEND_DELAY_X = $c3 - SERIAL_SEND_DELAY_Y = $02 -#endif -#ifdef CLOCK_2_MHZ - SERIAL_SEND_DELAY_X = $c2 - SERIAL_SEND_DELAY_Y = $01 -#endif -#ifdef CLOCK_1_MHZ - SERIAL_SEND_DELAY_X = $88 - SERIAL_SEND_DELAY_Y = $01 -#endif -putc: ; EXPORT - .( - ;; Send character. - ;; Due to bugs in the register and interrupt - ;; handling of the WDC 65C02, we have to use - ;; a manual delay. - sta acia_data_reg - ldy #SERIAL_SEND_DELAY_Y -outer_loop: - ldx #SERIAL_SEND_DELAY_X -inner_loop: - dex - bne inner_loop - dey - bne outer_loop - rts - .) - -puth: ; EXPORT - ;; Send byte a as hex number - pha - lsr - lsr - lsr - lsr - jsr puth_nibble - pla - jsr puth_nibble - rts -puth_nibble: ; EXPORT - ;; Print hex digit - clc - and #$0F - adc #$30 ; Decimal number - cmp #$3A ; >10 ? - bmi _puth_putc - adc #$26 -_puth_putc: - jsr putc - rts - -putnl: ; EXPORT - lda #$0d - jsr putc - lda #$0a - jsr putc - rts - -getc: ; EXPORT - ;; Read character from acia - lda acia_status_reg - and #%00001000 - beq getc - lda acia_data_reg - rts - -getc_seed_rng: ; EXPORT - ;; Read character from acia - ;; We also use the time between keystrokes - ;; as entropy source for the RNG - jsr lfsr_step - lda acia_status_reg - and #%00001000 - beq getc_seed_rng - lda acia_data_reg - rts - -getc_nonblocking: ; EXPORT - .( - ;; Non-blocking read: If - ;; character has been read, - ;; it is returned in A and - ;; C is cleared. If not, - ;; C is set - sec - lda acia_status_reg - and #%00001000 - beq nothing_read - clc - lda acia_data_reg -nothing_read: - rts - .) - -gets: ; EXPORT - .( - ;; Read string terminated by CR - ldy #$00 -loop: - jsr getc - jsr putc - cmp #$0d ; GOT CR? - beq terminate_string ; if not - sta (gets_str), y ; store character - iny ; if max length - cpy gets_len ; not reached, - bne loop ; continue loop. -terminate_string: - lda #$00 - sta (gets_str), y - rts - .) - -;;; 16 Bit Galouis LFSR -;;; If you use the whole state as RNG, the quality of the -;;; random numbers is rather poor. Just extracting one -;;; bit from the state yields random numbers with reasonable -;;; statistical properties. - lfsr_seed = $beef -lfsr_init: ; EXPORT - .( - lda #<lfsr_seed - sta lfsr_state - lda #>lfsr_seed - sta lfsr_state+1 - .) -lfsr_step: ; EXPORT - .( - asl lfsr_state - lda lfsr_state+1 - rol - bcc cont - eor #$0b -cont: - sta lfsr_state+1 - lda lfsr_state - adc #$00 - sta lfsr_state - rts - .) - -#ifdef TESTS -;;; Test function for LFSR -test_lfsr: - .( -loop: - jsr lfsr - lda lfsr_state+1 - jsr puth - lda lfsr_state - jsr puth - PRINTSNL("") - jsr getc - jmp loop - .) -#endif // TESTS - - -;;; Default IRQ handler. Unless the user program changes -;;; the irq_vector, IRQs are handled here. -default_irq_handler: - rti - -derefer_ram_irq: - ;; Jump to the address given in the IRQ vector - ;; in RAM - jmp (irq_vector) - -;;; Default NMI handler. Unless the user program changes -;;; the nmi_vector, NMIs are handled here. -default_nmi_handler: - rti - -derefer_ram_nmi: - ;; Jump to the address given in the NMI vector - ;; in RAM - jmp (nmi_vector) - -;;; Include Tic-Tac-Toe binary -;;; This creates some circular dependencies, -;;; because ttt.s includes liba.h, which -;;; is generated from boot.h upon successful -;;; compiliation of boot.s. - .dsb start_ttt-*, $ff -#ifdef SYMON - .bin 0,0,"../../sw/ttt/ttt_boot_symon.bin" -#else - .bin 0,0,"../../sw/ttt/ttt_boot.bin" -#endif - -;;; Vectors - .dsb $fffa-*, $ff - .word derefer_ram_nmi ; nmi - .word init ; reset - .word derefer_ram_irq ; irq diff --git a/roms/boot/boot_macros.inc b/roms/boot/boot_macros.inc @@ -0,0 +1,139 @@ +;;; PRINT(addr) +;;; Send zero terminated string at addr via acia. +;;; Input: +;;; addr: +;;; Address of zero terminated string (<= 256 characters) +;;; Output: +;;; - +;;; Changes: +;;; a, x, y, puts_str, put_str+1 + +;;; PRINTS(string) (<= 255 characters) +;;; Send string via acia +;;; Input: +;;; string: +;;; String to be send. +;;; Output: +;;; - +;;; Changes: +;;; a, x, y, puts_str, put_str+1 + +;;; PRINTSNL(string) (<= 253 characters) +;;; Send string and newline via acia +;;; Input: +;;; string: +;;; String to be send. +;;; Output: +;;; - +;;; Changes: +;;; a, x, y, puts_str, put_str+1 + +;;; INPUTS(addr, len) +;;; Read up to len characters from acai and store them at addr +;;; This is a wrapper for gets +;;; Input: +;;; addr: +;;; Target address for zero-terminated string +;;; len: +;;; Maximal length of string to be read (not terminating zero!) +;;; +-------------------------------------------------------+ +;;; |IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! IMPORTANT! | +;;; +-------------------------------------------------------+ +;;; Note that a terminating zero is added to the string. +;;; Therefore len+1 bytes may be written! +;;; Output: +;;; - +;;; Changes: +;;; a, y, ACAI registers + + +;;; PRINTNL +;;; Send newline via acia +;;; Input: +;;; - +;;; Output: +;;; - +;;; Changes: +;;; a, x, acai registers + +;;; STORE_WORD(word,dst) +;;; Store word at dst +;;; Input: +;;; word +;;; Output: +;;; - +;;; Changes: +;;; a, dst + +;;; This file may be included from boot.asm or from +;;; some userland program. If included from a +;;; boot.asm, external symbols are defined in +;;; namespace export. We define this namespace here +;;; in case this file is included from a userland program. +;;; We use the definition of CLOCK_SPPED in boot.asm to +;;; check if we are compiling in the context of a userland +;;; program. +.weak + CLOCK_SPEED = 0 +.endweak +.if CLOCK_SPEED == 0 + export .namespace + puts = puts + puts_str = puts_str + gets_str = gets_str + gets_len = gets_len + gets = gets + putc = putc + .endn + BOOT_EMBEDDED = false +.else + BOOT_EMBEDDED = true +.endif + +PRINT .macro + lda #<\1 + sta export.puts_str + lda #>\1 + sta export.puts_str+1 + jsr export.puts + .endm + +PRINTS .macro + PRINT(saddr) + jmp cont +saddr: + .null \1 +cont: + .endm + +PRINTSNL .macro + PRINT(saddr) + jmp cont +saddr: + .null \1, $0d, $0a +cont: + .endm + +INPUTS .macro + lda #<\1 + sta export.gets_str + lda #>\1 + sta export.gets_str+1 + lda \2 + sta export.gets_len + jsr export.gets + .endm + +PRINTNL .macro + lda #$0d + jsr export.putc + lda #$0a + jsr export.putc + .endm + +STORE_WORD .macro + lda #<\1 + sta \2 + lda #>\1 + sta \2+1 + .endm diff --git a/roms/boot/export_symbols.py b/roms/boot/export_symbols.py @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -"""Extract labels from boot.s - -This script extracts exported labels from boot.s. liba.h is created -from boot.h and the exported templates. This allows user programs -to access ROM functions by importing liba.h. -""" - -import re -import pdb - - -def get_labels_from_file(filename): - labels = {} - with open(filename, 'r') as f: - # Get addresses of labels - for l in f: - m = re.search("^(.*?), 0x(.*?),", l) - if m != None: - labels.update({m[1] : m[2]}) - return labels - - -def get_exports_from_file(filename, labels): - out = "" - with open(filename, 'r') as f: - # Get list of exported symbols - for l in f: - m = re.search("^(.*?)[: ].*;.*EXPORT", l) - if m != None: - if m[1] in labels: - out += " {} = ${}\n".format(m[1], labels[m[1]]) - return out - - -with open('liba.h', 'w') as fout: - with open('boot.h', 'r') as fin: - for l in fin: - fout.write(l) - fout.write('#ifdef SYMON\n') - labels = get_labels_from_file('boot_symon.l') - fout.write(get_exports_from_file('boot.s', labels)) - fout.write('#else\n') - labels = get_labels_from_file('boot.l') - fout.write(get_exports_from_file('boot.s', labels)) - fout.write('#endif // SYMON\n') diff --git a/roms/serial_char_out/Makefile b/roms/serial_char_out/Makefile @@ -1,11 +1,3 @@ -TARGET=serial_char_out.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=serial_char_out -all: $(TARGET_BASENAME).s - xa -o $(TARGET) $< - -flash: $(TARGET) - sudo ~/opt/minipro-0.3/minipro -p AT28C64B -w $< - -clean: - rm -f $(TARGET) +include ../Makefile.common diff --git a/roms/serial_char_out/serial_char_out.asm b/roms/serial_char_out/serial_char_out.asm @@ -0,0 +1,51 @@ + ;; Output sequence of 'A' via ACIA 65C51 + +export .namespace +.if SYMON + * = $c000 + acia_base = $8800 +.else + * = $e000 + acia_base = $dc00 +.endif + acia_data_reg = acia_base + acia_status_reg = acia_base + 1 + acia_cmd_reg = acia_base + 2 + acia_ctrl_reg = acia_base + 3 + +init: + ;; Reset acai + sta acia_status_reg + ;; 300 bps, 8 data bits, 1 stop bit + lda #%00010110 + sta acia_ctrl_reg + ;; No parity, no echo, no interrupts, DTR ready + lda #%11001011 + sta acia_cmd_reg +send_loop: + lda #"A" + sta acia_data_reg + ;; 4 cycles per inner loop run + ;; At 1 Mhz and 300 bps, transmitting one bit + ;; takes 1*10**6/300 = 3333.3 cycles + ;; Including start bit and stop bit, we have + ;; to transmit 10 bit for 1 byte. + ;; Thus, we should run the loop for + ;; 3333.3*10/4 = 8333.25 0 $208d iterations. + ;; We run for 2100 iterations. + ldx #$21 ; 2 cycles +wait_loop: + ldy #$ff ; 2 cycles +inner_loop: + dey ; 2 cycles + bne inner_loop ; 2 cycles + dex ; 2 cycles + bne wait_loop ; 2 cycles + jmp send_loop ; 4 cycles + +;;; Vectors + .fill $fffa-*, $ff + .word $0000 ; nmi + .word init ; reset + .word $0000 ; irq +.endn diff --git a/roms/serial_char_out/serial_char_out.s b/roms/serial_char_out/serial_char_out.s @@ -1,51 +0,0 @@ - ;; Output sequence of 'A' via ACIA 65C51 - -// #define SYMON - -#ifdef SYMON - * = $c000 ; Symon - acia_base = $8800 ; Symon -#else - * = $e000 - acia_base = $dc00 -#endif - acia_data_reg = acia_base - acia_status_reg = acia_base + 1 - acia_cmd_reg = acia_base + 2 - acia_ctrl_reg = acia_base + 3 - -init: - ;; Reset acai - sta acia_status_reg - ;; 300 bps, 8 data bits, 1 stop bit - lda #%00010110 - sta acia_ctrl_reg - ;; No parity, no echo, no interrupts, DTR ready - lda #%11001011 - sta acia_cmd_reg -send_loop: - lda #"A" - sta acia_data_reg - ;; 4 cycles per inner loop run - ;; At 1 Mhz and 300 bps, transmitting one bit - ;; takes 1*10**6/300 = 3333.3 cycles - ;; Including start bit and stop bit, we have - ;; to transmit 10 bit for 1 byte. - ;; Thus, we should run the loop for - ;; 3333.3*10/4 = 8333.25 0 $208d iterations. - ;; We run for 2100 iterations. - ldx #$21 ; 2 cycles -wait_loop: - ldy #$ff ; 2 cycles -inner_loop: - dey ; 2 cycles - bne inner_loop ; 2 cycles - dex ; 2 cycles - bne wait_loop ; 2 cycles - jmp send_loop ; 4 cycles - - ;; Vectors - .dsb $fffa-*, $ff - .word $0000 ; nmi - .word init ; reset - .word $0000 ; irq diff --git a/roms/simple_loop/Makefile b/roms/simple_loop/Makefile @@ -1,11 +1,3 @@ -TARGET=loop.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=loop -all: $(TARGET_BASENAME).s - xa -o $(TARGET) $< - -flash: $(TARGET) - sudo ~/opt/minipro-0.3/minipro -p AT28C64B -w $< - -clean: - rm -f $(TARGET) +include ../Makefile.common diff --git a/roms/simple_loop/loop.asm b/roms/simple_loop/loop.asm @@ -0,0 +1,23 @@ +export .namespace +.if SYMON + rom_start = $c000 +.else + rom_start = $e000 +.endif + * = rom_start +start: + jmp middle + + .fill (rom_start+$10)-*, $ff +middle: + jmp high + + .fill (rom_start+$100)-*, $ff +high: jmp start + +;;; Vectors + .fill $fffa-*, $ff + .word $0000 ; nmi + .word start ; reset + .word $0000 ; irq +.endn diff --git a/roms/simple_loop/loop.s b/roms/simple_loop/loop.s @@ -1,16 +0,0 @@ - * = $e000 ; ROM starts here -start: - jmp middle - - .dsb $e010-*, $ff -middle: - jmp high - - .dsb $e100-*, $ff -high: jmp start - - ;; Vectors - .dsb $fffa-*, $ff - .word $0000 ; nmi - .word start ; reset - .word $0000 ; irq diff --git a/sw/10print/10print.asm b/sw/10print/10print.asm @@ -0,0 +1,56 @@ +;;; If we are included in boot.asm, we +;;; should not set the start address +.weak + BOOT_EMBEDDED = false +.endweak +.if BOOT_EMBEDDED + init_acia = export.init_acia + lfsr_init = export.lfsr_init + lfsr_step = export.lfsr_step + putc = export.putc +.else + .if SYMON + .include "boot_symon.l" + .else + .include "boot.l" + .endif + .include "boot_macros.inc" + * = $0300 +.endif +init: + cld + jsr init_acia + jsr lfsr_init + jmp ten_print + +ten_print: + .block + ldx #$10 +l1: + ldy #$00 +l0: + dey + bne l0 + dex + bne l1 + jsr lfsr_step + and #$01 + bne slash +.if SYMON + lda #'\' + jsr putc +.else + PRINTS('╲') +.endif + jsr putc + jmp ten_print +slash: +.if SYMON + lda #'/' + jsr putc +.else + PRINTS('╱') +.endif + jsr putc + jmp ten_print + .bend diff --git a/sw/10print/10print.s b/sw/10print/10print.s @@ -1,40 +0,0 @@ -#include "../../roms/boot/liba.h" - - * = $0300 -init: - cld - jsr init_acia - jsr lfsr_init - jmp ten_print - -ten_print: - .( - ldx #$10 -l1: - ldy #$00 -l0: - dey - bne l0 - dex - bne l1 - jsr lfsr_step - and #$01 - bne slash -#ifdef SYMON - lda #'\' - jsr putc -#else - PRINTS('╲') -#endif - jsr putc - jmp ten_print -slash: -#ifdef SYMON - lda #'/' - jsr putc -#else - PRINTS('╱') -#endif - jsr putc - jmp ten_print - .) diff --git a/sw/10print/Makefile b/sw/10print/Makefile @@ -1,16 +1,3 @@ -TARGET=10print.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=10print -all: $(TARGET) $(TARGET_BASENAME)_symon.bin - -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -clean: - rm -f *.bin *.l +include ../Makefile.common diff --git a/sw/Makefile.common b/sw/Makefile.common @@ -0,0 +1,15 @@ +CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b -I../../roms/boot/ + +all: $(TARGET).bin $(TARGET)_symon.bin + +%.bin: %.asm + 64tass $(CFLAGS) -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" + +%_symon.bin: %.asm + 64tass $(CFLAGS) -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" + +upload: $(TARGET).bin + ../../roms/boot/boot.py $(TARGET).bin + +clean: + rm -f *.bin *.l *.lst diff --git a/sw/aaa/Makefile b/sw/aaa/Makefile @@ -1,16 +1,3 @@ -TARGET=aaa.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=aaa -all: $(TARGET) $(TARGET_BASENAME)_symon.bin - -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -clean: - rm -f *.bin *.l +include ../Makefile.common diff --git a/sw/aaa/aaa.asm b/sw/aaa/aaa.asm @@ -0,0 +1,44 @@ +.if SYMON + .include "boot_symon.l" +.else + .include "boot.l" +.endif + .include "boot_macros.inc" + * = $0300 + +init: .block + jsr init_acia +loop: + lda #'A' + jsr my_putc + jmp loop + .bend + + clock = 4 +.switch CLOCK_SPEED +.case 4 ; 4 Mhz Clock + SERIAL_SEND_DELAY_X = $c3 + SERIAL_SEND_DELAY_Y = $02 +.case 2 ; 4 Mhz Clock + SERIAL_SEND_DELAY_X = $c2 + SERIAL_SEND_DELAY_Y = $01 +.default ; 1 Mhz Clock + SERIAL_SEND_DELAY_X = $88 + SERIAL_SEND_DELAY_Y = $01 +.endswitch +my_putc: .block + ;; Send character. + ;; Due to bugs in the register and interrupt + ;; handling of the WDC 65C02, we have to use + ;; a manual delay. + sta acia_data_reg + ldy #SERIAL_SEND_DELAY_Y +outer_loop: + ldx #SERIAL_SEND_DELAY_X +inner_loop: + dex + bne inner_loop + dey + bne outer_loop + rts + .bend diff --git a/sw/aaa/aaa.s b/sw/aaa/aaa.s @@ -1,44 +0,0 @@ -#include "../../roms/boot/liba.h" - - * = $0300 -init: - jsr init_acia - .( -loop: - lda #'A' - jsr my_putc - jmp loop - .) - -#define CLOCK_4_MHZ - -#ifdef CLOCK_4_MHZ - SERIAL_SEND_DELAY_X = $c3 - SERIAL_SEND_DELAY_Y = $02 -#endif -#ifdef CLOCK_2_MHZ - SERIAL_SEND_DELAY_X = $c2 - SERIAL_SEND_DELAY_Y = $01 -#endif -#ifdef CLOCK_1_MHZ - SERIAL_SEND_DELAY_X = $88 - SERIAL_SEND_DELAY_Y = $01 -#endif -my_putc: ; EXPORT - .( - ;; Send character. - ;; Due to bugs in the register and interrupt - ;; handling of the WDC 65C02, we have to use - ;; a manual delay. - sta acia_data_reg - ldy #SERIAL_SEND_DELAY_Y -outer_loop: - ldx #SERIAL_SEND_DELAY_X -inner_loop: - dex - bne inner_loop - dey - bne outer_loop - rts - .) - diff --git a/sw/interrupts/Makefile b/sw/interrupts/Makefile @@ -1,16 +1,3 @@ -TARGET=interrupts.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=interrupts -all: $(TARGET) $(TARGET_BASENAME)_symon.bin - -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -clean: - rm -f *.bin *.l +include ../Makefile.common diff --git a/sw/interrupts/interrupts.asm b/sw/interrupts/interrupts.asm @@ -0,0 +1,81 @@ +.if SYMON + .include "boot_symon.l" +.else + .include "boot.l" +.endif + .include "boot_macros.inc" + * = $0300 + + flag = $30 + tmp = $31 + +init: + ;; jmp transmitter_interrupt + jmp receiver_interrupt + +;;; Looks like interrupt controlled tranmission is buggy. +;;; The only way to transmit seems to be a delay loop. +transmitter_interrupt: + .block + jsr init_acia + #STORE_WORD isr, irq_vector + #PRINTSNL 'Push key to start.' + jsr getc +loop: + ;; Transmitter interrupt on + lda #%11000111 + sta acia_cmd_reg + ;; Send a byte + lda flag + jsr putc_irq + ;; Transmitter interrupt off + lda #%11001011 + sta acia_cmd_reg + jsr wait + jmp loop + +wait: + ldx #$7f +wait_loop: + dex + bne wait_loop + rts + .bend + +receiver_interrupt: + .block + jsr init_acia + #STORE_WORD isr, irq_vector + ;; Receiver interrupt on + lda acia_cmd_reg + lda #%11001001 + sta acia_cmd_reg + #PRINTSNL "Start typing ..." +loop: + jsr getc + lda flag + jsr puth + PRINTNL + jmp loop + .bend + +isr: + lda flag + inc a + sta flag + ldx #$00 ; Indicate end of transmission + lda acia_status_reg + rti + + +putc_irq: + .block + ldx #$ff + sta acia_data_reg +wait_for_transmission_finish: + ;; Interrupt upon successful transmission + ;; sets X to $00 + cpx #$00 + bne wait_for_transmission_finish + rts + .bend diff --git a/sw/interrupts/interrupts.s b/sw/interrupts/interrupts.s @@ -1,77 +0,0 @@ -#include "../../roms/boot/liba.h" - - * = $0300 - - flag = $30 - tmp = $31 - -init: - // jmp transmitter_interrupt - jmp receiver_interrupt - -;;; Looks like interrupt controlled tranmission is buggy. -;;; The only way to transmit seems to be a delay loop. -transmitter_interrupt: - .( - jsr init_acia - STORE_WORD(isr, irq_vector) - PRINTSNL('Push key to start.') - jsr getc -loop: - ;; Transmitter interrupt on - lda #%11000111 - sta acia_cmd_reg - ;; Send a byte - lda flag - jsr putc_irq - ;; Transmitter interrupt off - lda #%11001011 - sta acia_cmd_reg - jsr wait - jmp loop - -wait: - ldx #$7f -wait_loop: - dex - bne wait_loop - rts - .) - -receiver_interrupt: - .( - jsr init_acia - STORE_WORD(isr, irq_vector) - ;; Receiver interrupt on - lda acia_cmd_reg - lda #%11001001 - sta acia_cmd_reg - PRINTSNL("Start typing ...") -loop: - jsr getc - lda flag - jsr puth - PRINTNL - jmp loop - .) - -isr: - lda flag - inc - sta flag - ldx #$00 ; Indicate end of transmission - lda acia_status_reg - rti - - -putc_irq: - .( - ldx #$ff - sta acia_data_reg -wait_for_transmission_finish: - ;; Interrupt upon successful transmission - ;; sets X to $00 - cpx #$00 - bne wait_for_transmission_finish - rts - .) diff --git a/sw/mem_test/Makefile b/sw/mem_test/Makefile @@ -1,16 +1,3 @@ -TARGET=mem_test.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=mem_test -all: $(TARGET) $(TARGET_BASENAME)_symon.bin - -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -clean: - rm -f *.bin *.l +include ../Makefile.common diff --git a/sw/mem_test/mem_test.asm b/sw/mem_test/mem_test.asm @@ -0,0 +1,60 @@ +.if SYMON + .include "boot_symon.l" +.else + .include "boot.l" +.endif + .include "boot_macros.inc" + * = $0300 +init: + .block + jsr init_acia +loop: + jsr mem_test +end: + jmp loop + ;; jmp end + .bend + +mem_test: + .block + #PRINTSNL "Memory test" + ;; Address to be tested is stored as 16 bit value at $40. + ;; We start the memory test at address $0400. + ;; The test runs up to $8000. It fails at the last address, + ;; because memory ends at $7fff. + start = $0400 + lda #<start + sta $40 + lda #>start + sta $41 +loop: + jsr mem_cell_test + inc $40 + bne loop + inc $41 + lda $41 + cmp #$80 + bne loop + rts + +mem_cell_test: + lda $41 + jsr puth + lda $40 + jsr puth + lda #$00 + sta ($40) + lda ($40) + cmp #$00 + bne fail + lda #$ff + sta ($40) + lda ($40) + cmp #$ff + bne fail + #PRINTSNL " OK" + rts +fail: + #PRINTSNL " Fail!" + rts + .bend diff --git a/sw/mem_test/mem_test.s b/sw/mem_test/mem_test.s @@ -1,56 +0,0 @@ -#include "../../roms/boot/liba.h" - - * = $0300 -init: - .( - jsr init_acia -loop: - jsr mem_test -end: - jmp loop - // jmp end - .) - -mem_test: - .( - PRINTSNL("Memory test") - ;; Address to be tested is stored as 16 bit value at $40. - ;; We start the memory test at address $0400. - ;; The test runs up to $8000. It fails at the last address, - ;; because memory ends at $7fff. - start = $0400 - lda #<start - sta $40 - lda #>start - sta $41 -loop: - jsr mem_cell_test - inc $40 - bne loop - inc $41 - lda $41 - cmp #$80 - bne loop - rts - -mem_cell_test: - lda $41 - jsr puth - lda $40 - jsr puth - lda #$00 - sta ($40) - lda ($40) - cmp #$00 - bne fail - lda #$ff - sta ($40) - lda ($40) - cmp #$ff - bne fail - PRINTSNL(" OK") - rts -fail: - PRINTSNL(" Fail!") - rts - .) diff --git a/sw/serial_line_echo/Makefile b/sw/serial_line_echo/Makefile @@ -1,16 +1,3 @@ -TARGET=serial_line_echo.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=serial_line_echo -all: $(TARGET) $(TARGET_BASENAME)_symon.bin - -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -clean: - rm -f *.bin *.l +include ../Makefile.common diff --git a/sw/serial_line_echo/serial_line_echo.asm b/sw/serial_line_echo/serial_line_echo.asm @@ -0,0 +1,17 @@ +.if SYMON + .include "boot_symon.l" +.else + .include "boot.l" +.endif + .include "boot_macros.inc" + * = $0300 + +init: + jsr init_acia +loop: + #PRINTSNL "Serial line echo" + #INPUTS $1000, #$10 + #PRINTNL + #PRINT $1000 + #PRINTNL + jmp loop diff --git a/sw/serial_line_echo/serial_line_echo.s b/sw/serial_line_echo/serial_line_echo.s @@ -1,14 +0,0 @@ -#include "../../roms/boot/liba.h" - - * = $0300 -init: - jsr init_acia - .( -loop: - PRINTSNL("Serial line echo") - INPUTS($1000, #$10) - PRINTSNL("") - PRINT($1000) - PRINTSNL("") - jmp loop - .) diff --git a/sw/ttt/Makefile b/sw/ttt/Makefile @@ -1,33 +1,14 @@ -TARGET=ttt.bin -TARGET_BASENAME=$(basename $(TARGET)) +TARGET=ttt -all: $(TARGET) $(TARGET_BASENAME)_boot.bin \ - $(TARGET_BASENAME)_symon.bin $(TARGET_BASENAME)_boot_symon.bin \ - $(TARGET_BASENAME)_test.bin $(TARGET_BASENAME)_symon_test.bin +include ../Makefile.common -%.bin: %.s - xa -l "$(basename $@).l" -r -o "$@" "$<" +test: $(TARGET)_test.bin $(TARGET)_symon_test.bin -%_boot.bin: %.s - xa -DBOOT -l "$(basename $@).l" -r -o "$@" "$<" +%_test.bin: %.asm + 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=false -l $(TARGET)_test.l -L $(TARGET)_test.lst "$<" -o "$@" -%_test.bin: %.s - xa -DRUN_TESTS -l "$(basename $@).l" -r -o "$@" "$<" +%_symon_test.bin: %.asm + 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=true -l $(TARGET)_symon_test.l -L $(TARGET)_symon_test.lst "$<" -o "$@" -%_symon.bin: %.s - xa -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -%_boot_symon.bin: %.s - xa -DBOOT -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -%_symon_test.bin: %.s - xa -DRUN_TESTS -DSYMON -l "$(basename $@).l" -r -o "$@" "$<" - -upload: $(TARGET) - ../../roms/boot/boot.py $(TARGET) - -upload_test: $(TARGET_BASENAME)_test.bin - ../../roms/boot/boot.py $(TARGET_BASENAME)_test.bin - -clean: - rm -f *.bin *.l +upload_test: $(TARGET).bin + ../../roms/boot/boot.py $(TARGET)_test.bin diff --git a/sw/ttt/board.asm b/sw/ttt/board.asm @@ -0,0 +1,592 @@ +;;; This file contains the definitions and subroutines +;;; related to the game boards, like printing the +;;; game board and placing pieces. It also contains +;;; the subroutines to check for a win constallation +;;; on the board, and for playing the game. + +;;; Orchester a game between two players +;;; Each player has an init subroutine, called +;;; once at the beginning, and a ply subroutine, +;;; called on every turn of the player. +;;; player_x_init_ptr - Init function of player X +;;; player_x_ply_ptr - Ply function of player X +;;; player_o_init_ptr - Init function of player O +;;; player_o_ply_ptr - Ply function of player O +play_game: + .block + jsr init_board + ;; jsr does not allow indirect addressing, + ;; threfore we have to wrap the indirect + ;; calls this way. + jsr player_x_init + jsr player_o_init +play_game_loop: + lda #piece_x + jsr next_ply + bne game_finished + lda #piece_o + jsr next_ply + bne game_finished + jmp play_game_loop +game_finished: + pha + jsr print_board + pla + jsr print_game_state + PRINTNL + rts +next_ply: + cmp #piece_x + bne ply_player_o + ;; Player X's ply + ;; jsr does not allow indirect addressing, + ;; threfore we have to wrap the indirect + ;; calls this way. + jsr player_x_ply + jmp cont +ply_player_o: + jsr player_o_ply +cont: + jsr get_game_state + cmp #$ff + rts +player_x_init: + jmp (player_x_init_ptr) +player_o_init: + jmp (player_o_init_ptr) +player_x_ply: + jmp (player_x_ply_ptr) +player_o_ply: +before_o: + jmp (player_o_ply_ptr) +after_o: + .bend + +;;; We store the Tic-Tac-Toe board in three +;;; bytes, one for each row. When passing +;;; a board on the stack, the first row is +;;; pushed first, followed by the second, +;;; and third. +;;; +;;; Each field in a row is represented by +;;; two bits: +;;; %00 - Field unoccupied +;;; %01 - Field occupied by player "X" +;;; %10 - Field occupied by player "O" + +piece_none = %00 +piece_x = %01 +piece_o = %10 +;;; +;;; In the byte for a row, the first +;;; two bits represent the leftmost field. +;;; The last two bits are not used. +;;; +;;; A contains the piece +;;; X contains the position (0-8) +;;; The location of the board is read +;;; as a 16 bit address from address +;;; board. + + +;;; Initialize and clear the board. +;;; When init_board is called, the +;;; "standard" board location as given +;;; in variable board is transferred +;;; to board_ptr before clearing the +;;; board. +;;; If clear_board is called, the +;;; board at board_ptr is cleared +;;; without changing board_ptr. +init_board: + lda #<main_board + sta board_ptr + lda #>main_board + sta board_ptr+1 +clear_board: + ;; Clear board + lda #$00 + ldy #$00 + sta (board_ptr),y + iny + sta (board_ptr),y + iny + sta (board_ptr),y + rts + +mirror_board: + .block + ;; Mirror board on the diagonal axis + ;; such that we can + ;; use the win_row subroutine. + ;; First row to column + ldy #$00 + lda (board_ptr),y + and #%00000011 + sta (other_board_ptr),y + lda (board_ptr),y + and #%00001100 + lsr a + lsr a + ldy #$01 + sta (other_board_ptr),y + ldy #$00 + lda (board_ptr),y + and #%00110000 + lsr a + lsr a + lsr a + lsr a + ldy #$02 + sta (other_board_ptr),y + ;; Second row to column + ldy #$01 + lda (board_ptr),y + and #%00000011 + asl a + asl a + ldy #$00 + ora (other_board_ptr),y + sta (other_board_ptr),y + ldy #$01 + lda (board_ptr),y + and #%00001100 + ora (other_board_ptr),y + sta (other_board_ptr),y + ldy #$01 + lda (board_ptr),y + and #%00110000 + lsr a + lsr a + ldy #$02 + ora (other_board_ptr),y + sta (other_board_ptr),y + ;; Third column to row + lda (board_ptr),y + and #%00000011 + asl a + asl a + asl a + asl a + ldy #$00 + ora (other_board_ptr),y + sta (other_board_ptr),y + ldy #$02 + lda (board_ptr),y + and #%00001100 + asl a + asl a + ldy #$01 + ora (other_board_ptr),y + sta (other_board_ptr),y + ldy #$02 + lda (board_ptr),y + and #%00110000 + ora (other_board_ptr),y + sta (other_board_ptr),y + rts + .bend + +;;; Set a field on the board. +;;; A is the field, X the position. +;;; This subroutine changes A, X, and Y. +set_field: + ;; Convert position on X + ;; to column/row in X/Y. + jsr pos_to_column_row + ;; Set field based on X/Y coordinates +set_field_x_y: + .block + cpx #$00 + beq change_board + asl a + asl a + dex + jmp set_field_x_y +change_board: + ora (board_ptr),y + sta (board_ptr),y + rts + .bend + +;;; Get value of field on the board. +;;; X is the position. Field value is +;;; returned in A. +get_field: + .block + ;; Convert position on X + ;; to column/row in X/Y. + jsr pos_to_column_row + lda (board_ptr),y +field_loop: + cpx #$00 + beq got_field + lsr a + lsr a + dex + jmp field_loop +got_field: + and #%11 + rts + .bend + +;;; Convert position in X +;;; register to row/column +;;; coordinates. +;;; X becomes the column, +;;; Y the row +pos_to_column_row: + .block + ldy #$00 +pos_loop: + cpx #$03 + bcc pos_found + iny + dex + dex + dex + jmp pos_loop +pos_found: + rts + .bend + + +copy_field: + ;; Copy field X from board_ptr + ;; to field Y of other_board_ptr + ;; Save variables + lda board_ptr + pha + lda board_ptr+1 + pha + phy + ;; Get value & save ot + jsr get_field + pha + ;; Write value + lda other_board_ptr + sta board_ptr + lda other_board_ptr+1 + sta board_ptr+1 + pla + plx + jsr set_field + ;; Restore variables + pla + sta board_ptr+1 + pla + sta board_ptr + rts + +COPY_FIELD .macro + ldx #\1 + ldy #\2 + jsr copy_field + .endm + +SET_FIELD .macro + lda #\1 + ldx #\2 + jsr set_field + .endm + +GET_FIELD .macro + #STORE_WORD \1, board_ptr + ldx #\2 + jsr get_field + .endm + +;;; Print Tic-Tac-Toe board. +;;; Note that the printed +;;; numbers start with 1, while +;;; the internal counting of +;;; fields starts with 0. +print_board: + .block + ldx #$ff + phx + #PRINTSNL '+---+---+---+' +print_field: + lda #$03 + sta tmp ; Print each line 3 times +print_line: + lda #'|' + jsr putc + plx + inx + phx + jsr get_field + jsr piece_to_ascii + jsr putc + plx + phx + jsr put_field_number_if_empty_and_middle + jsr putc + plx + phx + cpx #$02 + beq print_row_seperator + cpx #$05 + beq print_row_seperator + cpx #$08 + beq print_row_seperator +check_if_all_rows_printed: + plx + phx + cpx #$08 + bne print_line + plx ; Clean-up stack + rts +print_row_seperator: + #PRINTSNL '|' + dec tmp ; Check if each line has been printed 3 times + lda tmp + beq print_seperator + pla ; If not, + sec ; print + sbc #$03 ; line + pha + jmp print_line ;again. +print_seperator: + #PRINTSNL '+---+---+---+' + lda #$03 + sta tmp + jmp check_if_all_rows_printed +put_field_number_if_empty_and_middle: + pha + cmp #' ' + bne print_verbatim + ;; Check if we are in the + ;; middle line + lda tmp + cmp #$02 + bne print_verbatim + ;; Middle of empty field; + ;; print field number + txa + clc + adc #'1' + jsr putc + pla + rts +print_verbatim: + pla + jmp putc + .bend + + + +piece_to_ascii: + .block + cmp #piece_x + bne not_x + lda #'#' + rts +not_x: + cmp #piece_o + bne not_o + lda #':' + rts +not_o: + lda #' ' + rts + .bend + +;;; Check if a player won. +;;; Returns piece_x or piece_y +;;; if one of the player won, +;;; piece_none in case of a draw, +;;; and #$ff in case the game +;;; is not finished yet. + piece_x_row = (piece_x << 4) + (piece_x << 2) + piece_x + piece_o_row = (piece_o << 4) + (piece_o << 2) + piece_o +get_game_state: + .block + lda #piece_x_row + jsr check_player_won + bne done + lda #piece_o_row + jsr check_player_won + bne done + jsr check_draw +done: + rts + .bend + +;;; Check if a certain player one. +;;; This subroutine expects a complete +;;; row of pieces of the player to be +;;; checked for winning in A as input. +check_player_won: + .block + sta tmp ; Store pattern for following checks + jsr check_win_row + bne done + lda tmp + jsr check_win_column + bne done + lda tmp + jsr check_win_first_diagonal + bne done + lda tmp + jsr check_win_second_diagonal +done: + rts + .bend + +;;; Check for three in a row. +;;; Expects the row pattern of +;;; three identical pieces in A. +check_win_row: + .block + ldy #$00 +loop: + cmp (board_ptr),y + beq player_won + iny + cpy #$03 + bne loop + ;; No winning rows + lda #$00 + rts +player_won: + lda tmp + and #%00000011 + rts + .bend + +;;; Check for three in a column. +;;; Expects the row(!) pattern of +;;; three identical pieces in A. +check_win_column: + .block + and #%00110000 ; Field (column) pattern + tax ; We use X as temporary memory + ldy #$00 +loop: + and (board_ptr),y ; Piece in column? + beq check_next_column ; If not, check next column + iny ; If yes, advance to next row + cpy #$03 ; Until all three rows + bne loop ; have been checked +player_won: + lda tmp + and #%00000011 + rts +check_next_column: + ldy #$00 ; Restore row counter + txa ; Old field (column) pattern + lsr a ; Change to + lsr a ; next column + tax ; and save it in temporary memory + bne loop + ;; No winning columns + lda #$00 + rts + .bend + +check_win_first_diagonal: + .block + and #%00110000 ; Start with last field (column) + tax ; Store pattern in x + ldy #$00 ; Row number stored in y +loop: + and (board_ptr),y + beq no_win_first_diagonal + txa ; Move + lsr a ; field + lsr a ; (column) + tax ; pattern + iny ; and row by one + cpy #$03 + bne loop + ;; Player won + ;; Return player piece + lda tmp + and #%00000011 + rts +no_win_first_diagonal: + lda #$00 + rts + .bend + +check_win_second_diagonal: + .block + and #%00000011 ; Start with first field (column) + tax ; Store pattern in x + ldy #$00 ; Row number stored in y +loop: + and (board_ptr),y + beq no_win_second_diagonal + txa ; Move + asl a ; field + asl a ; (column) + tax ; pattern + iny ; and row by one + cpy #$03 + bne loop + ;; Player won + ;; Return player piece + lda tmp + and #%00000011 + rts +no_win_second_diagonal: + lda #$00 + rts + .bend + +check_draw: + .block + ldy #$00 ; Row +row_loop: + ldx #%00110000 ; Field (column) pattern + txa ; Store pattern in x +col_loop: + and (board_ptr),y ; Check + beq no_draw ; all + txa ; fields + lsr a ; for + lsr a ; emptiness + tax + bne col_loop + iny + cpy #$03 + bne row_loop + ;; Draw + lda #piece_none + rts +no_draw: + lda #$ff + rts + .bend + + + +;;; This subroutine can be called +;;; called after get_game_state +;;; in order to print the state +;;; of the game in human-friendly +;;; format to the acai. +print_game_state: + .block + cmp #piece_x + beq somebody_won + cmp #piece_o + beq somebody_won + cmp #piece_none + bne no_draw + #PRINTSNL "Draw!" + rts +no_draw: + #PRINTSNL "Let the game continue!" + rts +somebody_won: + pha + #PRINTS "Player " + pla + clc + adc #'0' + jsr putc + #PRINTSNL " wins!" + rts + .bend diff --git a/sw/ttt/board.s b/sw/ttt/board.s @@ -1,589 +0,0 @@ -;;; This file contains the definitions and subroutines -;;; related to the game boards, like printing the -;;; game board and placing pieces. It also contains -;;; the subroutines to check for a win constallation -;;; on the board, and for playing the game. - -;;; Orchester a game between two players -;;; Each player has an init subroutine, called -;;; once at the beginning, and a ply subroutine, -;;; called on every turn of the player. -;;; player_x_init_ptr - Init function of player X -;;; player_x_ply_ptr - Ply function of player X -;;; player_o_init_ptr - Init function of player O -;;; player_o_ply_ptr - Ply function of player O -play_game: - .( - jsr init_board - ;; jsr does not allow indirect addressing, - ;; threfore we have to wrap the indirect - ;; calls this way. - jsr player_x_init - jsr player_o_init -play_game_loop: - lda #piece_x - jsr next_ply - bne game_finished - lda #piece_o - jsr next_ply - bne game_finished - jmp play_game_loop -game_finished: - pha - jsr print_board - pla - jsr print_game_state - PRINTNL - rts -next_ply: - cmp #piece_x - bne ply_player_o - ;; Player X's ply - ;; jsr does not allow indirect addressing, - ;; threfore we have to wrap the indirect - ;; calls this way. - jsr player_x_ply - jmp cont -ply_player_o: - jsr player_o_ply -cont: - jsr get_game_state - cmp #$ff - rts -player_x_init: - jmp (player_x_init_ptr) -player_o_init: - jmp (player_o_init_ptr) -player_x_ply: - jmp (player_x_ply_ptr) -player_o_ply: -before_o: - jmp (player_o_ply_ptr) -after_o: - .) - -;;; We store the Tic-Tac-Toe board in three -;;; bytes, one for each row. When passing -;;; a board on the stack, the first row is -;;; pushed first, followed by the second, -;;; and third. -;;; -;;; Each field in a row is represented by -;;; two bits: -;;; %00 - Field unoccupied -;;; %01 - Field occupied by player "X" -;;; %10 - Field occupied by player "O" - -piece_none = %00 -piece_x = %01 -piece_o = %10 -;;; -;;; In the byte for a row, the first -;;; two bits represent the leftmost field. -;;; The last two bits are not used. -;;; -;;; A contains the piece -;;; X contains the position (0-8) -;;; The location of the board is read -;;; as a 16 bit address from address -;;; board. - - -;;; Initialize and clear the board. -;;; When init_board is called, the -;;; "standard" board location as given -;;; in variable board is transferred -;;; to board_ptr before clearing the -;;; board. -;;; If clear_board is called, the -;;; board at board_ptr is cleared -;;; without changing board_ptr. -init_board: - lda #<main_board - sta board_ptr - lda #>main_board - sta board_ptr+1 -clear_board: - // Clear board - lda #$00 - ldy #$00 - sta (board_ptr),y - iny - sta (board_ptr),y - iny - sta (board_ptr),y - rts - -mirror_board: - .( - ;; Mirror board on the diagonal axis - ;; such that we can - ;; use the win_row subroutine. - ;; First row to column - ldy #$00 - lda (board_ptr),y - and #%00000011 - sta (other_board_ptr),y - lda (board_ptr),y - and #%00001100 - lsr - lsr - ldy #$01 - sta (other_board_ptr),y - ldy #$00 - lda (board_ptr),y - and #%00110000 - lsr - lsr - lsr - lsr - ldy #$02 - sta (other_board_ptr),y - ;; Second row to column - ldy #$01 - lda (board_ptr),y - and #%00000011 - asl - asl - ldy #$00 - ora (other_board_ptr),y - sta (other_board_ptr),y - ldy #$01 - lda (board_ptr),y - and #%00001100 - ora (other_board_ptr),y - sta (other_board_ptr),y - ldy #$01 - lda (board_ptr),y - and #%00110000 - lsr - lsr - ldy #$02 - ora (other_board_ptr),y - sta (other_board_ptr),y - ;; Third column to row - lda (board_ptr),y - and #%00000011 - asl - asl - asl - asl - ldy #$00 - ora (other_board_ptr),y - sta (other_board_ptr),y - ldy #$02 - lda (board_ptr),y - and #%00001100 - asl - asl - ldy #$01 - ora (other_board_ptr),y - sta (other_board_ptr),y - ldy #$02 - lda (board_ptr),y - and #%00110000 - ora (other_board_ptr),y - sta (other_board_ptr),y - rts - .) - -;;; Set a field on the board. -;;; A is the field, X the position. -;;; This subroutine changes A, X, and Y. -set_field: - ;; Convert position on X - ;; to column/row in X/Y. - jsr pos_to_column_row - ;; Set field based on X/Y coordinates -set_field_x_y: - .( - cpx #$00 - beq change_board - asl - asl - dex - jmp set_field_x_y -change_board: - ora (board_ptr),y - sta (board_ptr),y - rts - .) - -;;; Get value of field on the board. -;;; X is the position. Field value is -;;; returned in A. -get_field: - .( - ;; Convert position on X - ;; to column/row in X/Y. - jsr pos_to_column_row - lda (board_ptr),y -field_loop: - cpx #$00 - beq got_field - lsr - lsr - dex - jmp field_loop -got_field: - and #%11 - rts - .) - -;;; Convert position in X -;;; register to row/column -;;; coordinates. -;;; X becomes the column, -;;; Y the row -pos_to_column_row: - .( - ldy #$00 -pos_loop: - cpx #$03 - bcc pos_found - iny - dex - dex - dex - jmp pos_loop -pos_found: - rts - .) - - -copy_field: - ;; Copy field X from board_ptr - ;; to field Y of other_board_ptr - ;; Save variables - lda board_ptr - pha - lda board_ptr+1 - pha - phy - ;; Get value & save ot - jsr get_field - pha - ;; Write value - lda other_board_ptr - sta board_ptr - lda other_board_ptr+1 - sta board_ptr+1 - pla - plx - jsr set_field - ;; Restore variables - pla - sta board_ptr+1 - pla - sta board_ptr - rts - -#define COPY_FIELD(from,to) \ - ldx #from: \ - ldy #to: \ - jsr copy_field - -#define SET_FIELD(piece,pos) \ - lda #piece: \ - ldx #pos: \ - jsr set_field - -#define GET_FIELD(ptr,pos) \ - STORE_WORD(ptr,board_ptr): \ - ldx #pos: \ - jsr get_field - -;;; Print Tic-Tac-Toe board. -;;; Note that the printed -;;; numbers start with 1, while -;;; the internal counting of -;;; fields starts with 0. -print_board: - .( - ldx #$ff - phx - PRINTSNL('+---+---+---+') -print_field: - lda #$03 - sta tmp ; Print each line 3 times -print_line: - lda #'|' - jsr putc - plx - inx - phx - jsr get_field - jsr piece_to_ascii - jsr putc - plx - phx - jsr put_field_number_if_empty_and_middle - jsr putc - plx - phx - cpx #$02 - beq print_row_seperator - cpx #$05 - beq print_row_seperator - cpx #$08 - beq print_row_seperator -check_if_all_rows_printed: - plx - phx - cpx #$08 - bne print_line - plx ; Clean-up stack - rts -print_row_seperator: - PRINTSNL('|') - dec tmp ; Check if each line has been printed 3 times - lda tmp - beq print_seperator - pla ; If not, - sec ; print - sbc #$03 ; line - pha - jmp print_line ;again. -print_seperator: - PRINTSNL('+---+---+---+') - lda #$03 - sta tmp - jmp check_if_all_rows_printed -put_field_number_if_empty_and_middle: - pha - cmp #' ' - bne print_verbatim - ;; Check if we are in the - ;; middle line - lda tmp - cmp #$02 - bne print_verbatim - ;; Middle of empty field; - ;; print field number - txa - clc - adc #'1' - jsr putc - pla - rts -print_verbatim: - pla - jmp putc - .) - - - -piece_to_ascii: - .( - cmp #piece_x - bne not_x - lda #'#' - rts -not_x: - cmp #piece_o - bne not_o - lda #':' - rts -not_o: - lda #' ' - rts - .) - -;;; Check if a player won. -;;; Returns piece_x or piece_y -;;; if one of the player won, -;;; piece_none in case of a draw, -;;; and #$ff in case the game -;;; is not finished yet. - piece_x_row = (piece_x << 4) + (piece_x << 2) + piece_x - piece_o_row = (piece_o << 4) + (piece_o << 2) + piece_o -get_game_state: - .( - lda #piece_x_row - jsr check_player_won - bne done - lda #piece_o_row - jsr check_player_won - bne done - jsr check_draw -done: - rts - .) - -;;; Check if a certain player one. -;;; This subroutine expects a complete -;;; row of pieces of the player to be -;;; checked for winning in A as input. -check_player_won: - .( - sta tmp ; Store pattern for following checks - jsr check_win_row - bne done - lda tmp - jsr check_win_column - bne done - lda tmp - jsr check_win_first_diagonal - bne done - lda tmp - jsr check_win_second_diagonal -done: - rts - .) - -;;; Check for three in a row. -;;; Expects the row pattern of -;;; three identical pieces in A. -check_win_row: - .( - ldy #$00 -loop: - cmp (board_ptr),y - beq player_won - iny - cpy #$03 - bne loop - ;; No winning rows - lda #$00 - rts -player_won: - lda tmp - and #%00000011 - rts - .) - -;;; Check for three in a column. -;;; Expects the row(!) pattern of -;;; three identical pieces in A. -check_win_column: - .( - and #%00110000 ; Field (column) pattern - tax ; We use X as temporary memory - ldy #$00 -loop: - and (board_ptr),y ; Piece in column? - beq check_next_column ; If not, check next column - iny ; If yes, advance to next row - cpy #$03 ; Until all three rows - bne loop ; have been checked -player_won: - lda tmp - and #%00000011 - rts -check_next_column: - ldy #$00 ; Restore row counter - txa ; Old field (column) pattern - lsr ; Change to - lsr ; next column - tax ; and save it in temporary memory - bne loop - ;; No winning columns - lda #$00 - rts - .) - -check_win_first_diagonal: - .( - and #%00110000 ; Start with last field (column) - tax ; Store pattern in x - ldy #$00 ; Row number stored in y -loop: - and (board_ptr),y - beq no_win_first_diagonal - txa ; Move - lsr ; field - lsr ; (column) - tax ; pattern - iny ; and row by one - cpy #$03 - bne loop - ;; Player won - ;; Return player piece - lda tmp - and #%00000011 - rts -no_win_first_diagonal: - lda #$00 - rts - .) - -check_win_second_diagonal: - .( - and #%00000011 ; Start with first field (column) - tax ; Store pattern in x - ldy #$00 ; Row number stored in y -loop: - and (board_ptr),y - beq no_win_second_diagonal - txa ; Move - asl ; field - asl ; (column) - tax ; pattern - iny ; and row by one - cpy #$03 - bne loop - ;; Player won - ;; Return player piece - lda tmp - and #%00000011 - rts -no_win_second_diagonal: - lda #$00 - rts - .) - -check_draw: - .( - ldy #$00 ; Row -row_loop: - ldx #%00110000 ; Field (column) pattern - txa ; Store pattern in x -col_loop: - and (board_ptr),y ; Check - beq no_draw ; all - txa ; fields - lsr ; for - lsr ; emptiness - tax - bne col_loop - iny - cpy #$03 - bne row_loop - ;; Draw - lda #piece_none - rts -no_draw: - lda #$ff - rts - .) - - - -;;; This subroutine can be called -;;; called after get_game_state -;;; in order to print the state -;;; of the game in human-friendly -;;; format to the acai. -print_game_state: - .( - cmp #piece_x - beq win - cmp #piece_o - beq win - cmp #piece_none - bne no_draw - PRINTSNL("Draw!") - rts -no_draw: - PRINTSNL("Let the game continue!") - rts -win: - pha - PRINTS("Player ") - pla - clc - adc #'0' - jsr putc - PRINTSNL(" wins!") - rts - .) diff --git a/sw/ttt/board_test.asm b/sw/ttt/board_test.asm @@ -0,0 +1,142 @@ +;;; Expected value is given in res. +;;; A is compared to this value. +CHECK_ERROR .macro + cmp #\1 + beq no_error + #PRINTSNL "Error" +stop_loop + jmp stop_loop +no_error + .endm + +PLAYER_WIN_ROW .macro + jsr init_board + ldx #\2 + lda #\1 + jsr set_field + ldx #(\2+1) + lda #\1 + jsr set_field + ldx #(\2+2) + lda #\1 + jsr set_field + jsr print_board + jsr get_game_state + #CHECK_ERROR \1 + jsr print_game_state + .endm + +PLAYER_WIN_COLUMN .macro + jsr init_board + lda #\1 + ldx #\2 + jsr set_field + lda #\1 + ldx #(\2+3) + jsr set_field + lda #\1 + ldx #(\2+6) + jsr set_field + jsr print_board + jsr get_game_state + #CHECK_ERROR \1 + jsr print_game_state + .endm + +PLAYER_WIN_SECOND_DIAGONAL .macro + jsr init_board + lda #\1 + ldx #$00 + jsr set_field + lda #\1 + ldx #$04 + jsr set_field + lda #\1 + ldx #$08 + jsr set_field + jsr print_board + jsr get_game_state + #CHECK_ERROR \1 + jsr print_game_state + .endm + +PLAYER_WIN_FIRST_DIAGONAL .macro + jsr init_board + lda #\1 + ldx #$02 + jsr set_field + lda #\1 + ldx #$04 + jsr set_field + lda #\1 + ldx #$06 + jsr set_field + jsr print_board + jsr get_game_state + #CHECK_ERROR \1 + jsr print_game_state + .endm + +;;; Place some pieces +;;; and print the field. +game_board_test: + .block + ;; + ;; Unfinished game. + ;; + jsr init_board + ;; Place "X" pieces + #SET_FIELD piece_x,$00 + #SET_FIELD piece_x,$01 + #SET_FIELD piece_x,$08 + ;; Place "O" pieces + #SET_FIELD piece_o,$04 + #SET_FIELD piece_o,$05 + #SET_FIELD piece_o,$06 + ;; Print board + jsr print_board + jsr get_game_state + #CHECK_ERROR $ff + jsr print_game_state + ;; + ;; Finished game. + ;; + jsr init_board + #SET_FIELD piece_x,$00 + #SET_FIELD piece_x,$01 + #SET_FIELD piece_o,$02 + #SET_FIELD piece_o,$03 + #SET_FIELD piece_o,$04 + #SET_FIELD piece_x,$05 + #SET_FIELD piece_x,$06 + #SET_FIELD piece_x,$07 + #SET_FIELD piece_o,$08 + jsr print_board + jsr get_game_state + #CHECK_ERROR piece_none + jsr print_game_state + ;; Let each player win in + ;; each row + #PLAYER_WIN_ROW piece_x,$00 + #PLAYER_WIN_ROW piece_x,$03 + #PLAYER_WIN_ROW piece_x,$06 + #PLAYER_WIN_ROW piece_o,$00 + #PLAYER_WIN_ROW piece_o,$03 + #PLAYER_WIN_ROW piece_o,$06 + ;; Let each player win in + ;; each column + #PLAYER_WIN_COLUMN piece_x,$00 + #PLAYER_WIN_COLUMN piece_x,$01 + #PLAYER_WIN_COLUMN piece_x,$02 + #PLAYER_WIN_COLUMN piece_o,$00 + #PLAYER_WIN_COLUMN piece_o,$01 + #PLAYER_WIN_COLUMN piece_o,$02 + ;; Let each player win in + ;; each diagonal + #PLAYER_WIN_SECOND_DIAGONAL piece_x + #PLAYER_WIN_FIRST_DIAGONAL piece_x + #PLAYER_WIN_SECOND_DIAGONAL piece_o + #PLAYER_WIN_FIRST_DIAGONAL piece_o + #PRINTSNL "Board tests completed. No errors found!" + rts + .bend diff --git a/sw/ttt/board_test.s b/sw/ttt/board_test.s @@ -1,143 +0,0 @@ -;;; Expected value is given in res. -;;; A is compared to this value. -#define CHECK_ERROR(res) \ - .(: \ - cmp #res: \ - beq no_error: \ - PRINTSNL("Error"): \ -stop_loop: \ - jmp stop_loop: \ -no_error: \ - .) - -#define PLAYER_WIN_ROW(player,row_start) \ - .(: \ - jsr init_board: \ - ldx #row_start: \ - lda #player: \ - jsr set_field: \ - ldx #(row_start+1): \ - lda #player: \ - jsr set_field: \ - ldx #(row_start+2): \ - lda #player: \ - jsr set_field: \ - jsr print_board: \ - jsr get_game_state: \ - CHECK_ERROR(player): \ - jsr print_game_state: \ - .) - -#define PLAYER_WIN_COLUMN(player,col) \ - .(: \ - jsr init_board: \ - lda #player: \ - ldx #col: \ - jsr set_field: \ - lda #player: \ - ldx #(col+3): \ - jsr set_field: \ - lda #player: \ - ldx #(col+6): \ - jsr set_field: \ - jsr print_board: \ - jsr get_game_state: \ - CHECK_ERROR(player): \ - jsr print_game_state: \ - .) - -#define PLAYER_WIN_SECOND_DIAGONAL(player) \ - jsr init_board: \ - lda #player: \ - ldx #$00: \ - jsr set_field: \ - lda #player: \ - ldx #$04: \ - jsr set_field: \ - lda #player: \ - ldx #$08: \ - jsr set_field: \ - jsr print_board: \ - jsr get_game_state: \ - CHECK_ERROR(player): \ - jsr print_game_state - -#define PLAYER_WIN_FIRST_DIAGONAL(player) \ - jsr init_board: \ - lda #player: \ - ldx #$02: \ - jsr set_field: \ - lda #player: \ - ldx #$04: \ - jsr set_field: \ - lda #player: \ - ldx #$06: \ - jsr set_field: \ - jsr print_board: \ - jsr get_game_state: \ - CHECK_ERROR(player): \ - jsr print_game_state - -;;; Place some pieces -;;; and print the field. -game_board_test: - .( - ;; - ;; Unfinished game. - ;; - jsr init_board - // Place "X" pieces - SET_FIELD(piece_x,$00) - SET_FIELD(piece_x,$01) - SET_FIELD(piece_x,$08) - // Place "O" pieces - SET_FIELD(piece_o,$04) - SET_FIELD(piece_o,$05) - SET_FIELD(piece_o,$06) - ;; Print board - jsr print_board - jsr get_game_state - CHECK_ERROR($ff) - jsr print_game_state - ;; - ;; Finished game. - ;; - jsr init_board - SET_FIELD(piece_x,$00) - SET_FIELD(piece_x,$01) - SET_FIELD(piece_o,$02) - SET_FIELD(piece_o,$03) - SET_FIELD(piece_o,$04) - SET_FIELD(piece_x,$05) - SET_FIELD(piece_x,$06) - SET_FIELD(piece_x,$07) - SET_FIELD(piece_o,$08) - jsr print_board - jsr get_game_state - CHECK_ERROR(piece_none) - jsr print_game_state - ;; Let each player win in - ;; each row - PLAYER_WIN_ROW(piece_x,$00) - PLAYER_WIN_ROW(piece_x,$03) - PLAYER_WIN_ROW(piece_x,$06) - PLAYER_WIN_ROW(piece_o,$00) - PLAYER_WIN_ROW(piece_o,$03) - PLAYER_WIN_ROW(piece_o,$06) - ;; Let each player win in - ;; each column - PLAYER_WIN_COLUMN(piece_x,$00) - PLAYER_WIN_COLUMN(piece_x,$01) - PLAYER_WIN_COLUMN(piece_x,$02) - PLAYER_WIN_COLUMN(piece_o,$00) - PLAYER_WIN_COLUMN(piece_o,$01) - PLAYER_WIN_COLUMN(piece_o,$02) - ;; Let each player win in - ;; each diagonal - PLAYER_WIN_SECOND_DIAGONAL(piece_x) - PLAYER_WIN_FIRST_DIAGONAL(piece_x) - PLAYER_WIN_SECOND_DIAGONAL(piece_o) - PLAYER_WIN_FIRST_DIAGONAL(piece_o) - PRINTSNL("Board tests completed. No errors found!") - rts - .) diff --git a/sw/ttt/computer_player.asm b/sw/ttt/computer_player.asm @@ -0,0 +1,756 @@ +;;; ************************************************ +;;; +;;; Random Computer Player +;;; +;;; ************************************************ +computer_random_init: + rts +computer_random_ply: + .block + pha ; Remember who we are + ;; We need a random number + ;; in the range 0..8. For + ;; this, we update the lfsr + ;; and take the lowest + ;; nibble until it is <= 8. + ;; BUG: Looks like we never get 0. +get_random: + jsr lfsr_step + lda lfsr_state + and #$0f + cmp #$09 + bcs get_random + ;; Check if field is occupied + sta tmp + tax + jsr get_field + cmp #piece_none + bne get_random + ;; Occupy field + ldx tmp + pla +.if RUN_TESTS + jsr print_random_ply +.endif + jsr set_field + clc + rts + .bend + +;;; ************************************************ +;;; +;;; Perfect Computer Player +;;; +;;; ************************************************ + +;;; See http://csjarchive.cogsci.rpi.edu/1993v17/i04/p0531p0561/MAIN.PDF +computer_perfect_init: + rts +computer_perfect_ply: + .block + ;; 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 + ;; Win + ;; If there is a row, column, or diagonal with two of my pieces + ;; and a blank space, + ;; Then play the blank space (thus winning the game). + pha + jsr win + pla + bcc execute_ply + ;; Block + ;; If there Is a row, column, or diagonal with two of my + ;; opponent's pieces and a blank space, + ;; Then play the blank space (thus blocking a potential win for + ;; my opponent). + pha + jsr block + pla + bcc execute_ply + ;; Fork + ;; If there are two intersecting rows, columns, or diagonals + ;; with one of my pieces and two blanks, and + ;; If the intersecting space Is empty, + ;; Then move to the intersecting space (thus creating two + ;; ways to win on my next turn). + pha + jsr fork + pla + bcc execute_ply + ;; Block Fork + ;; If there are two intersecting rows, columns, or diagonals + ;; with one of my opponent’s pieces ond two blanks, and + ;; If the intersecting space is empty, + ;; Then + ;; If there is an empty location that creates a + ;; two-in-a-row for me (thus forcing my opponent to + ;; block rather than fork), + ;; Then move to the location. + ;; Else move to the Intersection space (thus occupying + ;; the location that my opponent could use to fork). + pha + jsr block_fork + pla + bcc execute_ply + ;; Play Center + ;; If the center is blank, + ;; Then play the center. + pha + jsr play_center + pla + bcc execute_ply + ;; Play Opposite Corner + ;; If my opponent is in a corner, and + ;; If the opposite corner is empty, + ;; Then play the opposite corner. + pha + jsr play_opposite_corner + pla + bcc execute_ply + ;; Play Empty Corner + ;; If there is an empty corner, + ;; Then move to an empty corner. + pha + jsr play_empty_corner + pla + bcc execute_ply + ;; Play Empty Side + ;; If there is an empty side, + ;; Then move to an empty side. + pha + jsr play_empty_side + pla + bcc execute_ply + ;; No rule left; this should not happen. + #PRINTSNL 'Error' +error: + jmp error +execute_ply: +.if RUN_TESTS + jsr print_ply +.endif + jsr set_field_x_y +done: + rts + .bend + +random_first: + .block + sta tmp ; Remember who I am + cmp #piece_x ; If we are x + bne not_first ; + ldy #$00 +row_loop: + lda main_board,y ; Check if + bne not_first ; all + iny ; rows + cpy #$03 ; are + bne row_loop ; empty + lda tmp + jmp computer_random_ply +not_first: + sec + rts + .bend + +win: + .block + pha + jsr win_row + pla + bcc done + pha + jsr win_column + pla + bcc done + pha + jsr win_first_diagonal + pla + bcc done + pha + jsr win_second_diagonal + pla +done: + rts + .bend + +win_row: + .block + ;; Load complete winning row + cmp #piece_x + bne this_is_o + lda #piece_x_row + jmp win_row_set +this_is_o: + lda #piece_o_row +win_row_set: + sta tmp ; Store win row + ldy #$00 ; Row counter +row_loop: + ldx #$00 ; Column counter + lda tmp + and #%00111100 + cmp (board_ptr),y + beq win_ply + inx + lda tmp + and #%00110011 + cmp (board_ptr),y + beq win_ply + inx + lda tmp + and #%00001111 + cmp (board_ptr),y + beq win_ply + ;; No win here. Try next row + iny + cpy #$03 + bne row_loop + ;; No win row + ;; Carry set indicates failure + sec + rts +win_ply: + ;; Found a winning move + ;; return piece in A, + ;; and position in X/Y. + ;; Clear carry to indicate success. + lda tmp + and #%00000011 + clc + rts + .bend + +win_column: + .block + sta tmp ; Remember who I am + ;; We mirror the board in order to use + ;; win_column to check for a winning + ;; position. + #STORE_WORD board_mirrored,other_board_ptr + jsr mirror_board + #STORE_WORD board_mirrored,board_ptr + lda tmp ; Who am I? + jsr win_row + #STORE_WORD main_board,board_ptr + bcs no_win + ;; We found a winning position + ;; since we found the winning + ;; position on the mirrored board, + ;; we have to mirror it back. + phx + tya + tax + ply + clc +no_win: + rts + .bend + +win_first_diagonal: + .block + sta tmp ; Remember who I am + lda #$ff ; No empty field + sta empty_field ; found so far + lda #$00 ; Upper left field + jsr check_if_occupied_and_update_empty_field + bcs no_win + lda #$04 ; Middle field + jsr check_if_occupied_and_update_empty_field + bcs no_win + lda #$08 ; Lower right field + jsr check_if_occupied_and_update_empty_field + bcs no_win + ;; We can win! + clc + lda tmp + ldx empty_field + jsr pos_to_column_row + rts +no_win: + sec + rts + .bend +win_second_diagonal: + .block + sta tmp ; Remember who I am + lda #$ff ; No empty field + sta empty_field ; found so far + lda #$02 ; Upper right field + jsr check_if_occupied_and_update_empty_field + bcs no_win + lda #$04 ; Middle field + jsr check_if_occupied_and_update_empty_field + bcs no_win + lda #$06 ; Lower left field + jsr check_if_occupied_and_update_empty_field + bcs no_win + ;; We can win! + clc + lda tmp + ldx empty_field + jsr pos_to_column_row + rts +no_win: + sec + rts + .bend + + ;; We can complete the first diagonal if + ;; we occupy two of the field already, + ;; and the third field is empty. + ;; This subroutine checks if a field + ;; is occupied by us or if it is empty. + ;; In the latter case, empty_field is + ;; updated unless it was set already + ;; (i.e. this is the second empty field, + ;; thus no win ply available (yet)). +check_if_occupied_and_update_empty_field: + .block + pha ; Field number + tax ; stored in X, + jsr get_field ; field piece + plx ; stored in A. + cmp tmp ; Field occupied + beq cont ; by me? + cmp #$00 ; Field not occupied? + bne no_win ; Occupied by opponent. No win. + lda empty_field ; Have we seen an empty + cmp #$ff ; field before? + bne no_win ; Then we have too many empty fields. + stx empty_field ; Otherwise this is the empty field. +cont: + clc + rts +no_win: + sec + rts + .bend + + ;; We use subroutine win to check + ;; if the other player could win +block: + .block + cmp #piece_o + beq we_are_o + lda #piece_o + jmp win +we_are_o: + lda #piece_x + jmp win + .bend + +fork: + .block + ;; Forks require two intersecting rows, columns, or diagonals + ;; which both contain only one piece of the player and no + ;; other pieces. If the intersection is empty, then this + ;; is the fork field. + ;; To find these points, we create three additional boards: + ;; On the first board, all rows satisfying the critera + ;; are maintained, while all other rows are filled with $ff. + ;; On the second board, we do the same for columns, + ;; on the third board for the diagonals. + ;; Then we OR the boards pairwise. If there is + ;; an empty field after pairing, then this is the + ;; fork field. + pha ; Remember who I am + #STORE_WORD fork_board_rows, other_board_ptr + pla ; Store my piece + pha ; in A + jsr find_rows_for_fork + ;; Generate second board (potential fork columns) + ;; For this, we mirror the board and use + ;; find_rows_for_fork again. + ;; We use board_mirrored as an auxillary variable + ;; here. + #STORE_WORD main_board, board_ptr + #STORE_WORD fork_board_columns, other_board_ptr + jsr mirror_board + ;; Check for fork points on mirrored + ;; board and store the result in + ;; fork_board_columns + #STORE_WORD fork_board_columns, board_ptr + #STORE_WORD board_mirrored, other_board_ptr + pla ; Store my piece + pha ; in A + jsr find_rows_for_fork + ;; Reverse mirroring + #STORE_WORD board_mirrored, board_ptr + #STORE_WORD fork_board_columns, other_board_ptr + jsr mirror_board + pla + pha + #STORE_WORD fork_board_rows, board_ptr + #STORE_WORD fork_board_columns, other_board_ptr + jsr check_fork + bcs cont + jmp done +cont: + ;; We have generated the fork_boards + ;; for row and column, but there was + ;; no fork on rows and columns. + ;; Now we create the fork board for + ;; diagonals. + ;; In order to use our subroutine + ;; find_rows_for_fork + ;; again, we map the first diagonal + ;; to the first row of a new board, + ;; and the second diagonal to the + ;; second row. + ;; We use board_mirrored as + ;; temporary storage. + ;; First diagonal to first row + lda #$00 + sta fork_board_diagonals + sta fork_board_diagonals+1 + sta fork_board_diagonals+2 + #STORE_WORD main_board, board_ptr + #STORE_WORD fork_board_diagonals, other_board_ptr + #COPY_FIELD 0, 0 + #COPY_FIELD 4, 1 + #COPY_FIELD 8, 2 + ;; Second diagonal to second row + #COPY_FIELD 2, 3 + #COPY_FIELD 4, 4 + #COPY_FIELD 6, 5 + #STORE_WORD fork_board_diagonals, board_ptr + #STORE_WORD board_mirrored, other_board_ptr + pla ; Store my piece + pha ; in A + jsr find_rows_for_fork + ;; Store result in fork_board_diagonals + ;; Invalidate all non-diagonal fields + lda #%00001100 + sta fork_board_diagonals + sta fork_board_diagonals+2 + lda #%00110011 + sta fork_board_diagonals+1 + ;; First diagonal + #STORE_WORD board_mirrored, board_ptr + #STORE_WORD fork_board_diagonals, other_board_ptr + #COPY_FIELD 0, 0 + #COPY_FIELD 1, 4 + #COPY_FIELD 2, 8 + ;; Second diagonal + #COPY_FIELD 3, 2 + #COPY_FIELD 5, 6 + ;; Middle field can be used in either diagonal + ;; therefore we have to treat it seperately + #GET_FIELD board_mirrored, 4 + cmp #$00 + bne middle_field_not_part_of_potential_fork + #STORE_WORD fork_board_diagonals, board_ptr + #SET_FIELD $00, 4 +middle_field_not_part_of_potential_fork: + ;; No check if there is a fork between + ;; rows and diagonals ... + #STORE_WORD fork_board_diagonals, board_ptr + #STORE_WORD fork_board_rows, other_board_ptr + jsr check_fork + bcc done + ;; ... and columns and diagonals. + #STORE_WORD fork_board_columns, other_board_ptr + jsr check_fork +done: + ;; Some restore operatons + #STORE_WORD main_board, board_ptr + pla + rts + .bend + +;;; Check if we can create a fork with a +;;; suitable row and column. If this is +;;; the case, we set the carry flag +;;; and return the fork position. +check_fork: + .block + ;; Since empty fields in + ;; fork_boards_rows and + ;; fork roads_columns indicate potential + ;; fork fields, we OR these two + ;; boards and check if there are + ;; empty fields. + ;; We store the ORed boards in + ;; board_mirrored, because this + ;; board is not used here. + clc + ldy #$00 +copy_and_or_boards_loop: + lda (board_ptr),y + ora (other_board_ptr),y + sta board_mirrored,y + iny + cpy #$03 + bne copy_and_or_boards_loop + ;; No check for empty fields + ldy #$00 ; Row counter +row_loop: + lda #%00000011 ; Field pattern + sta tmp + ldx #$00 ; Column counter +pattern_loop: + lda board_mirrored,y + and tmp + beq found_empty_field + lda tmp ; Move + asl a ; to + asl a ; next + sta tmp ; pattern + inx + cpx #$03 + bne pattern_loop + iny + cpy #$03 + bne row_loop + sec +found_empty_field: + rts + .bend + + +;;; Check for all rows if they may be part +;;; of a fork. This is the case when the +;;; row contains one of our pieces and +;;; no other piece. In this case, the row +;;; stays intact. In all other cases, +;;; we overwrite the row with $ff. +;;; board_ptr points to the input board, +;;; other_board_ptr_to_the output board +find_rows_for_fork: + .block + pha + ldy #$00 ; Row counter +row_loop: + pla ; Get pattern we are looking for + pha + sta tmp + ldx #$00 ; Pattern counter +pattern_loop: + lda (board_ptr),y ; Load row + cmp tmp ; Compare row to pattern + beq potential_fork_row + inx ; Is there + cpx #$03 ; a next pattern? + beq no_potential_fork_row + lda tmp ; Generate next pattern + asl a + asl a + sta tmp + jmp pattern_loop ; Test for this pattern +no_potential_fork_row: + lda #$ff +potential_fork_row: + sta (other_board_ptr),y + iny + cpy #$03 + bne row_loop + pla ; Clean stack + rts + .bend + +block_fork: + .block + pha ; Remember who I am + cmp #piece_o + beq we_are_o + lda #piece_o + jmp check_for_fork +we_are_o: + lda #piece_x +check_for_fork: + jsr fork + bcs done + ;; We found a potential fork for the opponent. + ;; Before occupying it, we check if we could + ;; create a two-in-a-row + ;; situation for me. + pla + pha + jsr two_in_a_row +done: + pla ;Clean up stack + rts + .bend +two_in_a_row: + .block + pha ; Remember who I am + ldy #$00 ; Row counter +row_loop: + pla ; Set + pha ; start + sta tmp ; pattern. + ldx #$00 ; Pattern counter +pattern_loop: + lda (board_ptr),y + cmp tmp + beq found_row + lda tmp ; Next pattern + asl a + asl a + sta tmp + inx + cpx #$03 ; All patterns + bne pattern_loop ; checked for row + iny + cpy #$03 + bne row_loop +done: + pla ; Clean-up stack + sec + rts +found_row: + ;; Found empty spot in row + ;; It is important to place the next piece + ;; on the empty corner field, not the middle field. + pla ; Clean-up stack + clc + lda (board_ptr),y + and #%00000011 + beq first_spot_empty + ;; Since first spot is occupied, + ;; third spot must be empty + ldx #$02 + rts +first_spot_empty: + ldx #$00 + rts + .bend + + + +play_center: + .block + #GET_FIELD main_board, 4 + sec + cmp #piece_none + bne done + ldx #$01 + ldy #$01 + clc +done: + rts + .bend + +play_opposite_corner: + .block + pha ; Remember who I am + ldx #$00 + ldy #$08 + pla + pha + jsr check_if_occupied_and_empty + bcc done + ldx #$02 + ldy #$06 + pla + pha + jsr check_if_occupied_and_empty + bcc done + ldx #$06 + ldy #$02 + pla + pha + jsr check_if_occupied_and_empty + bcc done + ldx #$08 + ldy #$00 + pla + pha + jsr check_if_occupied_and_empty + sec +done: + pla + rts + .bend + ;; Check if field in X is + ;; occupied by opponent and + ;; field in Y is empty. + ;; In this case, return + ;; coordinates of empty + ;; field in X/Y +check_if_occupied_and_empty: + .block + phy + sta tmp ; This me + jsr get_field + cmp tmp ; Occupied + beq not_applicable ; by me + cmp #$00 + beq not_applicable ; Field emtpy + ;; Check field (originally) in Y + plx + phx + jsr get_field + cmp #$00 + bne not_applicable ; Not empty + ;; Field is empty. Occupy it + plx + jsr pos_to_column_row + clc + rts +not_applicable: + pla ; Clear stack + sec + rts + .bend + +play_empty_corner: + .block + sta tmp + ldx #$00 + jsr occupy_if_empty + bcc done + ldx #$02 + jsr occupy_if_empty + bcc done + ldx #$06 + jsr occupy_if_empty + bcc done + ldx #$08 + jsr occupy_if_empty + bcc done +done: + rts + .bend + +occupy_if_empty: + .block + phx + jsr get_field + cmp #$00 + bne already_occupied + plx + jsr pos_to_column_row + clc + rts +already_occupied: + plx ; Clear stack + sec + rts + .bend + +play_empty_side: + .block + sta tmp + ldx #$01 + jsr occupy_if_empty + bcc done + ldx #$03 + jsr occupy_if_empty + bcc done + ldx #$05 + jsr occupy_if_empty + bcc done + ldx #$07 + jsr occupy_if_empty + bcc done +done: + rts + .bend + diff --git a/sw/ttt/computer_player.s b/sw/ttt/computer_player.s @@ -1,755 +0,0 @@ -;;; ************************************************ -;;; -;;; Random Computer Player -;;; -;;; ************************************************ -computer_random_init: - rts -computer_random_ply: - .( - pha ; Remember who we are - ;; We need a random number - ;; in the range 0..8. For - ;; this, we update the lfsr - ;; and take the lowest - ;; nibble until it is <= 8. -get_random: - jsr lfsr_step - lda lfsr_state - and #$0f - cmp #$09 - bcs get_random - ;; Check if field is occupied - sta tmp - tax - jsr get_field - cmp #piece_none - bne get_random - ;; Occupy field - ldx tmp - pla -#ifdef RUN_TESTS - jsr print_random_ply -#endif - jsr set_field - clc - rts - .) - -;;; ************************************************ -;;; -;;; Perfect Computer Player -;;; -;;; ************************************************ - -;;; See http://csjarchive.cogsci.rpi.edu/1993v17/i04/p0531p0561/MAIN.PDF -computer_perfect_init: - rts -computer_perfect_ply: - .( - ;; 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 - ;; Win - ;; If there is a row, column, or diagonal with two of my pieces - ;; and a blank space, - ;; Then play the blank space (thus winning the game). - pha - jsr win - pla - bcc execute_ply - ;; Block - ;; If there Is a row, column, or diagonal with two of my - ;; opponent's pieces and a blank space, - ;; Then play the blank space (thus blocking a potential win for - ;; my opponent). - pha - jsr block - pla - bcc execute_ply - ;; Fork - ;; If there are two intersecting rows, columns, or diagonals - ;; with one of my pieces and two blanks, and - ;; If the intersecting space Is empty, - ;; Then move to the intersecting space (thus creating two - ;; ways to win on my next turn). - pha - jsr fork - pla - bcc execute_ply - ;; Block Fork - ;; If there are two intersecting rows, columns, or diagonals - ;; with one of my opponent’s pieces ond two blanks, and - ;; If the intersecting space is empty, - ;; Then - ;; If there is an empty location that creates a - ;; two-in-a-row for me (thus forcing my opponent to - ;; block rather than fork), - ;; Then move to the location. - ;; Else move to the Intersection space (thus occupying - ;; the location that my opponent could use to fork). - pha - jsr block_fork - pla - bcc execute_ply - ;; Play Center - ;; If the center is blank, - ;; Then play the center. - pha - jsr play_center - pla - bcc execute_ply - ;; Play Opposite Corner - ;; If my opponent is in a corner, and - ;; If the opposite corner is empty, - ;; Then play the opposite corner. - pha - jsr play_opposite_corner - pla - bcc execute_ply - ;; Play Empty Corner - ;; If there is an empty corner, - ;; Then move to an empty corner. - pha - jsr play_empty_corner - pla - bcc execute_ply - ;; Play Empty Side - ;; If there is an empty side, - ;; Then move to an empty side. - pha - jsr play_empty_side - pla - bcc execute_ply - ;; No rule left; this should not happen. - PRINTSNL('Error') -error: - jmp error -execute_ply: -#ifdef RUN_TESTS - jsr print_ply -#endif - jsr set_field_x_y -done: - rts - .) - -random_first: - .( - sta tmp ; Remember who I am - cmp #piece_x ; If we are x - bne not_first ; - ldy #$00 -row_loop: - lda main_board,y ; Check if - bne not_first ; all - iny ; rows - cpy #$03 ; are - bne row_loop ; empty - lda tmp - jmp computer_random_ply -not_first: - sec - rts - .) - -win: - .( - pha - jsr win_row - pla - bcc done - pha - jsr win_column - pla - bcc done - pha - jsr win_first_diagonal - pla - bcc done - pha - jsr win_second_diagonal - pla -done: - rts - .) - -win_row: - .( - ;; Load complete winning row - cmp #piece_x - bne this_is_o - lda #piece_x_row - jmp win_row_set -this_is_o: - lda #piece_o_row -win_row_set: - sta tmp ; Store win row - ldy #$00 ; Row counter -row_loop: - ldx #$00 ; Column counter - lda tmp - and #%00111100 - cmp (board_ptr),y - beq win_ply - inx - lda tmp - and #%00110011 - cmp (board_ptr),y - beq win_ply - inx - lda tmp - and #%00001111 - cmp (board_ptr),y - beq win_ply - ;; No win here. Try next row - iny - cpy #$03 - bne row_loop - ;; No win row - ;; Carry set indicates failure - sec - rts -win_ply: - ;; Found a winning move - ;; return piece in A, - ;; and position in X/Y. - ;; Clear carry to indicate success. - lda tmp - and #%00000011 - clc - rts - .) - -win_column: - .( - sta tmp ; Remember who I am - ;; We mirror the board in order to use - ;; win_column to check for a winning - ;; position. - STORE_WORD(board_mirrored,other_board_ptr) - jsr mirror_board - STORE_WORD(board_mirrored,board_ptr) - lda tmp ; Who am I? - jsr win_row - STORE_WORD(main_board,board_ptr) - bcs no_win - ;; We found a winning position - ;; since we found the winning - ;; position on the mirrored board, - ;; we have to mirror it back. - phx - tya - tax - ply - clc -no_win: - rts - .) - -win_first_diagonal: - .( - sta tmp ; Remember who I am - lda #$ff ; No empty field - sta empty_field ; found so far - lda #$00 ; Upper left field - jsr check_if_occupied_and_update_empty_field - bcs no_win - lda #$04 ; Middle field - jsr check_if_occupied_and_update_empty_field - bcs no_win - lda #$08 ; Lower right field - jsr check_if_occupied_and_update_empty_field - bcs no_win - ;; We can win! - clc - lda tmp - ldx empty_field - jsr pos_to_column_row - rts -no_win: - sec - rts - .) -win_second_diagonal: - .( - sta tmp ; Remember who I am - lda #$ff ; No empty field - sta empty_field ; found so far - lda #$02 ; Upper right field - jsr check_if_occupied_and_update_empty_field - bcs no_win - lda #$04 ; Middle field - jsr check_if_occupied_and_update_empty_field - bcs no_win - lda #$06 ; Lower left field - jsr check_if_occupied_and_update_empty_field - bcs no_win - ;; We can win! - clc - lda tmp - ldx empty_field - jsr pos_to_column_row - rts -no_win: - sec - rts - .) - - ;; We can complete the first diagonal if - ;; we occupy two of the field already, - ;; and the third field is empty. - ;; This subroutine checks if a field - ;; is occupied by us or if it is empty. - ;; In the latter case, empty_field is - ;; updated unless it was set already - ;; (i.e. this is the second empty field, - ;; thus no win ply available (yet)). -check_if_occupied_and_update_empty_field: - .( - pha ; Field number - tax ; stored in X, - jsr get_field ; field piece - plx ; stored in A. - cmp tmp ; Field occupied - beq cont ; by me? - cmp #$00 ; Field not occupied? - bne no_win ; Occupied by opponent. No win. - lda empty_field ; Have we seen an empty - cmp #$ff ; field before? - bne no_win ; Then we have too many empty fields. - stx empty_field ; Otherwise this is the empty field. -cont: - clc - rts -no_win: - sec - rts - .) - - ;; We use subroutine win to check - ;; if the other player could win -block: - .( - cmp #piece_o - beq we_are_o - lda #piece_o - jmp win -we_are_o: - lda #piece_x - jmp win - .) - -fork: - .( - ;; Forks require two intersecting rows, columns, or diagonals - ;; which both contain only one piece of the player and no - ;; other pieces. If the intersection is empty, then this - ;; is the fork field. - ;; To find these points, we create three additional boards: - ;; On the first board, all rows satisfying the critera - ;; are maintained, while all other rows are filled with $ff. - ;; On the second board, we do the same for columns, - ;; on the third board for the diagonals. - ;; Then we OR the boards pairwise. If there is - ;; an empty field after pairing, then this is the - ;; fork field. - pha ; Remember who I am - STORE_WORD(fork_board_rows,other_board_ptr) - pla ; Store my piece - pha ; in A - jsr find_rows_for_fork - ;; Generate second board (potential fork columns) - ;; For this, we mirror the board and use - ;; find_rows_for_fork again. - ;; We use board_mirrored as an auxillary variable - ;; here. - STORE_WORD(main_board,board_ptr) - STORE_WORD(fork_board_columns,other_board_ptr) - jsr mirror_board - ;; Check for fork points on mirrored - ;; board and store the result in - ;; fork_board_columns - STORE_WORD(fork_board_columns,board_ptr) - STORE_WORD(board_mirrored,other_board_ptr) - pla ; Store my piece - pha ; in A - jsr find_rows_for_fork - ;; Reverse mirroring - STORE_WORD(board_mirrored,board_ptr) - STORE_WORD(fork_board_columns,other_board_ptr) - jsr mirror_board - pla - pha - STORE_WORD(fork_board_rows,board_ptr) - STORE_WORD(fork_board_columns,other_board_ptr) - jsr check_fork - bcs cont - jmp done -cont: - ;; We have generated the fork_boards - ;; for row and column, but there was - ;; no fork on rows and columns. - ;; Now we create the fork board for - ;; diagonals. - ;; In order to use our subroutine - ;; find_rows_for_fork - ;; again, we map the first diagonal - ;; to the first row of a new board, - ;; and the second diagonal to the - ;; second row. - ;; We use board_mirrored as - ;; temporary storage. - ;; First diagonal to first row - lda #$00 - sta fork_board_diagonals - sta fork_board_diagonals+1 - sta fork_board_diagonals+2 - STORE_WORD(main_board,board_ptr) - STORE_WORD(fork_board_diagonals,other_board_ptr) - COPY_FIELD(0,0) - COPY_FIELD(4,1) - COPY_FIELD(8,2) - ;; Second diagonal to second row - COPY_FIELD(2,3) - COPY_FIELD(4,4) - COPY_FIELD(6,5) - STORE_WORD(fork_board_diagonals,board_ptr) - STORE_WORD(board_mirrored,other_board_ptr) - pla ; Store my piece - pha ; in A - jsr find_rows_for_fork - ;; Store result in fork_board_diagonals - ;; Invalidate all non-diagonal fields - lda #%00001100 - sta fork_board_diagonals - sta fork_board_diagonals+2 - lda #%00110011 - sta fork_board_diagonals+1 - ;; First diagonal - STORE_WORD(board_mirrored,board_ptr) - STORE_WORD(fork_board_diagonals,other_board_ptr) - COPY_FIELD(0,0) - COPY_FIELD(1,4) - COPY_FIELD(2,8) - ;; Second diagonal - COPY_FIELD(3,2) - COPY_FIELD(5,6) - ;; Middle field can be used in either diagonal - ;; therefore we have to treat it seperately - GET_FIELD(board_mirrored,4) - cmp #$00 - bne middle_field_not_part_of_potential_fork - STORE_WORD(fork_board_diagonals,board_ptr) - SET_FIELD($00,4) -middle_field_not_part_of_potential_fork: - ;; No check if there is a fork between - ;; rows and diagonals ... - STORE_WORD(fork_board_diagonals,board_ptr) - STORE_WORD(fork_board_rows,other_board_ptr) - jsr check_fork - bcc done - ;; ... and columns and diagonals. - STORE_WORD(fork_board_columns,other_board_ptr) - jsr check_fork -done: - ;; Some restore operatons - STORE_WORD(main_board,board_ptr) - pla - rts - .) - -;;; Check if we can create a fork with a -;;; suitable row and column. If this is -;;; the case, we set the carry flag -;;; and return the fork position. -check_fork: - .( - ;; Since empty fields in - ;; fork_boards_rows and - ;; fork roads_columns indicate potential - ;; fork fields, we OR these two - ;; boards and check if there are - ;; empty fields. - ;; We store the ORed boards in - ;; board_mirrored, because this - ;; board is not used here. - clc - ldy #$00 -copy_and_or_boards_loop: - lda (board_ptr),y - ora (other_board_ptr),y - sta board_mirrored,y - iny - cpy #$03 - bne copy_and_or_boards_loop - ;; No check for empty fields - ldy #$00 ; Row counter -row_loop: - lda #%00000011 ; Field pattern - sta tmp - ldx #$00 ; Column counter -pattern_loop: - lda board_mirrored,y - and tmp - beq found_empty_field - lda tmp ; Move - asl ; to - asl ; next - sta tmp ; pattern - inx - cpx #$03 - bne pattern_loop - iny - cpy #$03 - bne row_loop - sec -found_empty_field: - rts - .) - - -;;; Check for all rows if they may be part -;;; of a fork. This is the case when the -;;; row contains one of our pieces and -;;; no other piece. In this case, the row -;;; stays intact. In all other cases, -;;; we overwrite the row with $ff. -;;; board_ptr points to the input board, -;;; other_board_ptr_to_the output board -find_rows_for_fork: - .( - pha - ldy #$00 ; Row counter -row_loop: - pla ; Get pattern we are looking for - pha - sta tmp - ldx #$00 ; Pattern counter -pattern_loop: - lda (board_ptr),y ; Load row - cmp tmp ; Compare row to pattern - beq potential_fork_row - inx ; Is there - cpx #$03 ; a next pattern? - beq no_potential_fork_row - lda tmp ; Generate next pattern - asl - asl - sta tmp - jmp pattern_loop ; Test for this pattern -no_potential_fork_row: - lda #$ff -potential_fork_row: - sta (other_board_ptr),y - iny - cpy #$03 - bne row_loop - pla ; Clean stack - rts - .) - -block_fork: - .( - pha ; Remember who I am - cmp #piece_o - beq we_are_o - lda #piece_o - jmp check_for_fork -we_are_o: - lda #piece_x -check_for_fork: - jsr fork - bcs done - ;; We found a potential fork for the opponent. - ;; Before occupying it, we check if we could - ;; create a two-in-a-row - ;; situation for me. - pla - pha - jsr two_in_a_row -done: - pla ;Clean up stack - rts - .) -two_in_a_row: - .( - pha ; Remember who I am - ldy #$00 ; Row counter -row_loop: - pla ; Set - pha ; start - sta tmp ; pattern. - ldx #$00 ; Pattern counter -pattern_loop: - lda (board_ptr),y - cmp tmp - beq found_row - lda tmp ; Next pattern - asl - asl - sta tmp - inx - cpx #$03 ; All patterns - bne pattern_loop ; checked for row - iny - cpy #$03 - bne row_loop -done: - pla ; Clean-up stack - sec - rts -found_row: - ;; Found empty spot in row - ;; It is important to place the next piece - ;; on the empty corner field, not the middle field. - pla ; Clean-up stack - clc - lda (board_ptr),y - and #%00000011 - beq first_spot_empty - ;; Since first spot is occupied, - ;; third spot must be empty - ldx #$02 - rts -first_spot_empty: - ldx #$00 - rts - .) - - - -play_center: - .( - GET_FIELD(main_board,4) - sec - cmp #piece_none - bne done - ldx #$01 - ldy #$01 - clc -done: - rts - .) - -play_opposite_corner: - .( - pha ; Remember who I am - ldx #$00 - ldy #$08 - pla - pha - jsr check_if_occupied_and_empty - bcc done - ldx #$02 - ldy #$06 - pla - pha - jsr check_if_occupied_and_empty - bcc done - ldx #$06 - ldy #$02 - pla - pha - jsr check_if_occupied_and_empty - bcc done - ldx #$08 - ldy #$00 - pla - pha - jsr check_if_occupied_and_empty - sec -done: - pla - rts - .) - ;; Check if field in X is - ;; occupied by opponent and - ;; field in Y is empty. - ;; In this case, return - ;; coordinates of empty - ;; field in X/Y -check_if_occupied_and_empty: - .( - phy - sta tmp ; This me - jsr get_field - cmp tmp ; Occupied - beq not_applicable ; by me - cmp #$00 - beq not_applicable ; Field emtpy - ;; Check field (originally) in Y - plx - phx - jsr get_field - cmp #$00 - bne not_applicable ; Not empty - ;; Field is empty. Occupy it - plx - jsr pos_to_column_row - clc - rts -not_applicable: - pla ; Clear stack - sec - rts - .) - -play_empty_corner: - .( - sta tmp - ldx #$00 - jsr occupy_if_empty - bcc done - ldx #$02 - jsr occupy_if_empty - bcc done - ldx #$06 - jsr occupy_if_empty - bcc done - ldx #$08 - jsr occupy_if_empty - bcc done -done: - rts - .) - -occupy_if_empty: - .( - phx - jsr get_field - cmp #$00 - bne already_occupied - plx - jsr pos_to_column_row - clc - rts -already_occupied: - plx ; Clear stack - sec - rts - .) - -play_empty_side: - .( - sta tmp - ldx #$01 - jsr occupy_if_empty - bcc done - ldx #$03 - jsr occupy_if_empty - bcc done - ldx #$05 - jsr occupy_if_empty - bcc done - ldx #$07 - jsr occupy_if_empty - bcc done -done: - rts - .) - diff --git a/sw/ttt/computer_player_test.asm b/sw/ttt/computer_player_test.asm @@ -0,0 +1,584 @@ +computer_player_test: + .block + ;; 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 + .bend + +SET_BOARD: .macro + lda #\1 + sta main_board + lda #\2 + sta main_board+1 + lda #\3 + sta main_board+2 + .endm + +CHECK_BOARD: .macro + lda board_mirrored + cmp #\1 + bne check_board_err + lda board_mirrored+1 + cmp #\2 + bne check_board_err + lda board_mirrored+2 + cmp #\3 + beq check_board_cont +check_board_err: + jmp error +check_board_cont: + .endm + +mirror_board_test: + .block + jsr init_board + #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: + #PRINTSNL "OK" + rts +error: + #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 + PRINTNL + lda #piece_x + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr get_game_state + cmp #piece_x + bne win_by_column_error + #PRINTSNL "OK" + rts +win_by_column_error: + #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 + #PRINTNL + lda #piece_x + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr get_game_state + cmp #piece_x + bne win_by_diagonal_error + #PRINTSNL "OK" + rts +win_by_diagonal_error: + #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 + #PRINTNL + lda #piece_o + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr 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 + #PRINTNL + 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 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 + #PRINTNL + pha + jsr print_board + pla + lda #piece_x + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_x + jsr 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 + #PRINTNL + 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 getc + rts + .bend + +test_block_fork: + .block + jsr computer_perfect_init + #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 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,1 + #CHECK_ERROR piece_o + jsr 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 + #STORE_WORD main_board,board_ptr + #SET_FIELD piece_o, 0 + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 8 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,1 + #CHECK_ERROR piece_o + jsr getc + ;; The following scenario + ;; can be created by the plys + ;; X -> 1 + ;; O -> 5 + ;; X -> 9 + jsr computer_perfect_init + #STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 4 + #SET_FIELD piece_x, 8 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,3 + #CHECK_ERROR piece_o + jsr getc + rts + ;; The following scenario + ;; can be created by the plys + ;; X -> 5 + ;; O -> 1 + ;; X -> 9 + jsr computer_perfect_init + #STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 4 + #SET_FIELD piece_o, 0 + #SET_FIELD piece_x, 8 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_o + jsr getc + rts + .bend + +test_play_opposite_corner: + .block + jsr computer_perfect_init + #STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 4 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,8 + #CHECK_ERROR piece_o + jsr getc + rts + .bend + +test_play_empty_corner: + .block + jsr computer_perfect_init + #STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 4 + #SET_FIELD piece_o, 0 + #PRINTNL + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + #PRINTNL + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_o + jsr getc + rts + .bend + +test_perfect_vs_random_computer_player: + .block +game_loop: + ;; Set player X + #STORE_WORD computer_perfect_init, player_x_init_ptr + #STORE_WORD computer_perfect_ply, player_x_ply_ptr + ;; Set player O + #STORE_WORD computer_random_init, player_o_init_ptr + #STORE_WORD computer_random_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_o ; Random player + bne game_loop ; Should never win + #PRINTSNL 'Error' +error: + jmp error + .bend +test_random_vs_perfect_computer_player: + .block +game_loop: + ;; Set player X + #STORE_WORD computer_random_init, player_x_init_ptr + #STORE_WORD computer_random_ply, player_x_ply_ptr + ;; Set player O + #STORE_WORD computer_perfect_init, player_o_init_ptr + #STORE_WORD computer_perfect_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_x ; Random player + bne game_loop ; Should never win + #PRINTSNL 'Error' +error: + jmp error + .bend +test_perfect_vs_perfect_computer_player: + .block +game_loop: + ;; Set player X + #STORE_WORD computer_perfect_init, player_x_init_ptr + #STORE_WORD computer_perfect_ply, player_x_ply_ptr + ;; Set player O + #STORE_WORD computer_perfect_init, player_o_init_ptr + #STORE_WORD computer_perfect_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_none ; All games should end in + beq game_loop ; a draw. + #PRINTSNL 'Error' +error: + jmp error + .bend + +print_random_ply: + .block + ;; Debug output - Computer ply + pha + phx + clc + adc #'0' + jsr putc + #PRINTS ' plays ' + pla + pha + adc #'0' + jsr putc + #PRINTNL + plx + pla + rts + .bend + +print_ply: + .block + ;; Debug output - Computer ply + pha + phy + phx + clc + adc #'0' + jsr putc + #PRINTS ' plays ' + pla + pha + adc #'0' + jsr putc + lda #'/' + jsr putc + plx + pla + pha + phx + adc #'0' + jsr putc + #PRINTNL + plx + ply + pla + rts + .bend + diff --git a/sw/ttt/computer_player_test.s b/sw/ttt/computer_player_test.s @@ -1,582 +0,0 @@ -computer_player_test: - // 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 - -#define SET_BOARD(first_row,second_row,third_row) \ - lda #first_row: \ - sta main_board: \ - lda #second_row: \ - sta main_board+1: \ - lda #third_row: \ - sta main_board+2 - -#define CHECK_BOARD(first_row,second_row,third_row) \ - .(: \ - lda board_mirrored: \ - cmp #first_row: \ - bne check_board_err: \ - lda board_mirrored+1: \ - cmp #second_row: \ - bne check_board_err: \ - lda board_mirrored+2: \ - cmp #third_row: \ - beq check_board_cont: \ -check_board_err: \ - jmp error: \ -check_board_cont: \ - .) - -mirror_board_test: - .( - jsr init_board - 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: - PRINTSNL("OK") - rts -error: - PRINTSNL("ERROR") - rts - .) - - ;; 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 - PRINTNL - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_column_error - PRINTSNL("OK") - rts -win_by_column_error: - 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 - PRINTNL - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_diagonal_error - PRINTSNL("OK") - rts -win_by_diagonal_error: - 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 - PRINTNL - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr getc - rts - -fork_row_column: - .( - ;; 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 - PRINTNL - 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 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) - PRINTNL - pha - jsr print_board - pla - lda #piece_x - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,2) - CHECK_ERROR(piece_x) - jsr 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 - PRINTNL - 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 getc - rts - .) - -test_block_fork: - .( - jsr computer_perfect_init - 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) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,1) - CHECK_ERROR(piece_o) - jsr 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 - STORE_WORD(main_board,board_ptr) - SET_FIELD(piece_o, 0) - SET_FIELD(piece_x, 4) - SET_FIELD(piece_x, 8) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,1) - CHECK_ERROR(piece_o) - jsr getc - ;; The following scenario - ;; can be created by the plys - ;; X -> 1 - ;; O -> 5 - ;; X -> 9 - jsr computer_perfect_init - STORE_WORD(main_board,board_ptr) - SET_FIELD(piece_x, 0) - SET_FIELD(piece_o, 4) - SET_FIELD(piece_x, 8) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,3) - CHECK_ERROR(piece_o) - jsr getc - rts - ;; The following scenario - ;; can be created by the plys - ;; X -> 5 - ;; O -> 1 - ;; X -> 9 - jsr computer_perfect_init - STORE_WORD(main_board,board_ptr) - SET_FIELD(piece_x, 4) - SET_FIELD(piece_o, 0) - SET_FIELD(piece_x, 8) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,2) - CHECK_ERROR(piece_o) - jsr getc - rts - .) - -test_play_opposite_corner: - .( - jsr computer_perfect_init - STORE_WORD(main_board,board_ptr) - SET_FIELD(piece_x, 0) - SET_FIELD(piece_o, 4) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,8) - CHECK_ERROR(piece_o) - jsr getc - rts - .) - -test_play_empty_corner: - .( - jsr computer_perfect_init - STORE_WORD(main_board,board_ptr) - SET_FIELD(piece_x, 4) - SET_FIELD(piece_o, 0) - PRINTNL - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - PRINTNL - jsr print_board - pla - ;; Check for correct ply - GET_FIELD(main_board,2) - CHECK_ERROR(piece_o) - jsr getc - rts - .) - -test_perfect_vs_random_computer_player: - .( -game_loop: - ;; Set player X - STORE_WORD(computer_perfect_init, player_x_init_ptr) - STORE_WORD(computer_perfect_ply, player_x_ply_ptr) - ;; Set player O - STORE_WORD(computer_random_init, player_o_init_ptr) - STORE_WORD(computer_random_ply, player_o_ply_ptr) - jsr play_game - jsr get_game_state - cmp #piece_o ; Random player - bne game_loop ; Should never win - PRINTSNL('Error') -error: - jmp error - .) -test_random_vs_perfect_computer_player: - .( -game_loop: - ;; Set player X - STORE_WORD(computer_random_init, player_x_init_ptr) - STORE_WORD(computer_random_ply, player_x_ply_ptr) - ;; Set player O - STORE_WORD(computer_perfect_init, player_o_init_ptr) - STORE_WORD(computer_perfect_ply, player_o_ply_ptr) - jsr play_game - jsr get_game_state - cmp #piece_x ; Random player - bne game_loop ; Should never win - PRINTSNL('Error') -error: - jmp error - .) -test_perfect_vs_perfect_computer_player: - .( -game_loop: - ;; Set player X - STORE_WORD(computer_perfect_init, player_x_init_ptr) - STORE_WORD(computer_perfect_ply, player_x_ply_ptr) - ;; Set player O - STORE_WORD(computer_perfect_init, player_o_init_ptr) - STORE_WORD(computer_perfect_ply, player_o_ply_ptr) - jsr play_game - jsr get_game_state - cmp #piece_none ; All games should end in - beq game_loop ; a draw. - PRINTSNL('Error') -error: - jmp error - .) - -print_random_ply: - .( - ;; Debug output - Computer ply - pha - phx - clc - adc #'0' - jsr putc - PRINTS(' plays ') - pla - pha - adc #'0' - jsr putc - PRINTNL - plx - pla - rts - .) - -print_ply: - .( - ;; Debug output - Computer ply - pha - phy - phx - clc - adc #'0' - jsr putc - PRINTS(' plays ') - pla - pha - adc #'0' - jsr putc - lda #'/' - jsr putc - plx - pla - pha - phx - adc #'0' - jsr putc - PRINTNL - plx - ply - pla - rts - .) - diff --git a/sw/ttt/human_player.asm b/sw/ttt/human_player.asm @@ -0,0 +1,53 @@ +;;; No initialization necessary +human_player_init: + rts + +;;; Ask human player for his/her next +;;; move. A contains the piece_x or +;;; piece_o, depending which +;;; piece the human plays +human_player_ply: + .block + ;; Print board + pha + jsr print_board + ;; Print question for move + #PRINTS "Player " + pla + pha + clc + adc #'0' + jsr putc + #PRINTS ": " + ;; Get move and check + ;; if move is valid. + jsr getc + jsr putc + ;; Check if user input + ;; is in range [1..9] + cmp #"1" + bcc invalid + cmp #":" ; Follows 9 in ASCII table + bcs invalid + ;; Check if field is already + ;; occuped + sec + sbc #"1" + sta tmp ; Field number + tax + jsr get_field + cmp #piece_none + bne invalid + #PRINTNL + #PRINTNL + ;; Set the piece + ldx tmp ; Field number + pla ; Piece + jsr set_field + rts +invalid: + #PRINTNL + #PRINTSNL "Not a valid move." + pla + jmp human_player_ply + .bend diff --git a/sw/ttt/human_player.s b/sw/ttt/human_player.s @@ -1,53 +0,0 @@ -;;; No initialization necessary -human_player_init: - rts - -;;; Ask human player for his/her next -;;; move. A contains the piece_x or -;;; piece_o, depending which -;;; piece the human plays -human_player_ply: - .( - ;; Print board - pha - jsr print_board - ;; Print question for move - PRINTS("Player ") - pla - pha - clc - adc #'0' - jsr putc - PRINTS(": ") - ;; Get move and check - ;; if move is valid. - jsr getc - jsr putc - ;; Check if user input - ;; is in range [1..9] - cmp #"1" - bcc invalid - cmp #":" ; Follows 9 in ASCII table - bcs invalid - ;; Check if field is already - ;; occuped - sec - sbc #"1" - sta tmp ; Field number - tax - jsr get_field - cmp #piece_none - bne invalid - PRINTNL - PRINTNL - ;; Set the piece - ldx tmp ; Field number - pla ; Piece - jsr set_field - rts -invalid: - PRINTNL - PRINTSNL("Not a valid move.") - pla - jmp human_player_ply - .) diff --git a/sw/ttt/ttt.asm b/sw/ttt/ttt.asm @@ -0,0 +1,163 @@ +;;; If we are included in boot.asm, we +;;; should not set the start address +.weak + BOOT_EMBEDDED = false +.endweak +.if BOOT_EMBEDDED + init_acia = export.init_acia + lfsr_init = export.lfsr_init + lfsr_step = export.lfsr_step + lfsr_state = export.lfsr_state + getc = export.getc + getc_seed_rng = export.getc_seed_rng + putc = export.putc +.else + .if SYMON + .include "boot_symon.l" + .else + .include "boot.l" + .endif + .include "boot_macros.inc" + * = $0300 +.endif + +;;; Variables + ;; This temporary variable is used all over the place + tmp = $30 + ;; Used by win_first_diagonal and win_first_diagonal + empty_field = $3e + ;; Used to check for forking opportunities + fork_board_rows = $4006 + fork_board_columns = $4009 + fork_board_diagonals = $400c + ;; Used to pass location of board + ;; to subroutines. + board_ptr = $32 + ;; Sometimes we copy stuff from one board + ;; to the other. For this we have to + ;; point to the other board. + other_board_ptr = $34 + ;; Default board location in memory + main_board = $4000 + ;; Mirrored board location in memory + board_mirrored = $4003 + ;; Pointer to init subroutine of + ;; first player (2 bytes) + player_x_init_ptr = $36 + ;; Pointer to ply subroutine of + ;; first player (2 bytes) + player_x_ply_ptr = $38 + ;; Pointer to init subroutine of + ;; second player (2 bytes) + player_o_init_ptr = $3a + ;; Pointer to ply subroutine of + ;; first player (2 bytes) + player_o_ply_ptr = $3c + ;; Bord rendered as ASCII art + board_ascii = $5000 + + +start_ttt: + .block + cld + jsr init_acia + jsr lfsr_init +.if RUN_TESTS + jsr run_tests +.endif +game_loop: + #PRINTSNL '** Tic-Tac-Toe **' + #PRINTNL + lda #piece_x + jsr choose_player + lda #piece_o + jsr choose_player + jsr play_game + jmp game_loop + .bend + + +choose_player: + .block + pha + #PRINTS "Choose player " + pla + pha + clc + adc #'0' + jsr putc + #PRINTSNL ':' + #PRINTSNL '1 - Human' + #PRINTSNL '2 - Computer (perfect)' + #PRINTSNL '3 - Computer (random)' + jsr getc_seed_rng + cmp #'1' + beq set_human_player + cmp #'2' + beq set_computer_perfect_player + cmp #'3' + beq set_computer_random_player + pla + jmp choose_player + ;; Board is not initialized at + ;; this point. Therefore we can use + ;; board_ptr to store a temporary + ;; variable. +set_human_player: + #STORE_WORD human_player_init, tmp + #STORE_WORD human_player_ply, board_ptr + jmp set_player +set_computer_perfect_player: + #STORE_WORD computer_perfect_init, tmp + #STORE_WORD computer_perfect_ply, board_ptr + jmp set_player +set_computer_random_player: + #STORE_WORD computer_random_init, tmp + #STORE_WORD computer_random_ply, board_ptr +set_player: + pla + cmp #piece_x + bne set_second_player + lda tmp + sta player_x_init_ptr + lda tmp+1 + sta player_x_init_ptr+1 + lda board_ptr + sta player_x_ply_ptr + lda board_ptr+1 + sta player_x_ply_ptr+1 + rts +set_second_player: + lda tmp + sta player_o_init_ptr + lda tmp+1 + sta player_o_init_ptr+1 + lda board_ptr + sta player_o_ply_ptr + lda board_ptr+1 + sta player_o_ply_ptr+1 + rts + .bend + +.include "board.asm" +.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 + ;; #PRINTNL + ;; jsr getc + ;; jsr mirror_board_test + jsr computer_player_test + #PRINTNL + jsr getc + jmp run_tests +.endif + + diff --git a/sw/ttt/ttt.s b/sw/ttt/ttt.s @@ -1,159 +0,0 @@ -#include "../../roms/boot/liba.h" - -;;; Variables - ;; This temporary variable is used all over the place - tmp = $30 - ;; Used by win_first_diagonal and win_first_diagonal - empty_field = $3e - ;; Used to check for forking opportunities - fork_board_rows = $4006 - fork_board_columns = $4009 - fork_board_diagonals = $400c - ;; Used to pass location of board - ;; to subroutines. - board_ptr = $32 - ;; Sometimes we copy stuff from one board - ;; to the other. For this we have to - ;; point to the other board. - other_board_ptr = $34 - ;; Default board location in memory - main_board = $4000 - ;; Mirrored board location in memory - board_mirrored = $4003 - ;; Pointer to init subroutine of - ;; first player (2 bytes) - player_x_init_ptr = $36 - ;; Pointer to ply subroutine of - ;; first player (2 bytes) - player_x_ply_ptr = $38 - ;; Pointer to init subroutine of - ;; second player (2 bytes) - player_o_init_ptr = $3a - ;; Pointer to ply subroutine of - ;; first player (2 bytes) - player_o_ply_ptr = $3c - ;; Bord rendered as ASCII art - board_ascii = $5000 - - -#ifdef SYMON - rom_start = $c000 -#else - rom_start = $e000 -#endif - -#ifdef BOOT - ;; Program to be backed into - ;; boot ROM - * = rom_start + $0c00 -#else - ;; Program to be uploaded to - ;; RAM - * = $0300 -#endif - -start_ttt: - .( - cld - jsr init_acia - jsr lfsr_init -#ifdef RUN_TESTS - jsr run_tests -#endif -game_loop: - PRINTSNL('** Tic-Tac-Toe **') - PRINTNL - lda #piece_x - jsr choose_player - lda #piece_o - jsr choose_player - jsr play_game - jmp game_loop - .) - - -choose_player: - .( - pha - PRINTS("Choose player ") - pla - pha - clc - adc #'0' - jsr putc - PRINTSNL(':') - PRINTSNL('1 - Human') - PRINTSNL('2 - Computer (perfect)') - PRINTSNL('3 - Computer (random)') - jsr getc_seed_rng - cmp #'1' - beq set_human_player - cmp #'2' - beq set_computer_perfect_player - cmp #'3' - beq set_computer_random_player - pla - jmp choose_player - ;; Board is not initialized at - ;; this point. Therefore we can use - ;; board_ptr to store a temporary - ;; variable. -set_human_player: - STORE_WORD(human_player_init, tmp) - STORE_WORD(human_player_ply, board_ptr) - jmp set_player -set_computer_perfect_player: - STORE_WORD(computer_perfect_init, tmp) - STORE_WORD(computer_perfect_ply, board_ptr) - jmp set_player -set_computer_random_player: - STORE_WORD(computer_random_init, tmp) - STORE_WORD(computer_random_ply, board_ptr) -set_player: - pla - cmp #piece_x - bne set_second_player - lda tmp - sta player_x_init_ptr - lda tmp+1 - sta player_x_init_ptr+1 - lda board_ptr - sta player_x_ply_ptr - lda board_ptr+1 - sta player_x_ply_ptr+1 - rts -set_second_player: - lda tmp - sta player_o_init_ptr - lda tmp+1 - sta player_o_init_ptr+1 - lda board_ptr - sta player_o_ply_ptr - lda board_ptr+1 - sta player_o_ply_ptr+1 - rts - .) - - - - - -#include "board.s" -#include "human_player.s" -#include "computer_player.s" - -#ifdef RUN_TESTS -#include "board_test.s" -#include "computer_player_test.s" -run_tests: - // jsr game_board_test - // PRINTNL - // jsr getc - // jsr mirror_board_test - jsr computer_player_test - PRINTNL - jsr getc - jmp run_tests -#endif - -