Add session persistence and check handling.

This commit is contained in:
yukirij 2024-08-16 14:25:35 -07:00
parent adf3cdef10
commit 225cb34bab
9 changed files with 381 additions and 153 deletions

View File

@ -51,6 +51,10 @@ impl Board {
}
pub fn init(&mut self) {
self.tiles = [Tile::new(); 61];
self.columns = [Column::new(); 9];
self.pieces = [None; 38];
let layout = [
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(0, 1)),
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(1, 1)),

View File

@ -33,16 +33,24 @@ impl Game {
pool:[[0; 6]; 2],
history:Vec::new(),
}.init()
}
}
pub fn init(mut self) -> Self
pub fn init(&mut self)
{
self.board.init();
self
}
pub fn process(&mut self, play:Play) -> Result<(),()>
pub fn apply_history(&mut self, history:&Vec<Play>) -> Result<(),()>
{
self.init();
for play in history {
self.process(play)?;
}
Ok(())
}
pub fn process(&mut self, play:&Play) -> Result<(),()>
{
let player = (self.turn & 1) as u8;
@ -105,6 +113,7 @@ impl Game {
} else { false }
} {
self.turn += 1;
self.history.push(*play);
Ok(())
} else { Err(()) }
}

View File

@ -68,10 +68,15 @@ impl App {
let mut times = Vec::<(u64, SessionToken)>::new();
let session_count = filesystem.session_count()?;
for id in 0..session_count {
let session = filesystem.session_fetch(id as u32).unwrap();
let mut session = filesystem.session_fetch(id as u32).unwrap();
times.push((session.time, session.token.clone()));
// Load session history
if let Ok(history) = filesystem.session_history_fetch(id as u32) {
session.game.apply_history(&history).ok();
}
sessions.set(&session.token.clone(), session);
}

View File

@ -1,10 +1,11 @@
use bus::Bus;
use crate::{
app::{
App,
authentication::Authentication,
user::User,
connection::Connection,
user::User,
App,
session::Session,
},
protocol,
};
@ -359,6 +360,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
chain_id,
};
session.game.init();
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
conn.session = Some(session.token);
@ -386,6 +388,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
response.status = STATUS_ERROR;
response.token = request.token;
let mut send_gamestate = false;
// Verify that session exists
if let Some(session) = app.sessions.get_mut(&request.token) {
@ -416,6 +420,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
app.filesystem.session_update(session.id, session).ok();
response.status = STATUS_OK;
send_gamestate = true;
true
} else { false }
}
@ -451,6 +457,26 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
}
}
if send_gamestate {
if let Some(session) = app.sessions.get(&request.token) {
// Send GameState update to all connections.
let mut packets = Vec::new();
let game_state = generate_gamestate(&app, session);
for cid in &session.get_connections() {
if *cid != qr.id {
packets.push(QRPacket::new(
*cid,
QRPacketData::RGameState(game_state.clone()),
));
}
}
for packet in packets {
app.send_response(packet).await;
}
}
}
Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response)))
}
@ -487,41 +513,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
println!("Request: Game State");
if let Some(session) = app.sessions.get(&request.token) {
response.status = STATUS_OK;
response.turn = session.game.turn;
// Get Dawn handle
if let Some(id) = session.p_dawn.user {
if let Some(user) = app.get_user_by_id(id) {
response.dawn_handle = user.handle.clone();
}
}
// Get Dusk handle
if let Some(id) = session.p_dusk.user {
if let Some(user) = app.get_user_by_id(id) {
response.dusk_handle = user.handle.clone();
}
}
// Get pool sizes
response.dawn_pool = session.game.pool[0];
response.dusk_pool = session.game.pool[1];
// Get list of pieces
for i in 0..session.game.board.pieces.len() {
response.pieces[i] = if let Some(piece) = &session.game.board.pieces[i] {
PacketGameStateResponsePiece {
valid:true,
piece:piece.class,
promoted:piece.promoted,
player:piece.player,
tile:piece.tile,
}
} else {
PacketGameStateResponsePiece::new()
};
}
response = generate_gamestate(&app, &session);
} else {
response.status = STATUS_ERROR;
}
@ -538,22 +530,27 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
if let Some(sid) = session_id {
if let Some(session) = app.sessions.get_mut(&sid) {
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
if session.p_dawn.user.is_some() && session.p_dusk.user.is_some() {
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
// Check validation of play
if request.turn == session.game.turn {
// Check validation of play
if request.turn == session.game.turn {
// Update internal representation
if session.game.process(request.play).is_ok() {
request.status = STATUS_OK;
// Update internal representation
if session.game.process(&request.play).is_ok() {
request.status = STATUS_OK;
// Forward play to all clients
for cid in &session.get_connections() {
packets.push(QRPacket::new(
*cid,
QRPacketData::QGamePlay(request.clone())
));
// Save play to game history.
app.filesystem.session_history_push(session.id, request.play).ok();
// Forward play to all clients
for cid in &session.get_connections() {
packets.push(QRPacket::new(
*cid,
QRPacketData::QGamePlay(request.clone())
));
}
}
}
}
@ -583,3 +580,46 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
app.send_response(response).await;
}
}
fn generate_gamestate(app:&App, session:&Session) -> protocol::PacketGameStateResponse
{
use protocol::{PacketGameStateResponse, PacketGameStateResponsePiece};
let mut response = PacketGameStateResponse::new();
response.turn = session.game.turn;
// Get Dawn handle
if let Some(id) = session.p_dawn.user {
if let Some(user) = app.get_user_by_id(id) {
response.dawn_handle = user.handle.clone();
}
}
// Get Dusk handle
if let Some(id) = session.p_dusk.user {
if let Some(user) = app.get_user_by_id(id) {
response.dusk_handle = user.handle.clone();
}
}
// Get pool sizes
response.dawn_pool = session.game.pool[0];
response.dusk_pool = session.game.pool[1];
// Get list of pieces
for i in 0..session.game.board.pieces.len() {
response.pieces[i] = if let Some(piece) = &session.game.board.pieces[i] {
PacketGameStateResponsePiece {
valid:true,
piece:piece.class,
promoted:piece.promoted,
player:piece.player,
tile:piece.tile,
}
} else {
PacketGameStateResponsePiece::new()
};
}
response
}

View File

@ -2,7 +2,10 @@ use std::{
fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
};
use game::Game;
use game::{
history::Play,
Game,
};
use crate::{
app::{
@ -98,15 +101,18 @@ impl FileSystem {
self.index_session.seek(SeekFrom::Start(0)).map_err(|_| ())?;
self.index_session.write(&pack_u32(size + 1)).map_err(|_| ())?;
// Write session config file
match self.session_update(size, session) {
Ok(_) => {
// Create session history file
let bucket_index = size & !HANDLE_BUCKET_MASK;
let dir_index = size & HANDLE_BUCKET_MASK;
let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_index));
fs::write(bucket_path.join(GENERIC_HISTORY), Vec::<u8>::new()).map_err(|_| ())?;
fs::write(bucket_path.join(GENERIC_HISTORY), &[0; 2]).map_err(|_| ())?;
Ok(size)
}
Err(_) => Err(()),
@ -126,7 +132,7 @@ impl FileSystem {
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?;
}
// Open bucket file for record
// Open session config file
if let Ok(mut file) = File::options().write(true).create(true).open(bucket_path.join(GENERIC_CONFIG)) {
// Write session information
@ -153,11 +159,8 @@ impl FileSystem {
let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_index));
if !bucket_path.exists() {
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?;
}
// Open bucket file for record
// Open session config file
if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_CONFIG)) {
// Extract session information
@ -206,14 +209,73 @@ impl FileSystem {
} else { Err(()) }
}
pub fn session_history_push(&mut self, _id:u32, _history:()) -> Result<(),()>
pub fn session_history_push(&mut self, id:u32, history:Play) -> Result<(),()>
{
Err(())
let play_data :u16 = (history.source as u16) | ((history.from as u16) << 1) | ((history.to as u16) << 7);
let bucket_index = id & !HANDLE_BUCKET_MASK;
let dir_index = id & HANDLE_BUCKET_MASK;
let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_index));
// Open session history file
if let Ok(mut file) = File::options().read(true).write(true).open(bucket_path.join(GENERIC_HISTORY)) {
let mut buffer_size = [0u8; 2];
// Append history information
file.seek(SeekFrom::End(0)).map_err(|_| ())?;
file.write(&pack_u16(play_data)).map_err(|_| ())?;
// Update length
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
file.read_exact(&mut buffer_size).map_err(|_| ())?;
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
let size = unpack_u16(&buffer_size, &mut 0);
file.write(&pack_u16(size + 1)).map_err(|_| ())?;
Ok(())
} else { Err(()) }
}
pub fn session_history_fetch(&mut self, _id:u32) -> Result<Vec<()>,()>
pub fn session_history_fetch(&mut self, id:u32) -> Result<Vec<Play>,()>
{
Err(())
let mut result = Vec::new();
let bucket_index = id & !HANDLE_BUCKET_MASK;
let dir_index = id & HANDLE_BUCKET_MASK;
let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_index));
// Open session history file
if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_HISTORY)) {
// Read history length
let mut buffer = [0u8; 2];
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
file.read_exact(&mut buffer).map_err(|_| ())?;
let length = unpack_u16(&buffer, &mut 0);
// Read
for _ in 0..length {
file.read_exact(&mut buffer).map_err(|_| ())?;
let data = unpack_u16(&buffer, &mut 0);
result.push(Play {
source:(data & 1) as u8,
from:((data >> 1) & 0x3F) as u8,
to:((data >> 7) & 0x3F) as u8,
});
}
println!("len {}", result.len());
Ok(result)
} else { Err(()) }
}
pub fn session_count(&mut self) -> Result<usize, ()>

