diff --git a/game/src/history/mod.rs b/game/src/history/mod.rs index 7b1c8fa..5ef84a8 100644 --- a/game/src/history/mod.rs +++ b/game/src/history/mod.rs @@ -5,6 +5,7 @@ pub struct Play { pub source:u8, pub from:u8, pub to:u8, + pub meta:u8, } impl Play { pub fn new() -> Self @@ -13,6 +14,7 @@ impl Play { source:0, from:0, to:0, + meta:0, } } @@ -22,6 +24,7 @@ impl Play { source:0, from, to, + meta:0, } } @@ -31,6 +34,7 @@ impl Play { source:2, from, to, + meta:0, } } @@ -40,6 +44,7 @@ impl Play { source:1, from:piece, to:tile, + meta:0, } } } diff --git a/game/src/lib.rs b/game/src/lib.rs index f49e924..076b20f 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -200,6 +200,8 @@ impl Game { let player = (self.turn & 1) as u8; if self.play_is_valid(play) { + let mut meta = 0; + // Move piece on board. match play.source { 0 | 2 => { @@ -207,6 +209,8 @@ impl Game { if let Some(mut piece) = self.board.pieces[piece_id as usize] { let mut swap = false; + meta = piece.class | ((piece.promoted as u8) << 3) | (piece.player << 4); + if let Some(tid) = self.board.tiles[play.to as usize].piece { if let Some(target) = &mut self.board.pieces[tid as usize] { @@ -258,6 +262,8 @@ impl Game { // Place piece from pool. 1 => { + meta = play.from | (player << 4); + self.pool[player as usize][play.from as usize] -= 1; let piece = Piece::new(play.from, player); self.board.set_piece(piece, play.to); @@ -273,7 +279,12 @@ impl Game { _ => { } } - self.history.push(*play); + self.history.push(Play { + source: play.source, + from: play.from, + to: play.to, + meta, + }); self.update_board(); Ok(()) @@ -511,7 +522,7 @@ impl Game { plays.push(PlayInfo { valid, threat, - play:Play { source:0, from:piece.tile, to:current_hex.tile, }, + play:Play { source:0, from:piece.tile, to:current_hex.tile, meta:0 }, check:checkstate.immediate(), blocking:blocking, }); diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index 47be853..9f69d50 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -458,6 +458,20 @@ pub async fn thread_system(mut app:App, bus:Bus) user.handle.clone() } else { String::new() }; + let mut last_move = 0; + + let mut index = session.game.history.len(); + let mut move_count = 0; + while move_count < 4 && index > 0 { + let play = session.game.history[index - 1]; + if play.source <= 2 { + last_move <<= 8; + last_move |= 0x80 | play.meta as u32; + move_count += 1; + } + index -= 1; + } + response.records.push(PacketSessionListResponseRecord { token:session.token, handles:[ @@ -465,7 +479,7 @@ pub async fn thread_system(mut app:App, bus:Bus) dusk_handle, ], turn:session.game.turn, - last_move:[0; 3], + last_move, viewers:session.connections.len() as u32, player, is_turn, @@ -551,6 +565,7 @@ pub async fn thread_system(mut app:App, bus:Bus) source: 0xF, from: 0, to: 0, + meta: 0, }; if let Some(session) = app.sessions.get_mut(&request.token) { @@ -652,6 +667,7 @@ pub async fn thread_system(mut app:App, bus:Bus) _ => 0, }, from, to, + meta: 0, }; let text = format!("PLAY {} {} {}", play.source, play.from, play.to); @@ -685,6 +701,7 @@ pub async fn thread_system(mut app:App, bus:Bus) _ => 0, }, from, to, + meta: 0, }; if session.game.process(&play).is_ok() { diff --git a/server/src/protocol/packet/session_list.rs b/server/src/protocol/packet/session_list.rs index 4434ec5..f427115 100644 --- a/server/src/protocol/packet/session_list.rs +++ b/server/src/protocol/packet/session_list.rs @@ -57,7 +57,7 @@ pub struct PacketSessionListResponseRecord { pub token:SessionToken, pub handles:[String; 2], pub turn:u16, - pub last_move:[u8; 3], + pub last_move:u32, pub viewers:u32, pub player:u8, pub is_turn:bool, @@ -108,7 +108,7 @@ impl Packet for PacketSessionListResponse { chunk.append(&mut pack_u16(record.turn)); // Last move - chunk.append(&mut record.last_move.to_vec()); + chunk.append(&mut pack_u32(record.last_move)); // Spectator count chunk.append(&mut pack_u32(record.viewers)); diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs index 1982e6d..99f1009 100644 --- a/server/src/system/filesystem/mod.rs +++ b/server/src/system/filesystem/mod.rs @@ -299,6 +299,7 @@ impl FileSystem { source:(data & 0xF) as u8, from:((data >> 4) & 0x3F) as u8, to:((data >> 10) & 0x3F) as u8, + meta:0, }; result.push(play); diff --git a/www/css/ui.css b/www/css/ui.css index 0d8ca76..4641b26 100644 --- a/www/css/ui.css +++ b/www/css/ui.css @@ -74,6 +74,14 @@ main>table.list td:last-child>button:hover{ background-color:#303030; } +main>table.list td>canvas { + display: block; + position: relative; + width: 8em; + height: 2em; + margin: 0 -1rem 0 -1rem; +} + main>article{ display:block; position:relative; diff --git a/www/js/system.js b/www/js/system.js index aa99d94..0b5c3c4 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -188,7 +188,7 @@ function MESSAGE(event) { dawn: "", dusk: "", turn: 0, - move: "", + moves: [ ], viewers: 0, player: false, is_turn: false, @@ -227,14 +227,15 @@ function MESSAGE(event) { index = result.index; record.turn = result.data; - // Last move - if(index <= bytes.length + 3) { - let move = new Uint8Array(3); - for(let i = 0; i < 3; ++i) { - move[i] = bytes[index]; - index += 1; + // Last moves + result = UNPACK.u32(bytes, index); + index = result.index; + let moves = result.data; + for(let m = 0; m < 4; ++m) { + if((moves & 0x80) != 0) { + record.moves.push(moves & 0xFF); + moves >>= 8; } - record.move = UNPACK.move(move); } // Reviewer count diff --git a/www/js/ui.js b/www/js/ui.js index 938750b..c9a433d 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -1,4 +1,11 @@ const UI = { + Row:class{ + constructor(element, css_class) { + this.element = element; + this.css_class = css_class; + } + }, + text(value) { return document.createTextNode(value); }, @@ -185,6 +192,8 @@ const UI = { let rows = [ ]; for(let r = 0; r < records.length; ++r) { + let record = records[r]; + let buttons = [ ]; let join_callback = function() { LOAD(SCENES.GameLoad, { @@ -193,7 +202,7 @@ const UI = { }); }; join_callback = join_callback.bind({ - token: records[r].token, + token: record.token, }); let spectate_callback = function() { @@ -203,12 +212,12 @@ const UI = { }); }; spectate_callback = spectate_callback.bind({ - token: records[r].token, + token: record.token, }); - if(records[r].player != 0) { + if(record.player != 0) { let button_resume = UI.button(LANG("resume"), join_callback); - if(records[r].is_turn) { + if(record.is_turn) { button_resume.setAttribute("class", "highlight"); } @@ -218,20 +227,45 @@ const UI = { buttons.push(UI.button(LANG("view"), spectate_callback)); } - let dawn = UI.text(records[r].dawn); - let dusk = UI.text(records[r].dusk); + let dawn = UI.text(record.dawn); + let dusk = UI.text(record.dusk); + + let moves = document.createElement("canvas"); + setTimeout((canvas, moves) => { + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + let ctx = canvas.getContext("2d"); + + let size = canvas.height / 2; + + for(let i = 0; i < moves.length; ++i) { + let piece = moves[i] & 0x7; + let promoted = (moves[i] & 0x8) >> 3; + let player = (moves[i] & 0x10) >> 4; + + let color = null; + if(player == 0) { color = INTERFACE.Color.Dawn; } else { color = INTERFACE.Color.Dusk; } + + let piece_name = GAME.Const.Piece[piece].name; + if(promoted) { + GAME_ASSET.Image.Promote.draw(ctx, 1.5 * size, [size * (1 + (2 * i)), size], INTERFACE.Color.Promote); + } + GAME_ASSET.Image[piece_name].draw(ctx, 1.5 * size, [size * (1 + (2 * i)), size], color); + } + }, 10, moves, record.moves); rows.push([ dawn, dusk, UI.text(records[r].turn), + moves, UI.text(records[r].viewers), buttons, ]); } let tbody = UI.table_content( - [ LANG("dawn"), LANG("dusk"), LANG("turns"), LANG("viewers"), "" ], + [ LANG("dawn"), LANG("dusk"), LANG("turns"), "", LANG("viewers"), "" ], rows, ); @@ -329,6 +363,8 @@ const UI = { let rows = [ ]; for(let r = 0; r < records.length; ++r) { + let record = records[r]; + let buttons = [ ]; let view_callback = function() { SCENE_FORWARD = SCENE; @@ -338,28 +374,53 @@ const UI = { }); MESSAGE_SESSION_VIEW(this.token, false); }; - view_callback = view_callback.bind({token: records[r].token}); + view_callback = view_callback.bind({token: record.token}); buttons.push(UI.button(LANG("review"), view_callback)); - let dawn = UI.text(records[r].dawn); - let dusk = UI.text(records[r].dusk); + let dawn = UI.text(record.dawn); + let dusk = UI.text(record.dusk); - switch(records[r].is_complete) { + switch(record.is_complete) { case 1: dawn = UI.span([dawn], "c_dawn bold"); break; case 2: dusk = UI.span([dusk], "c_dusk bold"); break; } + let moves = document.createElement("canvas"); + setTimeout((canvas, moves) => { + canvas.width = canvas.clientWidth; + canvas.height = canvas.clientHeight; + let ctx = canvas.getContext("2d"); + + let size = canvas.height / 2; + + for(let i = 0; i < moves.length; ++i) { + let piece = moves[i] & 0x7; + let promoted = (moves[i] & 0x8) >> 3; + let player = (moves[i] & 0x10) >> 4; + + let color = null; + if(player == 0) { color = INTERFACE.Color.Dawn; } else { color = INTERFACE.Color.Dusk; } + + let piece_name = GAME.Const.Piece[piece].name; + if(promoted) { + GAME_ASSET.Image.Promote.draw(ctx, 1.5 * size, [size * (1 + (2 * i)), size], INTERFACE.Color.Promote); + } + GAME_ASSET.Image[piece_name].draw(ctx, 1.5 * size, [size * (1 + (2 * i)), size], color); + } + }, 10, moves, record.moves); + rows.push([ dawn, dusk, - UI.text(records[r].turn), + UI.text(record.turn), + moves, buttons, ]); } let tbody = UI.table_content( - [ LANG("dawn"), LANG("dusk"), LANG("turns"), "" ], + [ LANG("dawn"), LANG("dusk"), LANG("turns"), "", "" ], rows, );