Documentation & Tweaks
parent
7394e9db0b
commit
b344ae359b
|
@ -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`.
|
25
src/cli.rs
25
src/cli.rs
|
@ -6,24 +6,39 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[command(version, about)]
|
#[command(about)]
|
||||||
pub struct Cli {
|
pub struct Cli {
|
||||||
pub red: AgentSelector,
|
/// The agent that controls the Blue (opponent) player
|
||||||
pub blue: AgentSelector,
|
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")]
|
#[arg(long, short, default_value = "0")]
|
||||||
pub repeat: usize,
|
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")]
|
#[arg(long, short, default_value = "false")]
|
||||||
pub silent: bool,
|
pub silent: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
|
||||||
pub enum AgentSelector {
|
pub enum AgentSelector {
|
||||||
Random,
|
/// A human agent. Asks for input.
|
||||||
Diffuse,
|
|
||||||
Minimax,
|
|
||||||
Human,
|
Human,
|
||||||
|
|
||||||
|
/// A random agent (very easy)
|
||||||
|
Random,
|
||||||
|
|
||||||
|
/// A simple extremum-chaser (easy)
|
||||||
|
Minimax,
|
||||||
|
|
||||||
|
/// A smarter extremum-chaser (medium)
|
||||||
|
Diffuse,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AgentSelector {
|
impl AgentSelector {
|
||||||
|
|
13
src/main.rs
13
src/main.rs
|
@ -59,11 +59,18 @@ fn play(
|
||||||
let mut is_first_turn = true;
|
let mut is_first_turn = true;
|
||||||
let mut is_maxi_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() {
|
while !board.is_done() {
|
||||||
// Print board
|
// Print board
|
||||||
println!(
|
println!(
|
||||||
"\r{}{}{}{}",
|
"\r{}{}{}{}",
|
||||||
" ".repeat(6),
|
board_label,
|
||||||
if is_first_turn { '╓' } else { '║' },
|
if is_first_turn { '╓' } else { '║' },
|
||||||
board.prettyprint()?,
|
board.prettyprint()?,
|
||||||
if is_first_turn { '╖' } else { '║' },
|
if is_first_turn { '╖' } else { '║' },
|
||||||
|
@ -99,8 +106,8 @@ fn play(
|
||||||
is_maxi_turn = !is_maxi_turn;
|
is_maxi_turn = !is_maxi_turn;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("\r{}║{}║", " ".repeat(6), board.prettyprint()?);
|
println!("\r{}║{}║", board_label, board.prettyprint()?);
|
||||||
println!("\r{}╙{}╜", " ".repeat(6), " ".repeat(board.size()));
|
println!("\r{}╙{}╜", board_label, " ".repeat(board.size()));
|
||||||
Ok(board)
|
Ok(board)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue