Compare commits

...

8 Commits
0.1 ... master

Author SHA1 Message Date
d9740a28a4
build musl images
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2020-06-06 14:39:18 +02:00
419d3d7dcc
pick whoever makes the first move at random
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2019-11-16 18:18:19 +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
2 changed files with 145 additions and 66 deletions

32
.drone.yml Normal file
View File

@ -0,0 +1,32 @@
kind: pipeline
name: default
steps:
- name: test
image: rust:1.38.0-alpine
commands:
- cargo test
- name: build_relase
image: rust:1.38.0-alpine
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

View File

@ -1,8 +1,11 @@
use std::env::args;
use std::fmt; use std::fmt;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::iter;
use std::ops::{Index, IndexMut}; use std::ops::{Index, IndexMut};
use std::str::FromStr; use std::str::FromStr;
use std::time::SystemTime;
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq)]
enum State { enum State {
@ -11,6 +14,12 @@ enum State {
N, N,
} }
impl State {
fn players() -> &'static [State] {
&[State::O, State::X]
}
}
impl fmt::Display for State { impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!( write!(
@ -72,7 +81,7 @@ impl FromStr for Board {
match c { match c {
'X' | 'x' => board.0.push(State::X), 'X' | 'x' => board.0.push(State::X),
'O' | 'o' => board.0.push(State::O), 'O' | 'o' => board.0.push(State::O),
'N' | 'n' => board.0.push(State::N), 'N' | 'n' | '_' => board.0.push(State::N),
_ => (), _ => (),
} }
} }
@ -85,86 +94,101 @@ impl FromStr for Board {
impl Board { impl Board {
fn new(size: usize) -> Board { fn new(size: usize) -> Board {
let mut b = Vec::with_capacity(size * size); Board(iter::repeat(State::N).take(size * size).collect())
for _ in 0..b.capacity() {
b.push(State::N);
}
Board(b)
} }
fn dimension(&self) -> usize { fn dimension(&self) -> usize {
(self.0.len() as f64).sqrt() as usize (self.0.len() as f64).sqrt() as usize
} }
fn winner(&self) -> Option<State> { fn to_string_short(&self) -> String {
let mut winners = [State::N; 4]; let mut s = String::with_capacity(self.0.len() + self.dimension());
let dim = self.dimension(); for y in 0..self.dimension() {
fn winner(winners: &[State]) -> Option<State> { for x in 0..self.dimension() {
winners s.push_str(&self[(x, y)].to_string())
.into_iter() }
.find(|w| *w != &State::N)
.map(|w| w.clone())
} }
for x in 0..dim { s
for y in 0..dim { }
let row = (y, x);
let col = (x, y);
{ fn winner_with_criteria(&self, criteria: usize) -> Option<State> {
let r = self[row]; for s in State::players() {
let c = self[col]; let update = |coords: (usize, usize), counter: &mut usize| {
winners[0] = if y == 0 || winners[0] == r { if self[coords] == *s {
r *counter += 1;
} else { } else {
State::N *counter = 0;
}; }
};
winners[1] = if y == 0 || winners[1] == c { let winner = |candidates: &[usize]| -> bool {
c candidates.iter().find(|c| *c >= &criteria).is_some()
} else { };
State::N 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);
} }
} }
{
//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 None.or(Some(State::N).filter(|_| self.0.iter().filter(|s| *s == &State::N).count() == 0))
winner(&winners[2..4]) }
#[allow(unused)]
fn winner(&self) -> Option<State> {
self.winner_with_criteria(self.dimension())
} }
} }
fn main() { fn main() {
let mut board = Board::default(); 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 players_rev = State::players().iter().rev().cloned().collect::<Vec<_>>();
let players: &[State] = if SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|d| d.as_millis() % 2 == 0)
.unwrap_or(false)
{
State::players()
} else {
&players_rev[..]
};
let stdin = std::io::stdin(); let stdin = std::io::stdin();
println!("{}", &board); println!("{}", &board);
let winner = loop { let winner = loop {
if let Some(winner) = board.winner() { if let Some(winner) = board.winner_with_criteria(criteria) {
break winner; break winner;
} }
let mut input = String::new(); let mut input = String::new();
for s in &[State::X, State::O] { for s in players {
loop { loop {
let (x, y) = loop { let (x, y) = loop {
print!("{}, your move: (x y) ", s); print!("{}, your move: (x y) ", s);
@ -198,13 +222,13 @@ fn main() {
_ => eprintln!("({}, {}) is already occupied! Try again", x, y), _ => eprintln!("({}, {}) is already occupied! Try again", x, y),
} }
} }
if let Some(_) = board.winner() { if let Some(_) = board.winner_with_criteria(criteria) {
break; break;
} }
println!("{}", &board); println!("{}", &board);
} }
}; };
println!("The winner is {}", winner); println!("{}\nThe winner is {}", board.to_string_short(), winner);
} }
#[cfg(test)] #[cfg(test)]
@ -217,10 +241,7 @@ mod test {
Board::from_str("XXX,NNN,NNN").unwrap().winner(), Board::from_str("XXX,NNN,NNN").unwrap().winner(),
Some(State::X) Some(State::X)
); );
assert_eq!( assert_eq!(Board::from_str("XNX,NNN,NNN").unwrap().winner(), None);
Board::from_str("XNX,NNN,NNN").unwrap().winner(),
None
);
assert_eq!( assert_eq!(
Board::from_str("NNN,XXX,NNN").unwrap().winner(), Board::from_str("NNN,XXX,NNN").unwrap().winner(),
Some(State::X) Some(State::X)
@ -233,5 +254,31 @@ mod test {
Board::from_str("OXN,OON,XNO").unwrap().winner(), Board::from_str("OXN,OON,XNO").unwrap().winner(),
Some(State::O) 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______"
);
} }
} }