Implement server-side move validation.
This commit is contained in:
parent
180a782eff
commit
30eaa81d1b
@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts::*,
|
constant::*,
|
||||||
piece::*,
|
piece::*,
|
||||||
util::Hex,
|
util::Hex,
|
||||||
};
|
};
|
||||||
@ -87,7 +87,7 @@ impl Board {
|
|||||||
(Piece::new(PIECE_DRAGON, PLAYER_DAWN), Hex::from_hex(4, 2)),
|
(Piece::new(PIECE_DRAGON, PLAYER_DAWN), Hex::from_hex(4, 2)),
|
||||||
(Piece::new(PIECE_BEHEMOTH, PLAYER_DAWN), Hex::from_hex(4, 1)),
|
(Piece::new(PIECE_BEHEMOTH, PLAYER_DAWN), Hex::from_hex(4, 1)),
|
||||||
|
|
||||||
(Piece::new(PIECE_SOURCE, PLAYER_DAWN), Hex::from_hex(4, 0)),
|
(Piece::new(PIECE_HEART, PLAYER_DAWN), Hex::from_hex(4, 0)),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (piece, hex) in &layout {
|
for (piece, hex) in &layout {
|
||||||
|
4
game/src/config/mod.rs
Normal file
4
game/src/config/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GameConfig {
|
||||||
|
|
||||||
|
}
|
@ -11,4 +11,27 @@ pub const PIECE_TOWER :u8 = 3;
|
|||||||
pub const PIECE_CASTLE :u8 = 4;
|
pub const PIECE_CASTLE :u8 = 4;
|
||||||
pub const PIECE_DRAGON :u8 = 5;
|
pub const PIECE_DRAGON :u8 = 5;
|
||||||
pub const PIECE_BEHEMOTH :u8 = 6;
|
pub const PIECE_BEHEMOTH :u8 = 6;
|
||||||
pub const PIECE_SOURCE :u8 = 7;
|
pub const PIECE_HEART :u8 = 7;
|
||||||
|
|
||||||
|
pub mod flags {
|
||||||
|
pub const NONE :u32 = 0x00;
|
||||||
|
pub const IGNORE_EXTENT :u32 = 0x01;
|
||||||
|
pub const IGNORE_CHECK :u32 = 0x02;
|
||||||
|
pub const IGNORE_STACKING :u32 = 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DIRECTIONS :&[(i8, i8)] = &[
|
||||||
|
(0, 1),
|
||||||
|
(1, 1),
|
||||||
|
(1, 0),
|
||||||
|
(0, -1),
|
||||||
|
(-1, -1),
|
||||||
|
(-1, 0),
|
||||||
|
|
||||||
|
(1, 2),
|
||||||
|
(2, 1),
|
||||||
|
(1, -1),
|
||||||
|
(-1, -2),
|
||||||
|
(-2, -1),
|
||||||
|
(-1, 1),
|
||||||
|
];
|
68
game/src/game/checkstate.rs
Normal file
68
game/src/game/checkstate.rs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct CheckState {
|
||||||
|
data:u8,
|
||||||
|
}
|
||||||
|
impl CheckState {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
data:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn immediate(mut self) -> Self
|
||||||
|
{
|
||||||
|
self.data |= 0x80;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn direct(mut self) -> Self
|
||||||
|
{
|
||||||
|
self.data += 1;
|
||||||
|
self.data |= 0x40;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stride(mut self) -> Self
|
||||||
|
{
|
||||||
|
self.data += 1;
|
||||||
|
self.data |= 0x20;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&mut self, other:Self)
|
||||||
|
{
|
||||||
|
self.data += other.data & 0x1F;
|
||||||
|
self.data |= other.data & 0xC0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self)
|
||||||
|
{
|
||||||
|
self.data = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_check(&self) -> bool
|
||||||
|
{
|
||||||
|
self.data != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_immediate(&self) -> bool
|
||||||
|
{
|
||||||
|
(self.data & 0x80) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_direct(&self) -> bool
|
||||||
|
{
|
||||||
|
(self.data & 0x40) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_stride(&self) -> bool
|
||||||
|
{
|
||||||
|
(self.data & 0x20) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn count(&self) -> usize
|
||||||
|
{
|
||||||
|
(self.data & 0x1F) as usize
|
||||||
|
}
|
||||||
|
}
|
@ -1,172 +1 @@
|
|||||||
use crate::{
|
mod checkstate; pub use checkstate::CheckState;
|
||||||
//consts::*,
|
|
||||||
board::Board,
|
|
||||||
history::Play,
|
|
||||||
piece::Piece, util::Hex,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
enum GameStatus {
|
|
||||||
Normal,
|
|
||||||
Check(u32),
|
|
||||||
Checkmate,
|
|
||||||
Resign,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Pool = [u8; 7];
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Game {
|
|
||||||
status:GameStatus,
|
|
||||||
|
|
||||||
pub turn:u16,
|
|
||||||
pub board:Board,
|
|
||||||
pub pool:[Pool; 2],
|
|
||||||
|
|
||||||
pub history:Vec<Play>,
|
|
||||||
}
|
|
||||||
impl Game {
|
|
||||||
pub fn new() -> Self
|
|
||||||
{
|
|
||||||
Self {
|
|
||||||
status:GameStatus::Normal,
|
|
||||||
|
|
||||||
turn:0,
|
|
||||||
|
|
||||||
board:Board::new(),
|
|
||||||
pool:[Pool::default(); 2],
|
|
||||||
|
|
||||||
history:Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(&mut self)
|
|
||||||
{
|
|
||||||
*self = Self::new();
|
|
||||||
self.board.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_history(&mut self, history:&Vec<Play>) -> Result<(),()>
|
|
||||||
{
|
|
||||||
self.init();
|
|
||||||
for play in history {
|
|
||||||
self.process(play).ok();
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process(&mut self, play:&Play) -> Result<(),()>
|
|
||||||
{
|
|
||||||
let player = (self.turn & 1) as u8;
|
|
||||||
|
|
||||||
match self.status {
|
|
||||||
GameStatus::Checkmate | GameStatus::Resign => { return Err(()); }
|
|
||||||
_ => { }
|
|
||||||
}
|
|
||||||
|
|
||||||
let valid = true;
|
|
||||||
|
|
||||||
//
|
|
||||||
// TODO:
|
|
||||||
// - Check for piece promotion.
|
|
||||||
// - Validate plays against move sets and specific rules.
|
|
||||||
// - Determine game state (check, checkmate).
|
|
||||||
//
|
|
||||||
|
|
||||||
if valid {
|
|
||||||
// Move piece on board.
|
|
||||||
if match play.source {
|
|
||||||
0 | 2 => {
|
|
||||||
if let Some(pid) = self.board.tiles[play.from as usize].piece {
|
|
||||||
if let Some(mut piece) = self.board.pieces[pid as usize] {
|
|
||||||
let mut swap = false;
|
|
||||||
|
|
||||||
if let Some(tid) = self.board.tiles[play.to as usize].piece {
|
|
||||||
if let Some(target) = &mut self.board.pieces[tid as usize] {
|
|
||||||
|
|
||||||
// Check for piece swap.
|
|
||||||
if piece.player == target.player {
|
|
||||||
swap = true;
|
|
||||||
target.tile = play.from;
|
|
||||||
|
|
||||||
// Check for target promotion.
|
|
||||||
let hex = Hex::from_tile(play.from);
|
|
||||||
if !target.promoted && target.has_promotion() && Hex::is_back(hex.x, hex.y, target.player) {
|
|
||||||
target.promoted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add captured piece to pool.
|
|
||||||
else {
|
|
||||||
self.pool[piece.player as usize][target.class as usize] += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Destroy piece if captured.
|
|
||||||
if !swap { self.board.pieces[tid as usize] = None; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set tile/piece associations.
|
|
||||||
if swap {
|
|
||||||
self.board.tiles[play.from as usize].piece = self.board.tiles[play.to as usize].piece;
|
|
||||||
} else {
|
|
||||||
self.board.tiles[play.from as usize].piece = None;
|
|
||||||
}
|
|
||||||
self.board.tiles[play.to as usize].piece = Some(pid);
|
|
||||||
piece.tile = play.to;
|
|
||||||
|
|
||||||
self.board.pieces[pid as usize] = Some(piece);
|
|
||||||
|
|
||||||
// Check for piece promotion.
|
|
||||||
let hex = Hex::from_tile(play.to);
|
|
||||||
if !piece.promoted && piece.has_promotion() && Hex::is_back(hex.x, hex.y, piece.player) {
|
|
||||||
if let Some(piece) = &mut self.board.pieces[pid as usize] {
|
|
||||||
piece.promoted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.turn += 1;
|
|
||||||
true
|
|
||||||
} else { false }
|
|
||||||
} else { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place piece from pool.
|
|
||||||
1 => {
|
|
||||||
if self.pool[player as usize][play.from as usize] > 0 && self.board.tiles[play.to as usize].piece.is_none() {
|
|
||||||
self.pool[player as usize][play.from as usize] -= 1;
|
|
||||||
let piece = Piece::new(play.from, player);
|
|
||||||
self.board.set_piece(piece, play.to);
|
|
||||||
|
|
||||||
self.turn += 1;
|
|
||||||
true
|
|
||||||
} else { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player retired.
|
|
||||||
0xF => {
|
|
||||||
self.status = GameStatus::Resign;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => false,
|
|
||||||
} {
|
|
||||||
self.history.push(*play);
|
|
||||||
Ok(())
|
|
||||||
} else { Err(()) }
|
|
||||||
} else { Err(()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_moves(_piece:&Piece) -> Vec<Play>
|
|
||||||
{
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_complete(&self) -> bool
|
|
||||||
{
|
|
||||||
match self.status {
|
|
||||||
GameStatus::Checkmate | GameStatus::Resign => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use crate::CheckState;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Play {
|
pub struct Play {
|
||||||
pub source:u8,
|
pub source:u8,
|
||||||
@ -13,4 +15,38 @@ impl Play {
|
|||||||
to:0,
|
to:0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_move(from:u8, to:u8) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
source:0,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_alt(from:u8, to:u8) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
source:2,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_drop(piece:u8, tile:u8) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
source:1,
|
||||||
|
from:piece,
|
||||||
|
to:tile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PlayInfo {
|
||||||
|
pub play:Play,
|
||||||
|
pub check:CheckState,
|
||||||
|
pub blocking:u32,
|
||||||
}
|
}
|
||||||
|
717
game/src/lib.rs
717
game/src/lib.rs
@ -1,8 +1,713 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
pub mod consts;
|
pub mod constant; use constant::*;
|
||||||
pub mod util;
|
pub mod util; use util::Hex;
|
||||||
pub mod piece;
|
pub mod piece; use piece::Piece;
|
||||||
pub mod board;
|
pub mod board; use board::Board;
|
||||||
pub mod history;
|
pub mod history; use history::*;
|
||||||
pub mod game; pub use game::Game;
|
pub mod config; use config::GameConfig;
|
||||||
|
mod game; use game::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum GameStatus {
|
||||||
|
Normal,
|
||||||
|
Check(CheckState),
|
||||||
|
Checkmate,
|
||||||
|
Resign,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Pool = [u8; 7];
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Game {
|
||||||
|
pub config:GameConfig,
|
||||||
|
|
||||||
|
status:GameStatus,
|
||||||
|
pub turn:u16,
|
||||||
|
|
||||||
|
pub board:Board,
|
||||||
|
pub pool:[Pool; 2],
|
||||||
|
|
||||||
|
pub history:Vec<Play>,
|
||||||
|
}
|
||||||
|
impl Game {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
config:GameConfig { },
|
||||||
|
|
||||||
|
status:GameStatus::Normal,
|
||||||
|
turn:0,
|
||||||
|
|
||||||
|
board:Board::new(),
|
||||||
|
pool:[Pool::default(); 2],
|
||||||
|
|
||||||
|
history:Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self)
|
||||||
|
{
|
||||||
|
*self = Self::new();
|
||||||
|
self.board.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_history(&mut self, history:&Vec<Play>) -> Result<(),()>
|
||||||
|
{
|
||||||
|
self.init();
|
||||||
|
for play in history {
|
||||||
|
self.process(play).ok();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_board(&mut self)
|
||||||
|
{
|
||||||
|
let mut player_moves = 0usize;
|
||||||
|
let current_player = (self.turn & 1) as u8;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Reset board meta data.
|
||||||
|
*/
|
||||||
|
self.status = GameStatus::Normal;
|
||||||
|
|
||||||
|
// Reset columns
|
||||||
|
for column in &mut self.board.columns {
|
||||||
|
column.extent = [0, 9];
|
||||||
|
column.militia = [false; 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset tiles
|
||||||
|
for tile in &mut self.board.tiles {
|
||||||
|
tile.check = false;
|
||||||
|
tile.threat = [false; 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset pieces
|
||||||
|
for piece in &mut self.board.pieces {
|
||||||
|
if let Some(piece) = piece {
|
||||||
|
piece.blocking = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Fill board meta data; count piece moves.
|
||||||
|
*/
|
||||||
|
for piece in &self.board.pieces.clone() {
|
||||||
|
if let Some(piece) = piece {
|
||||||
|
let moves = self.get_moves_data(&piece);
|
||||||
|
let alt_moves = self.get_alts_data(&piece);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Mark threats, checks, and extents.
|
||||||
|
*/
|
||||||
|
for info in &moves {
|
||||||
|
let hex = Hex::from_tile(info.play.to);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Mark tile as threatened.
|
||||||
|
*/
|
||||||
|
self.board.tiles[info.play.to as usize].threat[piece.player as usize] = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Apply checks.
|
||||||
|
*/
|
||||||
|
if info.check.is_check() {
|
||||||
|
if info.check.is_immediate() {
|
||||||
|
if let GameStatus::Check(data) = &mut self.status {
|
||||||
|
data.apply(info.check);
|
||||||
|
} else {
|
||||||
|
self.status = GameStatus::Check(info.check);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark checking piece's tile.
|
||||||
|
self.board.tiles[info.play.from as usize].check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark tile on checking path.
|
||||||
|
self.board.tiles[info.play.to as usize].check = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Apply column extent.
|
||||||
|
*/
|
||||||
|
self.board.columns[hex.x as usize].extent[piece.player as usize] = hex.y as u8;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Mark column as having unpromoted militia, if present.
|
||||||
|
*/
|
||||||
|
if piece.class == PIECE_MILITIA && !piece.promoted {
|
||||||
|
self.board.columns[hex.x as usize].militia[piece.player as usize] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Count moves for current player.
|
||||||
|
*/
|
||||||
|
if piece.player == current_player {
|
||||||
|
player_moves += moves.len() + alt_moves.len();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Apply blocking to piece on tile, if present.
|
||||||
|
*/
|
||||||
|
if info.blocking != 0 {
|
||||||
|
if let Some(piece_id) = self.board.tiles[info.play.to as usize].piece {
|
||||||
|
if let Some(piece) = &mut self.board.pieces[piece_id as usize] {
|
||||||
|
piece.blocking = info.blocking;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Determine if game is in checkmate.
|
||||||
|
*/
|
||||||
|
if player_moves == 0 {
|
||||||
|
self.status = GameStatus::Checkmate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self, play:&Play) -> Result<(),()>
|
||||||
|
{
|
||||||
|
let player = (self.turn & 1) as u8;
|
||||||
|
|
||||||
|
if self.play_is_valid(play) {
|
||||||
|
// Move piece on board.
|
||||||
|
if match play.source {
|
||||||
|
0 | 2 => {
|
||||||
|
if let Some(pid) = self.board.tiles[play.from as usize].piece {
|
||||||
|
if let Some(mut piece) = self.board.pieces[pid as usize] {
|
||||||
|
let mut swap = false;
|
||||||
|
|
||||||
|
if let Some(tid) = self.board.tiles[play.to as usize].piece {
|
||||||
|
if let Some(target) = &mut self.board.pieces[tid as usize] {
|
||||||
|
|
||||||
|
// Check for piece swap.
|
||||||
|
if piece.player == target.player {
|
||||||
|
swap = true;
|
||||||
|
target.tile = play.from;
|
||||||
|
|
||||||
|
// Check for target promotion.
|
||||||
|
let hex = Hex::from_tile(play.from);
|
||||||
|
if !target.promoted && target.has_promotion() && Hex::is_back(hex.x, hex.y, target.player) {
|
||||||
|
target.promoted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add captured piece to pool.
|
||||||
|
else {
|
||||||
|
self.pool[piece.player as usize][target.class as usize] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy piece if captured.
|
||||||
|
if !swap { self.board.pieces[tid as usize] = None; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set tile/piece associations.
|
||||||
|
if swap {
|
||||||
|
self.board.tiles[play.from as usize].piece = self.board.tiles[play.to as usize].piece;
|
||||||
|
} else {
|
||||||
|
self.board.tiles[play.from as usize].piece = None;
|
||||||
|
}
|
||||||
|
self.board.tiles[play.to as usize].piece = Some(pid);
|
||||||
|
piece.tile = play.to;
|
||||||
|
|
||||||
|
self.board.pieces[pid as usize] = Some(piece);
|
||||||
|
|
||||||
|
// Check for piece promotion.
|
||||||
|
let hex = Hex::from_tile(play.to);
|
||||||
|
if !piece.promoted && piece.has_promotion() && Hex::is_back(hex.x, hex.y, piece.player) {
|
||||||
|
if let Some(piece) = &mut self.board.pieces[pid as usize] {
|
||||||
|
piece.promoted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.turn += 1;
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place piece from pool.
|
||||||
|
1 => {
|
||||||
|
if self.pool[player as usize][play.from as usize] > 0 && self.board.tiles[play.to as usize].piece.is_none() {
|
||||||
|
self.pool[player as usize][play.from as usize] -= 1;
|
||||||
|
let piece = Piece::new(play.from, player);
|
||||||
|
self.board.set_piece(piece, play.to);
|
||||||
|
|
||||||
|
self.turn += 1;
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Player retired.
|
||||||
|
0xF => {
|
||||||
|
self.status = GameStatus::Resign;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => false,
|
||||||
|
} {
|
||||||
|
self.history.push(*play);
|
||||||
|
Ok(())
|
||||||
|
} else { Err(()) }
|
||||||
|
} else { Err(()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_is_valid(&self, play:&Play) -> bool
|
||||||
|
// Returns whether a play may be made by the current player.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
//println!("play_is_valid {} {} {}", play.source, play.from, play.to);
|
||||||
|
|
||||||
|
let mut valid = false;
|
||||||
|
let player = (self.turn & 1) as u8;
|
||||||
|
|
||||||
|
match play.source {
|
||||||
|
0 => {
|
||||||
|
if let Some(piece_id) = self.board.tiles[play.from as usize].piece {
|
||||||
|
if let Some(piece) = &self.board.pieces[piece_id as usize] {
|
||||||
|
//println!("piece {} {}", piece.class, piece.player);
|
||||||
|
|
||||||
|
if piece.player == player {
|
||||||
|
for p in self.get_moves(piece) {
|
||||||
|
if p.to == play.to {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
if (play.from as usize) < self.pool[player as usize].len() {
|
||||||
|
if self.pool[player as usize][play.from as usize] > 0 {
|
||||||
|
for p in self.get_drops(&Piece::new(play.from, player)) {
|
||||||
|
if p.to == play.to {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
if let Some(piece_id) = self.board.tiles[play.from as usize].piece {
|
||||||
|
if let Some(piece) = &self.board.pieces[piece_id as usize] {
|
||||||
|
if piece.player == player {
|
||||||
|
for p in self.get_alts(piece) {
|
||||||
|
if p.to == play.to {
|
||||||
|
valid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
0xF => { true }
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_moves_data(&self, piece:&Piece) -> Vec<PlayInfo>
|
||||||
|
{
|
||||||
|
let mut plays = Vec::<PlayInfo>::new();
|
||||||
|
|
||||||
|
let moves = piece.moves();
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Get permitted move directions.
|
||||||
|
*/
|
||||||
|
let mut directions = moves.direction;
|
||||||
|
let blocking_directions = piece.blocking | util::rotate6(piece.blocking);
|
||||||
|
if blocking_directions != 0 {
|
||||||
|
if piece.class == PIECE_HEART {
|
||||||
|
directions &= !blocking_directions;
|
||||||
|
} else {
|
||||||
|
directions &= blocking_directions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//println!("dir {:b}", directions);
|
||||||
|
//println!("blk {:b}", blocking_directions);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Handle movable directions.
|
||||||
|
*/
|
||||||
|
while directions != 0 {
|
||||||
|
let mask_original = util::lsb(directions);
|
||||||
|
let mut multiplier = 1;
|
||||||
|
|
||||||
|
let mut mask = mask_original;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Shift second tile jumps to standard direction masks.
|
||||||
|
*/
|
||||||
|
if (mask & 0xFFF) == 0 {
|
||||||
|
mask >>= 12;
|
||||||
|
multiplier = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
let direction = util::ffs(mask);
|
||||||
|
|
||||||
|
//println!(" - dir {}", direction);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Extract stride value:
|
||||||
|
** 00 - 1 Tile
|
||||||
|
** 01 - 2 Tiles
|
||||||
|
** 10 - Reserved
|
||||||
|
** 11 - Unlimited
|
||||||
|
*/
|
||||||
|
let stride_offset: u32 = direction * 2;
|
||||||
|
let stride = if direction < 12 {
|
||||||
|
match ((3 << stride_offset) & moves.stride) >> stride_offset {
|
||||||
|
0 => 1,
|
||||||
|
1 => 2,
|
||||||
|
_ => 8,
|
||||||
|
}
|
||||||
|
} else { 1 };
|
||||||
|
|
||||||
|
let (mut rx, mut ry) = DIRECTIONS[direction as usize];
|
||||||
|
rx *= multiplier;
|
||||||
|
ry *= multiplier;
|
||||||
|
|
||||||
|
let mut block_count = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Step along direction up to max stride.
|
||||||
|
*/
|
||||||
|
let mut current_hex = Hex::from_tile(piece.tile);
|
||||||
|
for stride_dist in 0..stride {
|
||||||
|
if Hex::is_valid(current_hex.x + rx, current_hex.y + ry) {
|
||||||
|
current_hex = Hex::from_hex(current_hex.x + rx, current_hex.y + ry);
|
||||||
|
|
||||||
|
let mut valid = block_count == 0;
|
||||||
|
let tile = self.board.tiles[current_hex.tile as usize];
|
||||||
|
|
||||||
|
let mut checkstate = CheckState::new();
|
||||||
|
|
||||||
|
/*
|
||||||
|
** If in check, move must break check.
|
||||||
|
** King may only move to non-threatened tiles.
|
||||||
|
*/
|
||||||
|
if piece.class == PIECE_HEART {
|
||||||
|
valid = valid && !tile.threat[(piece.player == 0) as usize];
|
||||||
|
} else {
|
||||||
|
valid = valid && match self.status {
|
||||||
|
GameStatus::Check(state) => {
|
||||||
|
state.count() == 1 && tile.check
|
||||||
|
}
|
||||||
|
GameStatus::Normal => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** If tile is occupied, piece must be opposing or swappable.
|
||||||
|
*/
|
||||||
|
if let Some(target_id) = tile.piece {
|
||||||
|
if let Some(target) = &self.board.pieces[target_id as usize] {
|
||||||
|
if target.player == piece.player {
|
||||||
|
/*
|
||||||
|
** Move is valid if piece can swap.
|
||||||
|
*/
|
||||||
|
valid = valid && stride_dist == 0 && self.can_swap(piece, mask_original, current_hex.tile);
|
||||||
|
block_count += 2;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
** Handle capturing of king.
|
||||||
|
*/
|
||||||
|
if target.class == PIECE_HEART {
|
||||||
|
if stride_dist == 0 {
|
||||||
|
checkstate = checkstate.direct();
|
||||||
|
} else {
|
||||||
|
checkstate = checkstate.stride();
|
||||||
|
}
|
||||||
|
|
||||||
|
match block_count {
|
||||||
|
0 => {
|
||||||
|
// Mark plays in direction as check.
|
||||||
|
for i in 1..stride_dist {
|
||||||
|
let index = plays.len() - i;
|
||||||
|
plays[index].check = checkstate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
// Mark last piece as blocking.
|
||||||
|
let index = plays.len() - 1;
|
||||||
|
plays[index].blocking = mask;
|
||||||
|
}
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
plays.push(PlayInfo {
|
||||||
|
play:Play { source:0, from:piece.tile, to:current_hex.tile, },
|
||||||
|
check:checkstate,
|
||||||
|
blocking:0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
directions &= !mask_original;
|
||||||
|
}
|
||||||
|
|
||||||
|
plays
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_moves(&self, piece:&Piece) -> Vec<Play>
|
||||||
|
{
|
||||||
|
let mut plays = Vec::new();
|
||||||
|
for info in self.get_moves_data(piece) {
|
||||||
|
plays.push(info.play);
|
||||||
|
}
|
||||||
|
plays
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_alts_data(&self, piece:&Piece) -> Vec<PlayInfo>
|
||||||
|
{
|
||||||
|
let mut plays = Vec::<PlayInfo>::new();
|
||||||
|
|
||||||
|
let piece_moves = piece.moves();
|
||||||
|
let subject_tiles = if piece.blocking == 0 {
|
||||||
|
(0..61u8).collect()
|
||||||
|
} else {
|
||||||
|
let mut tiles = Vec::new();
|
||||||
|
let mut directions = piece.blocking | util::rotate6(piece.blocking);
|
||||||
|
|
||||||
|
while directions != 0 {
|
||||||
|
let mask = util::lsb(directions);
|
||||||
|
let direction = util::ffs(mask);
|
||||||
|
|
||||||
|
let (rx, ry) = DIRECTIONS[direction as usize];
|
||||||
|
|
||||||
|
let mut current_hex = Hex::from_tile(piece.tile);
|
||||||
|
for _ in 0..8 {
|
||||||
|
if Hex::is_valid(current_hex.x + rx, current_hex.y + ry) {
|
||||||
|
current_hex = Hex::from_hex(current_hex.x + rx, current_hex.y + ry);
|
||||||
|
|
||||||
|
if self.board.tiles[current_hex.tile as usize].piece.is_none() {
|
||||||
|
tiles.push(current_hex.tile);
|
||||||
|
} else { break }
|
||||||
|
} else { break }
|
||||||
|
}
|
||||||
|
|
||||||
|
directions &= !mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if let Some(alt_mode) = piece_moves.alt {
|
||||||
|
match alt_mode {
|
||||||
|
// Knight
|
||||||
|
1 => {
|
||||||
|
for tile_id in subject_tiles {
|
||||||
|
if self.can_drop(piece, tile_id, flags::IGNORE_CHECK) {
|
||||||
|
plays.push(PlayInfo {
|
||||||
|
play: Play::from_alt(piece.tile, tile_id),
|
||||||
|
check: CheckState::new(),
|
||||||
|
blocking: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caslte
|
||||||
|
2 => {
|
||||||
|
for tile_id in subject_tiles {
|
||||||
|
let hex = Hex::from_tile(piece.tile);
|
||||||
|
let tile_hex = Hex::from_tile(tile_id);
|
||||||
|
|
||||||
|
let in_rear_cone = if piece.player == 0 {
|
||||||
|
if tile_hex.x >= hex.x {
|
||||||
|
tile_hex.y <= hex.y
|
||||||
|
} else {
|
||||||
|
tile_hex.y <= hex.y - (hex.x - tile_hex.x)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if tile_hex.x >= hex.x {
|
||||||
|
tile_hex.y >= hex.y + (tile_hex.x - hex.x)
|
||||||
|
} else {
|
||||||
|
tile_hex.y >= hex.y
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if in_rear_cone && self.can_drop(piece, tile_id, flags::IGNORE_CHECK | flags::IGNORE_EXTENT) {
|
||||||
|
plays.push(PlayInfo {
|
||||||
|
play: Play::from_alt(piece.tile, tile_id),
|
||||||
|
check: CheckState::new(),
|
||||||
|
blocking: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plays
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_alts(&self, piece:&Piece) -> Vec<Play>
|
||||||
|
{
|
||||||
|
let mut plays = Vec::new();
|
||||||
|
for info in self.get_alts_data(piece) {
|
||||||
|
plays.push(info.play);
|
||||||
|
}
|
||||||
|
plays
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_drops(&self, piece:&Piece) -> Vec<Play>
|
||||||
|
{
|
||||||
|
let mut piece = piece.clone();
|
||||||
|
let mut moves = Vec::new();
|
||||||
|
|
||||||
|
for tile_id in 0..self.board.tiles.len() {
|
||||||
|
let tile = &self.board.tiles[tile_id];
|
||||||
|
|
||||||
|
if tile.piece.is_none() {
|
||||||
|
piece.tile = tile_id as u8;
|
||||||
|
|
||||||
|
if self.can_drop(&piece, tile_id as u8, flags::NONE) {
|
||||||
|
moves.push(Play::from_drop(piece.class, tile_id as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
moves
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_swap(&self, piece:&Piece, mask:u32, tile_id:u8) -> bool
|
||||||
|
{
|
||||||
|
let mut valid = false;
|
||||||
|
|
||||||
|
if let Some(target_id) = self.board.tiles[tile_id as usize].piece {
|
||||||
|
let target_id = target_id as usize;
|
||||||
|
if let Some(target) = self.board.pieces[target_id as usize] {
|
||||||
|
valid = true;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Target must be same color.
|
||||||
|
*/
|
||||||
|
valid = valid && piece.player == target.player;
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Target must not be same piece.
|
||||||
|
*/
|
||||||
|
valid = valid && !(piece.class == target.class && piece.promoted == target.promoted);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** King may not swap onto a contested tile.
|
||||||
|
*/
|
||||||
|
if target.class == PIECE_HEART
|
||||||
|
&& self.board.tiles[piece.tile as usize].threat[(piece.player == 0) as usize]
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Target must have movement in reverse direction.
|
||||||
|
*/
|
||||||
|
let moves = target.moves().rotate().direction;
|
||||||
|
valid = valid && (mask & moves) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_drop(&self, piece:&Piece, tile_id:u8, flags:u32) -> bool
|
||||||
|
{
|
||||||
|
let hex = Hex::from_tile(tile_id);
|
||||||
|
let tile = &self.board.tiles[tile_id as usize];
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Tile must not be occupied.
|
||||||
|
*/
|
||||||
|
let mut valid = tile.piece.is_none();
|
||||||
|
|
||||||
|
/*
|
||||||
|
** If in check, a piece may only be dropped if check
|
||||||
|
** is due to a multi-tile move from a single piece.
|
||||||
|
*/
|
||||||
|
valid = valid && match self.status {
|
||||||
|
GameStatus::Check(state) => {
|
||||||
|
!state.is_direct() && state.count() == 1 && tile.check
|
||||||
|
}
|
||||||
|
GameStatus::Normal => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
** A piece may not be dropped behind the first opposing piece in a column.
|
||||||
|
*/
|
||||||
|
if (flags & flags::IGNORE_EXTENT as u32) == 0 {
|
||||||
|
valid = valid && if piece.player == 0 {
|
||||||
|
hex.y > self.board.columns[hex.x as usize].extent[1] as i8
|
||||||
|
} else {
|
||||||
|
hex.y < self.board.columns[hex.x as usize].extent[0] as i8
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Militia may not be dropped in a column with another militia present.
|
||||||
|
*/
|
||||||
|
if (flags & flags::IGNORE_STACKING as u32) == 0 {
|
||||||
|
if piece.class == PIECE_MILITIA
|
||||||
|
&& self.board.columns[hex.x as usize].militia[piece.player as usize]
|
||||||
|
{
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** A piece must be able to move from its drop position.
|
||||||
|
** A piece may not be dropped onto a position that puts the opposing king in check.
|
||||||
|
*/
|
||||||
|
let piece_moves = self.get_moves(piece);
|
||||||
|
if piece_moves.len() > 0 {
|
||||||
|
if (flags & flags::IGNORE_CHECK as u32) == 0 {
|
||||||
|
for mv in piece_moves {
|
||||||
|
if let Some(target_id) = self.board.tiles[mv.to as usize].piece {
|
||||||
|
if let Some(target) = self.board.pieces[target_id as usize] {
|
||||||
|
if target.class == PIECE_HEART && target.player != piece.player {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
valid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_complete(&self) -> bool
|
||||||
|
{
|
||||||
|
match self.status {
|
||||||
|
GameStatus::Checkmate | GameStatus::Resign => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,19 +1,59 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
consts::*,
|
constant::*,
|
||||||
util::bit,
|
util::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct MoveSet {
|
pub struct MoveSet {
|
||||||
pub direction:u32,
|
pub direction:u32,
|
||||||
pub stride:u32,
|
pub stride:u32,
|
||||||
|
pub alt:Option<u32>,
|
||||||
}
|
}
|
||||||
impl MoveSet {
|
impl MoveSet {
|
||||||
pub fn rotate(&self) -> Self
|
pub const fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
direction:0,
|
direction:0,
|
||||||
stride:0,
|
stride:0,
|
||||||
|
alt:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn add(mut self, bits:u32) -> Self
|
||||||
|
{
|
||||||
|
self.direction |= bits;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn add_stride(mut self, bits:u32, mode:u32) -> Self
|
||||||
|
{
|
||||||
|
self.direction |= bits;
|
||||||
|
|
||||||
|
let mut bits_set = bits;
|
||||||
|
while bits_set != 0 {
|
||||||
|
let mask = lsb(bits_set);
|
||||||
|
let index = ffs(mask);
|
||||||
|
|
||||||
|
self.stride |= (mode & 3) << (index * 2);
|
||||||
|
|
||||||
|
bits_set &= !mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn add_alt(mut self, mode:u32) -> Self
|
||||||
|
{
|
||||||
|
self.alt = Some(mode);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate(&self) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
direction:rotate6(self.direction),
|
||||||
|
stride:rotate12(self.stride),
|
||||||
|
alt:None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -28,119 +68,113 @@ pub struct PieceClass {
|
|||||||
pub const PIECES :[PieceClass; PIECES_COUNT] = [
|
pub const PIECES :[PieceClass; PIECES_COUNT] = [
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Militia",
|
name: "Militia",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:bit(0) | bit(1) | bit(5),
|
.add(bit(0) | bit(1) | bit(5)),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5)),
|
||||||
pmoves: MoveSet{
|
|
||||||
direction:bit(0) | bit(1) | bit(2) | bit(4) | bit(5),
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Lance",
|
name: "Lance",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:1,
|
.add(bit(1) | bit(5))
|
||||||
stride:0,
|
.add_stride(bit(0), 3),
|
||||||
},
|
pmoves: MoveSet::new()
|
||||||
pmoves: MoveSet {
|
.add(bit(0) | bit(3))
|
||||||
direction:1,
|
.add_stride(bit(1) | bit(2) | bit(4) | bit(5), 1),
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Knight",
|
name: "Knight",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:bit(3) | bit(6) | bit(11) | bit(13) | bit(17),
|
.add(bit(6) | bit(8) | bit(9) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17)),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add(bit(6) | bit(8) | bit(9) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17))
|
||||||
pmoves: MoveSet{
|
.add_alt(1),
|
||||||
direction:0,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Tower",
|
name: "Tower",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:1,
|
.add(bit(0) | bit(1) | bit(3) | bit(5) | bit(6) | bit(11)),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5) | bit(6) | bit(8) | bit(9) | bit(11)),
|
||||||
pmoves: MoveSet {
|
|
||||||
direction:1,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Castle",
|
name: "Castle",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:1,
|
.add(bit(0) | bit(1) | bit(2) | bit(4) | bit(5) | bit(7) | bit(10)),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5) | bit(7) | bit(10))
|
||||||
pmoves: MoveSet {
|
.add_alt(2),
|
||||||
direction:1,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Dragon",
|
name: "Dragon",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:1,
|
.add_stride(bit(6) | bit(7) | bit(8) | bit(9) | bit(10) | bit(11), 3),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add_stride(bit(6) | bit(7) | bit(8) | bit(9) | bit(10) | bit(11), 3)
|
||||||
pmoves: MoveSet {
|
.add(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5)),
|
||||||
direction:1,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Behemoth",
|
name: "Behemoth",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:1,
|
.add_stride(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5), 3),
|
||||||
stride:0,
|
pmoves: MoveSet::new()
|
||||||
},
|
.add_stride(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5), 3)
|
||||||
pmoves: MoveSet {
|
.add(bit(12) | bit(13) | bit(14) | bit(15) | bit(16) | bit(17)),
|
||||||
direction:1,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Source",
|
name: "Heart",
|
||||||
moves: MoveSet {
|
moves: MoveSet::new()
|
||||||
direction:0,
|
.add(bit(0) | bit(1) | bit(2) | bit(3) | bit(4) | bit(5) | bit(7) | bit(10)),
|
||||||
stride:0,
|
pmoves: MoveSet::new(),
|
||||||
},
|
|
||||||
pmoves: MoveSet {
|
|
||||||
direction:0,
|
|
||||||
stride:0,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Piece {
|
pub struct Piece {
|
||||||
pub class:u8,
|
pub class:u8,
|
||||||
pub promoted:bool,
|
|
||||||
pub player:u8,
|
pub player:u8,
|
||||||
|
pub promoted:bool,
|
||||||
pub tile:u8,
|
pub tile:u8,
|
||||||
|
pub blocking:u32,
|
||||||
}
|
}
|
||||||
impl Piece {
|
impl Piece {
|
||||||
pub fn new(class:u8, player:u8) -> Self
|
pub fn new(class:u8, player:u8) -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
class,
|
class,
|
||||||
promoted:false,
|
|
||||||
player,
|
player,
|
||||||
|
promoted:false,
|
||||||
tile:0,
|
tile:0,
|
||||||
|
blocking:0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_at(class:u8, player:u8, tile:u8) -> Self
|
pub fn at(mut self, tile:u8) -> Self
|
||||||
{
|
{
|
||||||
Self {
|
self.tile = tile;
|
||||||
class,
|
self
|
||||||
promoted:false,
|
}
|
||||||
player,
|
|
||||||
tile,
|
pub fn promote(mut self, promoted:bool) -> Self
|
||||||
|
{
|
||||||
|
self.promoted = promoted;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn moves(&self) -> MoveSet
|
||||||
|
{
|
||||||
|
let class = &crate::piece::PIECES[self.class as usize];
|
||||||
|
|
||||||
|
let moves = if self.promoted {
|
||||||
|
class.pmoves
|
||||||
|
} else {
|
||||||
|
class.moves
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.player == 0 {
|
||||||
|
moves
|
||||||
|
} else {
|
||||||
|
moves.rotate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,32 @@
|
|||||||
pub const fn bit(v:u32) -> u32 { 1u32 << v }
|
pub const fn bit(v:u32) -> u32 { 1u32 << v }
|
||||||
pub const fn mask(b:u32, s:u32) -> u32 { ((1u32 << b) - 1) << s }
|
pub const fn mask(b:u32, s:u32) -> u32 { ((1u32 << b) - 1) << s }
|
||||||
|
|
||||||
|
pub const fn lsb(x:u32) -> u32 { x & x.wrapping_neg() }
|
||||||
|
pub const fn ffs(x:u32) -> u32 { 31 - x.leading_zeros() }
|
||||||
|
|
||||||
|
pub const fn rotate6(x:u32) -> u32
|
||||||
|
{
|
||||||
|
const R1 :u32 = 0x0000_003F;
|
||||||
|
const R2 :u32 = 0x0000_0FC0;
|
||||||
|
const R3 :u32 = 0x0003_F000;
|
||||||
|
|
||||||
|
let a = (x & R1) << 3;
|
||||||
|
let b = (x & R2) << 3;
|
||||||
|
let c = (x & R3) << 3;
|
||||||
|
|
||||||
|
(a & R1) | ((a >> 6) & R1)
|
||||||
|
| (b & R2) | ((b >> 6) & R2)
|
||||||
|
| (c & R3) | ((c >> 6) & R3)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn rotate12(x:u32) -> u32
|
||||||
|
{
|
||||||
|
const R1 :u32 = 0x0000_0FFF;
|
||||||
|
const R2 :u32 = 0x00FF_F000;
|
||||||
|
|
||||||
|
let a = (x & R1) << 3;
|
||||||
|
let b = (x & R2) << 3;
|
||||||
|
|
||||||
|
(a & R1) | ((a >> 12) & R1)
|
||||||
|
| (b & R2) | ((b >> 12) & R2)
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
const ROWS :[u8; 10] = [ 0, 5, 11, 18, 26, 35, 43, 50, 56, 61 ];
|
const ROWS :[i8; 10] = [ 0, 5, 11, 18, 26, 35, 43, 50, 56, 61 ];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Hex {
|
pub struct Hex {
|
||||||
pub x:u8,
|
pub x:i8,
|
||||||
pub y:u8,
|
pub y:i8,
|
||||||
pub tile:u8,
|
pub tile:u8,
|
||||||
}
|
}
|
||||||
impl Hex {
|
impl Hex {
|
||||||
@ -16,7 +16,7 @@ impl Hex {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_hex(x:u8, y:u8) -> Self
|
pub fn from_hex(x:i8, y:i8) -> Self
|
||||||
{
|
{
|
||||||
let x = x as i32;
|
let x = x as i32;
|
||||||
let y = y as i32;
|
let y = y as i32;
|
||||||
@ -24,8 +24,8 @@ impl Hex {
|
|||||||
let b = (x > 4) as i32 * ((x - 4) + ((x - 5) * (x - 4)));
|
let b = (x > 4) as i32 * ((x - 4) + ((x - 5) * (x - 4)));
|
||||||
let tile = a - b + y;
|
let tile = a - b + y;
|
||||||
Self {
|
Self {
|
||||||
x:x as u8,
|
x:x as i8,
|
||||||
y:y as u8,
|
y:y as i8,
|
||||||
tile:tile as u8,
|
tile:tile as u8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,16 +33,16 @@ impl Hex {
|
|||||||
pub fn from_tile(tile:u8) -> Self
|
pub fn from_tile(tile:u8) -> Self
|
||||||
{
|
{
|
||||||
let mut x = 0i32;
|
let mut x = 0i32;
|
||||||
while tile >= ROWS[x as usize + 1] { x += 1; }
|
while tile as i8 >= ROWS[x as usize + 1] { x += 1; }
|
||||||
let y = tile - ROWS[x as usize] + ((x > 4) as i32 * (x - 4)) as u8;
|
let y = tile as i8 - ROWS[x as usize] + ((x > 4) as i32 * (x - 4)) as i8;
|
||||||
Self {
|
Self {
|
||||||
x:x as u8,
|
x:x as i8,
|
||||||
y,
|
y,
|
||||||
tile,
|
tile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*pub fn is_valid(x:i8, y:i8) -> bool
|
pub fn is_valid(x:i8, y:i8) -> bool
|
||||||
{
|
{
|
||||||
const COLUMNS :[(i8, i8); 9] = [
|
const COLUMNS :[(i8, i8); 9] = [
|
||||||
(0, 4),
|
(0, 4),
|
||||||
@ -61,11 +61,11 @@ impl Hex {
|
|||||||
let (min, max) = COLUMNS[x as usize];
|
let (min, max) = COLUMNS[x as usize];
|
||||||
y >= min && y <= max
|
y >= min && y <= max
|
||||||
} else { false }
|
} else { false }
|
||||||
}*/
|
}
|
||||||
|
|
||||||
pub fn is_back(x:u8, y:u8, player:u8) -> bool
|
pub fn is_back(x:i8, y:i8, player:u8) -> bool
|
||||||
{
|
{
|
||||||
const COLUMNS :[[u8; 2]; 9] = [
|
const COLUMNS :[[i8; 2]; 9] = [
|
||||||
[0, 4],
|
[0, 4],
|
||||||
[0, 5],
|
[0, 5],
|
||||||
[0, 6],
|
[0, 6],
|
||||||
|
@ -217,6 +217,12 @@ impl App {
|
|||||||
)).await.ok();
|
)).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRPacketData::TestResult(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_TEST_RESULT, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
_ => { }
|
_ => { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,6 +245,8 @@ impl App {
|
|||||||
session.p_dusk.connections.len() > 0,
|
session.p_dusk.connections.len() > 0,
|
||||||
session.connections.len() as u32,
|
session.connections.len() as u32,
|
||||||
),
|
),
|
||||||
|
test: false,
|
||||||
|
expected: false,
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
|
|
||||||
println!("Serving: {}", request.uri().path());
|
println!("Serving: {}", request.uri().path());
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Get client language.
|
||||||
|
*/
|
||||||
let mut language_code = 0;
|
let mut language_code = 0;
|
||||||
if let Some(languages) = request.headers().get(ACCEPT_LANGUAGE) {
|
if let Some(languages) = request.headers().get(ACCEPT_LANGUAGE) {
|
||||||
if let Ok(languages) = languages.to_str() {
|
if let Ok(languages) = languages.to_str() {
|
||||||
@ -55,6 +58,9 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Upgrade to websocket, if reqeusted.
|
||||||
|
*/
|
||||||
if hyper_tungstenite::is_upgrade_request(&request) {
|
if hyper_tungstenite::is_upgrade_request(&request) {
|
||||||
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
@ -71,7 +77,12 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
.body(Full::new(Bytes::new()))
|
.body(Full::new(Bytes::new()))
|
||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Otherwise, serve cached assets.
|
||||||
|
*/
|
||||||
|
else {
|
||||||
match args.cache.fetch(request.uri().path()) {
|
match args.cache.fetch(request.uri().path()) {
|
||||||
Some(data) => {
|
Some(data) => {
|
||||||
let mut output = data.data;
|
let mut output = data.data;
|
||||||
@ -137,16 +148,20 @@ async fn main()
|
|||||||
{
|
{
|
||||||
println!("Server Version: {}", config::VERSION);
|
println!("Server Version: {}", config::VERSION);
|
||||||
|
|
||||||
// Initialize application data.
|
/*
|
||||||
let app;
|
** Initialize application data.
|
||||||
if let Ok(result) = App::init() {
|
*/
|
||||||
app = result;
|
let app = if let Ok(result) = App::init() {
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
println!("fatal: failed to initialize server.");
|
println!("fatal: failed to initialize server.");
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Initialize central bus and data serivce.
|
|
||||||
|
/*
|
||||||
|
** Initialize central bus and data serivce.
|
||||||
|
*/
|
||||||
let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
|
let b_main :Bus<protocol::QRPacket> = Bus::new_as(bus::Mode::Transmitter);
|
||||||
match b_main.connect() {
|
match b_main.connect() {
|
||||||
Ok(bus) => {
|
Ok(bus) => {
|
||||||
@ -160,7 +175,10 @@ async fn main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load image assets
|
|
||||||
|
/*
|
||||||
|
** Load image assets.
|
||||||
|
*/
|
||||||
let mut js_asset_data = String::from("const GAME_ASSET = { Image: {");
|
let mut js_asset_data = String::from("const GAME_ASSET = { Image: {");
|
||||||
let asset_path = Path::new("www/asset/");
|
let asset_path = Path::new("www/asset/");
|
||||||
for name in [
|
for name in [
|
||||||
@ -182,9 +200,12 @@ async fn main()
|
|||||||
}
|
}
|
||||||
js_asset_data += "} };";
|
js_asset_data += "} };";
|
||||||
|
|
||||||
// Initialize HTTPS service.
|
|
||||||
match b_main.connect() {
|
match b_main.connect() {
|
||||||
Ok(bus) => {
|
Ok(bus) => {
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Cache source files.
|
||||||
|
*/
|
||||||
let cache = WebCache::new();
|
let cache = WebCache::new();
|
||||||
cache.cache("text/html", "/.html", &[
|
cache.cache("text/html", "/.html", &[
|
||||||
WebCache::file("www/.html"),
|
WebCache::file("www/.html"),
|
||||||
@ -242,6 +263,10 @@ async fn main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Initialize network services.
|
||||||
|
*/
|
||||||
let mut tcp_server = TcpServer::new();
|
let mut tcp_server = TcpServer::new();
|
||||||
match tcp_server.bind("127.0.0.1:38611").await {
|
match tcp_server.bind("127.0.0.1:38611").await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
@ -566,6 +566,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
cid,
|
cid,
|
||||||
QRPacketData::GameMessage(PacketGameMessage {
|
QRPacketData::GameMessage(PacketGameMessage {
|
||||||
data: GameMessageData::Resign,
|
data: GameMessageData::Resign,
|
||||||
|
test: false,
|
||||||
|
expected: false,
|
||||||
}),
|
}),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -628,6 +630,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
println!("Request: Game Message");
|
println!("Request: Game Message");
|
||||||
|
|
||||||
let mut packets = Vec::<QRPacket>::new();
|
let mut packets = Vec::<QRPacket>::new();
|
||||||
|
let mut response = QRPacketData::None;
|
||||||
|
|
||||||
if let Some(sid) = session_id {
|
if let Some(sid) = session_id {
|
||||||
if let Some(session) = app.sessions.get_mut(&sid) {
|
if let Some(session) = app.sessions.get_mut(&sid) {
|
||||||
@ -637,7 +640,37 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
| GameMessageData::PlayDrop(turn, from, to)
|
| GameMessageData::PlayDrop(turn, from, to)
|
||||||
| GameMessageData::PlayAlt(turn, from, to)
|
| GameMessageData::PlayAlt(turn, from, to)
|
||||||
=> {
|
=> {
|
||||||
if !session.game.is_complete() {
|
if request.test {
|
||||||
|
let play = Play {
|
||||||
|
source: match request.data {
|
||||||
|
GameMessageData::PlayMove(..) => 0,
|
||||||
|
GameMessageData::PlayDrop(..) => 1,
|
||||||
|
GameMessageData::PlayAlt(..) => 2,
|
||||||
|
_ => 0,
|
||||||
|
},
|
||||||
|
from, to,
|
||||||
|
};
|
||||||
|
|
||||||
|
let text = format!("PLAY {} {} {}", play.source, play.from, play.to);
|
||||||
|
let result = if session.game.play_is_valid(&play) {
|
||||||
|
response = QRPacketData::TestResult(PacketTestResult::new(
|
||||||
|
request.expected,
|
||||||
|
&text,
|
||||||
|
));
|
||||||
|
request.expected
|
||||||
|
} else {
|
||||||
|
response = QRPacketData::TestResult(PacketTestResult::new(
|
||||||
|
!request.expected,
|
||||||
|
&text,
|
||||||
|
));
|
||||||
|
!request.expected
|
||||||
|
};
|
||||||
|
if result {
|
||||||
|
println!("OK {} {}", request.expected, text);
|
||||||
|
} else {
|
||||||
|
println!("NO {} {}", request.expected, text);
|
||||||
|
}
|
||||||
|
} else if !session.game.is_complete() {
|
||||||
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0)
|
||||||
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
|| (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) {
|
||||||
if turn == session.game.turn {
|
if turn == session.game.turn {
|
||||||
@ -780,7 +813,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Updates already sent; nothing to do here.
|
// Updates already sent; nothing to do here.
|
||||||
Some(QRPacket::new(qr.id, QRPacketData::None))
|
Some(QRPacket::new(qr.id, response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ pub const CODE_USER_LIST :u16 = 0x0100;
|
|||||||
//pub const CODE_USER_AWAIT_GET :u16 = 0x0110;
|
//pub const CODE_USER_AWAIT_GET :u16 = 0x0110;
|
||||||
//pub const CODE_USER_AWAIT_SET :u16 = 0x0111;
|
//pub const CODE_USER_AWAIT_SET :u16 = 0x0111;
|
||||||
|
|
||||||
|
pub const CODE_TEST_RESULT :u16 = 0xFFFF;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Game Messages
|
** Game Messages
|
||||||
|
@ -51,6 +51,8 @@ pub enum QRPacketData {
|
|||||||
|
|
||||||
QChallengeList,
|
QChallengeList,
|
||||||
RChallengeList(PacketChallengeListResponse),
|
RChallengeList(PacketChallengeListResponse),
|
||||||
|
|
||||||
|
TestResult(PacketTestResult),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -27,18 +27,26 @@ pub enum GameMessageData {
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PacketGameMessage {
|
pub struct PacketGameMessage {
|
||||||
pub data:GameMessageData,
|
pub data:GameMessageData,
|
||||||
|
pub test:bool,
|
||||||
|
pub expected:bool,
|
||||||
}
|
}
|
||||||
impl PacketGameMessage {
|
impl PacketGameMessage {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
data:GameMessageData::Error,
|
data:GameMessageData::Error,
|
||||||
|
test:false,
|
||||||
|
expected:false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with(data:GameMessageData) -> Self
|
pub fn with(data:GameMessageData) -> Self
|
||||||
{
|
{
|
||||||
Self { data }
|
Self {
|
||||||
|
data,
|
||||||
|
test:false,
|
||||||
|
expected:false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Packet for PacketGameMessage {
|
impl Packet for PacketGameMessage {
|
||||||
@ -79,15 +87,20 @@ impl Packet for PacketGameMessage {
|
|||||||
),
|
),
|
||||||
|
|
||||||
_ => GameMessageData::Error,
|
_ => GameMessageData::Error,
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
Ok(result)
|
result.test = (data & (1<<63)) != 0;
|
||||||
|
result.expected = (data & (1<<62)) != 0;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode(&self) -> Vec<u8>
|
fn encode(&self) -> Vec<u8>
|
||||||
{
|
{
|
||||||
pack_u64(match self.data {
|
let mut output = match self.data {
|
||||||
GameMessageData::PlayMove(turn, from, to) => {
|
GameMessageData::PlayMove(turn, from, to) => {
|
||||||
GMSG_PLAY_MOVE as u64
|
GMSG_PLAY_MOVE as u64
|
||||||
| ((turn as u64) << 8)
|
| ((turn as u64) << 8)
|
||||||
@ -129,6 +142,11 @@ impl Packet for PacketGameMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_ => { 0 }
|
_ => { 0 }
|
||||||
})
|
};
|
||||||
|
|
||||||
|
output |= (self.test as u64) << 63;
|
||||||
|
output |= (self.expected as u64) << 62;
|
||||||
|
|
||||||
|
pack_u64(output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ mod challenge_list; pub use challenge_list::*;
|
|||||||
|
|
||||||
mod user_list; pub use user_list::*;
|
mod user_list; pub use user_list::*;
|
||||||
|
|
||||||
|
mod test_result; pub use test_result::*;
|
||||||
|
|
||||||
mod prelude {
|
mod prelude {
|
||||||
pub trait Packet {
|
pub trait Packet {
|
||||||
type Data;
|
type Data;
|
||||||
|
28
server/src/protocol/packet/test_result.rs
Normal file
28
server/src/protocol/packet/test_result.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use crate::util::pack::{pack_u8, pack_u16};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketTestResult {
|
||||||
|
pub result:bool,
|
||||||
|
pub text:String,
|
||||||
|
}
|
||||||
|
impl PacketTestResult {
|
||||||
|
pub fn new(result:bool, text:&str) -> Self
|
||||||
|
{
|
||||||
|
Self { result, text:text.to_string() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketTestResult {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
let bytes = self.text.as_bytes().to_vec();
|
||||||
|
[
|
||||||
|
pack_u8(self.result as u8),
|
||||||
|
pack_u16(bytes.len() as u16),
|
||||||
|
bytes,
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
}
|
@ -54,6 +54,8 @@ const OpCode = {
|
|||||||
ChallengeList :0x0062,
|
ChallengeList :0x0062,
|
||||||
|
|
||||||
UserList :0x0100,
|
UserList :0x0100,
|
||||||
|
|
||||||
|
TestResult :0xFFFF,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GameState = {
|
const GameState = {
|
||||||
|
@ -419,6 +419,56 @@ GAME.Game = class {
|
|||||||
this.update_board();
|
this.update_board();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
play_is_valid(play) {
|
||||||
|
let player = this.turn & 1;
|
||||||
|
|
||||||
|
switch(play.source) {
|
||||||
|
case 0: {
|
||||||
|
let piece_id = this.board.tiles[play.from].piece;
|
||||||
|
if(piece_id !== null) {
|
||||||
|
let piece = this.board.pieces[piece_id];
|
||||||
|
if(piece.player == player) {
|
||||||
|
let moves = this.movement_tiles(piece, play.from);
|
||||||
|
for(let move of moves) {
|
||||||
|
if(move.tile == play.to && move.valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 1: {
|
||||||
|
if(play.from >= 0 && play.from <= 6) {
|
||||||
|
if(this.pools[player].pieces[play.from] > 0) {
|
||||||
|
let moves = this.placement_tiles(play.from, player);
|
||||||
|
for(let move of moves) {
|
||||||
|
if(move.tile == play.to && move.valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case 2: {
|
||||||
|
let piece_id = this.board.tiles[play.from].piece;
|
||||||
|
if(piece_id !== null) {
|
||||||
|
let piece = this.board.pieces[piece_id];
|
||||||
|
if(piece.player == player) {
|
||||||
|
let moves = this.movement_tiles_alt(piece);
|
||||||
|
for(let move of moves) {
|
||||||
|
if(move.tile == play.to && move.valid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
movement_tiles(piece, tile) {
|
movement_tiles(piece, tile) {
|
||||||
let tiles = [ ];
|
let tiles = [ ];
|
||||||
let moves = piece.moves();
|
let moves = piece.moves();
|
||||||
@ -449,7 +499,7 @@ GAME.Game = class {
|
|||||||
let max_dist = 1;
|
let max_dist = 1;
|
||||||
switch(stride) {
|
switch(stride) {
|
||||||
case 1: max_dist = 2; break;
|
case 1: max_dist = 2; break;
|
||||||
case 3: max_dist = 9; break;
|
case 3: max_dist = 8; break;
|
||||||
}
|
}
|
||||||
for(let dist = 1; dist <= max_dist; ++dist) {
|
for(let dist = 1; dist <= max_dist; ++dist) {
|
||||||
move_hex.add(direction);
|
move_hex.add(direction);
|
||||||
@ -735,9 +785,9 @@ GAME.Game = class {
|
|||||||
// Check off-sides.
|
// Check off-sides.
|
||||||
if(params.extent !== false) {
|
if(params.extent !== false) {
|
||||||
if(piece.player == 0) {
|
if(piece.player == 0) {
|
||||||
position_valid = position_valid && (hex.y <= this.board.columns[hex.x].extent[+(!piece.player)]);
|
position_valid = position_valid && (hex.y <= this.board.columns[hex.x].extent[1]);
|
||||||
} else {
|
} else {
|
||||||
position_valid = position_valid && (hex.y >= this.board.columns[hex.x].extent[+(!piece.player)]);
|
position_valid = position_valid && (hex.y >= this.board.columns[hex.x].extent[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,6 +214,7 @@ const INTERFACE = {
|
|||||||
click(event) {
|
click(event) {
|
||||||
console.log("CLICK");
|
console.log("CLICK");
|
||||||
switch(event.button) {
|
switch(event.button) {
|
||||||
|
|
||||||
// Main button
|
// Main button
|
||||||
case 0: {
|
case 0: {
|
||||||
console.log("A");
|
console.log("A");
|
||||||
@ -1829,6 +1830,51 @@ const INTERFACE = {
|
|||||||
setTimeout(INTERFACE.reaction_generate, 50);
|
setTimeout(INTERFACE.reaction_generate, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
run_test(source=0, from=0, to=0, all=true)
|
||||||
|
{
|
||||||
|
let next_source = source;
|
||||||
|
let next_from = from;
|
||||||
|
let next_to = to + 1;
|
||||||
|
|
||||||
|
if(next_to == 61) {
|
||||||
|
next_to = 0;
|
||||||
|
next_from += 1;
|
||||||
|
}
|
||||||
|
if(next_from == 61 || (next_source == 1 && next_from == 7)) {
|
||||||
|
next_from = 0;
|
||||||
|
next_source += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg = GAME_DATA.turn | (from << 16) | (to << 22);
|
||||||
|
|
||||||
|
let high = msg >> 24;
|
||||||
|
let low = (msg << 8) & 0xFFFF_FFFF;
|
||||||
|
|
||||||
|
switch(source) {
|
||||||
|
case 0: low |= GameMessage.Move; break;
|
||||||
|
case 1: low |= GameMessage.Drop; break;
|
||||||
|
case 2: low |= GameMessage.Alt; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark test and expected value
|
||||||
|
let expect = GAME_DATA.play_is_valid(new GAME.Play(source, from, to));
|
||||||
|
|
||||||
|
high |= 1 << 31;
|
||||||
|
high |= (+expect) << 30;
|
||||||
|
|
||||||
|
console.log("Test Expect: " + expect);
|
||||||
|
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.GameMessage),
|
||||||
|
PACK.u32(high),
|
||||||
|
PACK.u32(low),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if(all && next_source < 3) {
|
||||||
|
setTimeout(INTERFACE.run_test, 100, next_source, next_from, next_to);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -470,6 +470,20 @@ function MESSAGE(event) {
|
|||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case OpCode.TestResult: {
|
||||||
|
result = UNPACK.u8(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
let test_result = result.data;
|
||||||
|
|
||||||
|
result = UNPACK.string(bytes, index, UNPACK.u16);
|
||||||
|
index = result.index;
|
||||||
|
let text = result.data;
|
||||||
|
|
||||||
|
if(test_result == 0) {
|
||||||
|
console.log("BD " + text);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log("RECV Undefined " + code);
|
console.log("RECV Undefined " + code);
|
||||||
return;
|
return;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user