Remove tictactoe programs
This commit is contained in:
parent
c9e8346e6a
commit
07c656093c
17
src/bank.rs
17
src/bank.rs
@ -35,8 +35,6 @@ use std::time::Instant;
|
||||
use storage_program::StorageProgram;
|
||||
use system_program::SystemProgram;
|
||||
use system_transaction::SystemTransaction;
|
||||
use tictactoe_dashboard_program::TicTacToeDashboardProgram;
|
||||
use tictactoe_program::TicTacToeProgram;
|
||||
use timing::{duration_as_us, timestamp};
|
||||
use token_program::TokenProgram;
|
||||
use tokio::prelude::Future;
|
||||
@ -602,21 +600,6 @@ impl Bank {
|
||||
{
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
} else if TicTacToeProgram::check_id(&tx_program_id) {
|
||||
if TicTacToeProgram::process_transaction(&tx, instruction_index, program_accounts)
|
||||
.is_err()
|
||||
{
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
} else if TicTacToeDashboardProgram::check_id(&tx_program_id) {
|
||||
if TicTacToeDashboardProgram::process_transaction(
|
||||
&tx,
|
||||
instruction_index,
|
||||
program_accounts,
|
||||
).is_err()
|
||||
{
|
||||
return Err(BankError::ProgramRuntimeError(instruction_index as u8));
|
||||
}
|
||||
} else if TokenProgram::check_id(&tx_program_id) {
|
||||
if TokenProgram::process_transaction(&tx, instruction_index, program_accounts).is_err()
|
||||
{
|
||||
|
@ -72,8 +72,6 @@ pub mod streamer;
|
||||
pub mod system_program;
|
||||
pub mod system_transaction;
|
||||
pub mod thin_client;
|
||||
pub mod tictactoe_dashboard_program;
|
||||
pub mod tictactoe_program;
|
||||
pub mod timing;
|
||||
pub mod token_program;
|
||||
pub mod tpu;
|
||||
|
@ -1,172 +0,0 @@
|
||||
//! tic-tac-toe dashboard program
|
||||
|
||||
use serde_cbor;
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use tictactoe_program::{Error, Game, Result, State, TicTacToeProgram};
|
||||
use transaction::Transaction;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct TicTacToeDashboardProgram {
|
||||
pending: Pubkey, // Latest pending game
|
||||
completed: Vec<Pubkey>, // Last N completed games (0 is the latest)
|
||||
total: usize, // Total number of completed games
|
||||
}
|
||||
|
||||
pub const TICTACTOE_DASHBOARD_PROGRAM_ID: [u8; 32] = [
|
||||
4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
impl TicTacToeDashboardProgram {
|
||||
fn deserialize(input: &[u8]) -> Result<TicTacToeDashboardProgram> {
|
||||
if input.len() < 2 {
|
||||
Err(Error::InvalidUserdata)?;
|
||||
}
|
||||
let len = input[0] as usize + (0x100 * input[1] as usize);
|
||||
if len == 0 {
|
||||
Ok(TicTacToeDashboardProgram::default())
|
||||
} else if input.len() < len + 2 {
|
||||
Err(Error::InvalidUserdata)
|
||||
} else {
|
||||
serde_cbor::from_slice(&input[2..(2 + len)]).map_err(|err| {
|
||||
error!("Unable to deserialize: {:?}", err);
|
||||
Error::InvalidUserdata
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn update(
|
||||
self: &mut TicTacToeDashboardProgram,
|
||||
game_pubkey: &Pubkey,
|
||||
game: &Game,
|
||||
) -> Result<()> {
|
||||
match game.state {
|
||||
State::Waiting => {
|
||||
self.pending = *game_pubkey;
|
||||
}
|
||||
State::XMove | State::OMove => {
|
||||
// Nothing to do. In progress games are not managed by the dashboard
|
||||
}
|
||||
State::XWon | State::OWon | State::Draw => {
|
||||
if !self.completed.iter().any(|pubkey| pubkey == game_pubkey) {
|
||||
// TODO: Once the PoH height is exposed to programs, it could be used to ensure
|
||||
// that old games are not being re-added and causing |total| to increment
|
||||
// incorrectly.
|
||||
self.total += 1;
|
||||
self.completed.insert(0, *game_pubkey);
|
||||
|
||||
// Only track the last N completed games to
|
||||
// avoid overrunning Account userdata
|
||||
if self.completed.len() > 5 {
|
||||
self.completed.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize(self: &TicTacToeDashboardProgram, output: &mut [u8]) -> Result<()> {
|
||||
let self_serialized = serde_cbor::to_vec(self).unwrap();
|
||||
|
||||
if output.len() < 2 + self_serialized.len() {
|
||||
warn!(
|
||||
"{} bytes required to serialize but only have {} bytes",
|
||||
self_serialized.len() + 2,
|
||||
output.len()
|
||||
);
|
||||
return Err(Error::UserdataTooSmall);
|
||||
}
|
||||
|
||||
assert!(self_serialized.len() <= 0xFFFF);
|
||||
output[0] = (self_serialized.len() & 0xFF) as u8;
|
||||
output[1] = (self_serialized.len() >> 8) as u8;
|
||||
output[2..(2 + self_serialized.len())].clone_from_slice(&self_serialized);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == TICTACTOE_DASHBOARD_PROGRAM_ID
|
||||
}
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&TICTACTOE_DASHBOARD_PROGRAM_ID)
|
||||
}
|
||||
|
||||
pub fn process_transaction(
|
||||
tx: &Transaction,
|
||||
pix: usize,
|
||||
accounts: &mut [&mut Account],
|
||||
) -> Result<()> {
|
||||
info!("process_transaction: {:?}", tx);
|
||||
// accounts[0] doesn't matter, anybody can cause a dashboard update
|
||||
// accounts[1] must be a Dashboard account
|
||||
// accounts[2] must be a Game account
|
||||
if accounts.len() != 3 {
|
||||
error!("Expected 3 accounts");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
if !Self::check_id(&accounts[1].program_id) {
|
||||
error!("accounts[1] is not a TICTACTOE_DASHBOARD_PROGRAM_ID");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
if accounts[1].userdata.is_empty() {
|
||||
error!("accounts[1] userdata is empty");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
|
||||
let mut dashboard = Self::deserialize(&accounts[1].userdata)?;
|
||||
|
||||
if !TicTacToeProgram::check_id(&accounts[2].program_id) {
|
||||
error!("accounts[2] is not a TICTACTOE_PROGRAM_ID");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
let ttt = TicTacToeProgram::deserialize(&accounts[2].userdata)?;
|
||||
|
||||
match ttt.game {
|
||||
None => Err(Error::NoGame),
|
||||
Some(game) => dashboard.update(tx.key(pix, 2).unwrap(), &game),
|
||||
}?;
|
||||
|
||||
dashboard.serialize(&mut accounts[1].userdata)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn serde() {
|
||||
let mut dashboard1 = TicTacToeDashboardProgram::default();
|
||||
dashboard1.total = 1234567890;
|
||||
dashboard1.pending = Pubkey::new(&[100; 32]);
|
||||
for i in 1..5 {
|
||||
dashboard1.completed.push(Pubkey::new(&[100 + i; 32]));
|
||||
}
|
||||
|
||||
let mut userdata = vec![0xff; 512];
|
||||
dashboard1.serialize(&mut userdata).unwrap();
|
||||
|
||||
let dashboard2 = TicTacToeDashboardProgram::deserialize(&userdata).unwrap();
|
||||
assert_eq!(dashboard1, dashboard2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn serde_userdata_too_small() {
|
||||
let dashboard = TicTacToeDashboardProgram::default();
|
||||
let mut userdata = vec![0xff; 1];
|
||||
assert_eq!(
|
||||
dashboard.serialize(&mut userdata),
|
||||
Err(Error::UserdataTooSmall)
|
||||
);
|
||||
|
||||
let err = TicTacToeDashboardProgram::deserialize(&userdata);
|
||||
assert!(err.is_err());
|
||||
assert_eq!(err.err().unwrap(), Error::InvalidUserdata);
|
||||
}
|
||||
|
||||
// TODO: add tests for business logic
|
||||
}
|
@ -1,504 +0,0 @@
|
||||
//! tic-tac-toe program
|
||||
|
||||
use serde_cbor;
|
||||
use solana_sdk::account::Account;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use std;
|
||||
use transaction::Transaction;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Error {
|
||||
GameInProgress,
|
||||
InvalidArguments,
|
||||
InvalidMove,
|
||||
InvalidUserdata,
|
||||
InvalidTimestamp,
|
||||
NoGame,
|
||||
NotYourTurn,
|
||||
PlayerNotFound,
|
||||
UserdataTooSmall,
|
||||
}
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "error")
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
enum BoardItem {
|
||||
F, // Free
|
||||
X,
|
||||
O,
|
||||
}
|
||||
|
||||
impl Default for BoardItem {
|
||||
fn default() -> BoardItem {
|
||||
BoardItem::F
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub enum State {
|
||||
Waiting,
|
||||
XMove,
|
||||
OMove,
|
||||
XWon,
|
||||
OWon,
|
||||
Draw,
|
||||
}
|
||||
impl Default for State {
|
||||
fn default() -> State {
|
||||
State::Waiting
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct Game {
|
||||
player_x: Pubkey,
|
||||
player_o: Option<Pubkey>,
|
||||
pub state: State,
|
||||
board: [BoardItem; 9],
|
||||
keep_alive: [i64; 2],
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn create(player_x: &Pubkey) -> Game {
|
||||
let mut game = Game::default();
|
||||
game.player_x = *player_x;
|
||||
assert_eq!(game.state, State::Waiting);
|
||||
game
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn new(player_x: Pubkey, player_o: Pubkey) -> Game {
|
||||
let mut game = Game::create(&player_x);
|
||||
game.join(player_o, 1).unwrap();
|
||||
game
|
||||
}
|
||||
|
||||
pub fn join(self: &mut Game, player_o: Pubkey, timestamp: i64) -> Result<()> {
|
||||
if self.state == State::Waiting {
|
||||
self.player_o = Some(player_o);
|
||||
self.state = State::XMove;
|
||||
|
||||
if timestamp <= self.keep_alive[1] {
|
||||
Err(Error::InvalidTimestamp)
|
||||
} else {
|
||||
self.keep_alive[1] = timestamp;
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(Error::GameInProgress)
|
||||
}
|
||||
}
|
||||
|
||||
fn same(x_or_o: BoardItem, triple: &[BoardItem]) -> bool {
|
||||
triple.iter().all(|&i| i == x_or_o)
|
||||
}
|
||||
|
||||
pub fn next_move(self: &mut Game, player: Pubkey, x: usize, y: usize) -> Result<()> {
|
||||
let board_index = y * 3 + x;
|
||||
if board_index >= self.board.len() || self.board[board_index] != BoardItem::F {
|
||||
Err(Error::InvalidMove)?;
|
||||
}
|
||||
|
||||
let (x_or_o, won_state) = match self.state {
|
||||
State::XMove => {
|
||||
if player != self.player_x {
|
||||
return Err(Error::PlayerNotFound);
|
||||
}
|
||||
self.state = State::OMove;
|
||||
(BoardItem::X, State::XWon)
|
||||
}
|
||||
State::OMove => {
|
||||
if player != self.player_o.unwrap() {
|
||||
return Err(Error::PlayerNotFound);
|
||||
}
|
||||
self.state = State::XMove;
|
||||
(BoardItem::O, State::OWon)
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::NotYourTurn);
|
||||
}
|
||||
};
|
||||
self.board[board_index] = x_or_o;
|
||||
|
||||
let winner =
|
||||
// Check rows
|
||||
Game::same(x_or_o, &self.board[0..3])
|
||||
|| Game::same(x_or_o, &self.board[3..6])
|
||||
|| Game::same(x_or_o, &self.board[6..9])
|
||||
// Check columns
|
||||
|| Game::same(x_or_o, &[self.board[0], self.board[3], self.board[6]])
|
||||
|| Game::same(x_or_o, &[self.board[1], self.board[4], self.board[7]])
|
||||
|| Game::same(x_or_o, &[self.board[2], self.board[5], self.board[8]])
|
||||
// Check both diagonals
|
||||
|| Game::same(x_or_o, &[self.board[0], self.board[4], self.board[8]])
|
||||
|| Game::same(x_or_o, &[self.board[2], self.board[4], self.board[6]]);
|
||||
|
||||
if winner {
|
||||
self.state = won_state;
|
||||
} else if self.board.iter().all(|&p| p != BoardItem::F) {
|
||||
self.state = State::Draw;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keep_alive(self: &mut Game, player: Pubkey, timestamp: i64) -> Result<()> {
|
||||
match self.state {
|
||||
State::Waiting | State::XMove | State::OMove => {
|
||||
if player == self.player_x {
|
||||
if timestamp <= self.keep_alive[0] {
|
||||
Err(Error::InvalidTimestamp)?;
|
||||
}
|
||||
self.keep_alive[0] = timestamp;
|
||||
} else if Some(player) == self.player_o {
|
||||
if timestamp <= self.keep_alive[1] {
|
||||
Err(Error::InvalidTimestamp)?;
|
||||
}
|
||||
self.keep_alive[1] = timestamp;
|
||||
} else {
|
||||
Err(Error::PlayerNotFound)?;
|
||||
}
|
||||
}
|
||||
// Ignore keep_alive when game is no longer in progress
|
||||
State::XWon | State::OWon | State::Draw => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[repr(C)]
|
||||
pub enum Command {
|
||||
Init, // player X initializes a new game
|
||||
Join(i64), // player O wants to join (seconds since UNIX epoch)
|
||||
KeepAlive(i64), // player X/O keep alive (seconds since UNIX epoch)
|
||||
Move(u8, u8), // player X/O mark board position (x, y)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||
pub struct TicTacToeProgram {
|
||||
pub game: Option<Game>,
|
||||
}
|
||||
|
||||
pub const TICTACTOE_PROGRAM_ID: [u8; 32] = [
|
||||
3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
impl TicTacToeProgram {
|
||||
pub fn deserialize(input: &[u8]) -> Result<TicTacToeProgram> {
|
||||
let len = input[0] as usize;
|
||||
|
||||
if len == 0 {
|
||||
Ok(TicTacToeProgram::default())
|
||||
} else if input.len() < len + 1 {
|
||||
Err(Error::InvalidUserdata)
|
||||
} else {
|
||||
serde_cbor::from_slice(&input[1..=len]).map_err(|err| {
|
||||
error!("Unable to deserialize game: {:?}", err);
|
||||
Error::InvalidUserdata
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_command(self: &mut TicTacToeProgram, cmd: &Command, player: &Pubkey) -> Result<()> {
|
||||
info!("dispatch_command: cmd={:?} player={}", cmd, player);
|
||||
info!("dispatch_command: account={:?}", self);
|
||||
|
||||
if let Command::Init = cmd {
|
||||
return if self.game.is_some() {
|
||||
Err(Error::GameInProgress)
|
||||
} else {
|
||||
let game = Game::create(player);
|
||||
self.game = Some(game);
|
||||
Ok(())
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(ref mut game) = self.game {
|
||||
match cmd {
|
||||
Command::Join(timestamp) => game.join(*player, *timestamp),
|
||||
Command::Move(x, y) => game.next_move(*player, *x as usize, *y as usize),
|
||||
Command::KeepAlive(timestamp) => game.keep_alive(*player, *timestamp),
|
||||
Command::Init => panic!("Unreachable"),
|
||||
}
|
||||
} else {
|
||||
Err(Error::NoGame)
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize(self: &TicTacToeProgram, output: &mut [u8]) -> Result<()> {
|
||||
let self_serialized = serde_cbor::to_vec(self).unwrap();
|
||||
|
||||
if output.len() + 1 < self_serialized.len() {
|
||||
warn!(
|
||||
"{} bytes required to serialize but only have {} bytes",
|
||||
self_serialized.len(),
|
||||
output.len() + 1
|
||||
);
|
||||
return Err(Error::UserdataTooSmall);
|
||||
}
|
||||
|
||||
assert!(self_serialized.len() <= 255);
|
||||
output[0] = self_serialized.len() as u8;
|
||||
output[1..=self_serialized.len()].clone_from_slice(&self_serialized);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_id(program_id: &Pubkey) -> bool {
|
||||
program_id.as_ref() == TICTACTOE_PROGRAM_ID
|
||||
}
|
||||
|
||||
pub fn id() -> Pubkey {
|
||||
Pubkey::new(&TICTACTOE_PROGRAM_ID)
|
||||
}
|
||||
|
||||
pub fn process_transaction(
|
||||
tx: &Transaction,
|
||||
pix: usize,
|
||||
accounts: &mut [&mut Account],
|
||||
) -> Result<()> {
|
||||
// accounts[1] must always be the Tic-tac-toe game state account
|
||||
if accounts.len() < 2 || !Self::check_id(&accounts[1].program_id) {
|
||||
error!("accounts[1] is not assigned to the TICTACTOE_PROGRAM_ID");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
if accounts[1].userdata.is_empty() {
|
||||
error!("accounts[1] userdata is empty");
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
|
||||
let mut program_state = Self::deserialize(&accounts[1].userdata)?;
|
||||
|
||||
let command = serde_cbor::from_slice::<Command>(tx.userdata(pix)).map_err(|err| {
|
||||
error!("{:?}", err);
|
||||
Error::InvalidUserdata
|
||||
})?;
|
||||
|
||||
if let Command::Init = command {
|
||||
// Init must be signed by the game state account itself, who's private key is
|
||||
// known only to player X
|
||||
if !Self::check_id(&accounts[0].program_id) {
|
||||
error!("accounts[0] is not assigned to the TICTACTOE_PROGRAM_ID");
|
||||
return Err(Error::InvalidArguments);
|
||||
}
|
||||
// player X public key is in keys[2]
|
||||
if tx.key(pix, 2).is_none() {
|
||||
Err(Error::InvalidArguments)?;
|
||||
}
|
||||
program_state.dispatch_command(&command, tx.key(pix, 2).unwrap())?;
|
||||
} else {
|
||||
program_state.dispatch_command(&command, tx.key(pix, 0).unwrap())?;
|
||||
}
|
||||
program_state.serialize(&mut accounts[1].userdata)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn serde_no_game() {
|
||||
let account = TicTacToeProgram::default();
|
||||
assert!(account.game.is_none());
|
||||
|
||||
let mut userdata = vec![0xff; 256];
|
||||
account.serialize(&mut userdata).unwrap();
|
||||
|
||||
let account = TicTacToeProgram::deserialize(&userdata).unwrap();
|
||||
assert!(account.game.is_none());
|
||||
|
||||
let account = TicTacToeProgram::deserialize(&[0]).unwrap();
|
||||
assert!(account.game.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn serde_with_game() {
|
||||
let mut account = TicTacToeProgram::default();
|
||||
assert!(account.game.is_none());
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
account.dispatch_command(&Command::Init, &player_x).unwrap();
|
||||
|
||||
let mut userdata = vec![0xff; 256];
|
||||
account.serialize(&mut userdata).unwrap();
|
||||
|
||||
let account2 = TicTacToeProgram::deserialize(&userdata).unwrap();
|
||||
|
||||
assert_eq!(account.game.unwrap(), account2.game.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn serde_no_room() {
|
||||
let account = TicTacToeProgram::default();
|
||||
let mut userdata = vec![0xff; 1];
|
||||
assert_eq!(
|
||||
account.serialize(&mut userdata),
|
||||
Err(Error::UserdataTooSmall)
|
||||
);
|
||||
|
||||
let err = TicTacToeProgram::deserialize(&userdata);
|
||||
assert!(err.is_err());
|
||||
assert_eq!(err.err().unwrap(), Error::InvalidUserdata);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn column_1_x_wins() {
|
||||
/*
|
||||
X|O|
|
||||
-+-+-
|
||||
X|O|
|
||||
-+-+-
|
||||
X| |
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
let player_o = Pubkey::new(&[2; 32]);
|
||||
|
||||
let mut g = Game::new(player_x, player_o);
|
||||
assert_eq!(g.state, State::XMove);
|
||||
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
assert_eq!(g.state, State::OMove);
|
||||
g.next_move(player_o, 1, 0).unwrap();
|
||||
assert_eq!(g.state, State::XMove);
|
||||
g.next_move(player_x, 0, 1).unwrap();
|
||||
assert_eq!(g.state, State::OMove);
|
||||
g.next_move(player_o, 1, 1).unwrap();
|
||||
assert_eq!(g.state, State::XMove);
|
||||
g.next_move(player_x, 0, 2).unwrap();
|
||||
assert_eq!(g.state, State::XWon);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn right_diagonal_x_wins() {
|
||||
/*
|
||||
X|O|X
|
||||
-+-+-
|
||||
O|X|O
|
||||
-+-+-
|
||||
X| |
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
let player_o = Pubkey::new(&[2; 32]);
|
||||
let mut g = Game::new(player_x, player_o);
|
||||
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
g.next_move(player_o, 1, 0).unwrap();
|
||||
g.next_move(player_x, 2, 0).unwrap();
|
||||
g.next_move(player_o, 0, 1).unwrap();
|
||||
g.next_move(player_x, 1, 1).unwrap();
|
||||
g.next_move(player_o, 2, 1).unwrap();
|
||||
g.next_move(player_x, 0, 2).unwrap();
|
||||
assert_eq!(g.state, State::XWon);
|
||||
|
||||
assert_eq!(g.next_move(player_o, 1, 2), Err(Error::NotYourTurn));
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn bottom_row_o_wins() {
|
||||
/*
|
||||
X|X|
|
||||
-+-+-
|
||||
X| |
|
||||
-+-+-
|
||||
O|O|O
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
let player_o = Pubkey::new(&[2; 32]);
|
||||
let mut g = Game::new(player_x, player_o);
|
||||
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
g.next_move(player_o, 0, 2).unwrap();
|
||||
g.next_move(player_x, 1, 0).unwrap();
|
||||
g.next_move(player_o, 1, 2).unwrap();
|
||||
g.next_move(player_x, 0, 1).unwrap();
|
||||
g.next_move(player_o, 2, 2).unwrap();
|
||||
assert_eq!(g.state, State::OWon);
|
||||
|
||||
assert!(g.next_move(player_x, 1, 2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn left_diagonal_x_wins() {
|
||||
/*
|
||||
X|O|X
|
||||
-+-+-
|
||||
O|X|O
|
||||
-+-+-
|
||||
O|X|X
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
let player_o = Pubkey::new(&[2; 32]);
|
||||
let mut g = Game::new(player_x, player_o);
|
||||
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
g.next_move(player_o, 1, 0).unwrap();
|
||||
g.next_move(player_x, 2, 0).unwrap();
|
||||
g.next_move(player_o, 0, 1).unwrap();
|
||||
g.next_move(player_x, 1, 1).unwrap();
|
||||
g.next_move(player_o, 2, 1).unwrap();
|
||||
g.next_move(player_x, 1, 2).unwrap();
|
||||
g.next_move(player_o, 0, 2).unwrap();
|
||||
g.next_move(player_x, 2, 2).unwrap();
|
||||
assert_eq!(g.state, State::XWon);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn draw() {
|
||||
/*
|
||||
X|O|O
|
||||
-+-+-
|
||||
O|O|X
|
||||
-+-+-
|
||||
X|X|O
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
let player_o = Pubkey::new(&[2; 32]);
|
||||
let mut g = Game::new(player_x, player_o);
|
||||
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
g.next_move(player_o, 1, 1).unwrap();
|
||||
g.next_move(player_x, 0, 2).unwrap();
|
||||
g.next_move(player_o, 0, 1).unwrap();
|
||||
g.next_move(player_x, 2, 1).unwrap();
|
||||
g.next_move(player_o, 1, 0).unwrap();
|
||||
g.next_move(player_x, 1, 2).unwrap();
|
||||
g.next_move(player_o, 2, 2).unwrap();
|
||||
g.next_move(player_x, 2, 0).unwrap();
|
||||
|
||||
assert_eq!(g.state, State::Draw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn solo() {
|
||||
/*
|
||||
X|O|
|
||||
-+-+-
|
||||
| |
|
||||
-+-+-
|
||||
| |
|
||||
*/
|
||||
|
||||
let player_x = Pubkey::new(&[1; 32]);
|
||||
|
||||
let mut g = Game::new(player_x, player_x);
|
||||
assert_eq!(g.state, State::XMove);
|
||||
g.next_move(player_x, 0, 0).unwrap();
|
||||
assert_eq!(g.state, State::OMove);
|
||||
g.next_move(player_x, 1, 0).unwrap();
|
||||
assert_eq!(g.state, State::XMove);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user