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 04f9a95..0000000 Binary files a/www/asset/png/behemoth-dawn.png and /dev/null differ diff --git a/www/asset/png/behemoth-dusk.png b/www/asset/png/behemoth-dusk.png deleted file mode 100644 index d824ba6..0000000 Binary files a/www/asset/png/behemoth-dusk.png and /dev/null differ diff --git a/www/asset/png/castle-dawn.png b/www/asset/png/castle-dawn.png deleted file mode 100644 index a0314b9..0000000 Binary files a/www/asset/png/castle-dawn.png and /dev/null differ diff --git a/www/asset/png/castle-dusk.png b/www/asset/png/castle-dusk.png deleted file mode 100644 index 48a8688..0000000 Binary files a/www/asset/png/castle-dusk.png and /dev/null differ diff --git a/www/asset/png/dragon-dawn.png b/www/asset/png/dragon-dawn.png deleted file mode 100644 index b60cd71..0000000 Binary files a/www/asset/png/dragon-dawn.png and /dev/null differ diff --git a/www/asset/png/dragon-dusk.png b/www/asset/png/dragon-dusk.png deleted file mode 100644 index d268bb1..0000000 Binary files a/www/asset/png/dragon-dusk.png and /dev/null differ diff --git a/www/asset/png/heart-dawn.png b/www/asset/png/heart-dawn.png deleted file mode 100644 index d85719b..0000000 Binary files a/www/asset/png/heart-dawn.png and /dev/null differ diff --git a/www/asset/png/heart-dusk.png b/www/asset/png/heart-dusk.png deleted file mode 100644 index a2b84af..0000000 Binary files a/www/asset/png/heart-dusk.png and /dev/null differ diff --git a/www/asset/png/knight-dawn.png b/www/asset/png/knight-dawn.png deleted file mode 100644 index c51e0ec..0000000 Binary files a/www/asset/png/knight-dawn.png and /dev/null differ diff --git a/www/asset/png/knight-dusk.png b/www/asset/png/knight-dusk.png deleted file mode 100644 index 5dc1978..0000000 Binary files a/www/asset/png/knight-dusk.png and /dev/null differ diff --git a/www/asset/png/lance-dawn.png b/www/asset/png/lance-dawn.png deleted file mode 100644 index a4a12eb..0000000 Binary files a/www/asset/png/lance-dawn.png and /dev/null differ diff --git a/www/asset/png/lance-dusk.png b/www/asset/png/lance-dusk.png deleted file mode 100644 index 6fdd126..0000000 Binary files a/www/asset/png/lance-dusk.png and /dev/null differ diff --git a/www/asset/png/militia-dawn.png b/www/asset/png/militia-dawn.png deleted file mode 100644 index f0f40db..0000000 Binary files a/www/asset/png/militia-dawn.png and /dev/null differ diff --git a/www/asset/png/militia-dusk.png b/www/asset/png/militia-dusk.png deleted file mode 100644 index 078b79c..0000000 Binary files a/www/asset/png/militia-dusk.png and /dev/null differ diff --git a/www/asset/png/tower-dawn.png b/www/asset/png/tower-dawn.png deleted file mode 100644 index 34c1bf5..0000000 Binary files a/www/asset/png/tower-dawn.png and /dev/null differ diff --git a/www/asset/png/tower-dusk.png b/www/asset/png/tower-dusk.png deleted file mode 100644 index db8200a..0000000 Binary files a/www/asset/png/tower-dusk.png and /dev/null differ 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: {