commit 822e76c00ceadee13fd533320aa460a502c57169
parent a33ba6bc619440ee0f9b704d519255cabdc5dda3
Author: Gerd Beuster <gerd@frombelow.net>
Date: Sun, 29 Nov 2020 13:16:29 +0100
Switched to 64tass
Diffstat:
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
-
-