Add game history.
This commit is contained in:
parent
91523a80a1
commit
7bdab36739
@ -187,6 +187,12 @@ impl App {
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
QRPacketData::RGameHistory(response) => {
|
||||
socket.send(Message::Binary(
|
||||
encode_response(CODE_GAME_HISTORY, response.encode())
|
||||
)).await.ok();
|
||||
}
|
||||
|
||||
_ => { }
|
||||
}
|
||||
}
|
||||
|
@ -463,7 +463,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
if let Some(session) = app.sessions.get(&request.token) {
|
||||
// Send GameState update to all connections.
|
||||
let mut packets = Vec::new();
|
||||
let game_state = generate_gamestate(&app, session);
|
||||
let game_state = generate_game_state(&app, session);
|
||||
|
||||
for (cid, player) in session.get_connections() {
|
||||
if cid != qr.id {
|
||||
@ -546,7 +546,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
println!("Request: Game State");
|
||||
|
||||
if let Some(session) = app.sessions.get(&request.token) {
|
||||
response = generate_gamestate(&app, &session);
|
||||
response.status = STATUS_OK;
|
||||
response = generate_game_state(&app, &session);
|
||||
|
||||
if user_id.is_some() {
|
||||
if user_id == session.p_dawn.user { response.player = 0; }
|
||||
@ -610,6 +611,21 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
QRPacketData::QGameHistory(request) => {
|
||||
let mut response = PacketGameHistoryResponse::new();
|
||||
|
||||
println!("Request: Game History");
|
||||
|
||||
if let Some(session) = app.sessions.get(&request.token) {
|
||||
response.status = STATUS_OK;
|
||||
response = generate_game_history(&app, &session);
|
||||
} else {
|
||||
response.status = STATUS_ERROR;
|
||||
}
|
||||
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RGameHistory(response)))
|
||||
}
|
||||
|
||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
||||
}
|
||||
}
|
||||
@ -619,7 +635,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_gamestate(app:&App, session:&Session) -> protocol::PacketGameStateResponse
|
||||
fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateResponse
|
||||
{
|
||||
use protocol::{PacketGameStateResponse, PacketGameStateResponsePiece};
|
||||
|
||||
@ -666,3 +682,28 @@ fn generate_gamestate(app:&App, session:&Session) -> protocol::PacketGameStateRe
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
fn generate_game_history(app:&App, session:&Session) -> protocol::PacketGameHistoryResponse
|
||||
{
|
||||
use protocol::PacketGameHistoryResponse;
|
||||
|
||||
let mut response = PacketGameHistoryResponse::new();
|
||||
|
||||
// Get Dawn handle
|
||||
if let Some(id) = session.p_dawn.user {
|
||||
if let Some(user) = app.get_user_by_id(id) {
|
||||
response.dawn_handle = user.handle.clone();
|
||||
}
|
||||
}
|
||||
|
||||
// Get Dusk handle
|
||||
if let Some(id) = session.p_dusk.user {
|
||||
if let Some(user) = app.get_user_by_id(id) {
|
||||
response.dusk_handle = user.handle.clone();
|
||||
}
|
||||
}
|
||||
|
||||
response.history = session.game.history.clone();
|
||||
|
||||
response
|
||||
}
|
||||
|
@ -161,6 +161,16 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
|
||||
Err(_) => { println!("error: packet decode failed."); }
|
||||
}
|
||||
|
||||
CODE_GAME_HISTORY => match PacketGameHistory::decode(&data, &mut index) {
|
||||
Ok(packet) => {
|
||||
args.bus.send(
|
||||
bus_ds,
|
||||
QRPacket::new(conn_id, QRPacketData::QGameHistory(packet))
|
||||
).ok();
|
||||
}
|
||||
Err(_) => { println!("error: packet decode failed."); }
|
||||
}
|
||||
|
||||
_ => { }
|
||||
}
|
||||
true
|
||||
|
@ -31,3 +31,4 @@ 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_HISTORY :u16 = 0x0032;
|
||||
|
@ -40,6 +40,9 @@ pub enum QRPacketData {
|
||||
RGameState(PacketGameStateResponse),
|
||||
|
||||
QGamePlay(PacketGamePlay),
|
||||
|
||||
QGameHistory(PacketGameHistory),
|
||||
RGameHistory(PacketGameHistoryResponse),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
83
server/src/protocol/packet/game_history.rs
Normal file
83
server/src/protocol/packet/game_history.rs
Normal file
@ -0,0 +1,83 @@
|
||||
use crate::{
|
||||
app::session::SessionToken,
|
||||
util::pack::pack_u16,
|
||||
};
|
||||
|
||||
use game::history::Play;
|
||||
|
||||
use super::Packet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGameHistory {
|
||||
pub token:SessionToken,
|
||||
}
|
||||
impl PacketGameHistory {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
token:SessionToken::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGameHistory {
|
||||
type Data = Self;
|
||||
|
||||
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||
{
|
||||
let mut result = Self::new();
|
||||
|
||||
if data.len() - *index == 8 {
|
||||
for i in 0..8 {
|
||||
result.token[i] = data[*index];
|
||||
*index += 1;
|
||||
}
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGameHistoryResponse {
|
||||
pub status:u16,
|
||||
pub dawn_handle:String,
|
||||
pub dusk_handle:String,
|
||||
pub history:Vec<Play>,
|
||||
}
|
||||
impl PacketGameHistoryResponse {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
status:0,
|
||||
dawn_handle:String::new(),
|
||||
dusk_handle:String::new(),
|
||||
history:Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGameHistoryResponse {
|
||||
type Data = Self;
|
||||
|
||||
fn encode(&self) -> Vec<u8>
|
||||
{
|
||||
let mut history_bytes = Vec::new();
|
||||
for play in &self.history {
|
||||
let mut data = 0;
|
||||
data |= play.source as u16;
|
||||
data |= (play.from as u16) << 4;
|
||||
data |= (play.to as u16) << 10;
|
||||
history_bytes.append(&mut pack_u16(data));
|
||||
}
|
||||
|
||||
[
|
||||
pack_u16(self.status),
|
||||
pack_u16(self.dawn_handle.len() as u16),
|
||||
self.dawn_handle.as_bytes().to_vec(),
|
||||
pack_u16(self.dusk_handle.len() as u16),
|
||||
self.dusk_handle.as_bytes().to_vec(),
|
||||
pack_u16(self.history.len() as u16),
|
||||
history_bytes,
|
||||
].concat()
|
||||
}
|
||||
}
|
@ -98,8 +98,8 @@ impl Packet for PacketGameStateResponse {
|
||||
|
||||
let mut play = 0;
|
||||
play |= self.play.source as u16;
|
||||
play |= (self.play.from as u16) << 1;
|
||||
play |= (self.play.to as u16) << 7;
|
||||
play |= (self.play.from as u16) << 4;
|
||||
play |= (self.play.to as u16) << 10;
|
||||
|
||||
let mut piece_bytes = Vec::new();
|
||||
for piece in &self.pieces {
|
||||
|
@ -11,6 +11,7 @@ mod session_retire; pub use session_retire::*;
|
||||
|
||||
mod game_state; pub use game_state::*;
|
||||
mod game_play; pub use game_play::*;
|
||||
mod game_history; pub use game_history::*;
|
||||
|
||||
mod prelude {
|
||||
pub trait Packet {
|
||||
|
@ -35,6 +35,7 @@ const OpCode = {
|
||||
|
||||
GameState :0x0030,
|
||||
GamePlay :0x0031,
|
||||
GameHistory :0x0032,
|
||||
};
|
||||
|
||||
const GameState = {
|
||||
|
@ -1,6 +1,12 @@
|
||||
let INTERFACE_DATA = null;
|
||||
|
||||
const INTERFACE = {
|
||||
Mode: {
|
||||
Local: 0,
|
||||
Online: 1,
|
||||
Replay: 2,
|
||||
},
|
||||
|
||||
Color: {
|
||||
Background: "#101010",
|
||||
Text: "#c0c0c0",
|
||||
@ -196,18 +202,10 @@ const INTERFACE = {
|
||||
if(is_valid) {
|
||||
|
||||
// Send message to server for online game.
|
||||
if(INTERFACE_DATA.online) {
|
||||
let move_data = INTERFACE_DATA.select.source | (INTERFACE_DATA.select.tile << 1) | (INTERFACE_DATA.hover.tile << 7);
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.GamePlay),
|
||||
PACK.u16(0),
|
||||
PACK.u16(GAME_DATA.turn),
|
||||
PACK.u16(move_data),
|
||||
]);
|
||||
}
|
||||
switch(INTERFACE_DATA.mode) {
|
||||
|
||||
// Apply action and change turn for offline game.
|
||||
else {
|
||||
// Apply action and change turn for local game.
|
||||
case INTERFACE.Mode.Local: {
|
||||
let play = new GAME.Play(INTERFACE_DATA.select.source, INTERFACE_DATA.select.tile, INTERFACE_DATA.hover.tile);
|
||||
INTERFACE_DATA.play = play;
|
||||
GAME_DATA.process(play);
|
||||
@ -216,7 +214,25 @@ const INTERFACE = {
|
||||
INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate);
|
||||
|
||||
INTERFACE.draw();
|
||||
} break;
|
||||
|
||||
// Send action to server for validation.
|
||||
case INTERFACE.Mode.Online: {
|
||||
let move_data = INTERFACE_DATA.select.source | (INTERFACE_DATA.select.tile << 1) | (INTERFACE_DATA.hover.tile << 7);
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.GamePlay),
|
||||
PACK.u16(0),
|
||||
PACK.u16(GAME_DATA.turn),
|
||||
PACK.u16(move_data),
|
||||
]);
|
||||
} break;
|
||||
|
||||
// Branch into local game from here.
|
||||
case INTERFACE.Mode.Replay: {
|
||||
|
||||
} break;
|
||||
}
|
||||
|
||||
INTERFACE_DATA.select = null;
|
||||
}
|
||||
|
||||
@ -288,6 +304,10 @@ const INTERFACE = {
|
||||
draw() {
|
||||
if(INTERFACE_DATA === null) return;
|
||||
|
||||
if(INTERFACE_DATA.mode == INTERFACE.Mode.Replay) {
|
||||
document.getElementById("ind_turn").innerText = INTERFACE_DATA.replay_turn + " of " + INTERFACE_DATA.history.length;
|
||||
}
|
||||
|
||||
let canvas = INTERFACE_DATA.canvas;
|
||||
let ctx = INTERFACE_DATA.context;
|
||||
|
||||
@ -363,7 +383,7 @@ const INTERFACE = {
|
||||
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
|
||||
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
|
||||
}
|
||||
if(GAME_DATA.turn > 0 && (INTERFACE_DATA.play.to == i || (INTERFACE_DATA.play.source == 0 && INTERFACE_DATA.play.from == i))) {
|
||||
if(GAME_DATA.turn > 0 && INTERFACE_DATA.play.source < 2 && (INTERFACE_DATA.play.to == i || (INTERFACE_DATA.play.source == 0 && INTERFACE_DATA.play.from == i))) {
|
||||
ctx.fillStyle = INTERFACE.Color.HintPlay;
|
||||
} else if(GAME_DATA.state.check && piece !== null && piece.piece == GAME.Const.PieceId.Omen && piece.player == (GAME_DATA.turn & 1)) {
|
||||
ctx.fillStyle = INTERFACE.Color.HintCheck;
|
||||
@ -691,18 +711,28 @@ const INTERFACE = {
|
||||
},
|
||||
},
|
||||
|
||||
init(data, online) {
|
||||
init(data, mode) {
|
||||
GAME.init();
|
||||
|
||||
let token = null;
|
||||
let player = 0;
|
||||
let history = [ ];
|
||||
if(data !== null) {
|
||||
switch(mode) {
|
||||
case INTERFACE.Mode.Online: {
|
||||
token = data.token;
|
||||
player = data.mode;
|
||||
} break;
|
||||
|
||||
case INTERFACE.Mode.Replay: {
|
||||
history = data.history;
|
||||
} break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
INTERFACE_DATA = {
|
||||
online: online,
|
||||
mode: mode,
|
||||
|
||||
token: token,
|
||||
|
||||
@ -721,6 +751,9 @@ const INTERFACE = {
|
||||
play: null,
|
||||
retire:false,
|
||||
|
||||
replay_turn: 0,
|
||||
history: history,
|
||||
|
||||
Ui: {
|
||||
scale: 0,
|
||||
margin: 0,
|
||||
@ -742,20 +775,25 @@ const INTERFACE = {
|
||||
canvas.addEventListener("mousedown", INTERFACE.click);
|
||||
window.addEventListener("resize", INTERFACE.draw);
|
||||
|
||||
if(INTERFACE_DATA.online) {
|
||||
switch(INTERFACE_DATA.mode) {
|
||||
case INTERFACE.Mode.Replay:
|
||||
case INTERFACE.Mode.Local: {
|
||||
INTERFACE.draw();
|
||||
} break;
|
||||
|
||||
case INTERFACE.Mode.Online: {
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.GameState),
|
||||
INTERFACE_DATA.token,
|
||||
]);
|
||||
} else {
|
||||
INTERFACE.draw();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
if(INTERFACE_DATA !== null) {
|
||||
if(INTERFACE_DATA.online) {
|
||||
if(INTERFACE_DATA.mode == INTERFACE.Mode.Online) {
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.SessionLeave),
|
||||
]);
|
||||
@ -833,13 +871,44 @@ const INTERFACE = {
|
||||
},
|
||||
|
||||
retire() {
|
||||
if(INTERFACE_DATA.online) {
|
||||
if(INTERFACE_DATA.mode == INTERFACE.Mode.Online) {
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.SessionRetire),
|
||||
INTERFACE_DATA.token,
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
replay_prev() {
|
||||
if(INTERFACE_DATA.mode == INTERFACE.Mode.Replay) {
|
||||
if(INTERFACE_DATA.replay_turn > 0) {
|
||||
INTERFACE_DATA.replay_turn--;
|
||||
|
||||
GAME.init();
|
||||
for(let i = 0; i < INTERFACE_DATA.replay_turn; ++i) {
|
||||
GAME_DATA.process(INTERFACE_DATA.history[i]);
|
||||
}
|
||||
|
||||
INTERFACE_DATA.play = null;
|
||||
if(INTERFACE_DATA.replay_turn > 0) {
|
||||
INTERFACE_DATA.play = INTERFACE_DATA.history[INTERFACE_DATA.replay_turn - 1];
|
||||
}
|
||||
|
||||
INTERFACE.draw();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
replay_next() {
|
||||
if(INTERFACE_DATA.mode == INTERFACE.Mode.Replay) {
|
||||
if(INTERFACE_DATA.replay_turn < INTERFACE_DATA.history.length) {
|
||||
GAME_DATA.process(INTERFACE_DATA.history[INTERFACE_DATA.replay_turn]);
|
||||
INTERFACE_DATA.play = INTERFACE_DATA.history[INTERFACE_DATA.replay_turn];
|
||||
INTERFACE_DATA.replay_turn++;
|
||||
INTERFACE.draw();
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
INTERFACE.Radius = 2.0 / Math.sqrt(3.0);
|
||||
|
@ -464,13 +464,12 @@ const SCENES = {
|
||||
UI.clear(table);
|
||||
|
||||
if(data !== null) {
|
||||
table.appendChild(UI.session_table(data.records));
|
||||
table.appendChild(UI.session_table_history(data.records));
|
||||
}
|
||||
} break;
|
||||
case OpCode.SessionCreate:
|
||||
case OpCode.SessionJoin: {
|
||||
case OpCode.GameHistory: {
|
||||
if(data.status == Status.Ok) {
|
||||
LOAD(SCENES.Game, data);
|
||||
LOAD(SCENES.GameHistory, data);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
@ -525,7 +524,7 @@ const SCENES = {
|
||||
canvas.setAttribute("id", "game");
|
||||
MAIN.appendChild(canvas);
|
||||
|
||||
INTERFACE.init(data, true);
|
||||
INTERFACE.init(data, INTERFACE.Mode.Online);
|
||||
|
||||
return true;
|
||||
},
|
||||
@ -566,7 +565,51 @@ const SCENES = {
|
||||
canvas.setAttribute("id", "game");
|
||||
MAIN.appendChild(canvas);
|
||||
|
||||
INTERFACE.init(data, false);
|
||||
INTERFACE.init(data, INTERFACE.Mode.Local);
|
||||
|
||||
return true;
|
||||
},
|
||||
unload() {
|
||||
INTERFACE.uninit();
|
||||
},
|
||||
},
|
||||
|
||||
GameHistory:{
|
||||
load(data) {
|
||||
let buttons_bottom = [ ];
|
||||
buttons_bottom.push(UI.button("Back", () => { LOAD(SCENES.Browse) }));
|
||||
|
||||
UI.nav([
|
||||
UI.button("Rotate", () => { INTERFACE.rotate(); }),
|
||||
UI.button("Mirror", () => { INTERFACE.mirror(); }),
|
||||
], buttons_bottom);
|
||||
|
||||
|
||||
let left_buttons = [ ];
|
||||
if(CONTEXT.Auth !== null) {
|
||||
left_buttons.push(UI.button("Start", () => {
|
||||
MESSAGE_SESSION_START();
|
||||
}));
|
||||
}
|
||||
|
||||
let ind_turn = UI.div([UI.text("0 of 0")]);
|
||||
ind_turn.setAttribute("id", "ind_turn");
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
[
|
||||
ind_turn,
|
||||
UI.button("◀", () => { INTERFACE.replay_prev(); }),
|
||||
UI.button("▶", () => { INTERFACE.replay_next(); }),
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
let canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("id", "game");
|
||||
MAIN.appendChild(canvas);
|
||||
|
||||
INTERFACE.init(data, INTERFACE.Mode.Replay);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
@ -319,6 +319,47 @@ function MESSAGE(event) {
|
||||
data.play.to = (result.data >> 10) & 0x3F;
|
||||
} break;
|
||||
|
||||
case OpCode.GameHistory: {
|
||||
console.log("RECV GameHistory");
|
||||
|
||||
data = {
|
||||
status:0,
|
||||
dawn:"",
|
||||
dusk:"",
|
||||
history:[ ],
|
||||
};
|
||||
|
||||
// Status
|
||||
result = UNPACK.u16(bytes, index);
|
||||
index = result.index;
|
||||
data.status = result.data;
|
||||
|
||||
// Handles
|
||||
result = UNPACK.string(bytes, index);
|
||||
index = result.index;
|
||||
data.dawn = result.data;
|
||||
|
||||
result = UNPACK.string(bytes, index);
|
||||
index = result.index;
|
||||
data.dusk = result.data;
|
||||
|
||||
// Pieces
|
||||
result = UNPACK.u16(bytes, index);
|
||||
index = result.index;
|
||||
let history_length = result.data;
|
||||
|
||||
for(let i = 0; i < history_length; ++i) {
|
||||
result = UNPACK.u16(bytes, index);
|
||||
index = result.index;
|
||||
|
||||
data.history.push(new GAME.Play(
|
||||
result.data & 0xF,
|
||||
(result.data >> 4) & 0x3F,
|
||||
(result.data >> 10) & 0x3F,
|
||||
));
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
console.log("RECV Undefined " + code);
|
||||
return;
|
||||
|
37
www/js/ui.js
37
www/js/ui.js
@ -219,6 +219,43 @@ const UI = {
|
||||
return tbody;
|
||||
},
|
||||
|
||||
session_table_history(records) {
|
||||
let rows = [ ];
|
||||
|
||||
for(let r = 0; r < records.length; ++r) {
|
||||
let buttons = [ ];
|
||||
let view_callback = function() {
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.GameHistory),
|
||||
this.token,
|
||||
]);
|
||||
};
|
||||
view_callback = view_callback.bind({token: records[r].token});
|
||||
|
||||
buttons.push(UI.button("View", view_callback));
|
||||
|
||||
let dawn = UI.text(records[r].dawn);
|
||||
if(records[r].dawn == "") { dawn = UI.span([UI.text("Vacant")], "text-system"); }
|
||||
|
||||
let dusk = UI.text(records[r].dusk);
|
||||
if(records[r].dusk == "") { dusk = UI.span([UI.text("Vacant")], "text-system"); }
|
||||
|
||||
rows.push([
|
||||
dawn,
|
||||
dusk,
|
||||
UI.text(records[r].turn),
|
||||
buttons,
|
||||
]);
|
||||
}
|
||||
|
||||
let tbody = UI.table_content(
|
||||
[ "Dawn", "Dusk", "Turn", "" ],
|
||||
rows,
|
||||
);
|
||||
|
||||
return tbody;
|
||||
},
|
||||
|
||||
clear(dom) {
|
||||
while(dom.lastChild !== null) { dom.removeChild(document.body.lastChild); }
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user