summaryrefslogtreecommitdiff
path: root/docs/chapter31/examples/TicTacToe.smp
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-11-12 13:48:30 -0800
commit7824fa044ac7d2db13af5873ce2e90365153e0be (patch)
tree0795225a660a8bda2ae0b0bb6d7b013803c9775e /docs/chapter31/examples/TicTacToe.smp
parent2eb27046f9b2bca887fd9ea0a7c5a67547153ef3 (diff)
downloadSimple-linear-chapter31.tar.gz
Simple-linear-chapter31.zip
Chapter 31: Software Drawing and Graphical Gameslinear-chapter31linear
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 <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs/chapter31/examples/TicTacToe.smp')
-rw-r--r--docs/chapter31/examples/TicTacToe.smp133
1 files changed, 133 insertions, 0 deletions
diff --git a/docs/chapter31/examples/TicTacToe.smp b/docs/chapter31/examples/TicTacToe.smp
new file mode 100644
index 00000000..a710e440
--- /dev/null
+++ b/docs/chapter31/examples/TicTacToe.smp
@@ -0,0 +1,133 @@
+// 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];
+ for( int i=0; i<board#; i++ ) board[i]=' ';
+ // 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);
+};