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) { pub fn init(&mut self) {
self.tiles = [Tile::new(); 61];
self.columns = [Column::new(); 9];
self.pieces = [None; 38];
let layout = [ let layout = [
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(0, 1)), (Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(0, 1)),
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(1, 1)), (Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(1, 1)),

View File

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

View File

@ -68,10 +68,15 @@ impl App {
let mut times = Vec::<(u64, SessionToken)>::new(); let mut times = Vec::<(u64, SessionToken)>::new();
let session_count = filesystem.session_count()?; let session_count = filesystem.session_count()?;
for id in 0..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())); 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); sessions.set(&session.token.clone(), session);
} }

View File

@ -1,10 +1,11 @@
use bus::Bus; use bus::Bus;
use crate::{ use crate::{
app::{ app::{
App,
authentication::Authentication, authentication::Authentication,
user::User,
connection::Connection, connection::Connection,
user::User,
App,
session::Session,
}, },
protocol, 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, time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
chain_id, chain_id,
}; };
session.game.init();
if let Some(conn) = app.connections.get_mut(qr.id as usize) { if let Some(conn) = app.connections.get_mut(qr.id as usize) {
conn.session = Some(session.token); 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.status = STATUS_ERROR;
response.token = request.token; response.token = request.token;
let mut send_gamestate = false;
// Verify that session exists // Verify that session exists
if let Some(session) = app.sessions.get_mut(&request.token) { 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(); app.filesystem.session_update(session.id, session).ok();
response.status = STATUS_OK; response.status = STATUS_OK;
send_gamestate = true;
true true
} else { false } } 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))) 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"); println!("Request: Game State");
if let Some(session) = app.sessions.get(&request.token) { if let Some(session) = app.sessions.get(&request.token) {
response.status = STATUS_OK; response = generate_gamestate(&app, &session);
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()
};
}
} else { } else {
response.status = STATUS_ERROR; 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(sid) = session_id {
if let Some(session) = app.sessions.get_mut(&sid) { if let Some(session) = app.sessions.get_mut(&sid) {
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0) if session.p_dawn.user.is_some() && session.p_dusk.user.is_some() {
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) { 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 // Check validation of play
if request.turn == session.game.turn { if request.turn == session.game.turn {
// Update internal representation // Update internal representation
if session.game.process(request.play).is_ok() { if session.game.process(&request.play).is_ok() {
request.status = STATUS_OK; request.status = STATUS_OK;
// Forward play to all clients // Save play to game history.
for cid in &session.get_connections() { app.filesystem.session_history_push(session.id, request.play).ok();
packets.push(QRPacket::new(
*cid, // Forward play to all clients
QRPacketData::QGamePlay(request.clone()) 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; 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 fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
}; };
use game::Game; use game::{
history::Play,
Game,
};
use crate::{ use crate::{
app::{ app::{
@ -98,15 +101,18 @@ impl FileSystem {
self.index_session.seek(SeekFrom::Start(0)).map_err(|_| ())?; self.index_session.seek(SeekFrom::Start(0)).map_err(|_| ())?;
self.index_session.write(&pack_u32(size + 1)).map_err(|_| ())?; self.index_session.write(&pack_u32(size + 1)).map_err(|_| ())?;
// Write session config file
match self.session_update(size, session) { match self.session_update(size, session) {
Ok(_) => { Ok(_) => {
// Create session history file
let bucket_index = size & !HANDLE_BUCKET_MASK; let bucket_index = size & !HANDLE_BUCKET_MASK;
let dir_index = size & HANDLE_BUCKET_MASK; let dir_index = size & HANDLE_BUCKET_MASK;
let bucket_path = Path::new(DIR_SESSION) let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_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) Ok(size)
} }
Err(_) => Err(()), Err(_) => Err(()),
@ -126,7 +132,7 @@ impl FileSystem {
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?; 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)) { if let Ok(mut file) = File::options().write(true).create(true).open(bucket_path.join(GENERIC_CONFIG)) {
// Write session information // Write session information
@ -153,11 +159,8 @@ impl FileSystem {
let bucket_path = Path::new(DIR_SESSION) let bucket_path = Path::new(DIR_SESSION)
.join(format!("{:08x}", bucket_index)) .join(format!("{:08x}", bucket_index))
.join(format!("{:08x}", dir_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)) { if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_CONFIG)) {
// Extract session information // Extract session information
@ -206,14 +209,73 @@ impl FileSystem {
} else { Err(()) } } 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, ()> pub fn session_count(&mut self) -> Result<usize, ()>

View File

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

View File

@ -74,7 +74,7 @@ const INTERFACE = {
if(movements !== null) { if(movements !== null) {
// Generate hint for each potential movement. // Generate hint for each potential movement.
for(let movement of movements) { 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. // 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(INTERFACE_DATA.player == 2 || (player == INTERFACE_DATA.player && (GAME_DATA.turn & 1) == player)) {
if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) { if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) {
@ -309,6 +309,9 @@ const INTERFACE = {
ctx.save(); ctx.save();
ctx.translate(gui_x, gui_y); ctx.translate(gui_x, gui_y);
let piece = null;
if(tile.piece !== null) { piece = GAME_DATA.board.pieces[tile.piece]; }
// Draw background. // Draw background.
// Select indicator color or default to tile color. // Select indicator color or default to tile color.
switch(MATH.mod(coord.x + coord.y, 3)) { switch(MATH.mod(coord.x + coord.y, 3)) {
@ -316,6 +319,9 @@ const INTERFACE = {
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break; case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; 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) { switch(tile_state) {
case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValid; break; case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValid; break;
case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreat; break; case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreat; break;
@ -349,9 +355,7 @@ const INTERFACE = {
ctx.stroke(); ctx.stroke();
// Draw tile content // Draw tile content
if(tile.piece !== null) { if(piece !== null) {
let piece = GAME_DATA.board.pieces[tile.piece];
// Draw border hints // Draw border hints
if(!is_hover && border_state == 0) { draw.hints(piece); } 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); ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
// Game state message // Game state message
if(INTERFACE_DATA.message !== null) { let message = null;
ctx.fillStyle = INTERFACE.Color.Text; 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.textBaseline = "bottom";
ctx.textAlign = "left"; 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], handles: [null, null],
board_state: [ ], board_state: [ ],
message: null,
Ui: { Ui: {
scale: 0, scale: 0,
margin: 0, margin: 0,
@ -718,7 +731,7 @@ const INTERFACE = {
} break; } break;
case OpCode.GamePlay: { case OpCode.GamePlay: {
if(data.status == Status.Ok) { if(data.status == Status.Ok && data.turn == GAME_DATA.turn) {
GAME_DATA.process(data.move); GAME_DATA.process(data.move);
INTERFACE.draw(); INTERFACE.draw();
} }

View File

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

View File

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