From 072643643984f8e11eb69aa69ef1d8964b4e6071 Mon Sep 17 00:00:00 2001 From: yukirij Date: Fri, 4 Oct 2024 20:17:15 -0700 Subject: [PATCH] Rework game messages; change images to raw vector graphics. --- game/src/game/mod.rs | 8 +- server/Cargo.toml | 1 + server/src/app/mod.rs | 8 +- server/src/app/session.rs | 2 + server/src/main.rs | 108 ++-- server/src/manager/data.rs | 133 ++-- server/src/manager/ws.rs | 4 +- server/src/protocol/code.rs | 16 +- server/src/protocol/mod.rs | 2 +- server/src/protocol/packet/game_message.rs | 128 ++++ server/src/protocol/packet/game_play.rs | 58 -- server/src/protocol/packet/mod.rs | 2 +- server/src/system/cache/mod.rs | 102 ++-- server/src/system/filesystem/mod.rs | 2 + server/src/util/imager/mod.rs | 92 +++ server/src/util/mod.rs | 1 + www/asset/{behemoth_dawn.svg => Behemoth.svg} | 0 www/asset/{castle_dawn.svg => Castle.svg} | 0 www/asset/{dragon_dawn.svg => Dragon.svg} | 0 www/asset/{heart_dawn.svg => Heart.svg} | 0 www/asset/{knight_dawn.svg => Knight.svg} | 0 www/asset/{lance_dawn.svg => Lance.svg} | 0 www/asset/{militia_dawn.svg => Militia.svg} | 0 www/asset/{tower_dawn.svg => Tower.svg} | 0 www/asset/behemoth_dusk.svg | 86 --- www/asset/castle_dusk.svg | 82 --- www/asset/dragon_dusk.svg | 85 --- www/asset/heart_dusk.svg | 90 --- www/asset/knight_dusk.svg | 72 --- www/asset/lance_dusk.svg | 82 --- www/asset/militia_dusk.svg | 72 --- www/asset/png/behemoth-dawn.png | Bin 3368 -> 0 bytes www/asset/png/behemoth-dusk.png | Bin 3647 -> 0 bytes www/asset/png/castle-dawn.png | Bin 916 -> 0 bytes www/asset/png/castle-dusk.png | Bin 932 -> 0 bytes www/asset/png/dragon-dawn.png | Bin 3678 -> 0 bytes www/asset/png/dragon-dusk.png | Bin 4008 -> 0 bytes www/asset/png/heart-dawn.png | Bin 1457 -> 0 bytes www/asset/png/heart-dusk.png | Bin 1533 -> 0 bytes www/asset/png/knight-dawn.png | Bin 1950 -> 0 bytes www/asset/png/knight-dusk.png | Bin 2108 -> 0 bytes www/asset/png/lance-dawn.png | Bin 1237 -> 0 bytes www/asset/png/lance-dusk.png | Bin 1273 -> 0 bytes www/asset/png/militia-dawn.png | Bin 886 -> 0 bytes www/asset/png/militia-dusk.png | Bin 911 -> 0 bytes www/asset/png/tower-dawn.png | Bin 2296 -> 0 bytes www/asset/png/tower-dusk.png | Bin 2444 -> 0 bytes www/asset/tower_dusk.svg | 111 ---- www/js/const.js | 16 +- www/js/game.js | 20 +- www/js/game_asset.js | 81 ++- www/js/interface.js | 572 ++++++++++++------ www/js/scene.js | 12 +- www/js/system.js | 67 +- 54 files changed, 969 insertions(+), 1146 deletions(-) create mode 100644 server/src/protocol/packet/game_message.rs delete mode 100644 server/src/protocol/packet/game_play.rs create mode 100644 server/src/util/imager/mod.rs rename www/asset/{behemoth_dawn.svg => Behemoth.svg} (100%) rename www/asset/{castle_dawn.svg => Castle.svg} (100%) rename www/asset/{dragon_dawn.svg => Dragon.svg} (100%) rename www/asset/{heart_dawn.svg => Heart.svg} (100%) rename www/asset/{knight_dawn.svg => Knight.svg} (100%) rename www/asset/{lance_dawn.svg => Lance.svg} (100%) rename www/asset/{militia_dawn.svg => Militia.svg} (100%) rename www/asset/{tower_dawn.svg => Tower.svg} (100%) delete mode 100644 www/asset/behemoth_dusk.svg delete mode 100644 www/asset/castle_dusk.svg delete mode 100644 www/asset/dragon_dusk.svg delete mode 100644 www/asset/heart_dusk.svg delete mode 100644 www/asset/knight_dusk.svg delete mode 100644 www/asset/lance_dusk.svg delete mode 100644 www/asset/militia_dusk.svg delete mode 100644 www/asset/png/behemoth-dawn.png delete mode 100644 www/asset/png/behemoth-dusk.png delete mode 100644 www/asset/png/castle-dawn.png delete mode 100644 www/asset/png/castle-dusk.png delete mode 100644 www/asset/png/dragon-dawn.png delete mode 100644 www/asset/png/dragon-dusk.png delete mode 100644 www/asset/png/heart-dawn.png delete mode 100644 www/asset/png/heart-dusk.png delete mode 100644 www/asset/png/knight-dawn.png delete mode 100644 www/asset/png/knight-dusk.png delete mode 100644 www/asset/png/lance-dawn.png delete mode 100644 www/asset/png/lance-dusk.png delete mode 100644 www/asset/png/militia-dawn.png delete mode 100644 www/asset/png/militia-dusk.png delete mode 100644 www/asset/png/tower-dawn.png delete mode 100644 www/asset/png/tower-dusk.png delete mode 100644 www/asset/tower_dusk.svg diff --git a/game/src/game/mod.rs b/game/src/game/mod.rs index 9054ca5..ac30a0b 100644 --- a/game/src/game/mod.rs +++ b/game/src/game/mod.rs @@ -77,7 +77,7 @@ impl Game { // Move piece on board. if match play.source { - 0 | 3 => { + 0 | 2 => { if let Some(pid) = self.board.tiles[play.from as usize].piece { if let Some(mut piece) = self.board.pieces[pid as usize] { let mut swap = false; @@ -144,12 +144,6 @@ impl Game { } else { false } } - // Player retired. - 2 => { - self.status = GameStatus::Resign; - true - } - _ => false, } { self.history.push(*play); diff --git a/server/Cargo.toml b/server/Cargo.toml index ce37c1c..0004284 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,6 +21,7 @@ rust-argon2 = "2.1.0" ring = "0.17.8" const_format = "0.2.32" markdown = "0.3.0" +usvg = "0.44.0" game = { path = "../game" } diff --git a/server/src/app/mod.rs b/server/src/app/mod.rs index 0f6cbed..c3cdcb3 100644 --- a/server/src/app/mod.rs +++ b/server/src/app/mod.rs @@ -87,6 +87,10 @@ impl App { // Load session history if let Ok(history) = filesystem.session_history_fetch(id as u32) { + let mut history = history; + for play in &mut history { + if play.source == 3 { play.source = 2; } + } session.game.apply_history(&history).ok(); } @@ -193,9 +197,9 @@ impl App { )).await.ok(); } - QRPacketData::QGamePlay(response) => { + QRPacketData::GameMessage(response) => { socket.send(Message::Binary( - encode_response(CODE_GAME_PLAY, response.encode()) + encode_response(CODE_GAME_MESSAGE, response.encode()) )).await.ok(); } diff --git a/server/src/app/session.rs b/server/src/app/session.rs index 9db6f30..b24d843 100644 --- a/server/src/app/session.rs +++ b/server/src/app/session.rs @@ -22,6 +22,8 @@ pub struct Session { pub time:u64, pub chain_id:usize, + + pub undo:Option, } impl Session { pub fn get_connections(&self) -> Vec<(u32, u8)> diff --git a/server/src/main.rs b/server/src/main.rs index c23bab4..0b9d10a 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,4 +1,5 @@ use std::net::SocketAddr; +use std::path::Path; use bus::Bus; @@ -158,69 +159,72 @@ async fn main() return; } } - + + // Load image assets + let mut js_asset_data = String::from("const GAME_ASSET = { Image: {"); + let asset_path = Path::new("www/asset/"); + for name in [ + "Promote", + "Militia", + "Lance", + "Knight", + "Tower", + "Castle", + "Dragon", + "Behemoth", + "Heart", + ] { + if let Ok(output) = util::imager::load(asset_path.join(&format!("{}.svg", name))) { + js_asset_data += &format!("{}:{},", name, output); + } else { + println!("error: failed to load asset: {}", name); + } + } + js_asset_data += "} };"; // Initialize HTTPS service. match b_main.connect() { Ok(bus) => { let cache = WebCache::new(); - cache.cache_file("text/html", "/.html", "www/.html").ok(); + cache.cache("text/html", "/.html", &[ + WebCache::file("www/.html"), + ]).ok(); cache.cache_whitespace_minimize("/.html").ok(); - cache.cache_file_group("text/css", "/.css", &[ - "www/css/main.css", - "www/css/ui.css", - "www/css/form.css", - "www/css/game.css", - "www/css/util.css", + cache.cache("text/css", "/.css", &[ + WebCache::file("www/css/main.css"), + WebCache::file("www/css/ui.css"), + WebCache::file("www/css/form.css"), + WebCache::file("www/css/game.css"), + WebCache::file("www/css/util.css"), ]).ok(); - cache.cache_file_group("text/javascript", "/.js", &[ - "www/js/const.js", - "www/js/language.js", - "www/js/util.js", - "www/js/badge.js", - "www/js/game_asset.js", - "www/js/game.js", - "www/js/interface.js", - "www/js/ui.js", - "www/js/scene.js", - "www/js/system.js", - "www/js/main.js", + cache.cache("text/javascript", "/.js", &[ + WebCache::file("www/js/const.js"), + WebCache::file("www/js/language.js"), + WebCache::file("www/js/util.js"), + WebCache::file("www/js/badge.js"), + WebCache::file("www/js/game_asset.js"), + WebCache::string(&js_asset_data), + WebCache::file("www/js/game.js"), + WebCache::file("www/js/interface.js"), + WebCache::file("www/js/ui.js"), + WebCache::file("www/js/scene.js"), + WebCache::file("www/js/system.js"), + WebCache::file("www/js/main.js"), + ]).ok(); + cache.cache("image/png", "/favicon.png", &[ + WebCache::file("www/asset/favicon.png"), + ]).ok(); + cache.cache("image/png", "/favicon_notify.png", &[ + WebCache::file("www/asset/favicon_notify.png"), ]).ok(); - cache.cache_file("image/png", "/favicon.png", "www/asset/favicon.png").ok(); - cache.cache_file("image/png", "/favicon_notify.png", "www/asset/favicon_notify.png").ok(); - - let asset_path = std::path::Path::new("www/asset"); - for asset in [ - "promote.svg", - - "heart_dawn.svg", - "behemoth_dawn.svg", - "dragon_dawn.svg", - "castle_dawn.svg", - "tower_dawn.svg", - "lance_dawn.svg", - "knight_dawn.svg", - "militia_dawn.svg", - - "heart_dusk.svg", - "behemoth_dusk.svg", - "dragon_dusk.svg", - "castle_dusk.svg", - "tower_dusk.svg", - "lance_dusk.svg", - "knight_dusk.svg", - "militia_dusk.svg", - ] { - if cache.cache_file("image/svg+xml", &format!("/asset/{}", asset), asset_path.join(asset)).is_err() { - println!("error: failed to load: {}", asset); - } - } let about_path = std::path::Path::new("www/pages/about"); for doc in [ "main", ] { - if cache.cache_md(&format!("/about/{}.html", doc), about_path.join(format!("{}.md", doc))).is_err() { + if cache.cache("text/html", &format!("/about/{}.html", doc), &[ + WebCache::markdown(about_path.join(format!("{}.md", doc))) + ]).is_err() { println!("error: failed to load: {}", doc); } } @@ -231,7 +235,9 @@ async fn main() "pieces", "interface", ] { - if cache.cache_md(&format!("/guide/{}.html", doc), guide_path.join(format!("{}.md", doc))).is_err() { + if cache.cache("text/html", &format!("/guide/{}.html", doc), &[ + WebCache::markdown(guide_path.join(format!("{}.md", doc))), + ]).is_err() { println!("error: failed to load: {}", doc); } } diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index e75b3eb..d942143 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -1,4 +1,5 @@ use bus::Bus; +use game::history::Play; use crate::{ config, app::{ @@ -554,10 +555,8 @@ pub async fn thread_system(mut app:App, bus:Bus) for (cid, _) in session.get_connections() { packets.push(QRPacket::new( cid, - QRPacketData::QGamePlay(PacketGamePlay { - status: STATUS_OK, - turn: session.game.turn, - play, + QRPacketData::GameMessage(PacketGameMessage { + data: GameMessageData::Retire, }), )); } @@ -607,58 +606,114 @@ pub async fn thread_system(mut app:App, bus:Bus) Some(QRPacket::new(qr.id, QRPacketData::RGameState(response))) } - // GamePlay - QRPacketData::QGamePlay(mut request) => { - println!("Request: Game Play"); + // GameMessage + QRPacketData::GameMessage(request) => { + println!("Request: Game Message"); - request.status = STATUS_ERROR; let mut packets = Vec::::new(); if let Some(sid) = session_id { if let Some(session) = app.sessions.get_mut(&sid) { - if !session.game.is_complete() { - if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) - || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { - // Check validation of play - if request.turn == session.game.turn { + match request.data { + GameMessageData::PlayMove(turn, from, to) + | GameMessageData::PlayDrop(turn, from, to) + | GameMessageData::PlayAlt(turn, from, to) + => { + println!("HERE"); - // Update internal representation - if session.game.process(&request.play).is_ok() { - request.status = STATUS_OK; + if !session.game.is_complete() { + if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) + || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { + if turn == session.game.turn { + let play = Play { + source: match request.data { + GameMessageData::PlayMove(..) => 0, + GameMessageData::PlayDrop(..) => 1, + GameMessageData::PlayAlt(..) => 2, + _ => 0, + }, + from, to, + }; - // Save play to game history. - app.filesystem.session_history_push(session.id, request.play).ok(); + println!("play {} {} {}", play.source, play.from, play.to); - // Forward play to all clients - for (cid, _) in session.get_connections() { - packets.push(QRPacket::new( - cid, - QRPacketData::QGamePlay(request.clone()) - )); + if session.game.process(&play).is_ok() { + // Commit play to history + app.filesystem.session_history_push(session.id, play).ok(); + + // Forward messsage to all clients + for (cid, _) in session.get_connections() { + packets.push(QRPacket::new( + cid, + QRPacketData::GameMessage(request.clone()) + )); + } + + // Send status to players. + send_user_status.push(session.p_dawn.user); + send_user_status.push(session.p_dusk.user); + } } - - // Send status to players. - send_user_status.push(session.p_dawn.user); - send_user_status.push(session.p_dusk.user); } } } + + GameMessageData::Undo(turn, _) => { + if !session.game.is_complete() { + if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) + || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { + if turn == session.game.turn { + // Request or commit undo + } + } + } + } + + GameMessageData::Retire => { + if !session.game.is_complete() { + if (user_id == Some(session.p_dawn.user) && session.game.turn & 1 == 0) + || (user_id == Some(session.p_dusk.user) && session.game.turn & 1 == 1) { + + // Forward messsage to all clients + for (cid, _) in session.get_connections() { + packets.push(QRPacket::new( + cid, + QRPacketData::GameMessage(request.clone()) + )); + } + } + } + } + + GameMessageData::Reaction(_) => { + + // Forward messsage to all clients + for (cid, _) in session.get_connections() { + packets.push(QRPacket::new( + cid, + QRPacketData::GameMessage(request.clone()) + )); + } + } + + _ => { } } } } - if request.status != STATUS_ERROR { - for packet in packets { - app.send_response(packet).await; + match request.data { + GameMessageData::Error => { + Some(QRPacket::new(qr.id, QRPacketData::GameMessage(request))) + } + _ => { + for packet in packets { + app.send_response(packet).await; + } + + // Updates already sent; nothing to do here. + Some(QRPacket::new(qr.id, QRPacketData::None)) } - - // 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))) } } @@ -745,6 +800,8 @@ pub async fn thread_system(mut app:App, bus:Bus) connections:Vec::new(), time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64, chain_id, + + undo:None, }; session.game.init(); diff --git a/server/src/manager/ws.rs b/server/src/manager/ws.rs index dc7a71a..b8a812d 100644 --- a/server/src/manager/ws.rs +++ b/server/src/manager/ws.rs @@ -156,11 +156,11 @@ pub async fn handle_ws(ws:WebSocketStream>, args:HttpServiceAr Err(_) => { println!("error: packet decode failed."); } } - CODE_GAME_PLAY => match PacketGamePlay::decode(&data, &mut index) { + CODE_GAME_MESSAGE => match PacketGameMessage::decode(&data, &mut index) { Ok(packet) => { args.bus.send( bus_ds, - QRPacket::new(conn_id, QRPacketData::QGamePlay(packet)) + QRPacket::new(conn_id, QRPacketData::GameMessage(packet)) ).ok(); } Err(_) => { println!("error: packet decode failed."); } diff --git a/server/src/protocol/code.rs b/server/src/protocol/code.rs index 35a7a05..1b01df5 100644 --- a/server/src/protocol/code.rs +++ b/server/src/protocol/code.rs @@ -38,7 +38,7 @@ pub const CODE_SESSION_RETIRE :u16 = 0x002E; pub const CODE_SESSION_LEAVE :u16 = 0x002F; pub const CODE_GAME_STATE :u16 = 0x0030; -pub const CODE_GAME_PLAY :u16 = 0x0031; +pub const CODE_GAME_MESSAGE :u16 = 0x0031; pub const CODE_CHALLENGE :u16 = 0x0060; pub const CODE_CHALLENGE_ANSWER :u16 = 0x0061; @@ -47,3 +47,17 @@ pub const CODE_CHALLENGE_LIST :u16 = 0x0062; pub const CODE_USER_LIST :u16 = 0x0100; //pub const CODE_USER_AWAIT_GET :u16 = 0x0110; //pub const CODE_USER_AWAIT_SET :u16 = 0x0111; + + +/* +** Game Messages +*/ + +pub const GMSG_ERROR :u8 = 0x00; +pub const GMSG_PLAY_MOVE :u8 = 0x01; +pub const GMSG_PLAY_DROP :u8 = 0x02; +pub const GMSG_PLAY_ALT :u8 = 0x03; +pub const GMSG_ONLINE :u8 = 0x08; +pub const GMSG_UNDO :u8 = 0x10; +pub const GMSG_RETIRE :u8 = 0x11; +pub const GMSG_REACTION :u8 = 0x20; diff --git a/server/src/protocol/mod.rs b/server/src/protocol/mod.rs index 99ea3b8..5edb1b5 100644 --- a/server/src/protocol/mod.rs +++ b/server/src/protocol/mod.rs @@ -42,7 +42,7 @@ pub enum QRPacketData { QGameState(PacketGameState), RGameState(PacketGameStateResponse), - QGamePlay(PacketGamePlay), + GameMessage(PacketGameMessage), QChallenge(PacketChallenge), diff --git a/server/src/protocol/packet/game_message.rs b/server/src/protocol/packet/game_message.rs new file mode 100644 index 0000000..8abee51 --- /dev/null +++ b/server/src/protocol/packet/game_message.rs @@ -0,0 +1,128 @@ +use crate::{ + util::pack::{ + pack_u64, + unpack_u64, + }, + protocol::*, +}; + +use super::Packet; + +#[derive(Clone, Copy)] +pub enum GameMessageData { + Error, + + PlayMove(u16, u8, u8), + PlayDrop(u16, u8, u8), + PlayAlt(u16, u8, u8), + + Online(u8, u32), + + Undo(u16, u8), + Retire, + + Reaction(u16), +} + +#[derive(Clone)] +pub struct PacketGameMessage { + pub data:GameMessageData, +} +impl PacketGameMessage { + pub fn new() -> Self + { + Self { + data:GameMessageData::Error, + } + } +} +impl Packet for PacketGameMessage { + type Data = Self; + + fn decode(data:&Vec, index:&mut usize) -> Result + { + let mut result = Self::new(); + + if data.len() - *index >= 8 { + let data = unpack_u64(data, index); + result.data = match (data & 0xFF) as u8 { + GMSG_PLAY_MOVE => GameMessageData::PlayMove( + ((data >> 8) & 0xFFFF) as u16, + ((data >> 24) & 0x3F) as u8, + ((data >> 30) & 0x3F) as u8, + ), + GMSG_PLAY_DROP => GameMessageData::PlayDrop( + ((data >> 8) & 0xFFFF) as u16, + ((data >> 24) & 0x3F) as u8, + ((data >> 30) & 0x3F) as u8, + ), + GMSG_PLAY_ALT => GameMessageData::PlayAlt( + ((data >> 8) & 0xFFFF) as u16, + ((data >> 24) & 0x3F) as u8, + ((data >> 30) & 0x3F) as u8, + ), + + GMSG_UNDO => GameMessageData::Undo( + ((data >> 8) & 0xFFFF) as u16, + ((data >> 24) & 0x1) as u8, + ), + + GMSG_RETIRE => GameMessageData::Retire, + + GMSG_REACTION => GameMessageData::Reaction( + ((data >> 8) & 0xFFFF) as u16, + ), + + _ => GameMessageData::Error, + } + } + + Ok(result) + } + + fn encode(&self) -> Vec + { + pack_u64(match self.data { + GameMessageData::PlayMove(turn, from, to) => { + GMSG_PLAY_MOVE as u64 + | ((turn as u64) << 8) + | ((from as u64) << 24) + | ((to as u64) << 30) + } + GameMessageData::PlayDrop(turn, piece, to) => { + GMSG_PLAY_DROP as u64 + | ((turn as u64) << 8) + | ((piece as u64) << 24) + | ((to as u64) << 30) + } + GameMessageData::PlayAlt(turn, from, to) => { + GMSG_PLAY_ALT as u64 + | ((turn as u64) << 8) + | ((from as u64) << 24) + | ((to as u64) << 30) + } + + GameMessageData::Online(client, state) => { + GMSG_PLAY_ALT as u64 + | ((client as u64) << 8) + | ((state as u64) << 10) + } + + GameMessageData::Undo(turn, state) => { + GMSG_UNDO as u64 + | ((turn as u64) << 8) + | ((state as u64) << 24) + } + GameMessageData::Retire => { + GMSG_RETIRE as u64 + } + + GameMessageData::Reaction(index) => { + GMSG_REACTION as u64 + | ((index as u64) << 8) + } + + _ => { 0 } + }) + } +} diff --git a/server/src/protocol/packet/game_play.rs b/server/src/protocol/packet/game_play.rs deleted file mode 100644 index 13a0f83..0000000 --- a/server/src/protocol/packet/game_play.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::util::pack::{ - pack_u16, - unpack_u16, -}; -use game::{ - history::Play, - util::mask, -}; - -use super::Packet; - -#[derive(Clone)] -pub struct PacketGamePlay { - pub status:u16, - pub turn:u16, - pub play:Play, -} -impl PacketGamePlay { - pub fn new() -> Self - { - Self { - status:0, - turn:0, - play:Play::new(), - } - } -} -impl Packet for PacketGamePlay { - type Data = Self; - - fn decode(data:&Vec, index:&mut usize) -> Result - { - let mut result = Self::new(); - - result.status = unpack_u16(data, index); - result.turn = unpack_u16(data, index); - - let play = unpack_u16(data, index) as u32; - result.play.source = (play & mask(4, 0)) as u8; - result.play.from = ((play & mask(6, 4)) >> 4) as u8; - result.play.to = ((play & mask(6, 10)) >> 10) as u8; - - Ok(result) - } - - fn encode(&self) -> Vec - { - let mut data = 0; - data |= self.play.source as u16; - data |= (self.play.from as u16) << 4; - data |= (self.play.to as u16) << 10; - [ - pack_u16(self.status), - pack_u16(self.turn), - pack_u16(data), - ].concat() - } -} diff --git a/server/src/protocol/packet/mod.rs b/server/src/protocol/packet/mod.rs index 50fad86..50ea1fa 100644 --- a/server/src/protocol/packet/mod.rs +++ b/server/src/protocol/packet/mod.rs @@ -13,7 +13,7 @@ mod session_view; pub use session_view::*; mod session_retire; pub use session_retire::*; mod game_state; pub use game_state::*; -mod game_play; pub use game_play::*; +mod game_message; pub use game_message::*; //mod game_history; pub use game_history::*; mod challenge; pub use challenge::*; diff --git a/server/src/system/cache/mod.rs b/server/src/system/cache/mod.rs index 45036c1..40bd461 100644 --- a/server/src/system/cache/mod.rs +++ b/server/src/system/cache/mod.rs @@ -9,6 +9,14 @@ use trie::Trie; use crate::util::string::minimize_whitespace; +#[derive(Clone)] +pub enum Source { + Raw(Vec), + String(String), + File(std::path::PathBuf), + MarkdownFile(std::path::PathBuf), +} + #[derive(Clone)] pub struct CacheData { pub mime:String, @@ -46,55 +54,40 @@ impl WebCache { } } - pub fn cache(&self, mime:&str, object:&str, data:Vec) - { - match self.data.write() { - Ok(mut writer) => { - writer.objects.set(object.as_bytes(), CacheData { - mime:mime.to_string(), - data, - }); - }, - Err(_) => { }, - } - } - - pub fn cache_file

>(&self, mime:&str, object:&str, path:P) -> Result<(),()> + pub fn cache(&self, mime:&str, object:&str, sources:&[Source]) -> Result<(),()> { match self.data.write() { Ok(mut writer) => { let mut data = Vec::new(); - if let Ok(mut file) = File::open(path) { - file.read_to_end(&mut data).ok(); - writer.objects.set(object.as_bytes(), CacheData { - mime:mime.to_string(), - data, - }); - Ok(()) - } else { Err(()) } - } - Err(_) => Err(()), - } - } - pub fn cache_md

>(&self, object:&str, path:P) -> Result<(),()> - { - match self.data.write() { - Ok(mut writer) => { - match markdown::file_to_html(&path.as_ref()) { - Ok(text) => { - let text :Vec = text.trim().lines().map(|line| line.trim().to_string()).collect(); - let data = text.concat().as_bytes().to_vec(); - - writer.objects.set(object.as_bytes(), CacheData { - mime:String::from("text/html"), - data, - }); - Ok(()) + for source in sources { + match source { + Source::Raw(raw) => { + data.append(&mut raw.clone()); + } + Source::String(text) => { + data.append(&mut text.as_bytes().to_vec()); + } + Source::File(path) => { + let mut file_data = Vec::new(); + let mut file = File::open(path).map_err(|_| ())?; + file.read_to_end(&mut file_data).ok(); + data.append(&mut file_data); + } + Source::MarkdownFile(path) => { + let mut text = markdown::file_to_html(&path.as_ref()).map_err(|_| ())?; + text = text.trim().lines().map(|line| line.trim().to_string()).collect(); + data.append(&mut text.as_bytes().to_vec()); + } } - Err(_) => Err(()) } - } + + writer.objects.set(object.as_bytes(), CacheData { + mime:mime.to_string(), + data, + }); + Ok(()) + }, Err(_) => Err(()), } } @@ -112,24 +105,9 @@ impl WebCache { } } - pub fn cache_file_group

>(&self, mime:&str, object:&str, paths:&[P]) -> Result<(),()> - { - match self.data.write() { - Ok(mut writer) => { - let data = paths.into_iter().map(|path| { - let mut buffer = Vec::new(); - if let Ok(mut file) = File::open(path) { - file.read_to_end(&mut buffer).ok(); - } - buffer - }).collect::>>().concat(); - writer.objects.set(object.as_bytes(), CacheData { - mime:mime.to_string(), - data:data, - }); - Ok(()) - }, - Err(_) => Err(()), - } - } + + pub fn raw(data:Vec) -> Source { Source::Raw(data) } + pub fn string(text:&str) -> Source { Source::String(text.to_string()) } + pub fn file

>(path:P) -> Source { Source::File((path.as_ref() as &Path).to_path_buf())} + pub fn markdown

>(path:P) -> Source { Source::MarkdownFile((path.as_ref() as &Path).to_path_buf())} } diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs index e6f6b64..c7f1325 100644 --- a/server/src/system/filesystem/mod.rs +++ b/server/src/system/filesystem/mod.rs @@ -203,6 +203,8 @@ impl FileSystem { connections:Vec::new(), time, chain_id:0, + + undo:None, }) } else { Err(()) } } diff --git a/server/src/util/imager/mod.rs b/server/src/util/imager/mod.rs new file mode 100644 index 0000000..c557801 --- /dev/null +++ b/server/src/util/imager/mod.rs @@ -0,0 +1,92 @@ +use std::path::Path; +use usvg::tiny_skia_path::PathSegment; + +pub fn load

>(file:P) -> Result +{ + let mut output = String::from("new GameImage(["); + + let svg_data = std::fs::read(file).map_err(|_| ())?; + + let opt = usvg::Options::default(); + let rtree = usvg::Tree::from_data(&svg_data, &opt).map_err(|_| ())?; + + let bounds = rtree.size(); + let origin = [ + bounds.width() / 2., + bounds.height() / 2., + ]; + let scale = 1. / bounds.width().max(bounds.height()); + + for node in rtree.root().children() { + output += &load_node(node, origin, scale)?; + } + + output += "])"; + Ok(output) +} + +fn load_node(node:&usvg::Node, origin:[f32; 2], scale:f32) -> Result +{ + let mut output = String::new(); + + match &node { + usvg::Node::Group(group) => { + for node in group.children() { + output += &load_node(node, origin, scale)?; + } + } + usvg::Node::Path(path) => { + output += "["; + output += &if let Some(fill) = path.fill() { + match fill.paint() { + usvg::Paint::Color(color) => { + format!("\"#{:02x}{:02x}{:02x}\"", color.red, color.green, color.blue) + } + _ => { + String::from("\"#000000\"") + } + } + } else { + String::from("\"#000000\"") + }; + output += ",["; + + for ref segment in path.data().segments() { + match segment { + PathSegment::MoveTo(point) => { + output += &format!("[0, [{:.4},{:.4}]],", + (point.x - origin[0]) * scale, + (point.y - origin[1]) * scale, + ); + } + PathSegment::LineTo(point) => { + output += &format!("[1, [{:.4},{:.4}]],", + (point.x - origin[0]) * scale, + (point.y - origin[1]) * scale, + ); + } + PathSegment::CubicTo(a, b, c) => { + output += &format!("[2, [{:.4},{:.4},{:.4},{:.4},{:.4},{:.4},]],", + (a.x - origin[0]) * scale, + (a.y - origin[1]) * scale, + (b.x - origin[0]) * scale, + (b.y - origin[1]) * scale, + (c.x - origin[0]) * scale, + (c.y - origin[1]) * scale, + ); + } + PathSegment::Close => { + output += "[3],"; + } + _ => { } + } + } + + output += "]],"; + } + _ => { } + } + + Ok(output) +} + diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs index 7966c06..7261495 100644 --- a/server/src/util/mod.rs +++ b/server/src/util/mod.rs @@ -3,3 +3,4 @@ pub mod color; pub mod string; pub mod pack; mod chain; pub use chain::Chain; +pub mod imager; diff --git a/www/asset/behemoth_dawn.svg b/www/asset/Behemoth.svg similarity index 100% rename from www/asset/behemoth_dawn.svg rename to www/asset/Behemoth.svg diff --git a/www/asset/castle_dawn.svg b/www/asset/Castle.svg similarity index 100% rename from www/asset/castle_dawn.svg rename to www/asset/Castle.svg diff --git a/www/asset/dragon_dawn.svg b/www/asset/Dragon.svg similarity index 100% rename from www/asset/dragon_dawn.svg rename to www/asset/Dragon.svg diff --git a/www/asset/heart_dawn.svg b/www/asset/Heart.svg similarity index 100% rename from www/asset/heart_dawn.svg rename to www/asset/Heart.svg diff --git a/www/asset/knight_dawn.svg b/www/asset/Knight.svg similarity index 100% rename from www/asset/knight_dawn.svg rename to www/asset/Knight.svg diff --git a/www/asset/lance_dawn.svg b/www/asset/Lance.svg similarity index 100% rename from www/asset/lance_dawn.svg rename to www/asset/Lance.svg diff --git a/www/asset/militia_dawn.svg b/www/asset/Militia.svg similarity index 100% rename from www/asset/militia_dawn.svg rename to www/asset/Militia.svg diff --git a/www/asset/tower_dawn.svg b/www/asset/Tower.svg similarity index 100% rename from www/asset/tower_dawn.svg rename to www/asset/Tower.svg diff --git a/www/asset/behemoth_dusk.svg b/www/asset/behemoth_dusk.svg deleted file mode 100644 index c9ec66e..0000000 --- a/www/asset/behemoth_dusk.svg +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/www/asset/castle_dusk.svg b/www/asset/castle_dusk.svg deleted file mode 100644 index 64ff251..0000000 --- a/www/asset/castle_dusk.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/www/asset/dragon_dusk.svg b/www/asset/dragon_dusk.svg deleted file mode 100644 index da3735e..0000000 --- a/www/asset/dragon_dusk.svg +++ /dev/null @@ -1,85 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/www/asset/heart_dusk.svg b/www/asset/heart_dusk.svg deleted file mode 100644 index 2a58b61..0000000 --- a/www/asset/heart_dusk.svg +++ /dev/null @@ -1,90 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/www/asset/knight_dusk.svg b/www/asset/knight_dusk.svg deleted file mode 100644 index d6f2a11..0000000 --- a/www/asset/knight_dusk.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - diff --git a/www/asset/lance_dusk.svg b/www/asset/lance_dusk.svg deleted file mode 100644 index f0acbb3..0000000 --- a/www/asset/lance_dusk.svg +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/www/asset/militia_dusk.svg b/www/asset/militia_dusk.svg deleted file mode 100644 index 67a304e..0000000 --- a/www/asset/militia_dusk.svg +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - diff --git a/www/asset/png/behemoth-dawn.png b/www/asset/png/behemoth-dawn.png deleted file mode 100644 index 04f9a9570fbae5882b77d14abd4204252f63d4e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3368 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 diff --git a/www/asset/png/behemoth-dusk.png b/www/asset/png/behemoth-dusk.png deleted file mode 100644 index d824ba656c5340bfd518fc57de6f254b04602f50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3647 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 diff --git a/www/asset/png/castle-dawn.png b/www/asset/png/castle-dawn.png deleted file mode 100644 index a0314b9ec65db0c5b790ddcbec77400303efb1df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 916 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_owDPZ!6KiaBrZ?hg_UlxX|- zzB}V+1t+KN(ygj%RUh+gt>RqBVOXzmMz?}{$~LjgJ>1<6$&QJsPkW9Z5^PqxDZA*C$&fmRCMgx#?W?ampmeaQ*Lm3Sx~%1tVk#5wcJ2HL^#ZReSdD+q>6O#L7(U z*2`@7yn8%P`g&pNxd;0f@8r+!y=}AXUh?+d*E@`u7n?D4D{t6gbU!7x$hQ8+ap&)q zImZo~fmX%LY`lBhUcPkxmVIa0rbzCvkJ=)2vo`7P#VuRknx1ghk6jtXduz+35Yx23 zzs;vRd%ujm@^1FczdJ4-7gi`^3t!8Y8vVe)Hlnr-1a1m!ivK zyZHj|w3km*?pODC<8>|HckfAwn2N{t#?QB1uTK+*134LI=){OK-Ji8Q`16-TXZAh0 zHh3?)qW3+uw#_ej1a!u z`g#8O3B1Nw!!~B$ZH>|o$L20%6Q7s7861gV{~w>veH5IAFeBtIi|dVZ1-ErNCjoO5 NgQu&X%Q~loCIEp5ph*A# diff --git a/www/asset/png/castle-dusk.png b/www/asset/png/castle-dusk.png deleted file mode 100644 index 48a86882b8ddb4e55ae79454e165219aeca9f9ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 932 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_owBPZ!6KiaBrZ8s=YikZAw- zeUVaD$U*j(C%C#@!;f(VUQJ@ObGpeP{gW%lCF#_O15Hk1cHGR&(MpO!npmw#Rb=a(Lxy66K)*IgpEq+$jf82SR{i2JqjYkC| zV!ljWqqo}4a;*+;t<4#bpPtL^`2KnKv#v`;R`T!fZIWO8a!=*Fw^nle>l%N@72ewX z;=$7_>6^tiaWUbWw*9J_92k=N;4UOzZ*)>Zo;c^`f@ z(|K1K(_ep`d->6(Id^yK*#EcNRW0T8SMj5vY*N8v&#KbIz^TvA_RHJUe>r_7ZvC>) z;hlUh6`W=KUrF+$qs4*e`R3wXY%9AJ``RUS`VKte01^L|AGq%xHotX4WlZ#sS-%pJ zChiP>>KnE0k#%<=_cFx^^AGOUyVz4}yftJCbNhxJFMr)$q@^x)Na>NFtGcYkn(Nj2 z>)vhhJ$Hv$-@dN;#oqqdzIdC+FB^NOOWc?KeqixMwbS+}=vtv`73 zUP9`TuhlBGvsjo;gHr)f#14)BKY3mnGy~&M1{9GfgzE0zO9J_KUankmYC_U}aI}Kc j>+$v6M-8!S`O7L7{j@W9?~Av<9LC`3>gTe~DWM4fD#xY_ diff --git a/www/asset/png/dragon-dawn.png b/www/asset/png/dragon-dawn.png deleted file mode 100644 index b60cd71fa96116e76c925437b28035a7078117a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3678 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 diff --git a/www/asset/png/dragon-dusk.png b/www/asset/png/dragon-dusk.png deleted file mode 100644 index d268bb1d9e7ce8a5326ff948fe7cfbcf7a86a340..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4008 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 diff --git a/www/asset/png/heart-dawn.png b/www/asset/png/heart-dawn.png deleted file mode 100644 index d85719b33e9e673fd83c0de8f02c031c21fdfea1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1457 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 bhq?{ehw#)R;;MB-uN!c($J#a61c847q?O9F diff --git a/www/asset/png/knight-dawn.png b/www/asset/png/knight-dawn.png deleted file mode 100644 index c51e0ecd0951c58bc01947339c9498e48001d978..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1950 zcmbtVXHb)g7X2^~dWkec3#@=hTo7~3;q)0PM6Q!#Nf*?w$(h+G9 z7o-KqvdAJ}661yqBuFtBi6AOO5X)wNyq|C8&Agd&?wvVj&b>cRuDjbwc^M5E0088j z9MNY)JLz9afkpG!Im8Rml8$xsy#xRXqyJKDJm$eUkq9MXa72%oP-0?G{COZTG0`ME zI`UFTQ0#e=nE0@Yb!!a(kc2y-?LCt!*D6ott9Yqg`+;XJXQSn0VL@^>+C+nXJI+2` zrapus&IYZqUY_iiwK4A;rmERh8wYd4M?S>!paX?&dxBUzIt2&oATv;~J- zb`huWYNuI<^jI;w)BPZQlsP^NO10Al?Z>Ymbl%!8&&UFZ2H7Efw9wi`YDi+?pwVp3 z5+4Gj2krtg_nYp|ck!%CG)}~AU!GAm)9Y0rD2)V4r39eu3Tu`8z-04poA+#p_|n??;zAF=H4sy!s0bhqq-j{AdH~Hb-I%&p(pGWy%*B=-nkZ@@t!2keiRwG>(>&No@8C=shjk;yY8Da ze|auSLY>+-5`g?U^Ai=Z{g)SFPu@Z*U6Qv(hV{t1!Xpq8GC_a6$g zUOI?Jz*Tzo;3^`nPUdleA#?4QQzsJdNFof>+V2ePORJN^G&4^}vz2Pz-reBPOD1YU zor!ULEwDpC)<*=0{%Y67Se^g$Qj{WvI=7NluJ24d5I?Xb11%fo2Yv3lFpQAOF}|^? zZ8I|epvnO$15QVg!8Gq^l1%BesitM^*3lJONAqI-4y^~Oo7U_kChV=w^pNT-mn$`; zK4~pHvH0^0GgdBy`eXAQcq3a7-iv8RCV$ck%v7ZaQe=+RDCX=6TqPz87ba#c^>+-i z&xEkvEM2PrR@>7iaAnU67t#}+7QB7!m(pjyEzzL!Eqaz?SqY;$V`L2L&|PTOFXq$%4HT5iy#I17-xcugihw9G4r%qM zawr7EQ!l`Kg+T)yIckCmW2kFKm2lqXqb9 zHSP`j`^AOn0~2O$!K2mJ?Z~nt?;q8xDm-~QH(Dh+R`5*CKTDM~aJa_ccwPl*-AV#5 z^7<~GYz3eLi)8k+@}RK?Ee5Smwo@ST-ZihrRX+Hpiba0rR%qI8f^G;M#@qbN&AN>^ z(h6|e2j9Ic!j6$ws13tg##Sw=s2QuxK{{)1JN zUMAj;5k(VE%pC2~TeT56@VqT#^{^TJEA~?vGm+G*+=gcoi@SZu=D4)wV-lX_i?9}x zX@c%*gxA394KRFQ3n`P?VQrN*U4o}#tHK9f3&HS7k!WgB`)%q&WD_SauVB^1=6xXW z1q>H#&2-*F^#ouZ#rqF4)A}zh?!G;sQh1sAr8{YTwo9lM#1eG+D6o@`#+O#>a4Eb) z>z+Kj!}RL0ycn%)B#zxVtTP*EJ*OM>IdbU|>1{t=EEtO`qkF|%9 zQC+%{0#gR^!){R%y4A#(fffBr#-&wkqzs=ThxHOxn|`slNe07Vg7>~EpI^C>n~98~ z>|bzYox{rKfkq6ze&7w%@eVbkgbzhULqZ3?U!y36S~?oy!p)O-L_=uhu#bh5gRcVg zP=lTLk(2)e#<@p)Z=Fr#Hf!y}nkc}bQ(BP!AlWeQSn+=^q#YPi!wEK+Kr<9|E8v82 KLwBGEng0Od=%N<@ diff --git a/www/asset/png/knight-dusk.png b/www/asset/png/knight-dusk.png deleted file mode 100644 index 5dc1978b1cb5a56a7b78626e9c703f30662671a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2108 zcmb7FYdF&lAN~`wEvE_(LXr|Iba`?}@n+`S2oX6x&ce*bW{x2_q!6t%$2_ARP{k& zv}(2bWRzr@?akJ8`SvQ7R^cHqVnY#WqPlA75Ef&gmIrRiu+240R%@03=bF`EgNpIo z3Hb6GRst$!Vl9w0zw!lRvp9%KWNJ`>L7|#e#JfGvSg6?9KpGu6EbGhEG-9=La2!3T(v~d5>HHlY%9+nT+Kd3f79r zscfa>^ayYHuq{ZkavMOygUg5t-=kU`4^1WHwx%#bb2PKa#x<>N9;Rm9L?Mw(?I9`0 zDGLf1boue`#upNT=01l?r~mK*MXLKIrXFj+`NF?S0emq|+wgL~7LW#OcBO4*=ZJi9 z)YklJ=F9JX$ukcFM*xPB?5f~`q+0it1hB<3z%ckIwOyH8ar*oc8+I+J)&6~jYq`Y^ zG_QSt32xf^I%nKwyER+n;QqiaSK}SR!L*?K*WYvJ`e(s4&Dn>??!w%>4C{TyUOCat zP%mKT^q85VsHlq~Qt$Tc8`MA7KU5}^cc)ke`wd&DF)|!KV?R$=Df;lN@7HN8$;kcY zl<=5%5`R6UQ+%=EPmgqIlTpG_{gdCH=UUY{KNo%Aq)fgiA?4Va>C0C4+Ahm=iFdp5 zsbn>>W7FTAj)+vTLO+nB3)p?7M8&xfk-fF7&K@yHK48eqlB@{U`p8zgAx2oXpqZf~ z0_!nxYg^MIIUq}jhp)rJ2J{XmIRm*5&medl&gQ$rc)dSm!}DikF(IA(He-i0a6hEW zJ7Zh1i^A- zQ>wTYYa2w6Czt);!?gH)9ePd9z5^p)JgYb|fk~nUjExZCM9w*>mLZ0|x5>PX?hP+& zFq1re=&+)CZIREZSC-iZ0Dy>Q?IQIFk6P9e2=;( zapNvJ0Aj0JvtU4Qt}!&QBZ=KVdN<@IPHWQe{G+G80eULTaQnv!GtU-{rBn%P17*8M z4cVjVL9}|Gk*{G-S&M>Z2z(#*>;Q76z`&@VXSxM$g zjuDw~NkXo1M-Tbp6i*doCPLLy?0dd2yz!ntPm7O^!Qg>^qC)nL~2uEDFg>^3dHlT98U-FLea0{$6Vae-3yj9ckYXivzYR#KYGKdTjF>4= z#kuqou5H^YM~{96Q{>g~3E~1;Lme4)&_;cZ!Nvz_VMweKJ^dk``4SQ?fomuwyVsm4 zK@gb)|ms(-|-D}oJ32`XhbAqBimg)g!x%IqkF?$Jm+p}ex7S&7U^}w;3JL&=M z%K6r!T)#fa2v>CFsObBT=R8vv{CBPx&ejXyBP&e(+e<=A8H# z)T5tTY}&1!SjCN{oEVB$13q#TD*TuRS7RR}8K6Knsq5$S+)TwrqzCBr!HN`8Kecog z^LcWerO`EnsNsC7m>W8Kb!w2x3n;_vEKKra#ipU;4YISY4<7Mgb*tu=>TThlvXokO z*ACN8%Me!@DZQNk3o4g?^na~0Qf(i!rn#J8?^g7PnE*KzUHR|Mh7)fw{~`QWGjew+ XeG|nD`J|G)<_D}1Nb_fAJ}G|#7{mqp diff --git a/www/asset/png/lance-dawn.png b/www/asset/png/lance-dawn.png deleted file mode 100644 index a4a12eb050a63e3ff0954d89d5fa5afaaa966977..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1237 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_qWSPZ!6KiaBrR9`w8IAmaLd zM~&mIwFkPt=|?3l`8GR2(1R(YN$$JC~?D9}^C_L0-drta(rTeCq zCR@4ZPk#QKr6Q57D-y@`l2haEqNbC92cN7+C<-yK)S4k9>eI)i=FU2K^7IEcmmJ9n z;yRzS`iZK9>(+=*iuwmxzXc}5S4rJYaj#;EKDubqyhGbgE^iXwTfQ;nO8aNQo<*zv z&zxPuV*hThZ_>U*g@(YAyH=YH000{^NvmLG1anOZcBF?pXIc?Na+s zR{Qt+`*eO8>`xF0-F(0BfPO{r`m+o8KXc!^l5PCr@SUdX-{aY9<)4Z5Eb7|qSN~A# z&E@2izYVr0%$M`N*UtNPIrn=t<~!o{d#}}+*7uq(j+Z^}!E%r5U-X{Oao^9MyUmxb z#I^nb)4!(f>i?zHZ0jHF{JP#NID_?jpFpPBgQFLWJ6O*<`|YmkdMA6WC1ferJJIsV zj17(l#G0R)l@xICb$ak!T5&|vLPcrqwue1lbDEr$CN-OA6>{Y%b(?5S9$Lidli;zUNPjr!(%E4+5)~D{j8Klqi zGEm=(MMBdbm;v>PP2It=9jMQ62eQcV3C?0uPq1u<==0tU)_2KH$#TUaq5cOFKz$-p zD_D+qc&y%7fg&=+Nn~mVi#yOGwyx^#b3pn|CZ3tzkzCtrHqF4Msl0u{?VnoDn#P*{v`7{n5i9Im$YEtq}rEG>k_^)U9#QL9bOiz z`ujp}%*9u%OIW8L)@;c9Qs}H&t|~r5uRoCIr*-7!&s@jiPWmlUUSl~yOh-SI2KH&-g=P$J4K$-d$;4bNln*t=qN#@1HDMyuWk8vV`{k z!ux)5uA98)WvtA`Z?hRPxy#nnANo80!SPA8_ZJB#??3d#c|&s9HvZ;TiEhDDkKZTW zp5MpF@bqNG>fck3M4jIKXGToL8e_5f>FwTD;TK~+T(W)n?wzAL^Y(;zA%)Gz8DsJ!2JK8!S?)mZ606!i@+j@!PC{xWt~$(69AZdFAx9# diff --git a/www/asset/png/lance-dusk.png b/www/asset/png/lance-dusk.png deleted file mode 100644 index 6fdd1262e35a38af521ddb02c6a39437e1ea7276..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1273 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_qWkPZ!6KiaBrRItEEOO0>o& zn@m`$o-px_l4j$KWRFVjyO!@vPQM9WTG17vIiXeDE3~2a^359!EXQBY%-i++>;KT5 zo`$x(^Z(o2>u}3ltN7s(*Iveh`bslo1bzBA)ZAGnD>t1KJouy|p~%I+(vvgpj?eDy z*_q~F?8-cYnl?`VzG3=zr~KsUDykOe3#UA{;M&_C`$_K$TUlq&i`ILG*LkKj&ELG8 zTVbV)pWBD-H@+I}lLhz8+pGDsz^Li^XY-{xeoh~v{z*+b`A~%=|Lh%?A`8xgw~frF z$^gZ;o$*y|f61DEGTvFp(no)XLALOT#Hxd?k+YrnE_3Uhx+PLHBdWmhpVW(DAic6j z^?YL1!BnGZw(Tofr=N`YCbLZR&-3T&okgCnU-E+iCn z(Chh9R_(Rk_S-TqaGhIt%75vmik!m>MAzsn$#h^b=i7aHUCbisjOUIlBrQlKI#%+Vk-SAvq<^K(<_}1 zIDWp%Qe^#8ur(|rgQdK!Ih)~m>GeQ|YjS*gvN@Vu87$0SRX3kxJ;B0!Rn_?aMXUbkuH6 zzjNU-w~InWVe$RTYp)a?9x3V%|iU!aeH zgW1uEKKcSDS(rc89N(?5?72|ygKsZChOGS4-L|WI!<}UAU(#NGd?FZUzW&@+m%P=_ zWcrIzvGVWpi}#i;EVi`rj*x}L>uQRk}v@ZhaniOU9Vwz!FN znqm}q4<2ZH67qzJ(SDO~!{(bAdl(vkyi86%uQiHOy-PMhasR1P*ch-Q52>CaiBqgL!+d8~a(tKmRwJ%~(*&{9bO`%<7E!2Y$Eb zJ`=ko|6$kup6@enin8V3PpN#T`}wPprn=sPPpMPoAKcktn0~^p!TVsZrIVig4wE}G zgr96`^gh~a>7XaRqvTG+!xO1Y)dzb;dt&4^nBR$5cp{at`e?6cM~v8p^g9t8hwpg* z*uJUZ>N%`l{md(Kh-0Ch8^?OaJ8ue}NuJRYcy*lP;8n>rY3@74HzF zshD_wL-qN@GNuP7+lZ~CTk|B zdw!N&CNXD!MCI8eBOkZA3b{$^tR4R4UDVvcP*7jV^&tIF&-RFG>#xOKwcq`zV)r-J zIbGZg{Hvm8?2WNsKKId%UsFw%JEz2Nf03J48~5x^@V>GQw=A$l5^BuFGW^?@^yc=# zu!$@680zz6H;N{!d}N-zRry;}!`HAL c=Im$G-t%nD|KHh;z&yj?>FVdQ&MBb@0LGSt_5c6? diff --git a/www/asset/png/militia-dusk.png b/www/asset/png/militia-dusk.png deleted file mode 100644 index 078b79c496111c1d9be0a924946857804a7e3b57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 911 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?4j!ywFfJby(BP*AeO zHKHUqKdq!Zu_%?Hyu4g5GcUV1Ik6yBFTW^#_B$IX1_ovsPZ!6KiaBrZ?#&Z+lxTfu z9QQey(VS6rI)m#I1*=mWiY>0~BAlJ)N(!xlo^eg^NzYT<;mtl}AIrZfR&1Id-4A+)5bcHR2^&Cg{QRW>Wz&YGIryZPXm1r@JN_A-R#GpYF>c)~fuhH#&S%AA`AD zzl?-`=)ZpjUkVbB#x>Y_{h9ojFQ+7VLv6_GLsA`F6|6UkN-l^T$ZpVeXMQWSgY8Dq zyM?h#`3&3UL}!#A;9TGH`pp9K7yENx+|qvfDmUoxj)ueAL-QJp5A^PIDr1}@n0JP^ z=-UCwN5^6m(s}0?=IK;F$!U~*aBP#?IiVYZc{9M=t?to4&M4RaqK zYxC7l*tw?R>MyK*EasJI2z-9$9pg8)10Q!iGd+B!;qRgizZqiY9@M&?TgA9qtl{EW z$NK;G``a1#{_pu;QXcq#Q=p8&X2+?Tv>A#IlDBU-`Stnj%}W>Ftg&HjSYwy$r@K0M z>gSIpnTD!&R_#<{C==4ReaZXFUw>wPhs%?D3*?2?M_0egyQsN?p`dK-X70o1TQvR8 z9lrVcblpnx7xD)lzP%+{pryxfr*d0qlHwk(871Gpbsyg!^z2K=n^$6cPpv(FdQR!n zEcVr_@kSolx9eFCeEZvUFkd>_&3P+>Ma@bXqmcfIF8nzw+;|)hu-tsUYGP~aDZ^t<8 diff --git a/www/asset/png/tower-dawn.png b/www/asset/png/tower-dawn.png deleted file mode 100644 index 34c1bf570b31004159cbd3d33a4b0fe83c48e852..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2296 zcmZ{mXH?V85{Lh2h_nDkG}0k71;i3U2c@X=BGrfp(g~12&|pE3NGBjKFH%BBFo1zb z4T@kuqzDMoiP}=XXnh>GrMPZV=T?DLb!yu004j(85&$; zY4C630JFTho62XFaAFOw;{brS`M0sPV#sbRC>V6vA;=oz5rlUMbO-Qwypp#+8t3YQ zbyva!dXiSPgaCje!pK0^COC5?%fd^6~@g6UQI~bUGIt(ih6@3OSQF;}cZb zfXcqGYZzu>W(Y(N16p)ja((}d-qVy)wCYrY zJ;qBQy*04zbnb;T1^Q#1_Rp*6l#UwsjPwO;VS&*QR~ZN=sF?qyQ4i5KeW+iWVXKj` z#p#keuC2y)hW=gJWEUEL$%I;DtCi}kaQjU1E>to5nR9P7Yq6k?hkRh#F%jk+Dq`6D z6kQA-K0UHZ@16o_z~sUvm=dPc&(P&M&i&Nq~H;(~pYf z3k84;^5l~lFpf3!tvxD4gUE{S13l=OY&XegQ|i&Cmx}THp}6d(+w0P+KAmOEcu!I@ zhx{#I@o}3J7rb1*n7}dA)DPa2-R&+Busin$gr(?XPU2>{>6fuYh!j5&iQ% zW)}}z-GYxsg1YtD6ASDPy2+A83Y_&|lE(`+RNmy(CAn#KO}0H`#k_<;!n>SI=KbuE zgyc>IeX9I|V@#%9*H zB(R%|(J#gyZRDNQW9K8u`GO(u5#CM&qFKV_N!nUSc{d}FD*UuygE8e^RqwjHxxS)W zo8`lsj|>69%LNm*Pur|Z`w*G(&u$(Ls3g>yLgOVS0=6kX5|N|8c2mwzQ8=-!;FI&l z*CDl?L4QZg+1qWJyyUB{t(L*!)l)zZ2lVe2B)_U=@G%kb?YZUR&)K$`%4|vYfPvX) zOhiPE#6%*HKPMk8VnAtVjH`3_%s)6C$;^&^4sX_=zO`$By>oCQ*)fV_idqCSIbD;b z7{BpgOh3&##yijNE|%5hz_i6r{x+;GUto`R`Q;!W;Gu`~N137wNurrxlF*T&@0G%p zeLpZ;l$7`4Hizh>VxL*%KeNJ~h3PtRDK?DA9h0&YKZ`iDB&+5#BV*#F{JhHkcUI=e zBj;`QPQr`qW&=)DNd_xbeY--@3h$VPoe!Kc-bq&c{%QxZV49&!|1jOX_Fb#7j#Gca zJ}j10x~@|~uOtuTL|rR7d*D=-Qv2NUx_v++BE3y2-V)8{M7DN zE>JLbF|9u5T}kMh8paOI?vs2(RhwdmQlv7*`LkjSg`hrD{Yx~kT~(P#-wcm=L~*-I z>Ep_mj8W#Do9bmT6(e*{DvOn9R+cejdq2LeFgoT^N%-XK+AI;&xD+5-%bh-y0@*y& z+yQ`d9*O`H^#dXSnl9+myN4zv4z z2wnnx7c1zC5qMeup)2k=N-+1|aYlsvHy3|a=^&rkU7qZhBlK@EynS-&2HSZuKY*sQ zvc!-@P!Phq0)&HLv~>rUB@k)o;3duqk0Yscx8@rbPv3%-2#m@0-b72m&Q15{zi*0q z+^dy=*DkX9Pd0As$TyQ40$jeeyq`9r{v~?Zqz%I9<;0yxXY*3(h~*5AihV}qNubeUGc qKXRwSIKc$l9so`i@%%rsCth2kzl!wAsAm0I0He$124#BA;r{{}gF!z4 diff --git a/www/asset/png/tower-dusk.png b/www/asset/png/tower-dusk.png deleted file mode 100644 index db8200a9dc22e79e27069d80ade023d6f8314ba4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2444 zcmZ{mXFMBf8^$A6wHh=wsaA)gMWR%W5wld)C?ZCQ6`K?lt8GG;6109nYSUp?5sgtv z)Tr7;YXq@-YB$HOdUMWsKfdpW`?;>)|Gw`p_w(gSu(mWkepLD>0020C&CK{FMIffZKa!svRJGFLu))g%D#KpyLod1~^Nn&|=1w=QdbBmfH;TY;+806W^gt5seY z3{H7S7S+0u1S)z8TiMFYhyDQoIQ-U;OGiwM#!$dRrN`>q)1FmLj__Fn@#rOM0ae6c zH~giFom7*}m=3y$*?lQ>AgX`Y6S73o;Z86*hgI}{k)wVci1WNvd{K)qxPF~I(JMlR z5ZZ)CLayY{{b~y%-iO)wn4&d!g8k-rEI)ELgjGqirL|wvPcDE2_m+zunWJs`Bn$6g zV5p=-o7hUh*VRgAtdy%AD1K*k2_Z}u!SQ2x3!>?yd`i_5c3&_MZ>Y)JNH8t61EzUb zIY~HiStyQh`aD!B0-&pZ>Lg_H>Oua*OZGk2bGL z2_BVrTd1~a+mb-}1F4JOy{q2MIEX1=mD<-AO#HhUtS+>t-NxQknG8lr@c`V0Ho(YW zv=^V){a}*baQY7jwET13&TD$CRG>?^jWTQjolkc0k+X1M;EyVTmZo zlcVjccRSVTB5dAaSql=Q)WzX2)A}yI62SoJkt8sT%Rhm7(8VhaE?v_dcep!eb1PP*j<}pj#{ll}qCizYyJ^2g< zSbri4AY5YYqgGEh=U4zG31*ynteaz52{MHW1ikpt$G)p3$WYKEAY3Z-n@FRU{l=BP zM9Yl6d+fZlZ1%MCnj8tfhQr66+e!!S2t?ffbE@K@302i_Owg4Ux+IJrxk{_#rPzk( zdkN6fcax9gB1wpYwI4ZDHF6YoWh=wGmlhy`)WUyzc43CP$PDr2t$QIG zbH^9EHP?toa>af*00?EE>JMtk4Z zPZ(WhiH%@FBX=I}{tQ#iX?jz!1=T-1vF{}iCnK~(oOfRoSSph!XaMFjaraocX5q{9 zdhkI|hX;V#19Bp`L*aJMU9t1O*xT{QU74dz5;50#sG_cJceyiE$D7|g*laJu|B~If zCtP167UlN-(nugw?d{71UdtDLYh*gQJ^n z_3h2xX*HdnC-HN#m-Z4_YjP^n%!CRr@Sjk%EJ!O*;YSy-wj9=PUeO@3w1r$?IY*hW{0POB>GDO;hBd*y6a>-TH(P(+|}V<{8?J@{w~9A+>r`V%$v$Wbn#_qel&+NXY$yyG^CJf{8v5(^x+NVc4S zGxUPbUmbC%8*WL$@aNF0GG%X|gTCBlc6%}Zfy#b{1n~KetBU?e*jK9UF%MLQ%a(KE zlYiz5=n0m4+r=8IwUS6>?wypV?1AM|qEuS0C`&QPX4W#%fCA&OBAYUWa|qPV0($K7 z8FB#A@0pWwz@gQB$sUjr!PX*KWp;=<=%HT`Xy_AREs!0m(4hkUmoOjpEa#ulnqO3+ zU-oDEL5q=md83STRXua~W;jI}=d?WhStJ$3s3AiFX+oCa$@(e}tWMJCJLsFv5mgm0 zOJI@bb337eu$p)abY-s;zvkbj`HxULrm!xcOC62@ama%E%SIu@#-SME(Y(`T?WP!Ue19j0&^2d zSJC#d8f%iTkMy*e*L(_H7P>68JR+A(=j{-1!E!|5{Yne-_teAePhHQ#r2aww?R3B$ jc{{xSPt^Et{@6u?Ol-7GK|7ywk`&;YiKTI+Av*3qR&bs# diff --git a/www/asset/tower_dusk.svg b/www/asset/tower_dusk.svg deleted file mode 100644 index 459adef..0000000 --- a/www/asset/tower_dusk.svg +++ /dev/null @@ -1,111 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/www/js/const.js b/www/js/const.js index 7f3e55f..caf3d74 100644 --- a/www/js/const.js +++ b/www/js/const.js @@ -46,7 +46,7 @@ const OpCode = { SessionLeave :0x002F, GameState :0x0030, - GamePlay :0x0031, + GameMessage :0x0031, GameHistory :0x0032, Challenge :0x0060, @@ -61,3 +61,17 @@ const GameState = { Ongoing :0x01, Complete :0x02, }; + +const GameMessage = { + Error :0x00, + + Move :0x01, + Drop :0x02, + Alt :0x03, + + Online :0x08, + Undo :0x10, + Retire :0x11, + + Reaction :0x20, +}; diff --git a/www/js/game.js b/www/js/game.js index 898aeeb..c16c58a 100644 --- a/www/js/game.js +++ b/www/js/game.js @@ -246,7 +246,6 @@ GAME.Game = class { this.state = { code:0, check:false, - checkmate:false, }; this.update_board(); @@ -335,7 +334,9 @@ GAME.Game = class { } } - if(moves == 0) { this.state.checkmate = true; } + console.log(moves); + + if(moves == 0) { this.state.code = GAME.Const.State.Checkmate; } } } @@ -347,7 +348,7 @@ GAME.Game = class { // Move piece on board. switch(play.source) { case 0: - case 3: { + case 2: { let piece_id = this.board.tiles[play.from].piece; let piece = this.board.pieces[piece_id]; piece.tile = play.to; @@ -389,7 +390,7 @@ GAME.Game = class { } // Handle alt moves. - if(play.source == 3) { + if(play.source == 2) { switch(moves.alt) { case 1: { piece.promoted = false; @@ -410,11 +411,6 @@ GAME.Game = class { this.pools[this.turn & 1].pieces[play.from] -= 1; this.turn++; } break; - - // Play retired. - case 2: { - this.state.code = 2; - } break; } // Recalculate new board state. @@ -784,9 +780,9 @@ GAME.Const = { }, State: { - Ongoing: 0, - Complete: 1, - Resign: 2, + Current: 0, + Checkmate: 1, + Resign: 2, }, Direction: [ diff --git a/www/js/game_asset.js b/www/js/game_asset.js index 776f599..03c6533 100644 --- a/www/js/game_asset.js +++ b/www/js/game_asset.js @@ -1,21 +1,64 @@ -const GAME_ASSET = { }; +const GAME_EMOJI = [ + "Promote", + "Militia", + "Lance", + "Knight", + "Tower", + "Castle", + "Dragon", + "Behemoth", + "Heart", +]; +const GAME_EMOJI_COLOR = [ + "Promote", + "Dawn", + "Dusk", +]; -GAME_ASSET.load_image = (image) => { - let img = new Image(); - img.src = image; - return img; -}; +class GameImage { + constructor(paths=[]) { + this.paths = paths; + } -GAME_ASSET.Image = { - Promote: GAME_ASSET.load_image("/asset/promote.svg"), - Piece: [ - [ GAME_ASSET.load_image("/asset/militia_dawn.svg"), GAME_ASSET.load_image("/asset/militia_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/lance_dawn.svg"), GAME_ASSET.load_image("/asset/lance_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/knight_dawn.svg"), GAME_ASSET.load_image("/asset/knight_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/tower_dawn.svg"), GAME_ASSET.load_image("/asset/tower_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/castle_dawn.svg"), GAME_ASSET.load_image("/asset/castle_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/dragon_dawn.svg"), GAME_ASSET.load_image("/asset/dragon_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/behemoth_dawn.svg"), GAME_ASSET.load_image("/asset/behemoth_dusk.svg") ], - [ GAME_ASSET.load_image("/asset/heart_dawn.svg"), GAME_ASSET.load_image("/asset/heart_dusk.svg") ], - ], -}; + draw(ctx, scale=1., offset=[0, 0], color=null) { + for(let path of this.paths) { + if(color === null) { + ctx.fillStyle = path[0]; + } else { + ctx.fillStyle = color; + } + + let origin = [0, 0]; + + ctx.beginPath(); + for(let segment of path[1]) { + switch(segment[0]) { + case 0: { + origin = segment[1]; + ctx.moveTo( + (scale * segment[1][0]) + offset[0], + (scale * segment[1][1]) + offset[1], + ); + } break; + case 1: { + ctx.lineTo( + (scale * segment[1][0]) + offset[0], + (scale * segment[1][1]) + offset[1], + ); + } break; + case 2: { + + } break; + case 3: { + ctx.lineTo( + (scale * origin[0]) + offset[0], + (scale * origin[1]) + offset[1], + ); + } break; + default: console.log(segment.mode); + } + } + ctx.fill(); + } + } +} diff --git a/www/js/interface.js b/www/js/interface.js index 6917e8e..97654c6 100644 --- a/www/js/interface.js +++ b/www/js/interface.js @@ -18,6 +18,8 @@ const INTERFACE = { TileMedium: "#242424", TileDark: "#101010", + Promote: "#a52121", + Dawn: "#ffe082", DawnMedium: "#fca03f", DawnDark: "#ff6d00", @@ -60,8 +62,8 @@ const INTERFACE = { }, resolve_board() { - for(let i = 0; i < INTERFACE_DATA.board_state.length; ++i) { - INTERFACE_DATA.board_state[i] = [0, 0]; + for(let i = 0; i < INTERFACE_DATA.Game.board_state.length; ++i) { + INTERFACE_DATA.Game.board_state[i] = [0, 0]; } if(INTERFACE_DATA.select !== null) { INTERFACE.resolve_piece(INTERFACE_DATA.select, 1); } @@ -103,18 +105,18 @@ const INTERFACE = { 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) { - INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid; + INTERFACE_DATA.Game.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid; /*if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)] > 0) { - INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Threat; + INTERFACE_DATA.Game.board_state[movement.tile][zone] = INTERFACE.TileStatus.Threat; } else { - INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid; + INTERFACE_DATA.Game.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid; }*/ } else { - INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Opponent; + INTERFACE_DATA.Game.board_state[movement.tile][zone] = INTERFACE.TileStatus.Opponent; } } else { - INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Invalid; + INTERFACE_DATA.Game.board_state[movement.tile][zone] = INTERFACE.TileStatus.Invalid; } } } @@ -123,7 +125,7 @@ const INTERFACE = { hover(event) { let initial_hover = INTERFACE_DATA.hover; - let apothem = INTERFACE_DATA.Ui.scale; + let apothem = INTERFACE_DATA.Render.scale; let radius = INTERFACE.Radius * apothem; let halfradius = radius / 2; let grid_offset_x = 1.5 * radius; @@ -133,11 +135,11 @@ const INTERFACE = { INTERFACE_DATA.hover = null; // Handle board area - if(event.offsetY >= INTERFACE_DATA.Ui.margin.t && event.offsetY < INTERFACE_DATA.Ui.margin.l + INTERFACE_DATA.Ui.area.y) { - if(event.offsetX >= INTERFACE_DATA.Ui.offset.x && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.board_width) { + if(event.offsetY >= INTERFACE_DATA.Render.margin.t && event.offsetY < INTERFACE_DATA.Render.margin.l + INTERFACE_DATA.Render.area.y) { + if(event.offsetX >= INTERFACE_DATA.Render.offset.x && event.offsetX < INTERFACE_DATA.Render.offset.x + INTERFACE_DATA.Render.board_width) { - let basis_x = INTERFACE_DATA.Ui.offset.x + halfradius; - let basis_y = INTERFACE_DATA.Ui.offset.y + (14 * apothem); + let basis_x = INTERFACE_DATA.Render.offset.x + halfradius; + let basis_y = INTERFACE_DATA.Render.offset.y + (14 * apothem); let x = (event.offsetX - basis_x) / grid_offset_x; let y = -(event.offsetY - basis_y) / apothem; @@ -165,10 +167,10 @@ const INTERFACE = { } // Handle pool area - else if(event.offsetX >= INTERFACE_DATA.Ui.pool_offset && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.area.x) { + else if(event.offsetX >= INTERFACE_DATA.Render.pool_offset && event.offsetX < INTERFACE_DATA.Render.offset.x + INTERFACE_DATA.Render.area.x) { - let basis_x = INTERFACE_DATA.Ui.pool_offset + halfradius; - let basis_y = INTERFACE_DATA.Ui.offset.y + (3 * apothem); + let basis_x = INTERFACE_DATA.Render.pool_offset + halfradius; + let basis_y = INTERFACE_DATA.Render.offset.y + (3 * apothem); let x = (event.offsetX - basis_x) / grid_offset_x; let y = (event.offsetY - basis_y) / apothem; @@ -198,13 +200,13 @@ const INTERFACE = { } } - if(initial_hover != INTERFACE_DATA.hover) { INTERFACE.draw(); } + if(initial_hover != INTERFACE_DATA.hover) { INTERFACE.step(); } }, unhover() { let redraw = (INTERFACE_DATA.hover !== null); INTERFACE_DATA.hover = null; - if(redraw) { INTERFACE.draw(); } + if(redraw) { INTERFACE.step(); } }, click(event) { @@ -228,8 +230,20 @@ const INTERFACE = { // Play selection. if(INTERFACE_DATA.hover.source == 0 && (INTERFACE_DATA.mode == INTERFACE.Mode.Local || INTERFACE_DATA.player == (GAME_DATA.turn & 1))) { - let tile_state = INTERFACE_DATA.board_state[INTERFACE_DATA.hover.tile][1]; + let tile_state = INTERFACE_DATA.Game.board_state[INTERFACE_DATA.hover.tile][1]; result = +(tile_state == INTERFACE.TileStatus.Valid || tile_state == INTERFACE.TileStatus.Threat); + if(INTERFACE_DATA.select.source == 1) { + let pool_selected = +(INTERFACE_DATA.select.tile >= 7); + + pool_selected ^= (INTERFACE_DATA.player & 1); + if(INTERFACE_DATA.mode == INTERFACE.Mode.Local) { + pool_selected ^= INTERFACE_DATA.rotate; + } + + if((GAME_DATA.turn & 1) != pool_selected) { + result = 0; + } + } } // Alt move selection. @@ -254,7 +268,7 @@ const INTERFACE = { console.log("D1"); let source = INTERFACE_DATA.select.source; if(source == 0 && INTERFACE_DATA.alt_mode) { - source = 3; + source = 2; } let play = new GAME.Play( @@ -270,7 +284,7 @@ const INTERFACE = { case 0: { console.log("D2"); - // Handle new selection. + // Handle new selection. INTERFACE_DATA.select = null; INTERFACE_DATA.alt_mode = false; @@ -310,7 +324,7 @@ const INTERFACE = { } break; } - INTERFACE.draw(); + INTERFACE.step(); }, contextmenu() { @@ -325,7 +339,7 @@ const INTERFACE = { if(INTERFACE.Ui.match_select(INTERFACE_DATA.hover, INTERFACE_DATA.select)) { INTERFACE_DATA.select = null; INTERFACE_DATA.alt_mode = false; - INTERFACE.draw(); + INTERFACE.step(); } else { INTERFACE.click({button:0}); } @@ -337,11 +351,11 @@ const INTERFACE = { let width = INTERFACE_DATA.canvas.width = INTERFACE_DATA.canvas.clientWidth; let height = INTERFACE_DATA.canvas.height = INTERFACE_DATA.canvas.clientHeight; - INTERFACE_DATA.Ui.margin.t = Math.floor(Math.min(width, height) / 96); - INTERFACE_DATA.Ui.margin.l = 1.75 * INTERFACE_DATA.Ui.margin.t; - INTERFACE_DATA.Ui.margin.r = INTERFACE_DATA.Ui.margin.t; - INTERFACE_DATA.Ui.margin.b = 3 * INTERFACE_DATA.Ui.margin.t; - let margin = INTERFACE_DATA.Ui.margin; + INTERFACE_DATA.Render.margin.t = Math.floor(Math.min(width, height) / 96); + INTERFACE_DATA.Render.margin.l = 1.75 * INTERFACE_DATA.Render.margin.t; + INTERFACE_DATA.Render.margin.r = INTERFACE_DATA.Render.margin.t; + INTERFACE_DATA.Render.margin.b = 3 * INTERFACE_DATA.Render.margin.t; + let margin = INTERFACE_DATA.Render.margin; let gui_width = width - (margin.l + margin.r); let gui_height = height - (margin.t + margin.b); @@ -354,40 +368,43 @@ const INTERFACE = { let gui_scale = gui_height * INTERFACE.Scale; - INTERFACE_DATA.Ui.area.x = gui_width; - INTERFACE_DATA.Ui.area.y = gui_height; - INTERFACE_DATA.Ui.scale = gui_scale; + INTERFACE_DATA.Render.area.x = gui_width; + INTERFACE_DATA.Render.area.y = gui_height; + INTERFACE_DATA.Render.scale = gui_scale; - INTERFACE_DATA.Ui.offset.x = (INTERFACE_DATA.Ui.margin.l - INTERFACE_DATA.Ui.margin.r) + (width - gui_width) / 2; - INTERFACE_DATA.Ui.offset.y = (INTERFACE_DATA.Ui.margin.t - INTERFACE_DATA.Ui.margin.b) + (height - gui_height) / 2; + INTERFACE_DATA.Render.offset.x = (INTERFACE_DATA.Render.margin.l - INTERFACE_DATA.Render.margin.r) + (width - gui_width) / 2; + INTERFACE_DATA.Render.offset.y = (INTERFACE_DATA.Render.margin.t - INTERFACE_DATA.Render.margin.b) + (height - gui_height) / 2; - INTERFACE_DATA.Ui.board_width = Math.ceil(INTERFACE.BoardWidth * gui_scale); - INTERFACE_DATA.Ui.pool_offset = INTERFACE_DATA.Ui.offset.x + Math.floor(INTERFACE.PoolOffset * gui_scale); + INTERFACE_DATA.Render.board_width = Math.ceil(INTERFACE.BoardWidth * gui_scale); + INTERFACE_DATA.Render.pool_offset = INTERFACE_DATA.Render.offset.x + Math.floor(INTERFACE.PoolOffset * gui_scale); + }, + + step() { + if(INTERFACE_DATA === null) return; + + INTERFACE.resolve_board(); + + if(INTERFACE_DATA.Timeout.draw === null) { + INTERFACE.draw(); + } }, draw() { - if(INTERFACE_DATA === null) return; - INTERFACE.resize(); - INTERFACE.resolve_board(); - INTERFACE.render(); - }, - - render() { let canvas = INTERFACE_DATA.canvas; let ctx = INTERFACE_DATA.context; let width = canvas.width; let height = canvas.height; - let gui_margin = INTERFACE_DATA.Ui.margin; - let gui_offset = INTERFACE_DATA.Ui.offset; - let gui_scale = INTERFACE_DATA.Ui.scale; + let gui_margin = INTERFACE_DATA.Render.margin; + let gui_offset = INTERFACE_DATA.Render.offset; + let gui_scale = INTERFACE_DATA.Render.scale; let play = null; - if(INTERFACE_DATA.replay_turn > 0) { - play = INTERFACE_DATA.history[INTERFACE_DATA.replay_turn - 1]; + if(INTERFACE_DATA.Replay.turn > 0) { + play = INTERFACE_DATA.Game.history[INTERFACE_DATA.Replay.turn - 1]; } @@ -397,11 +414,49 @@ const INTERFACE = { ctx.fillRect(0, 0, width, height); + // Draw particles + for(let p = 0; p < INTERFACE_DATA.Animation.particles.length; ++p) { + let particle = INTERFACE_DATA.Animation.particles[p]; + + ctx.save(); + if(particle !== null) { + if(particle.time > 0) { + let name = GAME_EMOJI[particle.index]; + let color = INTERFACE.Color[GAME_EMOJI_COLOR[particle.color]]; + + ctx.translate((width / 10) + particle.position[0], height - particle.position[1]); + ctx.rotate(particle.rotation); + + if(particle.time < 1) { + ctx.globalAlpha = Math.max(0, particle.time); + } + + GAME_ASSET.Image[name].draw(ctx, particle.scale * gui_scale, [0, 0], color); + + particle.position[0] += particle.velocity[0]; + particle.position[1] += particle.velocity[1]; + particle.rotation += particle.angular; + + particle.time -= 30. / 1000; + } else { + INTERFACE_DATA.Animation.particles[p] = null; + } + } + ctx.restore(); + } + let temp = INTERFACE_DATA.Animation.particles; + INTERFACE_DATA.Animation.particles = [ ]; + for(particle of temp) { + if(particle !== null) { + INTERFACE_DATA.Animation.particles.push(particle); + } + } + + // Draw tiles let radius = INTERFACE.Radius * gui_scale; let basis_x = gui_offset.x + radius; let basis_y = gui_offset.y + (13 * gui_scale); - let icon_radius = 0.69 * radius; ctx.lineWidth = Math.min(gui_scale * 0.06, 3); @@ -413,13 +468,13 @@ const INTERFACE = { let is_hover = INTERFACE.Ui.tile_is_hover(0, i); let is_select = INTERFACE.Ui.tile_is_select(0, i); - let tile_state = INTERFACE_DATA.board_state[i][1]; - let hover_state = INTERFACE_DATA.board_state[i][0]; + let tile_state = INTERFACE_DATA.Game.board_state[i][1]; + let hover_state = INTERFACE_DATA.Game.board_state[i][0]; let draw_piece = true; - if(INTERFACE_DATA.Animate.play !== null) { - let play = INTERFACE_DATA.Animate.play; - draw_piece = draw_piece && !((play.source == 0 || play.source == 3) && (play.from == i || play.to == i)); + if(INTERFACE_DATA.Animation.piece !== null) { + let play = INTERFACE_DATA.Animation.piece.play; + draw_piece = draw_piece && !((play.source == 0 || play.source == 2) && (play.from == i || play.to == i)); draw_piece = draw_piece && !(play.source == 1 && play.to == i); } @@ -439,7 +494,7 @@ const INTERFACE = { if(tile.piece !== null) { piece = GAME_DATA.board.pieces[tile.piece]; } let is_play = null; - if(GAME_DATA.turn > 0 && (play.source < 2 || play.source == 3) && (play.to == i || ((play.source == 0 || play.source == 3) && play.from == i))) { + if(GAME_DATA.turn > 0 && (play.to == i || ((play.source == 0 || play.source == 2) && play.from == i))) { is_play = +!(GAME_DATA.turn & 1); } let is_check = GAME_DATA.state.check != 0 && piece !== null && piece.piece == GAME.Const.PieceId.Heart && piece.player == (GAME_DATA.turn & 1); @@ -520,16 +575,22 @@ const INTERFACE = { // Draw tile content if(draw_piece && piece !== null) { + let piece_def = GAME.Const.Piece[piece.piece]; + let piece_color = (piece.player == GAME.Const.Player.Dawn)? INTERFACE.Color.Dawn : INTERFACE.Color.Dusk; + // Draw border hints if(!is_hover) { draw.hints(piece); } - // Draw piece icon - if(INTERFACE_DATA.mirror && (piece.player ^ (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate) != 0) { - ctx.rotate(Math.PI); + if(GAME_ASSET.Image[piece_def.name] !== undefined) { + // Draw piece icon + if(INTERFACE_DATA.mirror && (piece.player ^ (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate) != 0) { + ctx.rotate(Math.PI); + } + if(piece.promoted) { + GAME_ASSET.Image.Promote.draw(ctx, 1.5 * gui_scale, [0, 0], INTERFACE.Color.Promote); + } + GAME_ASSET.Image[piece_def.name].draw(ctx, 1.5 * gui_scale, [0, 0], piece_color); } - if(piece.promoted) { ctx.drawImage(GAME_ASSET.Image.Promote, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); } - ctx.drawImage(GAME_ASSET.Image.Piece[piece.piece][piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); - } ctx.restore(); @@ -579,22 +640,22 @@ const INTERFACE = { // Player handles ctx.font = Math.ceil(gui_scale / 1.3) + "px sans-serif"; - if(INTERFACE_DATA.handles[0] !== null) { + if(INTERFACE_DATA.Session.Client.Dawn.handle !== null) { let pos = handle_pos[(1 ^ INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1]; ctx.fillStyle = INTERFACE.Color.Dawn; ctx.textBaseline = "middle"; ctx.textAlign = "center"; - ctx.fillText(INTERFACE_DATA.handles[0], pos.x, pos.y); + ctx.fillText(INTERFACE_DATA.Session.Client.Dawn.handle, pos.x, pos.y); } - if(INTERFACE_DATA.handles[1] !== null) { + if(INTERFACE_DATA.Session.Client.Dusk.handle !== null) { let pos = handle_pos[(INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1]; ctx.fillStyle = INTERFACE.Color.Dusk; ctx.textBaseline = "middle"; ctx.textAlign = "center"; - ctx.fillText(INTERFACE_DATA.handles[1], pos.x, pos.y); + ctx.fillText(INTERFACE_DATA.Session.Client.Dusk.handle, pos.x, pos.y); } // Tile information @@ -630,8 +691,8 @@ const INTERFACE = { let message = null; ctx.fillStyle = INTERFACE.Color.Text; - if(INTERFACE_DATA.auto_mode !== null) { - switch(INTERFACE_DATA.auto_mode) { + if(INTERFACE_DATA.Game.auto !== null) { + switch(INTERFACE_DATA.Game.auto) { case 0: message = LANG("auto") + " " + LANG("dawn"); break; case 1: message = LANG("auto") + " " + LANG("dusk"); break; } @@ -642,7 +703,7 @@ const INTERFACE = { message = LANG("resign"); } else if(GAME_DATA.state.check != 0) { ctx.fillStyle = INTERFACE.Color.HintCheck; - if(GAME_DATA.state.checkmate) { + if(GAME_DATA.state.code == GAME.Const.State.Checkmate) { message = LANG("checkmate"); } else { message = LANG("check"); @@ -703,13 +764,13 @@ const INTERFACE = { } - if(INTERFACE_DATA.Animate.play !== null) { - let time = Math.min(1 - (INTERFACE_DATA.Animate.time - Date.now()) / 1000, 1); + if(INTERFACE_DATA.Animation.piece !== null) { + let time = Math.min(1 - (INTERFACE_DATA.Animation.piece.time - Date.now()) / 1000, 1); time = time * time; - let play = INTERFACE_DATA.Animate.play; + let play = INTERFACE_DATA.Animation.piece.play; - let piece = INTERFACE_DATA.Animate.piece; - let target = INTERFACE_DATA.Animate.target; + let piece = INTERFACE_DATA.Animation.piece.piece; + let target = INTERFACE_DATA.Animation.piece.target; // Get to and from coordinates. let coord_to = HEX.tile_to_hex(play.to); @@ -725,7 +786,7 @@ const INTERFACE = { switch(play.source) { // Lerp between board positions. case 0: - case 3: { + case 2: { let coord_from = HEX.tile_to_hex(play.from); if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate == 1) { coord_from.x = 8 - coord_from.x; @@ -744,7 +805,6 @@ const INTERFACE = { case 0: ctx.fillStyle = INTERFACE.Color.DawnMedium; break; case 1: ctx.fillStyle = INTERFACE.Color.DuskMedium; break; } - //ctx.fillStyle = INTERFACE.Color.AnimateShadow; ctx.save(); ctx.translate(to_x, to_y); @@ -773,12 +833,20 @@ const INTERFACE = { // Draw moving piece. draw.animation_piece(piece, x, y); - if(Date.now() >= INTERFACE_DATA.Animate.time) { - INTERFACE_DATA.Animate.play = null; - INTERFACE_DATA.Animate.time = 0; + if(Date.now() >= INTERFACE_DATA.Animation.piece.time) { + INTERFACE_DATA.Animation.piece = null; } + } - setTimeout(INTERFACE.render, 1000 / 30); + if(INTERFACE_DATA.Animation.piece !== null + || INTERFACE_DATA.Animation.particles.length > 0) + { + INTERFACE_DATA.Timeout.draw = setTimeout(INTERFACE.draw, 1000 / 30); + } else { + if(INTERFACE_DATA.Timeout.draw !== null) { + INTERFACE_DATA.Timeout.draw = null; + setTimeout(INTERFACE.draw, 1000 / 30); + } } }, @@ -882,6 +950,8 @@ const INTERFACE = { // Draw tile content if(piece !== null) { + let piece_def = GAME.Const.Piece[piece.piece]; + // Draw border hints this.hints(piece); @@ -889,9 +959,14 @@ const INTERFACE = { if(INTERFACE_DATA.mirror && (piece.player ^ (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate) != 0) { this.ctx.rotate(Math.PI); } - if(piece.promoted) { this.ctx.drawImage(GAME_ASSET.Image.Promote, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); } - this.ctx.drawImage(GAME_ASSET.Image.Piece[piece.piece][piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); + if(piece.promoted) { + GAME_ASSET.Image.Promote.draw(this.ctx, 1.5 * this.scale, [0, 0], INTERFACE.Color.Promote); + } + if(GAME_ASSET.Image[piece_def.name] !== undefined) { + let piece_color = (piece.player == GAME.Const.Player.Dawn)? INTERFACE.Color.Dawn : INTERFACE.Color.Dusk; + GAME_ASSET.Image[piece_def.name].draw(this.ctx, 1.5 * this.scale, [0, 0], piece_color); + } } this.ctx.restore(); @@ -899,9 +974,10 @@ const INTERFACE = { pool(x, y, basis, player, mirror=false) { let radius = INTERFACE.Radius * this.scale; - let icon_radius = 0.69 * radius; for(let i = 0; i < 7; ++i) { + let piece_def = GAME.Const.Piece[i]; + let tile_id = i + basis; let is_hover = INTERFACE.Ui.tile_is_hover(1, tile_id); let is_select = INTERFACE.Ui.tile_is_select(1, tile_id); @@ -959,9 +1035,11 @@ const INTERFACE = { if(mirror) { this.ctx.rotate(Math.PI); } - - // Draw image - this.ctx.drawImage(GAME_ASSET.Image.Piece[i][player], -icon_radius * 0.55, -icon_radius * 0.8, icon_radius * 1.6, icon_radius * 1.6); + + if(GAME_ASSET.Image[piece_def.name] !== undefined) { + let piece_color = (player == GAME.Const.Player.Dawn)? INTERFACE.Color.Dawn : INTERFACE.Color.Dusk; + GAME_ASSET.Image[piece_def.name].draw(this.ctx, radius, [0.2 * radius, 0], piece_color); + } // Draw count this.ctx.fillStyle = player_color; @@ -1013,9 +1091,6 @@ const INTERFACE = { let dusk = null; INTERFACE_DATA = { - mode: mode, - token: token, - canvas: document.getElementById("game"), context: null, @@ -1028,19 +1103,46 @@ const INTERFACE = { clicked: null, alt_mode: false, - handles: [dawn, dusk], - board_state: [ ], - resign:false, - resign_warn:false, + mode: mode, + Session: { + token: token, + Client: { + Dawn: { + handle: null, + online: false, + }, + Dusk: { + handle: null, + online: false, + }, + Spectators: { + count: 0, + }, + }, + }, - history: [ ], - history_begin: [ ], - - replay_turn: 0, - replay_auto: false, - auto_mode: null, + Game: { + board_state: [ ], + history: [ ], + history_begin: [ ], + auto: null, + }, Ui: { + request_undo:false, + resign_warn:false, + }, + + Timeout: { + draw: null, + }, + + Replay: { + turn: 0, + auto: false, + }, + + Render: { scale: 0, margin: { t:0, l:0, r:0, b:0 }, offset: new MATH.Vec2(), @@ -1050,14 +1152,17 @@ const INTERFACE = { pool_offset: 0, }, - Animate: { - play: null, + Animation: { piece: null, - target: null, - time: 0, + // play: null, + // piece: null, + // target: null, + // time: 0, + particles: [ ], + queue: [ ], }, }; - for(let i = 0; i < 61; ++i) { INTERFACE_DATA.board_state.push([0, 0]); } + for(let i = 0; i < 61; ++i) { INTERFACE_DATA.Game.board_state.push([0, 0]); } let canvas = INTERFACE_DATA.canvas; if(canvas !== undefined) { @@ -1068,11 +1173,11 @@ const INTERFACE = { canvas.addEventListener("mousedown", INTERFACE.click); canvas.addEventListener("mouseup", INTERFACE.release); canvas.addEventListener("contextmenu", INTERFACE.contextmenu); - window.addEventListener("resize", INTERFACE.draw); + window.addEventListener("resize", INTERFACE.step); switch(INTERFACE_DATA.mode) { case INTERFACE.Mode.Local: { - INTERFACE.draw(); + INTERFACE.step(); } break; } } else { @@ -1084,7 +1189,7 @@ const INTERFACE = { case INTERFACE.Mode.Player: { MESSAGE_COMPOSE([ PACK.u16(OpCode.GameState), - INTERFACE_DATA.token, + INTERFACE_DATA.Session.token, ]); } break; } @@ -1104,23 +1209,23 @@ const INTERFACE = { }, reset() { - INTERFACE_DATA.auto_mode = null; + INTERFACE_DATA.Game.auto = null; - INTERFACE_DATA.history = [ ]; - for(let i = 0; i < INTERFACE_DATA.history_begin.length; ++i) { - INTERFACE_DATA.history.push(INTERFACE_DATA.history_begin[i]); + INTERFACE_DATA.Game.history = [ ]; + for(let i = 0; i < INTERFACE_DATA.Game.history_begin.length; ++i) { + INTERFACE_DATA.Game.history.push(INTERFACE_DATA.Game.history_begin[i]); } - INTERFACE_DATA.replay_turn = INTERFACE_DATA.history.length + 1; - INTERFACE.replay_jump(INTERFACE_DATA.history.length, false); + INTERFACE_DATA.Replay.turn = INTERFACE_DATA.Game.history.length + 1; + INTERFACE.replay_jump(INTERFACE_DATA.Game.history.length, false); }, undo() { - INTERFACE_DATA.auto_mode = null; - if(INTERFACE_DATA.history.length > 0) { - INTERFACE_DATA.replay_turn = INTERFACE_DATA.history.length + 1; - INTERFACE_DATA.history.pop(); - INTERFACE.replay_jump(INTERFACE_DATA.history.length, false); + INTERFACE_DATA.Game.auto = null; + if(INTERFACE_DATA.Game.history.length > 0) { + INTERFACE_DATA.Replay.turn = INTERFACE_DATA.Game.history.length + 1; + INTERFACE_DATA.Game.history.pop(); + INTERFACE.replay_jump(INTERFACE_DATA.Game.history.length, false); } }, @@ -1133,33 +1238,93 @@ const INTERFACE = { INTERFACE_DATA.player = data.player; } - INTERFACE_DATA.history = data.history; - let turn = INTERFACE_DATA.history.length; + INTERFACE_DATA.Game.history = data.history; + let turn = INTERFACE_DATA.Game.history.length; - if(INTERFACE_DATA.history.length > 0) { - if(INTERFACE_DATA.replay_turn == 0) { - if(INTERFACE_DATA.history[INTERFACE_DATA.history.length-1].source == 2) { - turn = 0; - } + if(INTERFACE_DATA.Game.history.length > 0) { + if(INTERFACE_DATA.Replay.turn == 0) { + //if(INTERFACE_DATA.Game.history[INTERFACE_DATA.Game.history.length-1].source == 2) { + // turn = 0; + //} } else { - turn = INTERFACE_DATA.replay_turn; + turn = INTERFACE_DATA.Replay.turn; } } - if(data.dawn.length > 0) { INTERFACE_DATA.handles[0] = data.dawn; } - if(data.dusk.length > 0) { INTERFACE_DATA.handles[1] = data.dusk; } + if(data.dawn.length > 0) { INTERFACE_DATA.Session.Client.Dawn.handle = data.dawn; } + if(data.dusk.length > 0) { INTERFACE_DATA.Session.Client.Dusk.handle = data.dusk; } if(INTERFACE_DATA.mode == INTERFACE.Mode.Review) { - document.getElementById("indicator-turn").innerText = INTERFACE_DATA.replay_turn + " / " + INTERFACE_DATA.history.length; - document.getElementById("turn-slider").setAttribute("max", INTERFACE_DATA.history.length); + document.getElementById("indicator-turn").innerText = INTERFACE_DATA.Replay.turn + " / " + INTERFACE_DATA.Game.history.length; + document.getElementById("turn-slider").setAttribute("max", INTERFACE_DATA.Game.history.length); } INTERFACE.replay_jump(turn); } break; - case OpCode.GamePlay: { - if(data.status == Status.Ok && data.turn == INTERFACE_DATA.history.length) { - INTERFACE.history_push(data.play, true); + case OpCode.GameMessage: { + switch(data.code) { + case GameMessage.Error: break; + + case GameMessage.Move: { + if(data.turn == INTERFACE_DATA.Game.history.length) { + INTERFACE.history_push(new GAME.Play(0, data.from, data.to), true); + } + } break; + case GameMessage.Drop: { + if(data.turn == INTERFACE_DATA.Game.history.length) { + INTERFACE.history_push(new GAME.Play(1, data.piece, data.to), true); + } + } break; + case GameMessage.Alt: { + if(data.turn == INTERFACE_DATA.Game.history.length) { + INTERFACE.history_push(new GAME.Play(2, data.from, data.to), true); + } + } break; + + case GameMessage.Online: { + switch(data.client) { + case 0: { + // Spectator + INTERFACE_DATA.Session.Client.Spectators.count = data.state; + } break; + + case 1: { + // Dawn + INTERFACE_DATA.Session.Client.Dawn.online = (data.state != 0); + } break; + + case 2: { + // Dusk + INTERFACE_DATA.Session.Client.Dawn.online = (data.state != 0); + } break; + } + } break; + + case GameMessage.Undo: { + switch(data.state) { + case 0: { + // Request undo + if(data.turn == INTERFACE_DATA.Game.history.length) { + INTERFACE_DATA.Ui.request_undo = true; + } + } break; + case 1: { + // Perform undo + INTERFACE.undo(); + } break; + } + + } break; + + case GameMessage.Retire: { + GAME_DATA.state.code = GAME.Const.State.Resign; + } break; + + case GameMessage.Reaction: { + INTERFACE_DATA.Animation.queue.push(data.index); + INTERFACE.reaction_generate(); + } break; } } break; } @@ -1170,23 +1335,16 @@ const INTERFACE = { switch(play.source) { case 0: - case 3: { + case 2: { let piece_id = GAME_DATA.board.tiles[play.from].piece; let piece = GAME_DATA.board.pieces[piece_id]; valid = piece.player == (GAME_DATA.turn & 1); } break; case 1: { - //let player = Math.floor(play.from / 7) ^ (INTERFACE_DATA.player & 1); - //if(INTERFACE_DATA.player == 2) { player ^= INTERFACE_DATA.rotate; } - //valid = player == (GAME_DATA.turn & 1); play.from %= 7; valid = true; } break; - - case 2: { - valid = INTERFACE_DATA.mode == INTERFACE.Mode.Player; - } break; } if(valid) { @@ -1196,21 +1354,29 @@ const INTERFACE = { case INTERFACE.Mode.Local: { INTERFACE.history_push(play, true); - INTERFACE.draw(); + INTERFACE.step(); - if(INTERFACE_DATA.auto_mode !== null && INTERFACE_DATA.auto_mode == (GAME_DATA.turn & 1)) { + if(INTERFACE_DATA.Game.auto !== null && INTERFACE_DATA.Game.auto == (GAME_DATA.turn & 1)) { setTimeout(INTERFACE.auto_play, 1000); } } break; // Send action to server for validation. case INTERFACE.Mode.Player: { - let move_data = play.source | (play.from << 4) | (play.to << 10); + let msg = GAME_DATA.turn | (play.from << 16) | (play.to << 22); + + let high = msg >> 24; + let low = (msg << 8) & 0xFFFF_FFFF; + switch(play.source) { + case 0: low |= GameMessage.Move; break; + case 1: low |= GameMessage.Drop; break; + case 2: low |= GameMessage.Alt; break; + } + MESSAGE_COMPOSE([ - PACK.u16(OpCode.GamePlay), - PACK.u16(0), - PACK.u16(GAME_DATA.turn), - PACK.u16(move_data), + PACK.u16(OpCode.GameMessage), + PACK.u32(high), + PACK.u32(low), ]); } break; } @@ -1219,12 +1385,12 @@ const INTERFACE = { rotate() { INTERFACE_DATA.rotate ^= 1; - INTERFACE.draw(); + INTERFACE.step(); }, mirror() { INTERFACE_DATA.mirror = !INTERFACE_DATA.mirror; - INTERFACE.draw(); + INTERFACE.step(); }, resign() { @@ -1235,7 +1401,7 @@ const INTERFACE = { INTERFACE.resign_reset(); MESSAGE_COMPOSE([ PACK.u16(OpCode.SessionResign), - INTERFACE_DATA.token, + INTERFACE_DATA.Session.token, ]); } else { INTERFACE_DATA.resign_warn = true; @@ -1258,12 +1424,12 @@ const INTERFACE = { }, history_push(play, animate=false) { - INTERFACE_DATA.history.push(play); + INTERFACE_DATA.Game.history.push(play); if(INTERFACE_DATA.mode == INTERFACE.Mode.Review) { - document.getElementById("indicator-turn").innerText = INTERFACE_DATA.replay_turn + " / " + INTERFACE_DATA.history.length; - document.getElementById("turn-slider").setAttribute("max", INTERFACE_DATA.history.length); + document.getElementById("indicator-turn").innerText = INTERFACE_DATA.Replay.turn + " / " + INTERFACE_DATA.Game.history.length; + document.getElementById("turn-slider").setAttribute("max", INTERFACE_DATA.Game.history.length); } - if(INTERFACE_DATA.replay_turn == INTERFACE_DATA.history.length - 1) { + if(INTERFACE_DATA.Replay.turn == INTERFACE_DATA.Game.history.length - 1) { INTERFACE.replay_next(animate); } }, @@ -1271,22 +1437,22 @@ const INTERFACE = { replay_jump(turn, animate=false) { turn = +turn; - if(turn >= 0 && turn <= INTERFACE_DATA.history.length) { - if(turn <= INTERFACE_DATA.replay_turn) { - INTERFACE_DATA.replay_turn = 0; + if(turn >= 0 && turn <= INTERFACE_DATA.Game.history.length) { + if(turn <= INTERFACE_DATA.Replay.turn) { + INTERFACE_DATA.Replay.turn = 0; GAME.init(); } let play = null; let piece = null; let target = null; - while(INTERFACE_DATA.replay_turn < turn) { - play = INTERFACE_DATA.history[INTERFACE_DATA.replay_turn]; + while(INTERFACE_DATA.Replay.turn < turn) { + play = INTERFACE_DATA.Game.history[INTERFACE_DATA.Replay.turn]; switch(play.source) { case 0: case 1: - case 3: { - if(play.source == 0 || play.source == 3) { + case 2: { + if(play.source == 0 || play.source == 2) { let piece_id = GAME_DATA.board.tiles[play.from].piece; if(piece_id !== null) { piece = GAME_DATA.board.pieces[piece_id].clone(); } @@ -1309,47 +1475,49 @@ const INTERFACE = { play = null; } } - INTERFACE_DATA.replay_turn++; + INTERFACE_DATA.Replay.turn++; } if(animate && play !== null) { - INTERFACE_DATA.Animate.time = Date.now() + 500; - INTERFACE_DATA.Animate.play = play; - INTERFACE_DATA.Animate.piece = piece; - INTERFACE_DATA.Animate.target = target; + INTERFACE_DATA.Animation.piece = { + time: Date.now() + 500, + play: play, + piece: piece, + target: target, + }; } else { - INTERFACE_DATA.Animate.play = null; + INTERFACE_DATA.Animation.piece = null; } - INTERFACE_DATA.replay_turn = turn; + INTERFACE_DATA.Replay.turn = turn; if(INTERFACE_DATA.mode == INTERFACE.Mode.Review) { - document.getElementById("indicator-turn").innerText = INTERFACE_DATA.replay_turn + " / " + INTERFACE_DATA.history.length; - document.getElementById("turn-slider").value = INTERFACE_DATA.replay_turn; + document.getElementById("indicator-turn").innerText = INTERFACE_DATA.Replay.turn + " / " + INTERFACE_DATA.Game.history.length; + document.getElementById("turn-slider").value = INTERFACE_DATA.Replay.turn; } - INTERFACE.draw(); + INTERFACE.step(); } }, replay_first() { INTERFACE.replay_jump(0); }, replay_last(animate=false) { - INTERFACE.replay_jump(INTERFACE_DATA.history.length, animate); + INTERFACE.replay_jump(INTERFACE_DATA.Game.history.length, animate); }, replay_prev(animate=false) { - INTERFACE.replay_jump(INTERFACE_DATA.replay_turn - 1, animate); + INTERFACE.replay_jump(INTERFACE_DATA.Replay.turn - 1, animate); }, replay_next(animate=false) { - INTERFACE.replay_jump(INTERFACE_DATA.replay_turn + 1, animate); + INTERFACE.replay_jump(INTERFACE_DATA.Replay.turn + 1, animate); }, replay_toggle_auto() { - INTERFACE_DATA.replay_auto = !INTERFACE_DATA.replay_auto; - if(INTERFACE_DATA.replay_auto) { + INTERFACE_DATA.Replay.auto = !INTERFACE_DATA.Replay.auto; + if(INTERFACE_DATA.Replay.auto) { setTimeout(INTERFACE.replay_auto, 1000); } }, replay_auto() { - if(INTERFACE_DATA.replay_auto) { - INTERFACE.replay_jump(INTERFACE_DATA.replay_turn + 1, true); + if(INTERFACE_DATA.Replay.auto) { + INTERFACE.replay_jump(INTERFACE_DATA.Replay.turn + 1, true); setTimeout(INTERFACE.replay_auto, 1100); } }, @@ -1359,17 +1527,17 @@ const INTERFACE = { auto() { - if(INTERFACE_DATA.auto_mode === null) { - INTERFACE_DATA.auto_mode = INTERFACE_DATA.rotate ^ 1; + if(INTERFACE_DATA.Game.auto === null) { + INTERFACE_DATA.Game.auto = INTERFACE_DATA.rotate ^ 1; setTimeout(INTERFACE.auto_play, 500); } else { - INTERFACE_DATA.auto_mode = null; + INTERFACE_DATA.Game.auto = null; } - INTERFACE.draw(); + INTERFACE.step(); }, auto_play() { - if(INTERFACE_DATA.auto_mode !== (GAME_DATA.turn & 1) || GAME_DATA.state.checkmate) { return; } + if(INTERFACE_DATA.Game.auto !== (GAME_DATA.turn & 1) || GAME_DATA.state.checkmate) { return; } function state_score(state, player) { let score = 0; @@ -1516,11 +1684,6 @@ const INTERFACE = { let result = determine_play(GAME_DATA, GAME_DATA.turn & 1, 1); if(result !== null) { - // Add 7 to tile to indicate Dusk player pool. - //if(result.play.source == 1 && (GAME_DATA.turn & 1) === 1) { - // result.play.from += 7; - //} - INTERFACE.process(result.play); } else { console.log("warn: autoplay move was null."); @@ -1541,6 +1704,43 @@ const INTERFACE = { } return null; }, + + react() + { + if(INTERFACE_DATA.mode == INTERFACE.Mode.Review) { + let high = 0; + let low = GameMessage.Reaction; + + MESSAGE_COMPOSE([ + PACK.u16(OpCode.GameMessage), + PACK.u32(high), + PACK.u32(low), + ]); + } + }, + + reaction_generate() + { + if(INTERFACE_DATA !== null) { + if(INTERFACE_DATA.Animation.queue.length > 0) { + let index = INTERFACE_DATA.Animation.queue.pop(); + INTERFACE_DATA.Animation.particles.push({ + index:index, + color:0, + scale:1.75 + (Math.random() * 0.25), + position:[0, 0], + rotation:0, + velocity:[1 - (Math.random() * 2), 2 + (Math.random() * 4)], + angular:0.01 - (Math.random() * 0.02), + time:1.5 + (Math.random() * 1.5), + }); + if(INTERFACE_DATA.Timeout.draw === null) { + INTERFACE_DATA.Timeout.draw = setTimeout(INTERFACE.draw, 1); + } + setTimeout(INTERFACE.reaction_generate, 50); + } + } + } }; INTERFACE.TileScale = 0.9; diff --git a/www/js/scene.js b/www/js/scene.js index 1c8577a..289efc9 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -620,10 +620,13 @@ const SCENES = { load(data) { // Bottom Buttons let buttons_bottom = [ ]; - if(data.mode != INTERFACE.Mode.Review) { + if(data.mode == INTERFACE.Mode.Player) { let button_resign = UI.button(LANG("resign"), () => { INTERFACE.resign(); }); button_resign.setAttribute("id", "button-resign"); buttons_bottom.push(button_resign); + } else { + let button_react = UI.button("Clap", () => { INTERFACE.react(); }); + buttons_bottom.push(button_react); } buttons_bottom.push(UI.button(LANG("back"), () => { LOAD(SCENES.Browse); @@ -728,12 +731,7 @@ const SCENES = { INTERFACE.uninit(); } message(code, data) { - switch(code) { - case OpCode.GameState: - case OpCode.GamePlay: { - INTERFACE.message(code, data); - } break; - } + INTERFACE.message(code, data); } disconnect() { LOAD(SCENES.Offline); diff --git a/www/js/system.js b/www/js/system.js index 5968ca6..45b7a64 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -309,30 +309,61 @@ function MESSAGE(event) { } } break; - case OpCode.GamePlay: { - console.log("RECV GamePlay"); + case OpCode.GameMessage: { + console.log("RECV GameMessage"); + + result = UNPACK.u32(bytes, index); + index = result.index; + let high = result.data; + + result = UNPACK.u32(bytes, index); + index = result.index; + let low = result.data; + + let opcode = low & 0xFF; + let dat = ((low >> 8) & 0xFFFFFF) | ((high & 0xFF) << 24); + let ext = (high >> 8) & 0xFFFFFF; data = { - status:0, - play:new GAME.Play(0, 0, 0), + code:opcode, }; - // Status - result = UNPACK.u16(bytes, index); - index = result.index; - data.status = result.data; + switch(opcode) { + case GameMessage.Error: { } break; - // Turn - result = UNPACK.u16(bytes, index); - index = result.index; - data.turn = result.data; + case GameMessage.Move: { + data.turn = dat & 0xFFFF; + data.from = (dat >> 16) & 0x3F; + data.to = (dat >> 22) & 0x3F; + } break; - // Play description - result = UNPACK.u16(bytes, index); - index = result.index; - data.play.source = result.data & 0xF; - data.play.from = (result.data >> 4) & 0x3F; - data.play.to = (result.data >> 10) & 0x3F; + case GameMessage.Drop: { + data.turn = dat & 0xFFFF; + data.piece = (dat >> 16) & 0x3F; + data.to = (dat >> 22) & 0x3F; + } break; + + case GameMessage.Alt: { + data.turn = dat & 0xFFFF; + data.from = (dat >> 16) & 0x3F; + data.to = (dat >> 22) & 0x3F; + } break; + + case GameMessage.Online: { + data.client = dat & 0x3; + data.state = (dat & 0x4) != 0; + } break; + + case GameMessage.Undo: { + data.state = dat & 0x3; + } break; + + case GameMessage.Retire: { } break; + + case GameMessage.Reaction: { + data.index = dat & 0xFFFF; + } break; + } } break; case OpCode.ChallengeAnswer: {