diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c6ac77 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Symbol Strike + + +## Rules + +This game is played in two rounds, starting with an empty eleven-space board. Red always goes first. + +On Red's board (i.e, duing the first round), Red's job is to maximize the value of the expression; Blue's job is to minimize it. +Players take turns placing the fourteen symbols `0123456789+-×÷` on the board, with the maximizing player taking the first move. + +A board's syntax must always be valid. Namely, the following rules are enforced: + - Each symbol may only be used once + - The binary operators `+-×÷` may not be next to one another, and may not be at the end slots. + - The unary operator `-` (negative) must have a number as an argument. Therefore, it cannot be left of an operator (like `-×`), and it may not be in the rightmost slot. + - `0` may not follow `÷`. This prevents most cases of zero-division, but isn't perfect. `÷-0` will break the game, and `÷0_+` is forbidden despite being valid syntax once the empty slot is filled (for example, with `÷03+`). This is done to simplyify game logic, and might be improved later. + + +## Building & Running + +As always, run this project with `cargo run`. The app takes one argument by default: the name of the blue player. This can be any of the following: + - `human`: Play against a human + - `random`: Play against a random agent (very easy) + - `minimax`: Play against a simple extremum-chasing agent (easy) + - `diffuse`: Play against a slightly more intellegent extremum chaser (medium) + +For example, `cargo run -- random` will play a game against a random player. + +Additional options are available, see `cargo run -- --help`. \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 6bfa0ba..4fd51cd 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,24 +6,39 @@ use crate::{ }; #[derive(Parser, Debug)] -#[command(version, about)] +#[command(about)] pub struct Cli { - pub red: AgentSelector, + /// The agent that controls the Blue (opponent) player pub blue: AgentSelector, + /// The agent that controls the Red (home) player + #[arg(long, default_value = "human")] + pub red: AgentSelector, + + /// If this is greater than one, repeat the game this many times and print a summary. + /// Best used with --silent. #[arg(long, short, default_value = "0")] pub repeat: usize, + /// If this is given, do not print boards. + /// Good for bulk runs with --repeat, bad for human players. #[arg(long, short, default_value = "false")] pub silent: bool, } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] pub enum AgentSelector { - Random, - Diffuse, - Minimax, + /// A human agent. Asks for input. Human, + + /// A random agent (very easy) + Random, + + /// A simple extremum-chaser (easy) + Minimax, + + /// A smarter extremum-chaser (medium) + Diffuse, } impl AgentSelector { diff --git a/src/main.rs b/src/main.rs index d9b82a5..cf66339 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,11 +59,18 @@ fn play( let mut is_first_turn = true; let mut is_maxi_turn = true; + let board_label = format!( + "{}{:6}{}", + color::Fg(color::LightBlack), + maxi.player().to_string(), + color::Fg(color::Reset) + ); + while !board.is_done() { // Print board println!( "\r{}{}{}{}", - " ".repeat(6), + board_label, if is_first_turn { '╓' } else { '║' }, board.prettyprint()?, if is_first_turn { '╖' } else { '║' }, @@ -99,8 +106,8 @@ fn play( is_maxi_turn = !is_maxi_turn; } - println!("\r{}║{}║", " ".repeat(6), board.prettyprint()?); - println!("\r{}╙{}╜", " ".repeat(6), " ".repeat(board.size())); + println!("\r{}║{}║", board_label, board.prettyprint()?); + println!("\r{}╙{}╜", board_label, " ".repeat(board.size())); Ok(board) }