Improve session filtering; add password UI to account.

This commit is contained in:
yukirij 2024-10-17 23:02:26 -07:00
parent 2cea7f4b86
commit ee332c4852
21 changed files with 711 additions and 296 deletions

View File

@ -7,7 +7,7 @@ use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
use crate::app::{ use crate::app::{
authentication::AuthToken, authentication::AuthToken,
session::SessionToken, context::Context,
}; };
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>; type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
@ -17,7 +17,8 @@ pub struct Connection {
pub bus:u32, pub bus:u32,
pub stream:StreamType, pub stream:StreamType,
pub auth:Option<AuthToken>, pub auth:Option<AuthToken>,
pub session:Option<SessionToken>,
pub context:Context,
pub prev:u32, pub prev:u32,
pub next:u32, pub next:u32,

View File

@ -1,3 +1,18 @@
use super::session::SessionToken;
#[derive(Clone)]
pub enum Context { pub enum Context {
None, 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],
} }

View File

@ -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<bool>,
pub is_live:Option<bool>,
pub is_player:Option<bool>,
pub player:[Option<String>; 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(),
}
}
}

View File

@ -1,5 +1,7 @@
use game::Game; use game::Game;
mod filter; pub use filter::SessionFilter;
pub type SessionToken = [u8; 8]; pub type SessionToken = [u8; 8];
pub type SessionSecret = [u8; 8]; pub type SessionSecret = [u8; 8];

View File

