diff --git a/game/src/board/mod.rs b/game/src/board/mod.rs index 448ab9c..007b877 100644 --- a/game/src/board/mod.rs +++ b/game/src/board/mod.rs @@ -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)), diff --git a/game/src/game/mod.rs b/game/src/game/mod.rs index bc97c23..edbe16a 100644 --- a/game/src/game/mod.rs +++ b/game/src/game/mod.rs @@ -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) -> 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(()) } } diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index 0728348..37b90d5 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -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); } diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index f1ccfdf..d0e55d8 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -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) 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) 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) 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) } } + 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) 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) 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) 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 +} diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs index c24d21a..c73f0dd 100644 --- a/server/src/system/filesystem/mod.rs +++ b/server/src/system/filesystem/mod.rs @@ -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::::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,()> + pub fn session_history_fetch(&mut self, id:u32) -> Result,()> { - 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 diff --git a/www/js/game.js b/www/js/game.js index 1c92aae..54e3c6c 100644 --- a/www/js/game.js +++ b/www/js/game.js @@ -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; }, }; diff --git a/www/js/interface.js b/www/js/interface.js index 692aee2..7897e68 100644 --- a/www/js/interface.js +++ b/www/js/interface.js @@ -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(); } diff --git a/www/js/scene.js b/www/js/scene.js index 2af47ab..c724b47 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -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); diff --git a/www/js/util.js b/www/js/util.js index 88e0a94..4974dc8 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -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; },