board.asm (14168B)
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 ;; therefore 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 jsr term.clear_screen 313 #term.SET_CURSOR #$01, #$01 314 #io.PRINTSNL '** Tic-Tac-Toe **' 315 #io.PRINTNL 316 #io.PRINTSNL '+---+---+---+' 317 print_field: 318 lda #$03 319 sta tmp ; Print each line 3 times 320 print_line: 321 lda #'|' 322 jsr io.putc 323 plx 324 inx 325 phx 326 jsr get_field 327 jsr piece_to_ascii 328 jsr io.putc 329 plx 330 phx 331 jsr put_field_number_if_empty_and_middle 332 jsr io.putc 333 plx 334 phx 335 cpx #$02 336 beq print_row_seperator 337 cpx #$05 338 beq print_row_seperator 339 cpx #$08 340 beq print_row_seperator 341 check_if_all_rows_printed: 342 plx 343 phx 344 cpx #$08 345 bne print_line 346 plx ; Clean-up stack 347 rts 348 print_row_seperator: 349 #io.PRINTSNL '|' 350 dec tmp ; Check if each line has been printed 3 times 351 lda tmp 352 beq print_seperator 353 pla ; If not, 354 sec ; print 355 sbc #$03 ; line 356 pha 357 jmp print_line ;again. 358 print_seperator: 359 #io.PRINTSNL '+---+---+---+' 360 lda #$03 361 sta tmp 362 jmp check_if_all_rows_printed 363 put_field_number_if_empty_and_middle: 364 pha 365 cmp #' ' 366 bne print_verbatim 367 ;; Check if we are in the 368 ;; middle line 369 lda tmp 370 cmp #$02 371 bne print_verbatim 372 ;; Middle of empty field; 373 ;; print field number 374 txa 375 clc 376 adc #'1' 377 jsr io.putc 378 pla 379 rts 380 print_verbatim: 381 pla 382 jmp io.putc 383 .bend 384 385 386 387 piece_to_ascii: 388 .block 389 cmp #piece_x 390 bne not_x 391 lda #'#' 392 rts 393 not_x: 394 cmp #piece_o 395 bne not_o 396 lda #':' 397 rts 398 not_o: 399 lda #' ' 400 rts 401 .bend 402 403 ;;; Check if a player won. 404 ;;; Returns piece_x or piece_y 405 ;;; if one of the player won, 406 ;;; piece_none in case of a draw, 407 ;;; and #$ff in case the game 408 ;;; is not finished yet. 409 piece_x_row = (piece_x << 4) + (piece_x << 2) + piece_x 410 piece_o_row = (piece_o << 4) + (piece_o << 2) + piece_o 411 get_game_state: 412 .block 413 lda #piece_x_row 414 jsr check_player_won 415 bne done 416 lda #piece_o_row 417 jsr check_player_won 418 bne done 419 jsr check_draw 420 done: 421 rts 422 .bend 423 424 ;;; Check if a certain player one. 425 ;;; This subroutine expects a complete 426 ;;; row of pieces of the player to be 427 ;;; checked for winning in A as input. 428 check_player_won: 429 .block 430 sta tmp ; Store pattern for following checks 431 jsr check_win_row 432 bne done 433 lda tmp 434 jsr check_win_column 435 bne done 436 lda tmp 437 jsr check_win_first_diagonal 438 bne done 439 lda tmp 440 jsr check_win_second_diagonal 441 done: 442 rts 443 .bend 444 445 ;;; Check for three in a row. 446 ;;; Expects the row pattern of 447 ;;; three identical pieces in A. 448 check_win_row: 449 .block 450 ldy #$00 451 loop: 452 cmp (board_ptr),y 453 beq player_won 454 iny 455 cpy #$03 456 bne loop 457 ;; No winning rows 458 lda #$00 459 rts 460 player_won: 461 lda tmp 462 and #%00000011 463 rts 464 .bend 465 466 ;;; Check for three in a column. 467 ;;; Expects the row(!) pattern of 468 ;;; three identical pieces in A. 469 check_win_column: 470 .block 471 and #%00110000 ; Field (column) pattern 472 tax ; We use X as temporary memory 473 ldy #$00 474 loop: 475 and (board_ptr),y ; Piece in column? 476 beq check_next_column ; If not, check next column 477 iny ; If yes, advance to next row 478 cpy #$03 ; Until all three rows 479 bne loop ; have been checked 480 player_won: 481 lda tmp 482 and #%00000011 483 rts 484 check_next_column: 485 ldy #$00 ; Restore row counter 486 txa ; Old field (column) pattern 487 lsr a ; Change to 488 lsr a ; next column 489 tax ; and save it in temporary memory 490 bne loop 491 ;; No winning columns 492 lda #$00 493 rts 494 .bend 495 496 check_win_first_diagonal: 497 .block 498 and #%00110000 ; Start with last field (column) 499 tax ; Store pattern in x 500 ldy #$00 ; Row number stored in y 501 loop: 502 and (board_ptr),y 503 beq no_win_first_diagonal 504 txa ; Move 505 lsr a ; field 506 lsr a ; (column) 507 tax ; pattern 508 iny ; and row by one 509 cpy #$03 510 bne loop 511 ;; Player won 512 ;; Return player piece 513 lda tmp 514 and #%00000011 515 rts 516 no_win_first_diagonal: 517 lda #$00 518 rts 519 .bend 520 521 check_win_second_diagonal: 522 .block 523 and #%00000011 ; Start with first field (column) 524 tax ; Store pattern in x 525 ldy #$00 ; Row number stored in y 526 loop: 527 and (board_ptr),y 528 beq no_win_second_diagonal 529 txa ; Move 530 asl a ; field 531 asl a ; (column) 532 tax ; pattern 533 iny ; and row by one 534 cpy #$03 535 bne loop 536 ;; Player won 537 ;; Return player piece 538 lda tmp 539 and #%00000011 540 rts 541 no_win_second_diagonal: 542 lda #$00 543 rts 544 .bend 545 546 check_draw: 547 .block 548 ldy #$00 ; Row 549 row_loop: 550 ldx #%00110000 ; Field (column) pattern 551 txa ; Store pattern in x 552 col_loop: 553 and (board_ptr),y ; Check 554 beq no_draw ; all 555 txa ; fields 556 lsr a ; for 557 lsr a ; emptiness 558 tax 559 bne col_loop 560 iny 561 cpy #$03 562 bne row_loop 563 ;; Draw 564 lda #piece_none 565 rts 566 no_draw: 567 lda #$ff 568 rts 569 .bend 570 571 ;;; This subroutine can be called 572 ;;; called after get_game_state 573 ;;; in order to print the state 574 ;;; of the game in human-friendly 575 ;;; format to the acai. 576 print_game_state: 577 .block 578 cmp #piece_x 579 beq somebody_won 580 cmp #piece_o 581 beq somebody_won 582 cmp #piece_none 583 bne no_draw 584 #io.PRINTSNL "Draw!" 585 rts 586 no_draw: 587 #io.PRINTSNL "Let the game continue!" 588 rts 589 somebody_won: 590 pha 591 #io.PRINTS "Player " 592 pla 593 clc 594 adc #'0' 595 jsr io.putc 596 #io.PRINTSNL " wins!" 597 rts 598 .bend