A complete game in one file: board drawing, win and draw detection, move validation, alternating players. No libraries, no dependencies, about 130 lines.
This is the real program running, captured frame by frame. X plays the centre, then the corners, and takes the diagonal.
Squares are numbered 0 to 8, left to right, top to bottom. List them in order, alternating X then O. Leave them off and it plays the demo above.
A square that is off the board, or one already taken, says why and does not cost you your turn.
Anything that is not a number is rejected as off-board rather than quietly becoming square 0.
Wins on any of the eight lines, and a full board with no winner is called a draw.
Note the first line. Because the game reads its moves from the command line, the file has to declare that power up front, and the compiler enforces it.
//! run uses [env] // Tic tac toe. Squares are numbered 0-8, left to right, top to bottom: // // 0 | 1 | 2 // 3 | 4 | 5 // 6 | 7 | 8 // // Play by listing your moves, alternating X then O: // quaryn run examples/tictactoe.qry 4 0 8 2 0 // With no moves given, it plays a short demo game. fn mark_of(v: Int) -> String { match v { 1 => "X", 2 => "O", _ => ".", } } fn render(b: &List<Int>) -> String { let mut out = "" let mut r = 0 while r < 3 { let mut line = "" let mut c = 0 while c < 3 { line = line + " " + mark_of(b[r * 3 + c]) c = c + 1 } out = out + line + "\n" r = r + 1 } out } // A line pays out only if all three squares match and are not empty. fn line_winner(b: &List<Int>, p: Int, q: Int, r: Int) -> Int { let first = b[p] if first != 0 && first == b[q] && first == b[r] { first } else { 0 } } fn winner(b: &List<Int>) -> Int { let lines = [0, 1, 2, 3, 4, 5, 6, 7, 8, 0, 3, 6, 1, 4, 7, 2, 5, 8, 0, 4, 8, 2, 4, 6] let mut i = 0 let mut found = 0 while i < lines.len() { let w = line_winner(b, lines[i], lines[i + 1], lines[i + 2]) if w != 0 && found == 0 { found = w } i = i + 3 } found } fn is_full(b: &List<Int>) -> Bool { let mut i = 0 let mut empty = 0 while i < b.len() { if b[i] == 0 { empty = empty + 1 } i = i + 1 } empty == 0 } // Applies one move. Answers whether it was accepted, so a rejected move // does not cost that player their turn. fn play(b: &mut List<Int>, square: Int, player: Int) -> Bool { if square < 0 || square > 8 { println(f"square {square} is off the board, ignored") return false } if b[square] != 0 { println(f"square {square} is already taken, ignored") return false } b[square] = player println(f"{mark_of(player)} takes {square}") true } fn main() { let mut board: List<Int> = [0, 0, 0, 0, 0, 0, 0, 0, 0] // Moves come from the command line; the demo runs when none are given. let given = args() let mut moves: List<Int> = [] let mut i = 0 while i < given.len() { moves.push(given[i].to_int().unwrap_or(-1)) i = i + 1 } if moves.len() == 0 { moves = [4, 1, 0, 2, 8] println("No moves given, playing the demo game.") } println("Squares are numbered 0-8:") println(" 0 1 2\n 3 4 5\n 6 7 8\n") let mut turn = 0 let mut player = 1 let mut done = 0 while turn < moves.len() { if done == 0 { if play(&mut board, moves[turn], player) { println(render(&board)) let w = winner(&board) if w != 0 { println(f"{mark_of(w)} wins.") done = 1 } else { if is_full(&board) { println("A draw.") done = 1 } } player = if player == 1 { 2 } else { 1 } } } turn = turn + 1 } if done == 0 { println("Game unfinished. Add more moves to keep playing.") } }