Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
d9740a28a4 | |||
419d3d7dcc | |||
6af10ba53d | |||
e703d914ed | |||
cc87ad378e | |||
befc8128cd | |||
3d243836eb | |||
38d638538b |
32
.drone.yml
Normal file
32
.drone.yml
Normal 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
|
179
src/main.rs
179
src/main.rs
@ -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______"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user