7 Commits
0.1 ... cvc

Author SHA1 Message Date
b29f0d4062 impl rand game
All checks were successful
continuous-integration/drone/push Build is passing
2019-11-11 00:22:25 +01:00
6af10ba53d use iter to init board
All checks were successful
continuous-integration/drone/push Build is passing
2019-11-10 21:38:10 +01:00
e703d914ed allow for larger boards to be won using less than a full path
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2019-11-10 21:16:13 +01:00
cc87ad378e handle simple draw
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2019-11-01 21:54:37 +01:00
befc8128cd strip binary 2019-10-31 21:15:58 +01:00
3d243836eb auto test
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2019-10-31 21:05:20 +01:00
38d638538b allow resumption 2019-10-31 20:54:44 +01:00
4 changed files with 260 additions and 75 deletions

32
.drone.yml Normal file
View File

@@ -0,0 +1,32 @@
kind: pipeline
name: default
steps:
- name: test
image: rust:1.38.0
commands:
- cargo test
- name: build_relase
image: rust:1.38.0
commands:
- cargo install --path . --root . -f
- strip bin/tictactoe
- tar cvzf ttt.tar.gz bin src Cargo.*
when:
event:
- tag
- name: gitea_release
image: plugins/gitea-release
settings:
api_key:
from_secret: gitea_tkn
base_url:
from_secret: gitea_url
files:
- ttt.tar.gz
checksum:
- md5
- sha512
when:
event:
- tag

89
Cargo.lock generated
View File

