diff --git a/server/src/app/connection.rs b/server/src/app/connection.rs index 6ce9840..bc6b94b 100644 --- a/server/src/app/connection.rs +++ b/server/src/app/connection.rs @@ -7,7 +7,7 @@ use tokio_tungstenite::{tungstenite::Message, WebSocketStream}; use crate::app::{ authentication::AuthToken, - session::SessionToken, + context::Context, }; type StreamType = Arc>, Message>>>; @@ -17,7 +17,8 @@ pub struct Connection { pub bus:u32, pub stream:StreamType, pub auth:Option, - pub session:Option, + + pub context:Context, pub prev:u32, pub next:u32, diff --git a/server/src/app/context.rs b/server/src/app/context.rs index aaed5cf..95a6836 100644 --- a/server/src/app/context.rs +++ b/server/src/app/context.rs @@ -1,3 +1,18 @@ +use super::session::SessionToken; + +#[derive(Clone)] pub enum Context { None, + Game(Game), + AccountSettings(AccountSettings), +} + +#[derive(Clone, Copy)] +pub struct Game { + pub token:SessionToken, +} + +#[derive(Clone, Copy)] +pub struct AccountSettings { + pub auth_token:[u8; 8], } diff --git a/server/src/app/session/filter.rs b/server/src/app/session/filter.rs new file mode 100644 index 0000000..f4df9d0 --- /dev/null +++ b/server/src/app/session/filter.rs @@ -0,0 +1,49 @@ +#[derive(Clone, Copy)] +pub enum SortOrder { + Recent, + Viewers, +} + +#[derive(Clone, Copy)] +pub struct Sort { + pub order:SortOrder, + pub reverse:bool, +} +impl Sort { + pub fn new() -> Self + { + Self { + order:SortOrder::Recent, + reverse:false, + } + } +} + +#[derive(Clone)] +pub struct SessionFilter { + pub start:usize, + pub count:usize, + + pub is_complete:Option, + pub is_live:Option, + pub is_player:Option, + pub player:[Option; 2], + + pub sort:Sort, +} +impl SessionFilter { + pub fn new() -> Self + { + Self { + start:0, + count:0, + + is_complete:None, + is_live:None, + is_player:None, + player:[None, None], + + sort:Sort::new(), + } + } +} diff --git a/server/src/app/session.rs b/server/src/app/session/mod.rs similarity index 97% rename from server/src/app/session.rs rename to server/src/app/session/mod.rs index 83e4b48..a1d9387 100644 --- a/server/src/app/session.rs +++ b/server/src/app/session/mod.rs @@ -1,5 +1,7 @@ use game::Game; +mod filter; pub use filter::SessionFilter; + pub type SessionToken = [u8; 8]; pub type SessionSecret = [u8; 8]; diff --git a/server/src/app/user.rs b/server/src/app/user.rs index f0a59d8..225ae16 100644 --- a/server/src/app/user.rs +++ b/server/src/app/user.rs @@ -18,6 +18,15 @@ pub struct UserStatistics { pub games_played:u32, pub games_won:u32, } +impl UserStatistics { + pub fn new() -> Self + { + Self { + games_played:0, + games_won:0, + } + } +} #[derive(Clone)] pub struct User { @@ -30,5 +39,26 @@ pub struct User { pub connection:Option, pub status:UserStatus, + pub statistics:UserStatistics, + pub challenges:Vec, } +impl User { + pub fn new() -> Self + { + Self { + id:0, + flags:0, + handle:String::new(), + secret:Vec::new(), + na_key:0, + + connection:None, + + status:UserStatus::new(), + statistics:UserStatistics::new(), + + challenges:Vec::new(), + } + } +} diff --git a/server/src/manager/data.rs b/server/src/manager/data.rs index 2db08b0..81c8077 100644 --- a/server/src/manager/data.rs +++ b/server/src/manager/data.rs @@ -5,9 +5,10 @@ use crate::{ app::{ authentication::Authentication, connection::Connection, - user::{User, UserStatus}, + user::User, App, - session::Session, + session::{Session, SessionFilter}, + context::{self, Context}, }, protocol, }; @@ -27,9 +28,9 @@ pub async fn thread_system(mut app:App, bus:Bus) let qr = packet.data; let mut user_id = None; - let mut session_id = None; + let mut context = Context::None; if let Some(conn) = app.connections.get(qr.id as usize) { - session_id = conn.session; + context = conn.context.clone(); if let Some(auth_id) = conn.auth { if let Some(auth) = app.auths.get(&auth_id) { @@ -44,7 +45,8 @@ pub async fn thread_system(mut app:App, bus:Bus) bus: request.bus_id, stream: request.stream, auth: None, - session: None, + + context:Context::None, prev:0, next:0, @@ -70,22 +72,8 @@ pub async fn thread_system(mut app:App, bus:Bus) // Uninitialize connection if if let Some(conn) = app.connections.get(qr.id as usize).cloned() { - // Disassociate session if present - if let Some(session_token) = conn.session { - if let Some(session) = app.sessions.get_mut(&session_token) { - if user_id == Some(session.p_dawn.user) { - session.remove_connection(0, qr.id); - } - else if user_id == Some(session.p_dusk.user) { - session.remove_connection(1, qr.id); - } - else { - session.remove_connection(2, qr.id); - } - } - - app.send_session_spectators(session_token).await; - } + // Disassociate context if present + change_context(&mut app, qr.id, user_id, Context::None).await; // Remove connection from chain. if let Some(auth_id) = conn.auth { @@ -168,18 +156,11 @@ pub async fn thread_system(mut app:App, bus:Bus) app.filesystem.handle_create(&handle, user_id).ok(); app.user_handle.set(handle.as_bytes(), user_id); - let user_data = User { - id:user_id, - flags:0, - handle:display_name, - secret, - na_key:salt_id, - - connection:Some(qr.id), - - status:UserStatus::new(), - challenges:Vec::new(), - }; + let mut user_data = User::new(); + user_data.id = user_id; + user_data.handle = display_name; + user_data.secret = secret; + user_data.na_key = salt_id; app.filesystem.user_create(&user_data).ok(); @@ -433,78 +414,20 @@ pub async fn thread_system(mut app:App, bus:Bus) QRPacketData::QSessionList(request) => { app.log.log("Request: Session List"); - let mut response = PacketSessionListResponse::new(); + println!("range {} {}", request.filter.start, request.filter.count); - let mut count = 0; - - let mut next_id = app.session_time.begin(); - while let Some(id) = next_id { - let token = app.session_time.get(id).unwrap(); - if let Some(session) = app.sessions.get(token) { - - let mut valid = match request.game_state { - 1 => !session.game.is_complete(), - 2 => !session.game.is_complete(), - 3 => session.game.is_complete(), - _ => true, - }; - valid &= !request.is_player || Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id; - valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0); - - if valid { - let player :u8 = if user_id.is_some() { - if Some(session.p_dawn.user) == user_id { 1 } - else if Some(session.p_dusk.user) == user_id { 2 } - else { 0 } - } else { 0 }; - - let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1; - let is_complete = (session.game.is_complete() as u8) * (((session.game.turn & 1) == 0) as u8 + 1); - - let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) { - user.handle.clone() - } else { String::new() }; - - let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) { - 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:[ - dawn_handle, - dusk_handle, - ], - turn:session.game.turn, - last_move, - viewers:session.connections.len() as u32, - player, - is_turn, - is_complete, - }); - - count += 1; - } - } - - if count >= 60 { break; } - next_id = app.session_time.next(id); + if let Some(value) = request.filter.is_complete { + println!("is_complete {}", value); + } + if let Some(value) = request.filter.is_live { + println!("is_live {}", value); + } + if let Some(value) = request.filter.is_player { + println!("is_player {}", value); } + let mut response = PacketSessionListResponse::new(); + response.records = filter_sessions(&app, user_id, request.filter); Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) } @@ -516,7 +439,7 @@ pub async fn thread_system(mut app:App, bus:Bus) response.token = request.token; // Verify that session exists - if let Some(session) = app.sessions.get_mut(&request.token) { + if if let Some(session) = app.sessions.get(&request.token) { response.is_complete = session.game.is_complete(); @@ -527,7 +450,7 @@ pub async fn thread_system(mut app:App, bus:Bus) } // Join game as player - if if request.join { + if request.join { // Verify client is authenticated if user_id.is_some() { @@ -545,21 +468,15 @@ pub async fn thread_system(mut app:App, bus:Bus) app.log.log("User spectates session."); response.status = STATUS_OK; true - } { - // Associate session and connection on join - if let Some(conn) = app.connections.get_mut(qr.id as usize) { - conn.session = Some(session.token); - } - let mode = if user_id == Some(session.p_dawn.user) { - 0 - } else if user_id == Some(session.p_dusk.user) { - 1 - } else { - 2 - }; - session.add_connection(mode, qr.id); - app.send_session_spectators(request.token).await; } + } else { + false + } { + change_context(&mut app, qr.id, user_id, Context::Game( + context::Game { + token:request.token, + } + )).await; } Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response))) @@ -616,20 +533,11 @@ pub async fn thread_system(mut app:App, bus:Bus) app.log.log("Request: Session Leave"); // Verify that session exists. - if let Some(session_token) = session_id { - if let Some(session) = app.sessions.get_mut(&session_token) { - if user_id == Some(session.p_dawn.user) { - session.remove_connection(0, qr.id); - } - else if user_id == Some(session.p_dusk.user) { - session.remove_connection(1, qr.id); - } - else { - session.remove_connection(2, qr.id); - } + match &context { + Context::Game(_) => { + change_context(&mut app, qr.id, user_id, Context::None).await; } - - app.send_session_spectators(session_token).await; + _ => { } } Some(QRPacket::new(0, QRPacketData::None)) @@ -661,8 +569,8 @@ pub async fn thread_system(mut app:App, bus:Bus) let mut packets = Vec::::new(); let mut response = QRPacketData::None; - if let Some(sid) = session_id { - if let Some(session) = app.sessions.get_mut(&sid) { + if let Context::Game(context) = &context { + if let Some(session) = app.sessions.get_mut(&context.token) { match request.data { GameMessageData::PlayMove(turn, from, to) @@ -938,9 +846,11 @@ pub async fn thread_system(mut app:App, bus:Bus) }; session.game.init(); - if let Some(conn) = app.connections.get_mut(qr.id as usize) { - conn.session = Some(session.token); - } + change_context(&mut app, qr.id, Some(user_id), Context::Game( + context::Game { + token:session.token, + } + )).await; session.id = app.filesystem.session_create(&session).unwrap(); @@ -1013,6 +923,31 @@ pub async fn thread_system(mut app:App, bus:Bus) Some(QRPacket::new(qr.id, QRPacketData::RUserList(response))) } + // UserInfo + QRPacketData::QUserInfo(request) => { + app.log.log("Request: User Info"); + + let mut response = PacketUserInfoResponse::new(); + + if let Some(uid) = app.user_handle.get(&request.handle.to_lowercase().as_bytes()).cloned() { + response.status = STATUS_OK; + + if let Some(user) = app.get_user_by_id(uid) { + response.handle = user.handle.clone(); + response.is_online = user.connection.is_some(); + } + } else { + response.status = STATUS_ERROR; + } + + // Get user sessions + if response.status == STATUS_OK { + + } + + Some(QRPacket::new(qr.id, QRPacketData::RUserInfo(response))) + } + // InviteAcquire QRPacketData::QInviteAcquire => { use crate::app::{Invitation, InviteToken}; @@ -1146,3 +1081,145 @@ fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateR response } + +async fn change_context(app:&mut App, conn_id:u32, user_id:Option, context:Context) +{ + // Clear existing context + if let Some(conn) = app.connections.get(conn_id as usize).cloned() { + match conn.context { + Context::Game(game) => { + if let Some(session) = app.sessions.get_mut(&game.token) { + let mode = if user_id == Some(session.p_dawn.user) { + 0 + } else if user_id == Some(session.p_dusk.user) { + 1 + } else { + 2 + }; + session.remove_connection(mode, conn_id); + app.send_session_spectators(game.token).await; + } + } + _ => { } + } + } + + // Add new context + if let Some(conn) = app.connections.get_mut(conn_id as usize) { + conn.context = context.clone(); + } + + match context { + Context::Game(game) => { + if let Some(session) = app.sessions.get_mut(&game.token) { + let mode = if user_id == Some(session.p_dawn.user) { + 0 + } else if user_id == Some(session.p_dusk.user) { + 1 + } else { + 2 + }; + session.add_connection(mode, conn_id); + app.send_session_spectators(game.token).await; + } + } + + _ => { } + } +} + +use crate::protocol::PacketSessionListResponseRecord; +fn filter_sessions(app:&App, user_id:Option, filter:SessionFilter) -> Vec +{ + let mut result = Vec::new(); + + let mut index = 0; + let mut count = 0; + let mut next_id = app.session_time.begin(); + + // Get first element in filter + while index < filter.start { + if let Some(id) = next_id { + next_id = app.session_time.next(id); + index += 1; + } else { + break; + } + } + + // Gather filtered sessions up to filter count + while let Some(id) = next_id { + if count >= filter.count { break; } + + let token = app.session_time.get(id).unwrap(); + if let Some(session) = app.sessions.get(token) { + + let mut valid = true; + + valid &= if let Some(is_complete) = filter.is_complete { is_complete == session.game.is_complete() } else { true }; + valid &= if let Some(is_player) = filter.is_player { is_player == (Some(session.p_dawn.user) == user_id || Some(session.p_dusk.user) == user_id) } else { true }; + valid &= if let Some(is_live) = filter.is_live { is_live == (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0) } else { true }; + + for i in 0..filter.player.len() { + if let Some(handle) = &filter.player[i] { + if let Some(uid) = app.user_handle.get(&handle.as_bytes()).cloned() { + valid &= session.p_dawn.user == uid || session.p_dusk.user == uid; + } + } + } + + if valid { + let player :u8 = if user_id.is_some() { + if Some(session.p_dawn.user) == user_id { 1 } + else if Some(session.p_dusk.user) == user_id { 2 } + else { 0 } + } else { 0 }; + + let is_turn = player != 0 && (session.game.turn & 1) == player as u16 - 1; + let is_complete = (session.game.is_complete() as u8) * (((session.game.turn & 1) == 0) as u8 + 1); + + let dawn_handle = if let Some(user) = app.get_user_by_id(session.p_dawn.user) { + user.handle.clone() + } else { String::new() }; + + let dusk_handle = if let Some(user) = app.get_user_by_id(session.p_dusk.user) { + 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; + } + + result.push(PacketSessionListResponseRecord { + token:session.token, + handles:[ + dawn_handle, + dusk_handle, + ], + turn:session.game.turn, + last_move, + viewers:session.connections.len() as u32, + player, + is_turn, + is_complete, + }); + + count += 1; + } + } + + next_id = app.session_time.next(id); + } + + result +} diff --git a/server/src/protocol/mod.rs b/server/src/protocol/mod.rs index 70d126b..eeb77f6 100644 --- a/server/src/protocol/mod.rs +++ b/server/src/protocol/mod.rs @@ -32,6 +32,11 @@ pub enum QRPacketData { QUserInfo(PacketUserInfo), RUserInfo(PacketUserInfoResponse), + QAccountInfo(PacketAccountInfo), + RAccountInfo(PacketAccountInfoResponse), + QAccountUpdate(PacketAccountUpdate), + RAccountUpdate(PacketAccountUpdateResponse), + QSessionList(PacketSessionList), RSessionList(PacketSessionListResponse), diff --git a/server/src/protocol/packet/account_info.rs b/server/src/protocol/packet/account_info.rs new file mode 100644 index 0000000..4d5d2a8 --- /dev/null +++ b/server/src/protocol/packet/account_info.rs @@ -0,0 +1,71 @@ +use crate::util::pack::{pack_u8, pack_u16}; + +use super::{ + Packet, + PacketSessionListResponseRecord, +}; + +#[derive(Clone)] +pub struct PacketAccountInfo { + pub secret:Vec, +} +impl PacketAccountInfo { + pub fn new() -> Self + { + Self { + secret:Vec::new(), + } + } +} +impl Packet for PacketAccountInfo { + type Data = Self; + + fn decode(_data:&Vec, _index:&mut usize) -> Result + { + Ok(Self::new()) + } +} + +#[derive(Clone)] +pub struct PacketAccountInfoResponse { + pub status:u16, + pub handle:String, + pub is_online:bool, + pub history:Vec, +} +impl PacketAccountInfoResponse { + pub fn new() -> Self + { + Self { + status:0, + handle:String::new(), + is_online:false, + history:Vec::new(), + } + } +} +impl Packet for PacketAccountInfoResponse { + type Data = Self; + + fn encode(&self) -> Vec + { + let handle_bytes = self.handle.as_bytes().to_vec(); + + let flags = self.is_online as u16; + + let mut history_bytes = pack_u16(self.history.len() as u16); + for record in &self.history { + history_bytes.append(&mut record.encode()); + } + + [ + pack_u16(self.status as u16), + pack_u16(flags), + + pack_u8(handle_bytes.len() as u8), + handle_bytes, + + history_bytes, + ].concat() + } +} diff --git a/server/src/protocol/packet/account_update.rs b/server/src/protocol/packet/account_update.rs new file mode 100644 index 0000000..282f454 --- /dev/null +++ b/server/src/protocol/packet/account_update.rs @@ -0,0 +1,72 @@ +use crate::util::pack::{pack_u8, pack_u16}; + +use super::{ + Packet, + PacketSessionListResponseRecord, +}; + +#[derive(Clone)] +pub struct PacketAccountUpdate { + pub handle:String, + +} +impl PacketAccountUpdate { + pub fn new() -> Self + { + Self { + handle:String::new(), + } + } +} +impl Packet for PacketAccountUpdate { + type Data = Self; + + fn decode(_data:&Vec, _index:&mut usize) -> Result + { + Ok(Self::new()) + } +} + +#[derive(Clone)] +pub struct PacketAccountUpdateResponse { + pub status:u16, + pub handle:String, + pub is_online:bool, + pub history:Vec, +} +impl PacketAccountUpdateResponse { + pub fn new() -> Self + { + Self { + status:0, + handle:String::new(), + is_online:false, + history:Vec::new(), + } + } +} +impl Packet for PacketAccountUpdateResponse { + type Data = Self; + + fn encode(&self) -> Vec + { + let handle_bytes = self.handle.as_bytes().to_vec(); + + let flags = self.is_online as u16; + + let mut history_bytes = pack_u16(self.history.len() as u16); + for record in &self.history { + history_bytes.append(&mut record.encode()); + } + + [ + pack_u16(self.status as u16), + pack_u16(flags), + + pack_u8(handle_bytes.len() as u8), + handle_bytes, + + history_bytes, + ].concat() + } +} diff --git a/server/src/protocol/packet/mod.rs b/server/src/protocol/packet/mod.rs index fdfe025..a2b9100 100644 --- a/server/src/protocol/packet/mod.rs +++ b/server/src/protocol/packet/mod.rs @@ -21,6 +21,9 @@ mod challenge_list; pub use challenge_list::*; mod user_list; pub use user_list::*; mod user_info; pub use user_info::*; +mod account_info; pub use account_info::*; +mod account_update; pub use account_update::*; + mod invite_acquire; pub use invite_acquire::*; mod invite_list; pub use invite_list::*; diff --git a/server/src/protocol/packet/session_list.rs b/server/src/protocol/packet/session_list.rs index bfaa814..081f1d9 100644 --- a/server/src/protocol/packet/session_list.rs +++ b/server/src/protocol/packet/session_list.rs @@ -1,26 +1,29 @@ use crate::{ - app::session::SessionToken, - util::pack::{pack_u16, pack_u32, unpack_u16}, + app::session::{SessionToken, SessionFilter}, + util::pack::{pack_u16, pack_u32, unpack_u8, unpack_u16, unpack_u32}, }; -use game::util::mask; use super::Packet; #[derive(Clone)] pub struct PacketSessionList { - pub page:u16, - pub game_state:u8, - pub is_player:bool, - pub is_live:bool, + pub filter:SessionFilter, + + //pub page:u16, + //pub game_state:u8, + //pub is_player:bool, + //pub is_live:bool, } impl PacketSessionList { pub fn new() -> Self { Self { - page:0, - game_state:0, - is_player:false, - is_live:false, + filter:SessionFilter::new(), + + //page:0, + //game_state:0, + //is_player:false, + //is_live:false, } } } @@ -31,18 +34,32 @@ impl Packet for PacketSessionList { { let mut result = Self::new(); - /* Read flags - ** 0:[2] - Game state - ** 2:[1] - User is player of session - ** 3:[1] - Both players are online - */ - if data.len() - *index == 4 { - let flags = unpack_u16(data, index); - result.game_state = (flags & mask(2, 0) as u16) as u8; - result.is_player = (flags & mask(1, 2) as u16) != 0; - result.is_live = (flags & mask(1, 3) as u16) != 0; + if data.len() - *index > 6 { + result.filter.start = unpack_u32(data, index) as usize; + result.filter.count = unpack_u32(data, index) as usize; - result.page = unpack_u16(data, index); + let filter_length = unpack_u16(data, index); + for _ in 0..filter_length { + let code = unpack_u8(data, index); + match code { + 0x00 => { result.filter.is_complete = Some(unpack_u8(data, index) != 0); } + 0x01 => { result.filter.is_live = Some(unpack_u8(data, index) != 0); } + 0x02 => { result.filter.is_player = Some(unpack_u8(data, index) != 0); } + 0x10 => { + let handle_length = unpack_u8(data, index) as usize; + if data.len() - *index >= handle_length { + if let Ok(handle) = String::from_utf8(data[*index..*index+handle_length].to_vec()) { + if result.filter.player[0].is_none() { + result.filter.player[0] = Some(handle); + } else if result.filter.player[1].is_none() { + result.filter.player[1] = Some(handle); + } + } + } + } + _ => { } + } + } Ok(result) } else { diff --git a/server/src/protocol/packet/user_info.rs b/server/src/protocol/packet/user_info.rs index 48879aa..81914dc 100644 --- a/server/src/protocol/packet/user_info.rs +++ b/server/src/protocol/packet/user_info.rs @@ -1,4 +1,4 @@ -use crate::util::pack::{pack_u8, pack_u16}; +use crate::util::pack::{pack_u8, pack_u16, unpack_u8}; use super::{ Packet, @@ -21,9 +21,16 @@ impl PacketUserInfo { impl Packet for PacketUserInfo { type Data = Self; - fn decode(_data:&Vec, _index:&mut usize) -> Result + fn decode(data:&Vec, index:&mut usize) -> Result { - Ok(Self::new()) + let mut result = Self::new(); + + let length = unpack_u8(data, index) as usize; + if data.len() - *index >= length { + result.handle = String::from_utf8(data[*index..*index + length].to_vec()).map_err(|_| ())?; + } + + Ok(result) } } diff --git a/server/src/system/filesystem/mod.rs b/server/src/system/filesystem/mod.rs index 99f1009..7419b94 100644 --- a/server/src/system/filesystem/mod.rs +++ b/server/src/system/filesystem/mod.rs @@ -10,7 +10,7 @@ use game::{ use crate::{ app::{ session::{Session, SessionToken, SessionSecret}, - user::{User, UserStatus}, + user::User, }, util::pack::*, }; @@ -436,18 +436,14 @@ impl FileSystem { let handle = String::from_utf8(handle).map_err(|_| ())?; - Ok(User { - id, - flags, - handle, - secret, - na_key, + let mut user = User::new(); + user.id = id; + user.flags = flags; + user.handle = handle; + user.secret = secret; + user.na_key = na_key; - connection:None, - - status:UserStatus::new(), - challenges:Vec::new(), - }) + Ok(user) } else { Err(()) } } diff --git a/www/js/const.js b/www/js/const.js index 32e6605..7b586dd 100644 --- a/www/js/const.js +++ b/www/js/const.js @@ -63,6 +63,13 @@ const OpCode = { TestResult :0xFFFF, }; +const FilterCode = { + IsComplete :0x00, + IsLive :0x01, + IsPlayer :0x02, + Player :0x10, +}; + const GameState = { Joinable :0x00, Ongoing :0x01, diff --git a/www/js/filter.js b/www/js/filter.js new file mode 100644 index 0000000..2288cb0 --- /dev/null +++ b/www/js/filter.js @@ -0,0 +1,6 @@ +class Filter { + constructor(code, value) { + this.code = code; + this.value = value; + } +} diff --git a/www/js/interface.js b/www/js/interface.js index 6797374..650828a 100644 --- a/www/js/interface.js +++ b/www/js/interface.js @@ -779,11 +779,10 @@ const INTERFACE = { let message = null; ctx.fillStyle = INTERFACE.Color.Text; - if(INTERFACE_DATA.Game.auto !== null) { - switch(INTERFACE_DATA.Game.auto) { - case 0: message = LANG("cpu") + " " + LANG("dawn"); break; - case 1: message = LANG("cpu") + " " + LANG("dusk"); break; - } + switch(INTERFACE_DATA.Game.auto) { + case 1: message = LANG("cpu") + " " + LANG("dawn"); break; + case 2: message = LANG("cpu") + " " + LANG("dusk"); break; + case 3: message = LANG("cpu"); break; } switch(GAME_DATA.state.code) { @@ -1233,7 +1232,7 @@ const INTERFACE = { board_state: [ ], history: [ ], history_begin: [ ], - auto: null, + auto: 0, }, Ui: { @@ -1317,7 +1316,7 @@ const INTERFACE = { }, reset() { - INTERFACE_DATA.Game.auto = null; + INTERFACE_DATA.Game.auto = 0; INTERFACE_DATA.Game.history = [ ]; for(let i = 0; i < INTERFACE_DATA.Game.history_begin.length; ++i) { @@ -1331,7 +1330,7 @@ const INTERFACE = { undo() { switch(INTERFACE_DATA.mode) { case INTERFACE.Mode.Local: { - INTERFACE_DATA.Game.auto = null; + INTERFACE_DATA.Game.auto = 0; if(INTERFACE_DATA.Game.history.length > 0) { INTERFACE_DATA.Replay.turn = INTERFACE_DATA.Game.history.length + 1; INTERFACE_DATA.Game.history.pop(); @@ -1501,7 +1500,7 @@ const INTERFACE = { case INTERFACE.Mode.Local: { INTERFACE.history_push(play, true); - if(INTERFACE_DATA.Game.auto !== null && INTERFACE_DATA.Game.auto == (GAME_DATA.turn & 1)) { + if((INTERFACE_DATA.Game.auto & (1 << (GAME_DATA.turn & 1))) != 0) { setTimeout(INTERFACE.auto_play, 1000); } } break; @@ -1676,76 +1675,79 @@ const INTERFACE = { auto() { - if(INTERFACE_DATA.Game.auto === null) { - INTERFACE_DATA.Game.auto = INTERFACE_DATA.rotate ^ 1; + let bit = 1 << (INTERFACE_DATA.rotate ^ 1); + if((INTERFACE_DATA.Game.auto & bit) == 0) { + INTERFACE_DATA.Game.auto |= bit; setTimeout(INTERFACE.auto_play, 500); } else { - INTERFACE_DATA.Game.auto = null; + INTERFACE_DATA.Game.auto &= ~bit; } INTERFACE.game_step(); }, auto_play() { - if(INTERFACE_DATA.Game.auto !== (GAME_DATA.turn & 1) || GAME_DATA.state.checkmate) { return; } + let bit = 1 << (GAME_DATA.turn & 1); + if((INTERFACE_DATA.Game.auto & bit) == 0 || GAME_DATA.state.checkmate) { return; } function state_score(state, player) { let score = 0; let opponent = player ^ 1; let turn = (state.turn & 1); - for(let i = 0; i < state.board.tiles.length; ++i) { - let tile = state.board.tiles[i]; - score += Math.floor((tile.threaten[player] - tile.threaten[opponent]) / 2); - } - - for(let i = 0; i < state.board.pieces.length; ++i) { - let piece_id = state.board.pieces[i]; - if(piece_id !== null) { - let piece = state.board.pieces[i]; - let tile = state.board.tiles[piece.tile]; - let hex = HEX.tile_to_hex(tile); - - let piece_score = 3 + (4 * (piece.piece + piece.promoted)); - - if(piece.player == player) { - if(tile.threaten[opponent] == 0) { score += piece_score; } - else { score -= piece_score; } - if(hex.y == state.board.columns[hex.x].extent[player]) { - score += piece_score * tile.threaten[player]; - } - } else { - score -= piece_score - 2; - } - } - } - - for(let i = 0; i < state.pools[player].pieces.length; ++i) { - score += 3 * (2 + i) * state.pools[player].pieces[i]; - } - for(let i = 0; i < state.pools[opponent].pieces.length; ++i) { - score -= 2 * (1 + i) * state.pools[opponent].pieces[i]; - } - - for(let i = 0; i < state.board.columns.length; ++i) { - let column = state.board.columns[i]; - let extent_score = 0; - if(player == 0) { - extent_score += column.extent[player]; - extent_score -= 8 - column.extent[opponent]; - } else { - extent_score += 8 - column.extent[player]; - extent_score -= column.extent[opponent]; - } - score += Math.floor(extent_score / 3); - } - - if(state.state.check != 0) { - if(turn == player) { score -= 20; } - else { score += 1; } - } if(state.state.checkmate) { if(turn == player) { score -= 1000; } else { score += 1000; } + } else { + if(state.state.check != 0) { + if(turn == player) { score -= 20; } + else { score += 1; } + } + + for(let i = 0; i < state.board.tiles.length; ++i) { + let tile = state.board.tiles[i]; + score += Math.floor((tile.threaten[player] - tile.threaten[opponent]) / 2); + } + + for(let i = 0; i < state.board.pieces.length; ++i) { + let piece_id = state.board.pieces[i]; + if(piece_id !== null) { + let piece = state.board.pieces[i]; + let tile = state.board.tiles[piece.tile]; + let hex = HEX.tile_to_hex(tile); + + let piece_score = 3 + (4 * (piece.piece + piece.promoted)); + + if(piece.player == player) { + if(tile.threaten[opponent] == 0) { score += piece_score; } + else { score -= piece_score; } + if(hex.y == state.board.columns[hex.x].extent[player]) { + score += piece_score * tile.threaten[player]; + } + } else { + score -= piece_score - 2; + } + } + } + + for(let i = 0; i < state.pools[player].pieces.length; ++i) { + score += 3 * (2 + i) * state.pools[player].pieces[i]; + } + for(let i = 0; i < state.pools[opponent].pieces.length; ++i) { + score -= 2 * (1 + i) * state.pools[opponent].pieces[i]; + } + + for(let i = 0; i < state.board.columns.length; ++i) { + let column = state.board.columns[i]; + let extent_score = 0; + if(player == 0) { + extent_score += column.extent[player]; + extent_score -= 8 - column.extent[opponent]; + } else { + extent_score += 8 - column.extent[player]; + extent_score -= column.extent[opponent]; + } + score += Math.floor(extent_score / 3); + } } return score; diff --git a/www/js/language.js b/www/js/language.js index a65caf8..5e78c35 100644 --- a/www/js/language.js +++ b/www/js/language.js @@ -22,6 +22,7 @@ LANGUAGE.Terms = { reconnect: new LANGUAGE.Term( "Reconnect", "再接続" ), register: new LANGUAGE.Term( "Register", "登録" ), login: new LANGUAGE.Term( "Log In", "ログイン" ), + auth: new LANGUAGE.Term( "Authenticate", "認証する" ), challenge: new LANGUAGE.Term( "Challenge", "挑戦" ), diff --git a/www/js/scene.js b/www/js/scene.js index 766eace..8b52cd2 100644 --- a/www/js/scene.js +++ b/www/js/scene.js @@ -392,6 +392,8 @@ const SCENES = { table.appendChild(UI.session_table(this.data)); UI.maincontent(table); + UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); + history.pushState(null, "Dzura", "/"); } else { SCENE.load(SCENES.Offline); @@ -399,7 +401,9 @@ const SCENES = { return true; } refresh() { - MESSAGE_SESSION_LIST(this.page, 2, false, false); + MESSAGE_SESSION_LIST(this.page, [ + filter(FilterCode.IsComplete, false), + ]); } /*message(code, data) { switch(code) { @@ -466,11 +470,16 @@ const SCENES = { table.appendChild(UI.session_table(this.data)); UI.maincontent(table); + UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); + history.pushState(null, "Dzura - Continue", "/continue/"); return true; } refresh() { - MESSAGE_SESSION_LIST(this.page, 2, true, false); + MESSAGE_SESSION_LIST(this.page, [ + filter(FilterCode.IsComplete, false), + filter(FilterCode.IsPlayer, true), + ]); } /*message(code, data) { switch(code) { @@ -535,11 +544,16 @@ const SCENES = { table.appendChild(UI.session_table(this.data)); UI.maincontent(table); + UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); + history.pushState(null, "Dzura - Live", "/live/"); return true; } refresh() { - MESSAGE_SESSION_LIST(this.page, 2, false, true); + MESSAGE_SESSION_LIST(this.page, [ + filter(FilterCode.IsComplete, false), + filter(FilterCode.IsLive, true), + ]); } /*message(code, data) { switch(code) { @@ -604,13 +618,17 @@ const SCENES = { table.appendChild(UI.session_table_history(this.data)); UI.maincontent(table); + UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length); + history.pushState(null, "Dzura - History", "/history/"); return true; } refresh() { - MESSAGE_SESSION_LIST(this.page, 3, false, false); + MESSAGE_SESSION_LIST(this.page, [ + filter(FilterCode.IsComplete, true), + ]); } - /*message(code, data) { + message(code, data) { switch(code) { case OpCode.SessionList: { let table = document.getElementById("content"); @@ -622,7 +640,7 @@ const SCENES = { } } break; } - }*/ + } disconnect() { SCENE.load(SCENES.Offline); } @@ -708,18 +726,18 @@ const SCENES = { }; this.history = [ ]; } - /*preload(data) { + preload(data) { this.data.handle = data.handle; MESSAGE_COMPOSE([ PACK.u16(OpCode.UserInfo), PACK.string(this.data.handle, PACK.u8), ]); return true; - }*/ + } load(msg) { - /*if(msg.code != OpCode.UserInfo) { + if(msg.code != OpCode.UserInfo) { return null; - }*/ + } this.handle = msg.handle; UI.mainmenu_account(this.handle, "profile"); @@ -779,8 +797,51 @@ const SCENES = { UI.mainnav(buttons_left, buttons_right); // Main Content + let container = document.createElement("section"); + let form = document.createElement("form"); - history.pushState(null, "Dzura - About", "/u/" + CONTEXT.Auth.handle); + form.appendChild(UI.table(null, [ + [ UI.label(LANG("secret"), "secret"), UI.password("secret") ], + ])); + + let button = UI.submit(LANG("auth")); + button.setAttribute("id", "submit"); + form.appendChild(button); + + form.addEventListener("submit", (event) => { + event.preventDefault(); + + /*let secret = document.getElementById("secret"); + + secret.removeAttribute("class"); + event.target.removeAttribute("class"); + + if(handle.value.length > 0 && handle.value.length <= 24 && secret.value.length > 0) { + event.target.setAttribute("disabled", ""); + + let enc = new TextEncoder(); + let enc_secret = enc.encode(secret.value); + + MESSAGE_COMPOSE([ + PACK.u16(OpCode.Authenticate), + PACK.u16(enc_handle.length), + enc_handle, + PACK.u16(enc_secret.length), + enc_secret, + ]); + } else { + if(handle.value.length == 0 || handle.value.length > 24) { handle.setAttribute("class", "error"); } + if(secret.value.length == 0) { secret.setAttribute("class", "error"); } + }*/ + }); + + container.appendChild(form); + MAIN.appendChild(container); + MAIN.setAttribute("class", "form"); + + document.getElementById("secret").focus(); + + history.pushState(null, "Dzura - Account", "/u/" + CONTEXT.Auth.handle); return true; } }, diff --git a/www/js/system.js b/www/js/system.js index fcc3097..930ad90 100644 --- a/www/js/system.js +++ b/www/js/system.js @@ -18,6 +18,7 @@ function RECONNECT() { console.log("Websocket closed."); SOCKET = null; if(SCENE.disconnect !== undefined) { SCENE.disconnect(); } + BADGE_UPDATE(false); RECONNECT(); }); @@ -597,17 +598,39 @@ function MESSAGE_COMPOSE(data) { } } -function MESSAGE_SESSION_LIST(page, game_state, is_player, is_live) { - let flags = 0; - flags |= game_state; - flags |= (+is_player) << 2; - flags |= (+is_live) << 3; - - MESSAGE_COMPOSE([ +function MESSAGE_SESSION_LIST(page, filters) { + let request = [ PACK.u16(OpCode.SessionList), - PACK.u16(flags), - PACK.u16(page), - ]); + PACK.u32((page - 1) * 30), + PACK.u32(30), + PACK.u16(filters.length), + ]; + + for(let i = 0; i < filters.length; ++i) { + switch(filters[i].code) { + case FilterCode.IsComplete: { + request.push(PACK.u8(FilterCode.IsComplete)); + request.push(PACK.u8(filters[i].value)); + } break; + + case FilterCode.IsLive: { + request.push(PACK.u8(FilterCode.IsLive)); + request.push(PACK.u8(filters[i].value)); + } break; + + case FilterCode.IsPlayer: { + request.push(PACK.u8(FilterCode.IsPlayer)); + request.push(PACK.u8(filters[i].value)); + } break; + + case FilterCode.Player: { + request.push(PACK.u8(FilterCode.IsComplete)); + request.push(PACK.string(filters[i].value, PACK.u8)); + } break; + } + } + + MESSAGE_COMPOSE(request); } function MESSAGE_SESSION_VIEW(token, player) { diff --git a/www/js/ui.js b/www/js/ui.js index 93dcb46..3888a65 100644 --- a/www/js/ui.js +++ b/www/js/ui.js @@ -320,43 +320,6 @@ const UI = { return tbody; }, - /*session_table_join(records) { - let rows = [ ]; - - for(let r = 0; r < records.length; ++r) { - let buttons = [ ]; - let join_callback = function() { - SCENE.load(SCENES.Game, { - token:this.token, - mode:INTERFACE.Mode.Player, - }); - MESSAGE_SESSION_VIEW(this.token, true); - }; - join_callback = join_callback.bind({token: records[r].token}); - - if(records[r].player) { - buttons.push(UI.button("View", join_callback)); - } else { - buttons.push(UI.button("Join", join_callback)); - } - - let host = UI.text(records[r].dawn); - if(records[r].dawn == "") { dawn = UI.span([UI.text("Vacant")], "text-system"); } - - rows.push([ - host, - buttons, - ]); - } - - let tbody = UI.table_content( - [ "Host", "" ], - rows, - ); - - return tbody; - },*/ - session_table_history(records) { let rows = [ ]; diff --git a/www/js/util.js b/www/js/util.js index 5730272..3333c90 100644 --- a/www/js/util.js +++ b/www/js/util.js @@ -290,3 +290,10 @@ const VALID = { return reg.test(text); }, }; + +function filter(code, value) { + return { + code: code, + value: value, + }; +}