@ -18,6 +18,15 @@ pub struct UserStatistics {
pub games_played:u32, pub games_played:u32,
pub games_won:u32, pub games_won:u32,
} }
impl UserStatistics {
pub fn new() -> Self
{
Self {
games_played:0,
games_won:0,
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct User { pub struct User {
@ -30,5 +39,26 @@ pub struct User {
pub connection:Option<u32>, pub connection:Option<u32>,
pub status:UserStatus, pub status:UserStatus,
pub statistics:UserStatistics,
pub challenges:Vec<u32>, pub challenges:Vec<u32>,
} }
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(),
}
}
}

View File

@ -5,9 +5,10 @@ use crate::{
app::{ app::{
authentication::Authentication, authentication::Authentication,
connection::Connection, connection::Connection,
user::{User, UserStatus}, user::User,
App, App,
session::Session, session::{Session, SessionFilter},
context::{self, Context},
}, },
protocol, protocol,
}; };
@ -27,9 +28,9 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
let qr = packet.data; let qr = packet.data;
let mut user_id = None; 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) { 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_id) = conn.auth {
if let Some(auth) = app.auths.get(&auth_id) { if let Some(auth) = app.auths.get(&auth_id) {
@ -44,7 +45,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
bus: request.bus_id, bus: request.bus_id,
stream: request.stream, stream: request.stream,
auth: None, auth: None,
session: None,
context:Context::None,
prev:0, prev:0,
next:0, next:0,
@ -70,22 +72,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
// Uninitialize connection // Uninitialize connection
if if let Some(conn) = app.connections.get(qr.id as usize).cloned() { if if let Some(conn) = app.connections.get(qr.id as usize).cloned() {
// Disassociate session if present // Disassociate context if present
if let Some(session_token) = conn.session { change_context(&mut app, qr.id, user_id, Context::None).await;
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;
}
// Remove connection from chain. // Remove connection from chain.
if let Some(auth_id) = conn.auth { if let Some(auth_id) = conn.auth {
@ -168,18 +156,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
app.filesystem.handle_create(&handle, user_id).ok(); app.filesystem.handle_create(&handle, user_id).ok();
app.user_handle.set(handle.as_bytes(), user_id); app.user_handle.set(handle.as_bytes(), user_id);
let user_data = User { let mut user_data = User::new();
id:user_id, user_data.id = user_id;
flags:0, user_data.handle = display_name;
handle:display_name, user_data.secret = secret;
secret, user_data.na_key = salt_id;
na_key:salt_id,
connection:Some(qr.id),
status:UserStatus::new(),
challenges:Vec::new(),
};
app.filesystem.user_create(&user_data).ok(); app.filesystem.user_create(&user_data).ok();
@ -433,78 +414,20 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
QRPacketData::QSessionList(request) => { QRPacketData::QSessionList(request) => {
app.log.log("Request: Session List"); app.log.log("Request: Session List");
println!("range {} {}", request.filter.start, request.filter.count);
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(); let mut response = PacketSessionListResponse::new();
response.records = filter_sessions(&app, user_id, request.filter);
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);
}
Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response))) Some(QRPacket::new(qr.id, QRPacketData::RSessionList(response)))
} }
@ -516,7 +439,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
response.token = request.token; response.token = request.token;
// Verify that session exists // 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(); response.is_complete = session.game.is_complete();
@ -527,7 +450,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
} }
// Join game as player // Join game as player
if if request.join { if request.join {
// Verify client is authenticated // Verify client is authenticated
if user_id.is_some() { if user_id.is_some() {
@ -545,21 +468,15 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
app.log.log("User spectates session."); app.log.log("User spectates session.");
response.status = STATUS_OK; response.status = STATUS_OK;
true 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 { } else {
2 false
}; } {
session.add_connection(mode, qr.id); change_context(&mut app, qr.id, user_id, Context::Game(
app.send_session_spectators(request.token).await; context::Game {
token:request.token,
} }
)).await;
} }
Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response))) Some(QRPacket::new(qr.id, QRPacketData::RSessionView(response)))
@ -616,20 +533,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
app.log.log("Request: Session Leave"); app.log.log("Request: Session Leave");
// Verify that session exists. // Verify that session exists.
if let Some(session_token) = session_id { match &context {
if let Some(session) = app.sessions.get_mut(&session_token) { Context::Game(_) => {
if user_id == Some(session.p_dawn.user) { change_context(&mut app, qr.id, user_id, Context::None).await;
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;
} }
Some(QRPacket::new(0, QRPacketData::None)) Some(QRPacket::new(0, QRPacketData::None))
@ -661,8 +569,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
let mut packets = Vec::<QRPacket>::new(); let mut packets = Vec::<QRPacket>::new();
let mut response = QRPacketData::None; let mut response = QRPacketData::None;
if let Some(sid) = session_id { if let Context::Game(context) = &context {
if let Some(session) = app.sessions.get_mut(&sid) { if let Some(session) = app.sessions.get_mut(&context.token) {
match request.data { match request.data {
GameMessageData::PlayMove(turn, from, to) GameMessageData::PlayMove(turn, from, to)
@ -938,9 +846,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
}; };
session.game.init(); session.game.init();
if let Some(conn) = app.connections.get_mut(qr.id as usize) { change_context(&mut app, qr.id, Some(user_id), Context::Game(
conn.session = Some(session.token); context::Game {
token:session.token,
} }
)).await;
session.id = app.filesystem.session_create(&session).unwrap(); session.id = app.filesystem.session_create(&session).unwrap();
@ -1013,6 +923,31 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
Some(QRPacket::new(qr.id, QRPacketData::RUserList(response))) 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 // InviteAcquire
QRPacketData::QInviteAcquire => { QRPacketData::QInviteAcquire => {
use crate::app::{Invitation, InviteToken}; use crate::app::{Invitation, InviteToken};
@ -1146,3 +1081,145 @@ fn generate_game_state(app:&App, session:&Session) -> protocol::PacketGameStateR
response response
} }
async fn change_context(app:&mut App, conn_id:u32, user_id:Option<u32>, 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<u32>, filter:SessionFilter) -> Vec<PacketSessionListResponseRecord>
{
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
}

View File

@ -32,6 +32,11 @@ pub enum QRPacketData {
QUserInfo(PacketUserInfo), QUserInfo(PacketUserInfo),
RUserInfo(PacketUserInfoResponse), RUserInfo(PacketUserInfoResponse),
QAccountInfo(PacketAccountInfo),
RAccountInfo(PacketAccountInfoResponse),
QAccountUpdate(PacketAccountUpdate),
RAccountUpdate(PacketAccountUpdateResponse),
QSessionList(PacketSessionList), QSessionList(PacketSessionList),
RSessionList(PacketSessionListResponse), RSessionList(PacketSessionListResponse),

View File

@ -0,0 +1,71 @@
use crate::util::pack::{pack_u8, pack_u16};
use super::{
Packet,
PacketSessionListResponseRecord,
};
#[derive(Clone)]
pub struct PacketAccountInfo {
pub secret:Vec<u8>,
}
impl PacketAccountInfo {
pub fn new() -> Self
{
Self {
secret:Vec::new(),
}
}
}
impl Packet for PacketAccountInfo {
type Data = Self;
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()>
{
Ok(Self::new())
}
}
#[derive(Clone)]
pub struct PacketAccountInfoResponse {
pub status:u16,
pub handle:String,
pub is_online:bool,
pub history:Vec<PacketSessionListResponseRecord>,
}
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<u8>
{
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()
}
}

View File

@ -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<u8>, _index:&mut usize) -> Result<Self::Data, ()>
{
Ok(Self::new())
}
}
#[derive(Clone)]
pub struct PacketAccountUpdateResponse {
pub status:u16,
pub handle:String,
pub is_online:bool,
pub history:Vec<PacketSessionListResponseRecord>,
}
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<u8>
{
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()
}
}

View File

@ -21,6 +21,9 @@ mod challenge_list; pub use challenge_list::*;
mod user_list; pub use user_list::*; mod user_list; pub use user_list::*;
mod user_info; pub use user_info::*; 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_acquire; pub use invite_acquire::*;
mod invite_list; pub use invite_list::*; mod invite_list; pub use invite_list::*;

View File

@ -1,26 +1,29 @@
use crate::{ use crate::{
app::session::SessionToken, app::session::{SessionToken, SessionFilter},
util::pack::{pack_u16, pack_u32, unpack_u16}, util::pack::{pack_u16, pack_u32, unpack_u8, unpack_u16, unpack_u32},
}; };
use game::util::mask;
use super::Packet; use super::Packet;
#[derive(Clone)] #[derive(Clone)]
pub struct PacketSessionList { pub struct PacketSessionList {
pub page:u16, pub filter:SessionFilter,
pub game_state:u8,
pub is_player:bool, //pub page:u16,
pub is_live:bool, //pub game_state:u8,
//pub is_player:bool,
//pub is_live:bool,
} }
impl PacketSessionList { impl PacketSessionList {
pub fn new() -> Self pub fn new() -> Self
{ {
Self { Self {
page:0, filter:SessionFilter::new(),
game_state:0,
is_player:false, //page:0,
is_live:false, //game_state:0,
//is_player:false,
//is_live:false,
} }
} }
} }
@ -31,18 +34,32 @@ impl Packet for PacketSessionList {
{ {
let mut result = Self::new(); let mut result = Self::new();
/* Read flags if data.len() - *index > 6 {
** 0:[2] - Game state result.filter.start = unpack_u32(data, index) as usize;
** 2:[1] - User is player of session result.filter.count = unpack_u32(data, index) as usize;
** 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;
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) Ok(result)
} else { } else {

View File

@ -1,4 +1,4 @@
use crate::util::pack::{pack_u8, pack_u16}; use crate::util::pack::{pack_u8, pack_u16, unpack_u8};
use super::{ use super::{
Packet, Packet,
@ -21,9 +21,16 @@ impl PacketUserInfo {
impl Packet for PacketUserInfo { impl Packet for PacketUserInfo {
type Data = Self; type Data = Self;
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()> fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
{ {
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)
} }
} }

View File

@ -10,7 +10,7 @@ use game::{
use crate::{ use crate::{
app::{ app::{
session::{Session, SessionToken, SessionSecret}, session::{Session, SessionToken, SessionSecret},
user::{User, UserStatus}, user::User,
}, },
util::pack::*, util::pack::*,
}; };
@ -436,18 +436,14 @@ impl FileSystem {
let handle = String::from_utf8(handle).map_err(|_| ())?; let handle = String::from_utf8(handle).map_err(|_| ())?;
Ok(User { let mut user = User::new();
id, user.id = id;
flags, user.flags = flags;
handle, user.handle = handle;
secret, user.secret = secret;
na_key, user.na_key = na_key;
connection:None, Ok(user)
status:UserStatus::new(),
challenges:Vec::new(),
})
} else { Err(()) } } else { Err(()) }
} }

View File

@ -63,6 +63,13 @@ const OpCode = {
TestResult :0xFFFF, TestResult :0xFFFF,
}; };
const FilterCode = {
IsComplete :0x00,
IsLive :0x01,
IsPlayer :0x02,
Player :0x10,
};
const GameState = { const GameState = {
Joinable :0x00, Joinable :0x00,
Ongoing :0x01, Ongoing :0x01,

6
www/js/filter.js Normal file
View File

@ -0,0 +1,6 @@
class Filter {
constructor(code, value) {
this.code = code;
this.value = value;
}
}

View File

@ -779,11 +779,10 @@ const INTERFACE = {
let message = null; let message = null;
ctx.fillStyle = INTERFACE.Color.Text; ctx.fillStyle = INTERFACE.Color.Text;
if(INTERFACE_DATA.Game.auto !== null) {
switch(INTERFACE_DATA.Game.auto) { switch(INTERFACE_DATA.Game.auto) {
case 0: message = LANG("cpu") + " " + LANG("dawn"); break; case 1: message = LANG("cpu") + " " + LANG("dawn"); break;
case 1: message = LANG("cpu") + " " + LANG("dusk"); break; case 2: message = LANG("cpu") + " " + LANG("dusk"); break;
} case 3: message = LANG("cpu"); break;
} }
switch(GAME_DATA.state.code) { switch(GAME_DATA.state.code) {
@ -1233,7 +1232,7 @@ const INTERFACE = {
board_state: [ ], board_state: [ ],
history: [ ], history: [ ],
history_begin: [ ], history_begin: [ ],
auto: null, auto: 0,
}, },
Ui: { Ui: {
@ -1317,7 +1316,7 @@ const INTERFACE = {
}, },
reset() { reset() {
INTERFACE_DATA.Game.auto = null; INTERFACE_DATA.Game.auto = 0;
INTERFACE_DATA.Game.history = [ ]; INTERFACE_DATA.Game.history = [ ];
for(let i = 0; i < INTERFACE_DATA.Game.history_begin.length; ++i) { for(let i = 0; i < INTERFACE_DATA.Game.history_begin.length; ++i) {
@ -1331,7 +1330,7 @@ const INTERFACE = {
undo() { undo() {
switch(INTERFACE_DATA.mode) { switch(INTERFACE_DATA.mode) {
case INTERFACE.Mode.Local: { case INTERFACE.Mode.Local: {
INTERFACE_DATA.Game.auto = null; INTERFACE_DATA.Game.auto = 0;
if(INTERFACE_DATA.Game.history.length > 0) { if(INTERFACE_DATA.Game.history.length > 0) {
INTERFACE_DATA.Replay.turn = INTERFACE_DATA.Game.history.length + 1; INTERFACE_DATA.Replay.turn = INTERFACE_DATA.Game.history.length + 1;
INTERFACE_DATA.Game.history.pop(); INTERFACE_DATA.Game.history.pop();
@ -1501,7 +1500,7 @@ const INTERFACE = {
case INTERFACE.Mode.Local: { case INTERFACE.Mode.Local: {
INTERFACE.history_push(play, true); 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); setTimeout(INTERFACE.auto_play, 1000);
} }
} break; } break;
@ -1676,23 +1675,34 @@ const INTERFACE = {
auto() { auto() {
if(INTERFACE_DATA.Game.auto === null) { let bit = 1 << (INTERFACE_DATA.rotate ^ 1);
INTERFACE_DATA.Game.auto = INTERFACE_DATA.rotate ^ 1; if((INTERFACE_DATA.Game.auto & bit) == 0) {
INTERFACE_DATA.Game.auto |= bit;
setTimeout(INTERFACE.auto_play, 500); setTimeout(INTERFACE.auto_play, 500);
} else { } else {
INTERFACE_DATA.Game.auto = null; INTERFACE_DATA.Game.auto &= ~bit;
} }
INTERFACE.game_step(); INTERFACE.game_step();
}, },
auto_play() { 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) { function state_score(state, player) {
let score = 0; let score = 0;
let opponent = player ^ 1; let opponent = player ^ 1;
let turn = (state.turn & 1); let turn = (state.turn & 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) { for(let i = 0; i < state.board.tiles.length; ++i) {
let tile = state.board.tiles[i]; let tile = state.board.tiles[i];
score += Math.floor((tile.threaten[player] - tile.threaten[opponent]) / 2); score += Math.floor((tile.threaten[player] - tile.threaten[opponent]) / 2);
@ -1738,14 +1748,6 @@ const INTERFACE = {
} }
score += Math.floor(extent_score / 3); 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; }
} }
return score; return score;

View File

@ -22,6 +22,7 @@ LANGUAGE.Terms = {
reconnect: new LANGUAGE.Term( "Reconnect", "再接続" ), reconnect: new LANGUAGE.Term( "Reconnect", "再接続" ),
register: new LANGUAGE.Term( "Register", "登録" ), register: new LANGUAGE.Term( "Register", "登録" ),
login: new LANGUAGE.Term( "Log In", "ログイン" ), login: new LANGUAGE.Term( "Log In", "ログイン" ),
auth: new LANGUAGE.Term( "Authenticate", "認証する" ),
challenge: new LANGUAGE.Term( "Challenge", "挑戦" ), challenge: new LANGUAGE.Term( "Challenge", "挑戦" ),

View File

@ -392,6 +392,8 @@ const SCENES = {
table.appendChild(UI.session_table(this.data)); table.appendChild(UI.session_table(this.data));
UI.maincontent(table); UI.maincontent(table);
UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length);
history.pushState(null, "Dzura", "/"); history.pushState(null, "Dzura", "/");
} else { } else {
SCENE.load(SCENES.Offline); SCENE.load(SCENES.Offline);
@ -399,7 +401,9 @@ const SCENES = {
return true; return true;
} }
refresh() { refresh() {
MESSAGE_SESSION_LIST(this.page, 2, false, false); MESSAGE_SESSION_LIST(this.page, [
filter(FilterCode.IsComplete, false),
]);
} }
/*message(code, data) { /*message(code, data) {
switch(code) { switch(code) {
@ -466,11 +470,16 @@ const SCENES = {
table.appendChild(UI.session_table(this.data)); table.appendChild(UI.session_table(this.data));
UI.maincontent(table); UI.maincontent(table);
UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length);
history.pushState(null, "Dzura - Continue", "/continue/"); history.pushState(null, "Dzura - Continue", "/continue/");
return true; return true;
} }
refresh() { 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) { /*message(code, data) {
switch(code) { switch(code) {
@ -535,11 +544,16 @@ const SCENES = {
table.appendChild(UI.session_table(this.data)); table.appendChild(UI.session_table(this.data));
UI.maincontent(table); UI.maincontent(table);
UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length);
history.pushState(null, "Dzura - Live", "/live/"); history.pushState(null, "Dzura - Live", "/live/");
return true; return true;
} }
refresh() { 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) { /*message(code, data) {
switch(code) { switch(code) {
@ -604,13 +618,17 @@ const SCENES = {
table.appendChild(UI.session_table_history(this.data)); table.appendChild(UI.session_table_history(this.data));
UI.maincontent(table); UI.maincontent(table);
UI.page_indicator((this.data.length > 0)? 1 : 0, this.data.length, this.data.length);
history.pushState(null, "Dzura - History", "/history/"); history.pushState(null, "Dzura - History", "/history/");
return true; return true;
} }
refresh() { 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) { switch(code) {
case OpCode.SessionList: { case OpCode.SessionList: {
let table = document.getElementById("content"); let table = document.getElementById("content");
@ -622,7 +640,7 @@ const SCENES = {
} }
} break; } break;
} }
}*/ }
disconnect() { disconnect() {
SCENE.load(SCENES.Offline); SCENE.load(SCENES.Offline);
} }
@ -708,18 +726,18 @@ const SCENES = {
}; };
this.history = [ ]; this.history = [ ];
} }
/*preload(data) { preload(data) {
this.data.handle = data.handle; this.data.handle = data.handle;
MESSAGE_COMPOSE([ MESSAGE_COMPOSE([
PACK.u16(OpCode.UserInfo), PACK.u16(OpCode.UserInfo),
PACK.string(this.data.handle, PACK.u8), PACK.string(this.data.handle, PACK.u8),
]); ]);
return true; return true;
}*/ }
load(msg) { load(msg) {
/*if(msg.code != OpCode.UserInfo) { if(msg.code != OpCode.UserInfo) {
return null; return null;
}*/ }
this.handle = msg.handle; this.handle = msg.handle;
UI.mainmenu_account(this.handle, "profile"); UI.mainmenu_account(this.handle, "profile");
@ -779,8 +797,51 @@ const SCENES = {
UI.mainnav(buttons_left, buttons_right); UI.mainnav(buttons_left, buttons_right);
// Main Content // 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; return true;
} }
}, },

