examples: Tictactoe (#609)
This commit is contained in:
parent
5532aca6ec
commit
541dbfbe37
|
@ -0,0 +1,3 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
|
@ -0,0 +1,4 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
|
@ -0,0 +1,12 @@
|
|||
// Migrations are an early feature. Currently, they're nothing more than this
|
||||
// single deploy script that's invoked from the CLI, injecting a provider
|
||||
// configured from the workspace's Anchor.toml.
|
||||
|
||||
const anchor = require("@project-serum/anchor");
|
||||
|
||||
module.exports = async function (provider) {
|
||||
// Configure client to use the provider.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
// Add your deploy script here.
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "tictactoe"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "tictactoe"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
anchor-spl = { path = "../../../../spl" }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,216 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use std::str::FromStr;
|
||||
|
||||
const BOARD_ITEM_FREE: u8 = 0; // Free slot
|
||||
const BOARD_ITEM_X: u8 = 1; // Player X
|
||||
const BOARD_ITEM_O: u8 = 2; // Player O
|
||||
|
||||
/// Game State
|
||||
/// 0 - Waiting
|
||||
/// 1 - XMove
|
||||
/// 2 - OMove
|
||||
/// 3 - XWon
|
||||
/// 4 - OWon
|
||||
/// 5 - Draw
|
||||
|
||||
#[program]
|
||||
pub mod tictactoe {
|
||||
use super::*;
|
||||
|
||||
pub fn initialize_dashboard(ctx: Context<Initializedashboard>) -> ProgramResult {
|
||||
let dashboard = &mut ctx.accounts.dashboard;
|
||||
dashboard.game_count = 0;
|
||||
dashboard.address = *dashboard.to_account_info().key;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initialize(ctx: Context<Initialize>) -> ProgramResult {
|
||||
let dashboard = &mut ctx.accounts.dashboard;
|
||||
let game = &mut ctx.accounts.game;
|
||||
dashboard.game_count = dashboard.game_count + 1;
|
||||
dashboard.latest_game = *game.to_account_info().key;
|
||||
game.player_x = *ctx.accounts.player_x.key;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn player_join(ctx: Context<Playerjoin>) -> ProgramResult {
|
||||
let game = &mut ctx.accounts.game;
|
||||
game.player_o = *ctx.accounts.player_o.key;
|
||||
game.game_state = 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[access_control(Playermove::accounts(&ctx, x_or_o, player_move))]
|
||||
pub fn player_move(ctx: Context<Playermove>, x_or_o: u8, player_move: u8) -> ProgramResult {
|
||||
let game = &mut ctx.accounts.game;
|
||||
game.board[player_move as usize] = x_or_o;
|
||||
game.status(x_or_o);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn status(ctx: Context<Status>) -> ProgramResult {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Status<'info> {
|
||||
dashboard: ProgramAccount<'info, Dashboard>,
|
||||
game: ProgramAccount<'info, Game>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initializedashboard<'info> {
|
||||
#[account(init)]
|
||||
dashboard: ProgramAccount<'info, Dashboard>,
|
||||
#[account(signer)]
|
||||
authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize<'info> {
|
||||
#[account(signer)]
|
||||
player_x: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
dashboard: ProgramAccount<'info, Dashboard>,
|
||||
#[account(init)]
|
||||
game: ProgramAccount<'info, Game>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Playerjoin<'info> {
|
||||
#[account(signer)]
|
||||
player_o: AccountInfo<'info>,
|
||||
#[account(mut, constraint = game.game_state != 0 && game.player_x != Pubkey::default())]
|
||||
game: ProgramAccount<'info, Game>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Playermove<'info> {
|
||||
#[account(signer)]
|
||||
player: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
game: ProgramAccount<'info, Game>,
|
||||
}
|
||||
|
||||
impl<'info> Playermove<'info> {
|
||||
pub fn accounts(ctx: &Context<Playermove>, x_or_o: u8, player_move: u8) -> Result<()> {
|
||||
if ctx.accounts.game.board[player_move as usize] != 0 {
|
||||
return Err(ErrorCode::Illegalmove.into());
|
||||
}
|
||||
if x_or_o == BOARD_ITEM_X {
|
||||
return Playermove::player_x_checks(ctx);
|
||||
} else if x_or_o == BOARD_ITEM_O {
|
||||
return Playermove::player_o_checks(ctx);
|
||||
} else {
|
||||
return Err(ErrorCode::UnexpectedValue.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn player_x_checks(ctx: &Context<Playermove>) -> Result<()> {
|
||||
if ctx.accounts.game.player_x != *ctx.accounts.player.key {
|
||||
return Err(ErrorCode::Unauthorized.into());
|
||||
}
|
||||
if ctx.accounts.game.game_state != 1 {
|
||||
return Err(ErrorCode::Gamestate.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn player_o_checks(ctx: &Context<Playermove>) -> Result<()> {
|
||||
if ctx.accounts.game.player_o != *ctx.accounts.player.key {
|
||||
return Err(ErrorCode::Unauthorized.into());
|
||||
}
|
||||
if ctx.accounts.game.game_state != 2 {
|
||||
return Err(ErrorCode::Gamestate.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct Dashboard {
|
||||
game_count: u64,
|
||||
latest_game: Pubkey,
|
||||
address: Pubkey,
|
||||
}
|
||||
|
||||
#[account]
|
||||
#[derive(Default)]
|
||||
pub struct Game {
|
||||
keep_alive: [u64; 2],
|
||||
player_x: Pubkey,
|
||||
player_o: Pubkey,
|
||||
game_state: u8,
|
||||
board: [u8; 9],
|
||||
}
|
||||
|
||||
#[event]
|
||||
pub struct GameStatus {
|
||||
keep_alive: [u64; 2],
|
||||
player_x: Pubkey,
|
||||
player_o: Pubkey,
|
||||
game_state: u8,
|
||||
board: [u8; 9],
|
||||
}
|
||||
|
||||
impl From<GameStatus> for Game {
|
||||
fn from(status: GameStatus) -> Self {
|
||||
Self {
|
||||
keep_alive: status.keep_alive,
|
||||
player_x: status.player_x,
|
||||
player_o: status.player_o,
|
||||
game_state: status.game_state,
|
||||
board: status.board,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn status(self: &mut Game, x_or_o: u8) {
|
||||
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.game_state = x_or_o + 2;
|
||||
} else if self.board.iter().all(|&p| p != BOARD_ITEM_FREE) {
|
||||
self.game_state = 5;
|
||||
} else {
|
||||
if x_or_o == BOARD_ITEM_X {
|
||||
self.game_state = 2;
|
||||
} else {
|
||||
self.game_state = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn same(x_or_o: u8, triple: &[u8]) -> bool {
|
||||
triple.iter().all(|&i| i == x_or_o)
|
||||
}
|
||||
}
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("You are not authorized to perform this action.")]
|
||||
Unauthorized,
|
||||
#[msg("Wrong dashboard")]
|
||||
Wrongdashboard,
|
||||
#[msg("Wrong expected state")]
|
||||
Gamestate,
|
||||
#[msg("Dashboard already initialized")]
|
||||
Initialized,
|
||||
#[msg("Unexpected value")]
|
||||
UnexpectedValue,
|
||||
#[msg("Illegal move")]
|
||||
Illegalmove,
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
const anchor = require('@project-serum/anchor');
|
||||
|
||||
describe('tictactoe', () => {
|
||||
|
||||
anchor.setProvider(anchor.Provider.env());
|
||||
const program = anchor.workspace.Tictactoe;
|
||||
let dashboard = anchor.web3.Keypair.generate()
|
||||
let game = anchor.web3.Keypair.generate()
|
||||
let player_o = anchor.web3.Keypair.generate()
|
||||
|
||||
it('Initialize Dashboard', async () => {
|
||||
const tx = await program.rpc.initializeDashboard({
|
||||
accounts: {
|
||||
authority: program.provider.wallet.publicKey,
|
||||
dashboard: dashboard.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [dashboard],
|
||||
instructions: [await program.account.dashboard.createInstruction(dashboard)]
|
||||
})
|
||||
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Initialize Game', async () => {
|
||||
const tx = await program.rpc.initialize({
|
||||
accounts: {
|
||||
playerX: program.provider.wallet.publicKey,
|
||||
dashboard: dashboard.publicKey,
|
||||
game: game.publicKey,
|
||||
rent: anchor.web3.SYSVAR_RENT_PUBKEY,
|
||||
},
|
||||
signers: [game],
|
||||
instructions: [await program.account.game.createInstruction(game)]
|
||||
})
|
||||
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player O joins', async () => {
|
||||
const tx = await program.rpc.playerJoin({
|
||||
accounts: {
|
||||
playerO: player_o.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
signers: [player_o],
|
||||
})
|
||||
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player x plays', async () => {
|
||||
const tx = await program.rpc.playerMove(1, 0, {
|
||||
accounts: {
|
||||
player: program.provider.wallet.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player o plays', async () => {
|
||||
const tx = await program.rpc.playerMove(2, 1, {
|
||||
accounts: {
|
||||
player: player_o.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
signers: [player_o]
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player x plays', async () => {
|
||||
const tx = await program.rpc.playerMove(1, 3, {
|
||||
accounts: {
|
||||
player: program.provider.wallet.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player o plays', async () => {
|
||||
const tx = await program.rpc.playerMove(2, 6, {
|
||||
accounts: {
|
||||
player: player_o.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
signers: [player_o]
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player x plays', async () => {
|
||||
const tx = await program.rpc.playerMove(1, 2, {
|
||||
accounts: {
|
||||
player: program.provider.wallet.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player o plays', async () => {
|
||||
const tx = await program.rpc.playerMove(2, 4, {
|
||||
accounts: {
|
||||
player: player_o.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
signers: [player_o]
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player x plays', async () => {
|
||||
const tx = await program.rpc.playerMove(1, 5, {
|
||||
accounts: {
|
||||
player: program.provider.wallet.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player o plays', async () => {
|
||||
const tx = await program.rpc.playerMove(2, 8, {
|
||||
accounts: {
|
||||
player: player_o.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
signers: [player_o]
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Player x plays', async () => {
|
||||
const tx = await program.rpc.playerMove(1, 7, {
|
||||
accounts: {
|
||||
player: program.provider.wallet.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
it('Status', async () => {
|
||||
const tx = await program.rpc.status({
|
||||
accounts: {
|
||||
dashboard: dashboard.publicKey,
|
||||
game: game.publicKey,
|
||||
},
|
||||
})
|
||||
|
||||
console.log("transaction: ", tx)
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue