From 207febef9924c8fa5391185e75d42a7ea480bb38 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 31: Software Drawing, Time, and Graphical Games Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- docs/chapter31/examples/TicTacToe.smp | 132 ++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 docs/chapter31/examples/TicTacToe.smp (limited to 'docs/chapter31/examples/TicTacToe.smp') diff --git a/docs/chapter31/examples/TicTacToe.smp b/docs/chapter31/examples/TicTacToe.smp new file mode 100644 index 00000000..ee4e3182 --- /dev/null +++ b/docs/chapter31/examples/TicTacToe.smp @@ -0,0 +1,132 @@ +// Player is X, CPU is O. Squares are numbered left-to-right, top-to-bottom. +val main = { u8[~][] args -> + sys.io.p("Tic-tac-toe!\nYou are X; the CPU is O. You go first.\n"); + sys.io.p("Press a square's number (no Enter needed), or q/Esc to quit.\n"); + sys.io.p("Squares:\n 1 | 2 | 3 \n---+---+---\n 4 | 5 | 6 \n---+---+---\n 7 | 8 | 9 \n\n"); + val board = new u8[9]{ int i -> ' '; }; // Initialize the board + // Draw empty board + _draw(board); + + while( true ) { + sys.io.p("Your move (1-9): "); + // Valid user move 1-9, or 0 for quitting + int square = _validMove(board) - 1; + if( square < 0 ) return 0; + // Set user choice; print board + board[square] = 'X'; + sys.io.p("\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + // Compute CPU move + int reply = _cpu(board); + // Set CPU choice; print board + board[reply] = 'O'; + sys.io.p("CPU plays %(reply+1).\n"); + _draw(board); + // A winner? + if( _finished(board) ) return 0; + } +}; + +// Return with a valid user move 1-9, or 0 to exit +val _validMove = { u8[~] board -> + while( true ) { + int key = sys.io.getch(); + if( key == -1 && sys.libc.errno() != sys.libc.EOF ) { + sys.io.err("\nUnable to read the keyboard.\n"); + return 0; + } + // Various quitting conditions + if( key == -1 || key == 'q' || key == 'Q' || key == 27 || key == 3 || key == 4 ) { + sys.io.p("\nGoodbye!\n"); + return 0; + } + // Ignore CR/LF + if( key == '\r' || key == '\n' ) continue; + + // Valid choice + if( key < '1' || key > '9' ) { + sys.io.p("\nChoose a square from 1 to 9: "); + continue; + } + if( board[key-'1'] != ' ' ) { + sys.io.p("\nThat square is occupied. Your move (1-9): "); + continue; + } + sys.io.p("%c(key)"); + return key-'0'; + } +}; + +// Return the winning mark, or zero. +val _winner = { u8[~] board -> + for( int i = 0; i < 3; i++ ) { + int row = i*3; + if( board[row] != ' ' && board[row] == board[row+1] && board[row] == board[row+2] ) + return board[row]; + if( board[i] != ' ' && board[i] == board[i+3] && board[i] == board[i+6] ) + return board[i]; + } + if( board[4] != ' ' ) { + if( board[0] == board[4] && board[4] == board[8] ) return board[4]; + if( board[2] == board[4] && board[4] == board[6] ) return board[4]; + } + return 0; +}; + +val _finished = { u8[~] board -> + int winner = _winner(board); + if( winner == 'X' ) { sys.io.p("You win!\n"); return true; } + if( winner == 'O' ) { sys.io.p("CPU wins!\n"); return true; } + for( int i = 0; i < board#; i++ ) + if( board[i] == ' ' ) return false; + sys.io.p("It's a draw!\n"); + return true; +}; + +// Try each empty square, restoring it before returning. +val _threat = { u8[] board, u8 mark -> + for( int i = 0; i < board#; i++ ) { + if( board[i] == ' ' ) { + board[i] = mark; + bool wins = _winner(board) == mark; + board[i] = ' '; // Restore board + if( wins ) return i; + } + } + return -1; +}; + +// Win if possible, otherwise block, then prefer center, corners, and edges. +// Called only after _finished has ruled out a full board. +val _cpu = { u8[] board -> + // If O can win, do so + int win = _threat(board,'O'); + if( win >= 0 ) return win; + // If X will win next move, block + int block = _threat(board,'X'); + if( block >= 0 ) return block; + // If center is open, take it + if( board[4] == ' ' ) return 4; + // If a corner is open, take it + for( int i = 0; i < board#; i += 2 ) + if( board[i] == ' ' ) return i; + // Take whatever is available + for( int i = 0; i < board#; i++ ) + if( board[i] == ' ' ) return i; + return -1; +}; + +val _draw = { u8[~] board -> + val out = new sys.aryu8(); + for( int row = 0; row < 3; row++ ) { + for( int col = 0; col < 3; col++ ) { + out.add(' ').add(board[row*3+col]).add(' '); + if( col < 2 ) out.add('|'); + } + out.add('\n'); + if( row < 2 ) out.addAry("---+---+---\n"); + } + out.write(sys.libc.STDOUT); +}; -- cgit v1.2.3