Add session persistence and check handling.
This commit is contained in:
parent
adf3cdef10
commit
225cb34bab
@ -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)),
|
||||||
|
@ -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(()) }
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,7 +513,79 @@ 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);
|
||||||
|
} else {
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::RGameState(response)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GamePlay
|
||||||
|
QRPacketData::QGamePlay(mut request) => {
|
||||||
|
println!("Request: Game Play");
|
||||||
|
|
||||||
|
request.status = STATUS_ERROR;
|
||||||
|
let mut packets = Vec::<QRPacket>::new();
|
||||||
|
|
||||||
|
if let Some(sid) = session_id {
|
||||||
|
if let Some(session) = app.sessions.get_mut(&sid) {
|
||||||
|
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 {
|
||||||
|
|
||||||
|
// Update internal representation
|
||||||
|
if session.game.process(&request.play).is_ok() {
|
||||||
|
request.status = STATUS_OK;
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.status != STATUS_ERROR {
|
||||||
|
for packet in packets {
|
||||||
|
app.send_response(packet).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates will have already been sent, so nothing is needed here.
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::None))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Return error status.
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::QGamePlay(request)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
} {
|
||||||
|
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;
|
response.turn = session.game.turn;
|
||||||
|
|
||||||
// Get Dawn handle
|
// Get Dawn handle
|
||||||
@ -522,64 +620,6 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
PacketGameStateResponsePiece::new()
|
PacketGameStateResponsePiece::new()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
response.status = STATUS_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(QRPacket::new(qr.id, QRPacketData::RGameState(response)))
|
response
|
||||||
}
|
|
||||||
|
|
||||||
// GamePlay
|
|
||||||
QRPacketData::QGamePlay(mut request) => {
|
|
||||||
println!("Request: Game Play");
|
|
||||||
|
|
||||||
request.status = STATUS_ERROR;
|
|
||||||
let mut packets = Vec::<QRPacket>::new();
|
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
|
|
||||||
// Forward play to all clients
|
|
||||||
for cid in &session.get_connections() {
|
|
||||||
packets.push(QRPacket::new(
|
|
||||||
*cid,
|
|
||||||
QRPacketData::QGamePlay(request.clone())
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if request.status != STATUS_ERROR {
|
|
||||||
for packet in packets {
|
|
||||||
app.send_response(packet).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates will have already been sent, so nothing is needed here.
|
|
||||||
Some(QRPacket::new(qr.id, QRPacketData::None))
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Return error status.
|
|
||||||
Some(QRPacket::new(qr.id, QRPacketData::QGamePlay(request)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
} {
|
|
||||||
app.send_response(response).await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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, ()>
|
||||||
|
240
www/js/game.js
240
www/js/game.js
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process(move) {
|
// 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(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;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user