eris2010

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

board.asm (14261B)


      1 ;;; Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free
      2 ;;; software under the GNU GPL v3 license or any later version. See
      3 ;;; COPYING in the root directory for details.
      4 
      5 ;;; This file contains the definitions and subroutines
      6 ;;; related to the game boards, like printing the
      7 ;;; game board and placing pieces. It also contains
      8 ;;; the subroutines to check for a win constallation
      9 ;;; on the board, and for playing the game.
     10 
     11 ;;; Orchester a game between two players
     12 ;;; Each player has an init subroutine, called
     13 ;;; once at the beginning, and a ply subroutine,
     14 ;;; called on every turn of the player.
     15 ;;; player_x_init_ptr - Init function of player X
     16 ;;; player_x_ply_ptr  - Ply function of player X
     17 ;;; player_o_init_ptr - Init function of player O
     18 ;;; player_o_ply_ptr  - Ply function of player O
     19 play_game:
     20         .block
     21         jsr init_board
     22         ;; jsr does not allow indirect addressing,
     23         ;; threfore we have to wrap the indirect
     24         ;; calls this way.
     25         jsr player_x_init
     26         jsr player_o_init
     27 play_game_loop:   
     28         lda #piece_x
     29         jsr next_ply
     30         bne game_finished
     31         lda #piece_o
     32         jsr next_ply
     33         bne game_finished
     34         jmp play_game_loop
     35 game_finished:
     36         pha
     37         jsr print_board
     38         pla
     39         jsr print_game_state
     40         jsr io.putnl
     41         rts
     42 next_ply:
     43         cmp #piece_x
     44         bne ply_player_o
     45         ;; Player X's ply
     46         ;; jsr does not allow indirect addressing,
     47         ;; threfore we have to wrap the indirect
     48         ;; calls this way.
     49         jsr player_x_ply
     50         jmp cont
     51 ply_player_o:   
     52         jsr player_o_ply
     53 cont:   
     54         jsr get_game_state
     55         cmp #$ff
     56         rts
     57 player_x_init:  
     58         jmp (player_x_init_ptr)
     59 player_o_init:  
     60         jmp (player_o_init_ptr)
     61 player_x_ply:  
     62         jmp (player_x_ply_ptr)
     63 player_o_ply:  
     64 before_o:       
     65         jmp (player_o_ply_ptr)
     66 after_o:        
     67         .bend
     68         
     69 ;;; We store the Tic-Tac-Toe board in three
     70 ;;; bytes, one for each row. When passing
     71 ;;; a board on the stack, the first row is
     72 ;;; pushed first, followed by the second,
     73 ;;; and third.
     74 ;;; 
     75 ;;; Each field in a row is represented by
     76 ;;; two bits:
     77 ;;; %00 - Field unoccupied
     78 ;;; %01 - Field occupied by player "X"
     79 ;;; %10 - Field occupied by player "O"
     80 
     81 piece_none = %00
     82 piece_x = %01
     83 piece_o = %10
     84 ;;; 
     85 ;;; In the byte for a row, the first
     86 ;;; two bits represent the leftmost field.
     87 ;;; The last two bits are not used.
     88 ;;; 
     89 ;;; A contains the piece
     90 ;;; X contains the position (0-8)
     91 ;;; The location of the board is read
     92 ;;; as a 16 bit address from address
     93 ;;; board.
     94 
     95 
     96 ;;; Initialize and clear the board.
     97 ;;; When init_board is called, the
     98 ;;; "standard" board location as given
     99 ;;; in variable board is transferred
    100 ;;; to board_ptr before clearing the
    101 ;;; board.
    102 ;;; If clear_board is called, the
    103 ;;; board at board_ptr is cleared
    104 ;;; without changing board_ptr.
    105 init_board:
    106         lda #<main_board
    107         sta board_ptr
    108         lda #>main_board
    109         sta board_ptr+1
    110 clear_board:
    111         ;; Clear board
    112         lda #$00
    113         ldy #$00
    114         sta (board_ptr),y
    115         iny
    116         sta (board_ptr),y
    117         iny
    118         sta (board_ptr),y
    119         rts
    120         
    121 mirror_board:
    122         .block
    123         ;; Mirror board on the diagonal axis
    124         ;; such that we can
    125         ;; use the win_row subroutine.
    126         ;; First row to column
    127         ldy #$00
    128         lda (board_ptr),y
    129         and #%00000011
    130         sta (other_board_ptr),y
    131         lda (board_ptr),y
    132         and #%00001100
    133         lsr a
    134         lsr a
    135         ldy #$01
    136         sta (other_board_ptr),y
    137         ldy #$00
    138         lda (board_ptr),y
    139         and #%00110000
    140         lsr a
    141         lsr a
    142         lsr a
    143         lsr a
    144         ldy #$02
    145         sta (other_board_ptr),y
    146         ;; Second row to column
    147         ldy #$01
    148         lda (board_ptr),y
    149         and #%00000011
    150         asl a
    151         asl a
    152         ldy #$00
    153         ora (other_board_ptr),y
    154         sta (other_board_ptr),y
    155         ldy #$01
    156         lda (board_ptr),y
    157         and #%00001100
    158         ora (other_board_ptr),y
    159         sta (other_board_ptr),y
    160         ldy #$01
    161         lda (board_ptr),y
    162         and #%00110000
    163         lsr a
    164         lsr a
    165         ldy #$02
    166         ora (other_board_ptr),y
    167         sta (other_board_ptr),y
    168         ;; Third column to row
    169         lda (board_ptr),y
    170         and #%00000011
    171         asl a
    172         asl a
    173         asl a
    174         asl a
    175         ldy #$00
    176         ora (other_board_ptr),y
    177         sta (other_board_ptr),y
    178         ldy #$02
    179         lda (board_ptr),y
    180         and #%00001100
    181         asl a
    182         asl a
    183         ldy #$01
    184         ora (other_board_ptr),y
    185         sta (other_board_ptr),y
    186         ldy #$02
    187         lda (board_ptr),y
    188         and #%00110000
    189         ora (other_board_ptr),y
    190         sta (other_board_ptr),y
    191         rts
    192         .bend
    193         
    194 ;;; Set a field on the board.
    195 ;;; A is the field, X the position.
    196 ;;; This subroutine changes A, X, and Y.
    197 set_field:
    198         ;; Convert position on X
    199         ;; to column/row in X/Y.
    200         jsr pos_to_column_row
    201         ;; Set field based on X/Y coordinates
    202 set_field_x_y:
    203         .block
    204         cpx #$00
    205         beq change_board
    206         asl a
    207         asl a
    208         dex
    209         jmp set_field_x_y
    210 change_board:
    211         ora (board_ptr),y
    212         sta (board_ptr),y
    213         rts
    214         .bend
    215 
    216 ;;; Get value of field on the board.
    217 ;;; X is the position. Field value is
    218 ;;; returned in A.
    219 get_field:
    220         .block
    221         ;; Convert position on X
    222         ;; to column/row in X/Y.
    223         jsr pos_to_column_row
    224         lda (board_ptr),y
    225 field_loop:
    226         cpx #$00
    227         beq got_field
    228         lsr a
    229         lsr a
    230         dex
    231         jmp field_loop
    232 got_field:
    233         and #%11
    234         rts
    235         .bend
    236         
    237 ;;; Convert position in X
    238 ;;; register to row/column
    239 ;;; coordinates.
    240 ;;; X becomes the column,
    241 ;;; Y the row
    242 pos_to_column_row:
    243         .block
    244         ldy #$00
    245 pos_loop:  
    246         cpx #$03
    247         bcc pos_found
    248         iny
    249         dex
    250         dex
    251         dex
    252         jmp pos_loop
    253 pos_found:
    254         rts
    255         .bend
    256 
    257 
    258 copy_field:
    259         ;; Copy field X from board_ptr
    260         ;; to field Y of other_board_ptr
    261         ;; Save variables
    262         lda board_ptr
    263         pha
    264         lda board_ptr+1
    265         pha
    266         phy
    267         ;; Get value & save ot
    268         jsr get_field
    269         pha
    270         ;; Write value
    271         lda other_board_ptr
    272         sta board_ptr
    273         lda other_board_ptr+1
    274         sta board_ptr+1
    275         pla
    276         plx
    277         jsr set_field
    278         ;; Restore variables
    279         pla
    280         sta board_ptr+1
    281         pla
    282         sta board_ptr
    283         rts
    284 
    285 COPY_FIELD	.macro
    286         ldx #\1
    287         ldy #\2
    288         jsr copy_field
    289 	.endm
    290         
    291 SET_FIELD	.macro
    292         lda #\1
    293         ldx #\2
    294         jsr set_field
    295 	.endm
    296 
    297 GET_FIELD	.macro
    298         #mem.STORE_WORD \1, board_ptr
    299         ldx #\2
    300         jsr get_field
    301 	.endm
    302 
    303 ;;; Print Tic-Tac-Toe board.
    304 ;;; Note that the printed
    305 ;;; numbers start with 1, while
    306 ;;; the internal counting of
    307 ;;; fields starts with 0.
    308 print_board:
    309         .block
    310         ldx #$ff
    311         phx
    312 .if !RUN_TESTS
    313 	jsr term.clear_screen
    314 	#term.SET_CURSOR #$01, #$01
    315         #io.PRINTSNL '** Tic-Tac-Toe **'
    316         #io.PRINTNL
    317 .endif
    318         #io.PRINTSNL '+---+---+---+'
    319 print_field:
    320         lda #$03
    321         sta tmp                 ; Print each line 3 times
    322 print_line:     
    323         lda #'|'
    324         jsr io.putc
    325         plx
    326         inx
    327         phx
    328         jsr get_field
    329         jsr piece_to_ascii
    330         jsr io.putc
    331         plx
    332         phx
    333         jsr put_field_number_if_empty_and_middle
    334         jsr io.putc
    335         plx
    336         phx
    337         cpx #$02
    338         beq print_row_seperator
    339         cpx #$05
    340         beq print_row_seperator
    341         cpx #$08
    342         beq print_row_seperator
    343 check_if_all_rows_printed:
    344         plx
    345         phx
    346         cpx #$08
    347         bne print_line
    348         plx                     ; Clean-up stack
    349         rts
    350 print_row_seperator:
    351         #io.PRINTSNL '|'
    352         dec tmp                 ; Check if each line has been printed 3 times
    353         lda tmp
    354         beq print_seperator
    355         pla                     ; If not,
    356         sec                     ; print
    357         sbc #$03                ; line
    358         pha
    359         jmp print_line          ;again.
    360 print_seperator:        
    361         #io.PRINTSNL '+---+---+---+'
    362         lda #$03
    363         sta tmp
    364         jmp check_if_all_rows_printed
    365 put_field_number_if_empty_and_middle:
    366         pha
    367         cmp #' '
    368         bne print_verbatim
    369         ;; Check if we are in the
    370         ;; middle line
    371         lda tmp
    372         cmp #$02
    373         bne print_verbatim
    374         ;; Middle of empty field;
    375         ;; print field number
    376         txa
    377         clc
    378         adc #'1'
    379         jsr io.putc
    380         pla
    381         rts
    382 print_verbatim:
    383         pla
    384         jmp io.putc
    385         .bend
    386 
    387 
    388         
    389 piece_to_ascii:
    390         .block
    391         cmp #piece_x
    392         bne not_x
    393         lda #'#'
    394         rts
    395 not_x:  
    396         cmp #piece_o
    397         bne not_o
    398         lda #':'
    399         rts
    400 not_o:  
    401         lda #' '
    402         rts
    403         .bend
    404         
    405 ;;; Check if a player won.
    406 ;;; Returns piece_x or piece_y
    407 ;;; if one of the player won,
    408 ;;; piece_none in case of a draw,
    409 ;;; and #$ff in case the game
    410 ;;; is not finished yet.
    411         piece_x_row = (piece_x << 4) + (piece_x << 2) + piece_x
    412         piece_o_row = (piece_o << 4) + (piece_o << 2) + piece_o
    413 get_game_state:
    414         .block
    415         lda #piece_x_row
    416         jsr check_player_won
    417         bne done
    418         lda #piece_o_row
    419         jsr check_player_won
    420         bne done
    421         jsr check_draw
    422 done:   
    423         rts
    424         .bend
    425 
    426 ;;; Check if a certain player one.
    427 ;;; This subroutine expects a complete
    428 ;;; row of pieces of the player to be
    429 ;;; checked for winning in A as input.
    430 check_player_won:
    431         .block
    432         sta tmp                 ; Store pattern for following checks
    433         jsr check_win_row
    434         bne done
    435         lda tmp
    436         jsr check_win_column
    437         bne done
    438         lda tmp
    439         jsr check_win_first_diagonal
    440         bne done
    441         lda tmp
    442         jsr check_win_second_diagonal
    443 done:   
    444         rts
    445         .bend
    446 
    447 ;;; Check for three in a row.
    448 ;;; Expects the row pattern of
    449 ;;; three identical pieces in A.
    450 check_win_row:
    451         .block
    452         ldy #$00
    453 loop:   
    454         cmp (board_ptr),y
    455         beq player_won
    456         iny
    457         cpy #$03
    458         bne loop
    459         ;; No winning rows
    460         lda #$00
    461         rts
    462 player_won:
    463         lda tmp
    464         and #%00000011
    465         rts
    466         .bend
    467 
    468 ;;; Check for three in a column.
    469 ;;; Expects the row(!) pattern of
    470 ;;; three identical pieces in A.
    471 check_win_column:
    472         .block
    473         and #%00110000          ; Field (column) pattern
    474         tax                     ; We use X as temporary memory
    475         ldy #$00
    476 loop:   
    477         and (board_ptr),y       ; Piece in column?
    478         beq check_next_column   ; If not, check next column
    479         iny                     ; If yes, advance to next row
    480         cpy #$03                ; Until all three rows
    481         bne loop                ; have been checked
    482 player_won:     
    483         lda tmp
    484         and #%00000011
    485         rts
    486 check_next_column:
    487         ldy #$00                ; Restore row counter
    488         txa                     ; Old field (column) pattern
    489         lsr a                     ; Change to
    490         lsr a                     ; next column
    491         tax                     ; and save it in temporary memory
    492         bne loop 
    493         ;; No winning columns
    494         lda #$00
    495         rts
    496         .bend
    497 
    498 check_win_first_diagonal:
    499         .block
    500         and #%00110000          ; Start with last field (column)
    501         tax                     ; Store pattern in x
    502         ldy #$00                ; Row number stored in y
    503 loop:
    504         and (board_ptr),y
    505         beq no_win_first_diagonal
    506         txa                     ; Move
    507         lsr a                     ; field
    508         lsr a                     ; (column)
    509         tax                     ; pattern
    510         iny                     ; and row by one
    511         cpy #$03
    512         bne loop
    513         ;; Player won
    514         ;; Return player piece
    515         lda tmp
    516         and #%00000011
    517         rts
    518 no_win_first_diagonal:
    519         lda #$00
    520         rts
    521         .bend
    522         
    523 check_win_second_diagonal:
    524         .block
    525         and #%00000011          ; Start with first field (column)
    526         tax                     ; Store pattern in x
    527         ldy #$00                ; Row number stored in y
    528 loop:
    529         and (board_ptr),y
    530         beq no_win_second_diagonal
    531         txa                     ; Move
    532         asl a                     ; field
    533         asl a                     ; (column)
    534         tax                     ; pattern
    535         iny                     ; and row by one
    536         cpy #$03
    537         bne loop
    538         ;; Player won
    539         ;; Return player piece
    540         lda tmp
    541         and #%00000011
    542         rts
    543 no_win_second_diagonal:
    544         lda #$00
    545         rts
    546         .bend
    547         
    548 check_draw:
    549         .block
    550         ldy #$00                ; Row
    551 row_loop:       
    552         ldx #%00110000          ; Field (column) pattern
    553         txa                     ; Store pattern in x
    554 col_loop:       
    555         and (board_ptr),y       ; Check
    556         beq no_draw             ; all
    557         txa                     ; fields
    558         lsr a                     ; for
    559         lsr a                     ; emptiness
    560         tax
    561         bne col_loop
    562         iny
    563         cpy #$03
    564         bne row_loop
    565         ;; Draw
    566         lda #piece_none
    567         rts
    568 no_draw:
    569         lda #$ff
    570         rts
    571         .bend      
    572         
    573 
    574 
    575 ;;; This subroutine can be called
    576 ;;; called after get_game_state
    577 ;;; in order to print the state
    578 ;;; of the game in human-friendly
    579 ;;; format to the acai.
    580 print_game_state:
    581         .block
    582         cmp #piece_x
    583         beq somebody_won
    584         cmp #piece_o
    585         beq somebody_won
    586         cmp #piece_none
    587         bne no_draw
    588         #io.PRINTSNL "Draw!"
    589 .if !RUN_TESTS
    590 	jsr io.getc
    591 .endif
    592         rts
    593 no_draw:        
    594         #io.PRINTSNL "Let the game continue!"
    595         rts
    596 somebody_won:
    597         pha
    598         #io.PRINTS "Player "
    599         pla
    600         clc
    601         adc #'0'
    602         jsr io.putc
    603         #io.PRINTSNL " wins!"
    604 .if !RUN_TESTS
    605 	jsr io.getc
    606 .endif
    607         rts
    608         .bend