View File

@ -18,6 +18,7 @@ function RECONNECT() {
console.log("Websocket closed."); console.log("Websocket closed.");
SOCKET = null; SOCKET = null;
if(SCENE.disconnect !== undefined) { SCENE.disconnect(); } if(SCENE.disconnect !== undefined) { SCENE.disconnect(); }
BADGE_UPDATE(false);
RECONNECT(); RECONNECT();
}); });
@ -597,17 +598,39 @@ function MESSAGE_COMPOSE(data) {
} }
} }
function MESSAGE_SESSION_LIST(page, game_state, is_player, is_live) { function MESSAGE_SESSION_LIST(page, filters) {
let flags = 0; let request = [
flags |= game_state;
flags |= (+is_player) << 2;
flags |= (+is_live) << 3;
MESSAGE_COMPOSE([
PACK.u16(OpCode.SessionList), PACK.u16(OpCode.SessionList),
PACK.u16(flags), PACK.u32((page - 1) * 30),
PACK.u16(page), 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) { function MESSAGE_SESSION_VIEW(token, player) {

View File

@ -320,43 +320,6 @@ const UI = {
return tbody; 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) { session_table_history(records) {
let rows = [ ]; let rows = [ ];

View File

@ -290,3 +290,10 @@ const VALID = {
return reg.test(text); return reg.test(text);
}, },
}; };
function filter(code, value) {
return {
code: code,
value: value,
};
}