@@ -1,6 +1,95 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "c2-chacha"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "getrandom"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "ppv-lite86"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rand"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_chacha"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "tictactoe"
version = "0.1.0"
dependencies = [
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "wasi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum getrandom 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "e7db7ca94ed4cd01190ceee0d8a8052f08a247aa1b469a7f68c6a3b71afcf407"
"checksum libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)" = "1a31a0627fdf1f6a39ec0dd577e101440b7db22672c0901fe00a9a6fbb5c24e8"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3ae1b169243eaf61759b8475a998f0a385e42042370f3a7dbaf35246eacc8412"
"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"

View File

@@ -5,5 +5,11 @@ authors = ["shimun <shimun@shimun.net>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[profile.release]
opt-level = 'z' # Optimize for size.
lto = true # Enable Link Time Optimization
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
panic = 'abort' # Abort on panic
[dependencies]
rand = "0.7.2"

View File

@@ -1,6 +1,9 @@
use rand::Rng;
use std::env::args;
use std::fmt;
use std::io;
use std::io::Write;
use std::iter;
use std::ops::{Index, IndexMut};
use std::str::FromStr;
@@ -11,6 +14,12 @@ enum State {
N,
}
impl State {
fn players() -> &'static [State] {
&[State::O, State::X]
}
}
impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@@ -72,7 +81,7 @@ impl FromStr for Board {
match c {
'X' | 'x' => board.0.push(State::X),
'O' | 'o' => board.0.push(State::O),
'N' | 'n' => board.0.push(State::N),
'N' | 'n' | '_' => board.0.push(State::N),
_ => (),
}
}
@@ -85,89 +94,91 @@ impl FromStr for Board {
impl Board {
fn new(size: usize) -> Board {
let mut b = Vec::with_capacity(size * size);
for _ in 0..b.capacity() {
b.push(State::N);
}
Board(b)
Board(iter::repeat(State::N).take(size * size).collect())
}
fn dimension(&self) -> usize {
(self.0.len() as f64).sqrt() as usize
}
fn to_string_short(&self) -> String {
let mut s = String::with_capacity(self.0.len() + self.dimension());
for y in 0..self.dimension() {
for x in 0..self.dimension() {
s.push_str(&self[(x, y)].to_string())
}
}
s
}
fn winner_with_criteria(&self, criteria: usize) -> Option<State> {
for s in State::players() {
let update = |coords: (usize, usize), counter: &mut usize| {
if self[coords] == *s {
*counter += 1;
} else {
*counter = 0;
}
};
let winner = |candidates: &[usize]| -> bool {
candidates.iter().find(|c| *c >= &criteria).is_some()
};
let (mut diar, mut dial) = (0usize, 0usize);
for i in 0..self.dimension() {
let (mut row, mut col) = (0usize, 0usize);
for j in 0..self.dimension() {
update((i, j), &mut row);
update((j, i), &mut col);
if winner(&[row, col]) {
return Some(*s);
}
}
update((i, i), &mut dial);
update((self.dimension() - i - 1, i), &mut diar);
if winner(&[diar, dial]) {
return Some(*s);
}
}
}
None.or(Some(State::N).filter(|_| self.0.iter().filter(|s| *s == &State::N).count() == 0))
}
#[allow(unused)]
fn winner(&self) -> Option<State> {
let mut winners = [State::N; 4];
let dim = self.dimension();
fn winner(winners: &[State]) -> Option<State> {
winners
.into_iter()
.find(|w| *w != &State::N)
.map(|w| w.clone())
}
for x in 0..dim {
for y in 0..dim {
let row = (y, x);
let col = (x, y);
{
let r = self[row];
let c = self[col];
winners[0] = if y == 0 || winners[0] == r {
r
} else {
State::N
};
winners[1] = if y == 0 || winners[1] == c {
c
} else {
State::N
};
}
}
{
//diagonal
let diag_left = (x, x);
let diag_right = (dim - x - 1, x);
let l = self[diag_left];
let r = self[diag_right];
winners[2] = if x == 0 || winners[2] == l {
l
} else {
State::N
};
winners[3] = if x == 0 || winners[3] == r {
r
} else {
State::N
};
}
//Row and Col winners can be determined after one Y pass
if let Some(winner) = winner(&winners[0..2]) {
return Some(winner);
}
}
//Diagonal winners require a full X pass
winner(&winners[2..4])
self.winner_with_criteria(self.dimension())
}
}
fn main() {
let mut board = Board::default();
let stdin = std::io::stdin();
println!("{}", &board);
let winner = loop {
if let Some(winner) = board.winner() {
break winner;
let (mut board, criteria) = {
let mut args = args().skip(1);
let (size, criteria) = (
args.next(),
args.next().and_then(|c| c.parse::<usize>().ok()),
);
let board = if let Some(arg) = size {
if let Ok(size) = arg.parse::<usize>() {
Board::new(size)
} else if let Ok(board) = Board::from_str(&arg) {
board
} else {
Board::default()
}
} else {
Board::default()
};
let dim = board.dimension();
(board, criteria.unwrap_or(dim))
};
let game_mode = 1usize;
let stdin = std::io::stdin();
let mut input = String::new();
for s in &[State::X, State::O] {
loop {
let mut rng = rand::thread_rng();
let mut next_move = |player: &State, board: &Board| -> (usize, usize) {
match game_mode {
0 => loop {
let (x, y) = loop {
print!("{}, your move: (x y) ", s);
print!("{}, your move: (x y) ", player);
io::stdout().flush().unwrap();
input.clear();
stdin.read_line(&mut input).unwrap();
@@ -192,19 +203,43 @@ fn main() {
};
match board[(x, y)] {
State::N => {
board[(x, y)] = *s;
break;
break (x, y);
}
_ => eprintln!("({}, {}) is already occupied! Try again", x, y),
}
},
_ => {
//Pretty naive
let dim = board.dimension();
std::thread::sleep(std::time::Duration::from_millis(
1000 * (9.0 / ((dim * dim) as f64)) as u64,
));
loop {
let x: usize = rng.gen_range(0, dim);
let y: usize = rng.gen_range(0, dim);
if board[(x, y)] == State::N {
break (x, y);
}
if let Some(_) = board.winner() {
}
}
}
};
println!("{}", &board);
let winner = loop {
if let Some(winner) = board.winner_with_criteria(criteria) {
break winner;
}
for s in State::players() {
let movee = next_move(s, &board);
board[movee] = *s;
if let Some(_) = board.winner_with_criteria(criteria) {
break;
}
println!("{}", &board);
}
};
println!("The winner is {}", winner);
println!("{}\nThe winner is {}", board.to_string_short(), winner);
}
#[cfg(test)]
@@ -217,10 +252,7 @@ mod test {
Board::from_str("XXX,NNN,NNN").unwrap().winner(),
Some(State::X)
);
assert_eq!(
Board::from_str("XNX,NNN,NNN").unwrap().winner(),
None
);
assert_eq!(Board::from_str("XNX,NNN,NNN").unwrap().winner(), None);
assert_eq!(
Board::from_str("NNN,XXX,NNN").unwrap().winner(),
Some(State::X)
@@ -233,5 +265,31 @@ mod test {
Board::from_str("OXN,OON,XNO").unwrap().winner(),
Some(State::O)
);
println!("{}", Board::from_str("XOX,OOX,OXO").unwrap());
assert_eq!(
Board::from_str("XOX,OOX,OXO").unwrap().winner(),
Some(State::N)
);
assert_eq!(
Board::from_str("XOX,OOX,OXO")
.unwrap()
.winner_with_criteria(1),
Some(State::O)
);
assert_eq!(
Board::from_str("XOX,OOX,OXO")
.unwrap()
.winner_with_criteria(2),
Some(State::O)
);
}
#[test]
fn to_string() {
assert_eq!(
&Board::from_str("XXX,NNN,NNN").unwrap().to_string_short()[..],
"XXX______"
);
}
}