A fast, reusable, and well-documented game engine for Tic-Tac-Toe (Noughts and Crosses) in Rust, featuring a robust Minimax AI. This library is designed to be embedded in CLI, GUI, and web applications, or used as an educational resource for turn-based game development and AI.
- Pure Rust: No unsafe code, zero dependencies.
- Robust API: Exposes core game logic and AI.
- Minimax AI: Unbeatable, optimized computer opponent.
- Easy to Integrate: Use in any Rust project as a dependency.
- Well-Documented: All public items have thorough documentation.
- Tested: Includes comprehensive unit tests.
- Add to your
Cargo.toml
:
[dependencies]
xo-core = "0.1"
(Replace the version with the latest on crates.io)
- Or simply use Cargo:
cargo add xo-core
use xo_core::{GameEngine, Player, Cell, GameState};
fn main() {
let mut game = GameEngine::new();
// Play moves: X at 0, O at 4, X at 1, O at 5, X at 2 (X wins)
let moves = [0, 4, 1, 5, 2];
for m in moves {
game.make_move(m).unwrap();
}
match game.check_state() {
GameState::Win(Player::X) => println!("X wins!"),
GameState::Win(Player::O) => println!("O wins!"),
GameState::Tie => println!("It's a tie!"),
GameState::InProgress => println!("Game is still in progress."),
}
}
use xo_core::{GameEngine, Player, Cell};
fn main() {
let mut game = GameEngine::new();
// Human is X, AI is O
loop {
// Human move (for example, always picking the first empty)
let board = game.get_board();
let my_move = board.iter().position(|&c| c == Cell::Empty).unwrap();
game.make_move(my_move).unwrap();
// Check for game over
if game.is_over() {
break;
}
// AI move
let ai_move = game.get_best_move().unwrap();
game.make_move(ai_move).unwrap();
// Check for game over
if game.is_over() {
break;
}
}
println!("Final board: {:?}", game.get_board());
}
GameEngine
: The main engine struct. Manages board state, moves, and AI.Player
: Enum forX
andO
.Cell
: Enum forX
,O
, orEmpty
cell.GameState
: Enum forWin(Player)
,Tie
, orInProgress
.MoveError
: Enum for move errors (OutOfBounds
,CellOccupied
).
GameEngine::new()
: Create a new game.make_move(index)
: Attempt a move at given cell (0-8).get_board()
: Get the current board state as[Cell; 9]
.check_state()
: Check if the game is over, won, tied, or still in progress.is_over()
: Boolean, true if game finished.get_best_move()
: Returns the best move for the current player (Minimax AI).
Cells are indexed left-to-right, top-to-bottom:
0 | 1 | 2
---|---|---
3 | 4 | 5
---|---|---
6 | 7 | 8
- Invalid moves return a
Result<(), MoveError>
. - Errors include:
MoveError::OutOfBounds
— index not in 0..=8MoveError::CellOccupied
— cell already filled
You can easily use xo-core
with web frameworks (e.g., Yew), desktop GUI (e.g., egui, gtk-rs), or in server-side logic.
The engine is detached from any I/O or UI, making it flexible for integration.
- Rust 1.88 (2024 Edition)
MIT
Contributions, bug reports, and feature requests welcome!
Please open an issue or submit a pull request on GitHub.
- This project started as a fork of tic-tac-toe-rs by rogue-87, whose original implementation and ideas laid the foundation for this crate.
While working on this, I stumbled across a couple of cool projects. Check them out while you're at it:
- bitboard_xo: XO game implemented in Rust with minimum memory usage
- tic-tac-toe-rs: A straightforward implementation of Tic-Tac-Toe in Rust and the original inspiration for this crate