View File

@ -42,25 +42,33 @@ GAME.Board = class {
];
// Add Dawn pieces
for(let piece of layout) {
this.set_piece(piece.piece, GAME.Const.Player.Dawn, piece.hex);
for(let lay of layout) {
this.set_piece(
lay.piece,
GAME.Const.Player.Dawn,
HEX.hex_to_tile(lay.hex)
);
}
// Add Dusk pieces
for(let piece of layout) {
let hex = new MATH.Vec2(8 - piece.hex.x, 8 - piece.hex.y);
this.set_piece(piece.piece, GAME.Const.Player.Dusk, hex);
for(let lay of layout) {
this.set_piece(
lay.piece,
GAME.Const.Player.Dusk,
HEX.hex_to_tile(new MATH.Vec2(8 - lay.hex.x, 8 - lay.hex.y))
);
}
}
set_piece(piece, player, hex) {
set_piece(piece, player, tile) {
let index = 0;
while(this.pieces[index] !== null) { index++; }
let game_piece = new GAME.Piece(piece, player);
game_piece.tile = HEX.hex_to_tile(hex);
this.tiles[game_piece.tile].piece = index;
game_piece.tile = tile;
this.tiles[tile].piece = index;
this.pieces[index] = game_piece;
return index;
}
reset() {
@ -92,21 +100,23 @@ GAME.Tile = class {
this.piece = null;
this.threaten = [false, false];
this.checking = [false, false];
this.checking = false;
this.hex = HEX.tile_to_hex(index);
}
reset() {
this.threaten = [0, 0];
this.checking = [false, false];
this.threaten = [false, false];
this.checking = false;
}
};
GAME.MovementTile = class {
constructor(tile, status) {
constructor(tile, valid, threat, check) {
this.tile = tile;
this.status = status;
this.valid = valid;
this.threat = threat;
this.check = check;
}
};
@ -213,24 +223,42 @@ GAME.Game = class {
if(piece.player == 0) { this.board.columns[hex.x].extent[0] = Math.max(hex.y, this.board.columns[hex.x].extent[0]); }
else { this.board.columns[hex.x].extent[1] = Math.min(hex.y, this.board.columns[hex.x].extent[1]); }
// Get threatened tiles.
for(let movement of this.movement_tiles_ext(piece, piece.tile)) {
if(movement.status != GAME.Const.MoveStatus.Invalid) {
if(movement.status == GAME.Const.MoveStatus.Check) {
this.state.check = true;
}
if(movement.threat) {
this.board.tiles[movement.tile].threaten[piece.player] = true;
}
if(movement.check) {
this.board.tiles[piece.tile].checking = true;
this.board.tiles[movement.tile].checking = true;
this.state.check = true;
}
}
}
}
// Count moves available to next turn player to determine checkmate.
if(this.state.check) {
let moves = 0;
// Search for valid board moves.
for(let piece of this.board.pieces) {
if(piece !== null && piece.player == (this.turn & 1)) {
moves += this.movement_tiles(piece, piece.tile).length;
}
}
// Search for valid pool placements.
for(let i = 0; i < 6; ++i) {
moves += this.placement_tiles(i, (this.turn & 1)).length;
}
console.log("moves " + moves);
if(moves == 0) { this.state.checkmate = true; }
}
}
process(move) {
process(play) {
// Check if swapped piece.
// Check if piece should be promoted.
// Check if swapped piece should be promoted.
@ -242,21 +270,23 @@ GAME.Game = class {
// - Improve data safety validation.
//
// Move piece on board.
if(move.source == 0) {
let piece_id = this.board.tiles[move.from].piece;
let piece = this.board.pieces[piece_id];
piece.tile = move.to;
this.board.tiles[move.from].piece = null;
let player = this.turn & 1;
let target_id = this.board.tiles[move.to].piece;
// Move piece on board.
if(play.source == 0) {
let piece_id = this.board.tiles[play.from].piece;
let piece = this.board.pieces[piece_id];
piece.tile = play.to;
this.board.tiles[play.from].piece = null;
let target_id = this.board.tiles[play.to].piece;
if(target_id !== null) {
let target = this.board.pieces[target_id];
// Swap piece with moving piece.
if(target.player == piece.player) {
target.tile = move.from;
this.board.tiles[move.from].piece = target_id;
target.tile = play.from;
this.board.tiles[play.from].piece = target_id;
// Check if swap is promoted.
let hex = HEX.tile_to_hex(target.tile);
@ -274,7 +304,7 @@ GAME.Game = class {
}
}
this.board.tiles[move.to].piece = piece_id;
this.board.tiles[play.to].piece = piece_id;
// Check if piece is promoted.
let hex = HEX.tile_to_hex(piece.tile);
@ -286,7 +316,11 @@ GAME.Game = class {
// Place piece from pool.
else {
this.board.set_piece(
play.from,
player,
play.to
);
}
this.turn++;
@ -298,11 +332,11 @@ GAME.Game = class {
}
movement_tiles(piece, tile) {
let tiles = movement_tiles_ext(piece, tile);
let tiles = this.movement_tiles_ext(piece, tile);
let result = [ ];
for(let movement in tiles) {
if(movement.status) { result.push(movement); }
if(movement.valid) { result.push(movement); }
}
return result;
@ -330,7 +364,7 @@ GAME.Game = class {
let stride = (moves.stride & mask) != 0;
// Get initial status in direction.
let status = (permitted_moves & mask)? GAME.Const.MoveStatus.Valid : GAME.Const.MoveStatus.Invalid;
let valid = (permitted_moves & mask) != 0;
let move_hex = hex.copy();
@ -339,15 +373,22 @@ GAME.Game = class {
for(let dist = 1; dist <= max_dist; ++dist) {
move_hex.add(direction);
let threat = valid;
let check = false;
if(HEX.is_valid(move_hex)) {
let tile_id = HEX.hex_to_tile(move_hex);
let tile_data = this.board.tiles[tile_id];
let result = status;
let result = valid;
if(piece.player == (this.turn & 1) && this.state.check && !tile_data.checking) {
result = false;
}
// King may not move onto threatened tile.
if(piece.piece == GAME.Const.PieceId.Omen && tile_data.threaten[+(!piece.player)]) {
status = GAME.Const.MoveStatus.Invalid;
valid = false;
}
// Handle occupied tile.
@ -358,25 +399,30 @@ GAME.Game = class {
if(target.player == piece.player) {
// Move is only valid if pieces are swappable.
if(!this.movement_swappable(target, mask, dist, tile)) {
result = GAME.Const.MoveStatus.Invalid;
if(!this.movement_swappable(piece, target, mask, dist, tile)) {
result = false;
}
status = GAME.Const.MoveStatus.Invalid;
valid = false;
}
// Target piece is opposing.
else {
// Check if target piece is opposing king.
if(target.piece == GAME.Const.PieceId.Omen) {
if(status == GAME.Const.MoveStatus.Valid) {
result = GAME.Const.MoveStatus.Check;
if(valid) {
check = true;
// Apply check to previous moves.
for(let idist = 1; idist < dist; idist++) {
tiles[tiles.length - idist].check = true;
}
}
}
status = GAME.Const.MoveStatus.Invalid;
valid = false;
}
}
tiles.push(new GAME.MovementTile(tile_id, result));
tiles.push(new GAME.MovementTile(tile_id, result, threat, check));
} else { break; }
}
@ -386,7 +432,11 @@ GAME.Game = class {
return tiles;
}
movement_swappable(target, mask, range, tile) {
movement_swappable(piece, target, mask, range, tile) {
if(piece.piece == target.piece && piece.promoted == target.promoted) {
return false;
}
mask = BITWISE.rotate_blocks(mask);
let moves = target.moves();
@ -398,6 +448,17 @@ GAME.Game = class {
return ((moves.direction & mask) != 0 && (range == 1 || (moves.stride & mask) != 0));
}
placement_tiles(piece_id, player) {
let tiles = this.placement_tiles_ext(piece_id, player);
let result = [ ];
for(let movement in tiles) {
if(movement.valid) { result.push(movement); }
}
return result;
}
placement_tiles_ext(piece_id, player) {
let tiles = [ ];
let piece = new GAME.Piece(piece_id, player);
@ -406,17 +467,21 @@ GAME.Game = class {
for(let i = 0; i < this.board.tiles.length; ++i) {
let hex = HEX.tile_to_hex(i);
let tile = this.board.tiles[i];
let status = GAME.Const.MoveStatus.Invalid;
let valid = false;
// Check if tile is occupied.
if(tile.piece === null) {
let position_valid = true;
if(player == (this.turn & 1) && this.state.check && !tile.checking) {
position_valid = false;
}
// Check off-sides.
if(piece.player == 0) {
position_valid = hex.y <= this.board.columns[hex.x].extent[+(!player)];
position_valid = position_valid && (hex.y <= this.board.columns[hex.x].extent[+(!player)]);
} else {
position_valid = hex.y >= this.board.columns[hex.x].extent[+(!player)];
position_valid = position_valid && (hex.y >= this.board.columns[hex.x].extent[+(!player)]);
}
// Check militia stacking.
@ -428,7 +493,7 @@ GAME.Game = class {
let checking = false;
let movements = this.movement_tiles_ext(piece, i);
for(let movement of movements) {
if(movement.status == GAME.Const.MoveStatus.Check) {
if(movement.check) {
checking = true;
break;
}
@ -436,11 +501,11 @@ GAME.Game = class {
// Piece must have movements and not put king in check.
if(position_valid && movements.length > 0 && !checking) {
status = GAME.Const.MoveStatus.Valid;
valid = true;
}
}
tiles.push(new GAME.MovementTile(i, status));
tiles.push(new GAME.MovementTile(i, valid, false, false));
}
return tiles;
@ -472,6 +537,19 @@ GAME.Const = {
new MATH.Vec2(-1, -2),
new MATH.Vec2(-2, -1),
new MATH.Vec2(-1, 1),
new MATH.Vec2(1, 3),
new MATH.Vec2(2, 3),
new MATH.Vec2(3, 2),
new MATH.Vec2(3, 1),
new MATH.Vec2(2, -1),
new MATH.Vec2(1, -2),
new MATH.Vec2(-1, -3),
new MATH.Vec2(-2, -3),
new MATH.Vec2(-3, -2),
new MATH.Vec2(-3, -1),
new MATH.Vec2(-2, 1),
new MATH.Vec2(-1, 2),
],
MoveStatus: {
@ -501,27 +579,41 @@ GAME.Const = {
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5),
),
new GAME.GamePiece(
"Knight",
new GAME.PieceMovement()
.add(3)
.add(6)
.add(11)
.add(13)
.add(17),
new GAME.PieceMovement()
.add(3)
.add(6)
.add(7)
.add(10)
.add(11)
.add(12)
.add(13)
.add(14)
.add(15)
.add(16)
.add(17),
.add(17)
.add(18)
.add(19)
.add(20)
.add(21)
.add(22)
.add(23),
new GAME.PieceMovement()
.add(1)
.add(3)
.add(5)
.add(12)
.add(13)
.add(14)
.add(15)
.add(16)
.add(17)
.add(18)
.add(19)
.add(20)
.add(21)
.add(22)
.add(23),
),
new GAME.GamePiece(
"Lance",
@ -530,12 +622,12 @@ GAME.Const = {
.add(1)
.add(5),
new GAME.PieceMovement()
.add_stride(0)
.add_stride(1)
.add_stride(2)
.add_stride(3)
.add_stride(4)
.add_stride(5),
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5),
),
new GAME.GamePiece(
"Tower",
@ -588,12 +680,12 @@ GAME.Const = {
.add_stride(10)
.add_stride(11),
new GAME.PieceMovement()
.add_stride(0)
.add_stride(1)
.add_stride(2)
.add_stride(3)
.add_stride(4)
.add_stride(5)
.add(0)
.add(1)
.add(2)
.add(3)
.add(4)
.add(5)
.add_stride(6)
.add_stride(7)
.add_stride(8)
@ -616,8 +708,8 @@ GAME.Const = {
],
get_direction(direction_id) {
let direction = GAME.Const.Direction[direction_id % 12].copy();
direction.mul(Math.ceil((direction_id + 1) / 12));
let direction = GAME.Const.Direction[direction_id % 24].copy();
direction.mul(Math.ceil((direction_id + 1) / 24));
return direction;
},
};

View File

@ -74,7 +74,7 @@ const INTERFACE = {
if(movements !== null) {
// Generate hint for each potential movement.
for(let movement of movements) {
if(movement.status != GAME.Const.MoveStatus.Invalid) {
if(movement.valid) {
// Show valid/threat hints if piece belongs to player and is player turn.
if(INTERFACE_DATA.player == 2 || (player == INTERFACE_DATA.player && (GAME_DATA.turn & 1) == player)) {
if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) {
@ -309,6 +309,9 @@ const INTERFACE = {
ctx.save();
ctx.translate(gui_x, gui_y);
let piece = null;
if(tile.piece !== null) { piece = GAME_DATA.board.pieces[tile.piece]; }
// Draw background.
// Select indicator color or default to tile color.
switch(MATH.mod(coord.x + coord.y, 3)) {
@ -316,6 +319,9 @@ const INTERFACE = {
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
}
if(GAME_DATA.state.check && piece !== null && piece.piece == GAME.Const.PieceId.Omen && piece.player == (GAME_DATA.turn & 1)) {
ctx.fillStyle = INTERFACE.Color.HintCheck;
}
switch(tile_state) {
case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValid; break;
case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreat; break;
@ -349,9 +355,7 @@ const INTERFACE = {
ctx.stroke();
// Draw tile content
if(tile.piece !== null) {
let piece = GAME_DATA.board.pieces[tile.piece];
if(piece !== null) {
// Draw border hints
if(!is_hover && border_state == 0) { draw.hints(piece); }
@ -519,11 +523,22 @@ const INTERFACE = {
ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
// Game state message
if(INTERFACE_DATA.message !== null) {
ctx.fillStyle = INTERFACE.Color.Text;
let message = null;
ctx.fillStyle = INTERFACE.Color.Text;
if(GAME_DATA.state.check) {
ctx.fillStyle = INTERFACE.Color.HintCheck;
if(GAME_DATA.state.checkmate) {
message = "Checkmate";
} else {
message = "Check";
}
}
if(message !== null) {
ctx.textBaseline = "bottom";
ctx.textAlign = "left";
ctx.fillText(INTERFACE_DATA.message, gui_margin, height - gui_margin);
ctx.fillText(message, gui_margin, height - gui_margin);
}
},
@ -653,8 +668,6 @@ const INTERFACE = {
handles: [null, null],
board_state: [ ],
message: null,
Ui: {
scale: 0,
margin: 0,
@ -718,7 +731,7 @@ const INTERFACE = {
} break;
case OpCode.GamePlay: {
if(data.status == Status.Ok) {
if(data.status == Status.Ok && data.turn == GAME_DATA.turn) {
GAME_DATA.process(data.move);
INTERFACE.draw();
}

View File

@ -277,6 +277,7 @@ const SCENES = {
table.appendChild(UI.session_table(data.records));
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);
@ -335,6 +336,7 @@ const SCENES = {
table.appendChild(UI.session_table(data.records));
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);
@ -393,6 +395,7 @@ const SCENES = {
table.appendChild(UI.session_table(data.records));
}
} break;
case OpCode.SessionCreate:
case OpCode.SessionJoin: {
if(data.status == Status.Ok) {
LOAD(SCENES.Game, data);

View File

@ -133,15 +133,15 @@ const BITWISE = {
{
const r1 = 0x00003F; // first 6 bits
const r2 = 0x000FC0; // second 6 bits
const r3 = 0x03F000; // third 6 bits
const r3 = 0xFFF000; // third 12 bits
let v1 = (r1 & mask) << 3;
let v2 = (r2 & mask) << 3;
let v3 = (r3 & mask) << 3;
let v3 = (r3 & mask) << 6;
v1 = (v1 & r1) | ((v1 & ~r1) >> 6);
v2 = (v2 & r2) | ((v2 & ~r2) >> 6);
v3 = (v3 & r3) | ((v3 & ~r3) >> 6);
v3 = (v3 & r3) | ((v3 & ~r3) >> 12);
return v1 | v2 | v3;
},