Add auth operations; update web interface.
This commit is contained in:
parent
7bae892c2e
commit
d5c0d74016
@ -1,7 +1,10 @@
|
|||||||
use crate::board::Board;
|
use crate::board::Board;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum GameState {
|
pub enum GameState {
|
||||||
Active,
|
None,
|
||||||
|
Joinable,
|
||||||
|
Ongoing,
|
||||||
Complete,
|
Complete,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,7 +16,7 @@ impl Game {
|
|||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
state:GameState::Active,
|
state:GameState::Joinable,
|
||||||
board:Board::new(),
|
board:Board::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,4 +4,4 @@ pub mod util;
|
|||||||
pub mod piece;
|
pub mod piece;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
pub mod history;
|
pub mod history;
|
||||||
mod game; pub use game::Game;
|
pub mod game; pub use game::Game;
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub const fn bit(v:u32) -> u32 { 1u32 << v }
|
pub const fn bit(v:u32) -> u32 { 1u32 << v }
|
||||||
|
pub const fn mask(b:u32, s:u32) -> u32 { ((1u32 << b) - 1) << s }
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
pub type AuthToken = [u8; 8];
|
pub type AuthToken = [u8; 8];
|
||||||
|
pub type AuthSecret = [u8; 16];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Authentication {
|
pub struct Authentication {
|
||||||
pub key:AuthToken,
|
pub key:AuthToken,
|
||||||
pub secret:[u8; 16],
|
pub secret:AuthSecret,
|
||||||
pub user:u32,
|
pub user:u32,
|
||||||
}
|
}
|
||||||
impl Authentication {
|
impl Authentication {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
key:[0; 8],
|
key:AuthToken::default(),
|
||||||
secret:[0; 16],
|
secret:AuthSecret::default(),
|
||||||
user:0,
|
user:0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,15 @@
|
|||||||
use sparse::Sparse;
|
use sparse::Sparse;
|
||||||
use pool::Pool;
|
use pool::Pool;
|
||||||
use trie::Trie;
|
use trie::Trie;
|
||||||
use crate::util::pack::pack_u32;
|
use crate::util::{
|
||||||
|
Chain,
|
||||||
|
pack::pack_u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod connection; use connection::Connection;
|
pub mod connection; use connection::Connection;
|
||||||
pub mod user; use user::User;
|
pub mod user; use user::User;
|
||||||
pub mod authentication; use authentication::Authentication;
|
pub mod authentication; use authentication::Authentication;
|
||||||
pub mod session; use session::Session;
|
pub mod session; use session::{Session, SessionToken};
|
||||||
pub mod context;
|
pub mod context;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
@ -22,6 +25,8 @@ pub struct App {
|
|||||||
|
|
||||||
pub auths:Trie<Authentication>,
|
pub auths:Trie<Authentication>,
|
||||||
pub sessions:Trie<Session>,
|
pub sessions:Trie<Session>,
|
||||||
|
|
||||||
|
pub session_time:Chain<SessionToken>,
|
||||||
}
|
}
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
@ -37,6 +42,8 @@ impl App {
|
|||||||
|
|
||||||
auths:Trie::new(),
|
auths:Trie::new(),
|
||||||
sessions:Trie::new(),
|
sessions:Trie::new(),
|
||||||
|
|
||||||
|
session_time:Chain::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +61,7 @@ impl App {
|
|||||||
fs::write(path_data.join("s/.i"), pack_u32(0)).ok();
|
fs::write(path_data.join("s/.i"), pack_u32(0)).ok();
|
||||||
fs::write(path_data.join("u/.i"), pack_u32(0)).ok();
|
fs::write(path_data.join("u/.i"), pack_u32(0)).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
use game::Game;
|
use game::Game;
|
||||||
|
|
||||||
pub struct Session {
|
pub type SessionToken = [u8; 8];
|
||||||
key:[u8; 8],
|
pub type SessionSecret = [u8; 8];
|
||||||
secret:[u8; 8],
|
|
||||||
|
|
||||||
game:Game,
|
pub struct Viewer {
|
||||||
|
pub connection:Option<u32>,
|
||||||
p_dawn:Option<u32>,
|
pub user:Option<u32>,
|
||||||
p_dusk:Option<u32>,
|
}
|
||||||
specators:Vec<u32>,
|
|
||||||
|
pub struct Session {
|
||||||
|
pub key:SessionToken,
|
||||||
|
pub secret:SessionSecret,
|
||||||
|
|
||||||
|
pub game:Game,
|
||||||
|
|
||||||
|
pub p_dawn:Viewer,
|
||||||
|
pub p_dusk:Viewer,
|
||||||
|
pub viewers:Vec<Viewer>,
|
||||||
|
|
||||||
|
pub chain_id:usize,
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,16 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
while match bus.receive_wait() {
|
while match bus.receive_wait() {
|
||||||
Some(packet) => {
|
Some(packet) => {
|
||||||
let qr = &packet.data;
|
let qr = &packet.data;
|
||||||
|
|
||||||
|
let mut user_id = None;
|
||||||
|
if let Some(conn) = app.connections.get(qr.id as usize) {
|
||||||
|
if let Some(auth_id) = conn.auth {
|
||||||
|
if let Some(auth) = app.auths.get(&auth_id) {
|
||||||
|
user_id = Some(auth.user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match &qr.data {
|
match &qr.data {
|
||||||
QConn(request) => {
|
QConn(request) => {
|
||||||
let id = app.connections.add(Connection {
|
let id = app.connections.add(Connection {
|
||||||
@ -177,6 +187,31 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
bus.send(packet.from, QRPacket::new(qr.id, RAuth(response))).is_ok()
|
bus.send(packet.from, QRPacket::new(qr.id, RAuth(response))).is_ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QAuthResume(request) => {
|
||||||
|
let mut response = PacketAuthResumeResponse::new();
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
|
if let Some(auth) = app.auths.get(&request.token) {
|
||||||
|
|
||||||
|
// Compare full secret length to reduce time-based attacks.
|
||||||
|
let mut valid = true;
|
||||||
|
for i in 0..16 {
|
||||||
|
valid |= auth.secret[i] == request.secret[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
conn.auth = Some(request.token);
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
} else {
|
||||||
|
response.status = STATUS_SERVER_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RAuthResume(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
QDeauth => {
|
QDeauth => {
|
||||||
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
match conn.auth {
|
match conn.auth {
|
||||||
@ -191,6 +226,173 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSessionList(request) => {
|
||||||
|
use game::game::GameState;
|
||||||
|
|
||||||
|
println!("Request: Session List");
|
||||||
|
|
||||||
|
let mut response = PacketSessionListResponse::new();
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
||||||
|
// Requirements:
|
||||||
|
// - GameState must be None or match state of session.
|
||||||
|
// - Joinable must have either player slot empty.
|
||||||
|
// - IsPlayer must have the current user in either player slot.
|
||||||
|
// - IsLive must have both users connected to the session.
|
||||||
|
|
||||||
|
let mut valid = request.game_state == GameState::None || request.game_state == session.game.state;
|
||||||
|
valid &= request.game_state != GameState::Joinable || session.p_dawn.user.is_none() || session.p_dusk.user.is_none();
|
||||||
|
valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id;
|
||||||
|
valid &= !request.is_live || (session.p_dawn.connection.is_some() && session.p_dusk.connection.is_some());
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
let is_player = user_id.is_some() && (session.p_dawn.user == user_id || session.p_dusk.user == user_id);
|
||||||
|
|
||||||
|
let dawn_handle = if let Some(uid) = session.p_dawn.user {
|
||||||
|
if let Some(cuid) = app.user_id.get(uid as isize) {
|
||||||
|
if let Some(user) = app.users.get(*cuid) {
|
||||||
|
user.handle.clone()
|
||||||
|
} else { String::new() }
|
||||||
|
} else { String::new() }
|
||||||
|
} else { String::new() };
|
||||||
|
|
||||||
|
let dusk_handle = if let Some(uid) = session.p_dusk.user {
|
||||||
|
if let Some(cuid) = app.user_id.get(uid as isize) {
|
||||||
|
if let Some(user) = app.users.get(*cuid) {
|
||||||
|
user.handle.clone()
|
||||||
|
} else { String::new() }
|
||||||
|
} else { String::new() }
|
||||||
|
} else { String::new() };
|
||||||
|
|
||||||
|
response.records.push(PacketSessionListResponseRecord {
|
||||||
|
token:session.key,
|
||||||
|
handles:[
|
||||||
|
dawn_handle,
|
||||||
|
dusk_handle,
|
||||||
|
],
|
||||||
|
turn:0,
|
||||||
|
last_move:[0; 5],
|
||||||
|
viewers:0,
|
||||||
|
player:is_player,
|
||||||
|
});
|
||||||
|
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count >= 60 { break; }
|
||||||
|
next_id = app.session_time.next(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RSessionList(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
QSessionCreate(_request) => {
|
||||||
|
use crate::app::session::*;
|
||||||
|
|
||||||
|
println!("Request: Session Create");
|
||||||
|
|
||||||
|
let mut response = PacketSessionCreateResponse::new();
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
|
if let Some(uid) = user_id {
|
||||||
|
// Generate session token
|
||||||
|
let mut token = SessionToken::default();
|
||||||
|
let mut secret = SessionSecret::default();
|
||||||
|
loop {
|
||||||
|
rng.fill(&mut token).ok();
|
||||||
|
if app.sessions.get(&token).is_none() { break; }
|
||||||
|
}
|
||||||
|
rng.fill(&mut secret).ok();
|
||||||
|
|
||||||
|
let chain_id = app.session_time.add(token);
|
||||||
|
app.sessions.set(&token, Session {
|
||||||
|
key:token,
|
||||||
|
secret:secret,
|
||||||
|
game:game::Game::new(),
|
||||||
|
p_dawn:Viewer {
|
||||||
|
connection:None,//Some(qr.id),
|
||||||
|
user:Some(uid),
|
||||||
|
},
|
||||||
|
p_dusk:Viewer {
|
||||||
|
connection:None,
|
||||||
|
user:None,
|
||||||
|
},
|
||||||
|
viewers:Vec::new(),
|
||||||
|
chain_id:chain_id,
|
||||||
|
});
|
||||||
|
app.session_time.set(chain_id, token);
|
||||||
|
|
||||||
|
// Set player to Dawn.
|
||||||
|
response.mode = 0;
|
||||||
|
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
response.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RSessionCreate(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
QSessionJoin(request) => {
|
||||||
|
println!("Request: Session Join");
|
||||||
|
|
||||||
|
let mut response = PacketSessionJoinResponse::new();
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
|
||||||
|
// Verify that session exists.
|
||||||
|
if let Some(session) = app.sessions.get_mut(&request.token) {
|
||||||
|
|
||||||
|
// Join game as player.
|
||||||
|
if request.join {
|
||||||
|
|
||||||
|
// Verify client is authenticated.
|
||||||
|
if let Some(uid) = user_id {
|
||||||
|
|
||||||
|
// User must not already be player.
|
||||||
|
if session.p_dawn.user != user_id && session.p_dusk.user != user_id {
|
||||||
|
|
||||||
|
// Add user to empty player slot.
|
||||||
|
if if session.p_dawn.user.is_none() {
|
||||||
|
session.p_dawn.user = Some(uid);
|
||||||
|
response.mode = 0;
|
||||||
|
true
|
||||||
|
} else if session.p_dusk.user.is_none() {
|
||||||
|
session.p_dusk.user = Some(uid);
|
||||||
|
response.mode = 1;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
// Session is not empty.
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
false
|
||||||
|
} {
|
||||||
|
println!("Add user to session.");
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("User resumes session.");
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
response.mode = (session.p_dusk.user == user_id) as u8;
|
||||||
|
}
|
||||||
|
} else { response.status = STATUS_NOAUTH; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join game as spectator.
|
||||||
|
else {
|
||||||
|
println!("User spectates session.");
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
response.mode = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.send(packet.from, QRPacket::new(qr.id, RSessionJoin(response))).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
_ => { true }
|
_ => { true }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,9 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
|
|||||||
Message::Binary(data) => {
|
Message::Binary(data) => {
|
||||||
let mut index :usize = 0;
|
let mut index :usize = 0;
|
||||||
let code: u16 = unpack_u16(&data, &mut index);
|
let code: u16 = unpack_u16(&data, &mut index);
|
||||||
|
|
||||||
|
println!("MESSAGE {:x}", code);
|
||||||
|
|
||||||
match code {
|
match code {
|
||||||
|
|
||||||
CODE_REGISTER => match PacketRegister::decode(&data, &mut index) {
|
CODE_REGISTER => match PacketRegister::decode(&data, &mut index) {
|
||||||
@ -103,10 +106,102 @@ pub async fn handle_ws(mut ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServi
|
|||||||
Err(_) => { }
|
Err(_) => { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CODE_AUTH_RESUME => match PacketAuthResume::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QAuthResume(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RAuth(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { }
|
||||||
|
}
|
||||||
|
|
||||||
CODE_DEAUTH => {
|
CODE_DEAUTH => {
|
||||||
args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok();
|
args.bus.send(bus_ds, QRPacket::new(conn_id, QDeauth)).ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CODE_SESSION_LIST => match PacketSessionList::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionList(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RSessionList(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_SESSION_CREATE => match PacketSessionCreate::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionCreate(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RSessionCreate(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_SESSION_JOIN => match PacketSessionJoin::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
if args.bus.send(bus_ds, QRPacket::new(conn_id, QSessionJoin(packet))).is_ok() {
|
||||||
|
while match args.bus.receive_wait() {
|
||||||
|
Some(resp) => {
|
||||||
|
let qr = &resp.data;
|
||||||
|
match &qr.data {
|
||||||
|
RSessionJoin(resp) => {
|
||||||
|
ws.send(Message::Binary(
|
||||||
|
encode_response(code, resp.encode())
|
||||||
|
)).await.ok();
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
} { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
|
}
|
||||||
|
|
||||||
_ => { }
|
_ => { }
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
#![allow(dead_code)]
|
/*
|
||||||
|
** Status Codes
|
||||||
|
*/
|
||||||
|
|
||||||
pub const STATUS_OK :u16 = 0x0000;
|
pub const STATUS_OK :u16 = 0x0000;
|
||||||
pub const STATUS_ERROR :u16 = 0x0001;
|
pub const STATUS_ERROR :u16 = 0x0001;
|
||||||
|
pub const STATUS_NOAUTH :u16 = 0x0002;
|
||||||
|
|
||||||
pub const STATUS_BAD_HANDLE :u16 = 0x0001;
|
pub const STATUS_BAD_HANDLE :u16 = 0x0010;
|
||||||
pub const STATUS_BAD_SECRET :u16 = 0x0002;
|
pub const STATUS_BAD_SECRET :u16 = 0x0011;
|
||||||
pub const STATUS_BAD_CODE :u16 = 0x0003;
|
pub const STATUS_BAD_CODE :u16 = 0x0012;
|
||||||
|
|
||||||
|
pub const STATUS_SERVER_ERROR :u16 = 0x00FE;
|
||||||
pub const STATUS_NOT_IMPL :u16 = 0x00FF;
|
pub const STATUS_NOT_IMPL :u16 = 0x00FF;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Operation Codes
|
||||||
|
*/
|
||||||
|
|
||||||
pub const CODE_REGISTER :u16 = 0x0010;
|
pub const CODE_REGISTER :u16 = 0x0010;
|
||||||
pub const CODE_AUTH :u16 = 0x0011;
|
pub const CODE_AUTH :u16 = 0x0011;
|
||||||
pub const CODE_DEAUTH :u16 = 0x0013;
|
pub const CODE_DEAUTH :u16 = 0x0013;
|
||||||
pub const CODE_AUTH_RESUME :u16 = 0x0012;
|
pub const CODE_AUTH_RESUME :u16 = 0x0012;
|
||||||
|
|
||||||
pub const CODE_LIST_SESSION :u16 = 0x0010;
|
pub const CODE_SESSION_LIST :u16 = 0x0020;
|
||||||
|
pub const CODE_SESSION_CREATE :u16 = 0x0021;
|
||||||
pub const CODE_SESSION_JOIN :u16 = 0x0020;
|
pub const CODE_SESSION_JOIN :u16 = 0x0022;
|
||||||
pub const CODE_SESSION_SPECTATE :u16 = 0x0021;
|
pub const CODE_SESSION_RETIRE :u16 = 0x002E;
|
||||||
pub const CODE_SESSION_LEAVE :u16 = 0x0022;
|
pub const CODE_SESSION_LEAVE :u16 = 0x002F;
|
||||||
pub const CODE_SESSION_RETIRE :u16 = 0x0023;
|
|
||||||
|
|
||||||
pub const CODE_GAME_PLAY :u16 = 0x0030;
|
pub const CODE_GAME_PLAY :u16 = 0x0030;
|
||||||
|
@ -18,10 +18,19 @@ pub enum QRPacketData {
|
|||||||
QAuth(PacketAuth),
|
QAuth(PacketAuth),
|
||||||
RAuth(PacketAuthResponse),
|
RAuth(PacketAuthResponse),
|
||||||
|
|
||||||
|
QAuthResume(PacketAuthResume),
|
||||||
|
RAuthResume(PacketAuthResumeResponse),
|
||||||
|
|
||||||
QDeauth,
|
QDeauth,
|
||||||
|
|
||||||
QAuthResume,
|
QSessionList(PacketSessionList),
|
||||||
RAuthResume,
|
RSessionList(PacketSessionListResponse),
|
||||||
|
|
||||||
|
QSessionCreate(PacketSessionCreate),
|
||||||
|
RSessionCreate(PacketSessionCreateResponse),
|
||||||
|
|
||||||
|
QSessionJoin(PacketSessionJoin),
|
||||||
|
RSessionJoin(PacketSessionJoinResponse),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
mod connect; pub use connect::*;
|
mod connect; pub use connect::*;
|
||||||
|
|
||||||
mod register; pub use register::*;
|
mod register; pub use register::*;
|
||||||
mod authenticate; pub use authenticate::*;
|
mod auth; pub use auth::*;
|
||||||
|
mod resume; pub use resume::*;
|
||||||
|
|
||||||
|
mod session_list; pub use session_list::*;
|
||||||
|
mod session_create; pub use session_create::*;
|
||||||
|
mod session_join; pub use session_join::*;
|
||||||
|
|
||||||
mod prelude {
|
mod prelude {
|
||||||
pub trait Packet {
|
pub trait Packet {
|
||||||
|
61
server/src/protocol/packet/resume.rs
Normal file
61
server/src/protocol/packet/resume.rs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
use crate::{
|
||||||
|
app::authentication::{AuthToken, AuthSecret},
|
||||||
|
util::pack::pack_u16,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketAuthResume {
|
||||||
|
pub token:AuthToken,
|
||||||
|
pub secret:AuthSecret,
|
||||||
|
}
|
||||||
|
impl PacketAuthResume {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
token:AuthToken::default(),
|
||||||
|
secret:AuthSecret::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketAuthResume {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||||
|
{
|
||||||
|
let mut result = Self::new();
|
||||||
|
|
||||||
|
if data.len() - *index == 24 {
|
||||||
|
for i in 0..8 { result.token[i] = data[*index]; *index += 1; }
|
||||||
|
for i in 0..16 { result.secret[i] = data[*index]; *index += 1; }
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketAuthResumeResponse {
|
||||||
|
pub status:u16,
|
||||||
|
}
|
||||||
|
impl PacketAuthResumeResponse {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
status:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketAuthResumeResponse {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
pack_u16(self.status),
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
}
|
55
server/src/protocol/packet/session_create.rs
Normal file
55
server/src/protocol/packet/session_create.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use crate::{
|
||||||
|
app::session::SessionToken,
|
||||||
|
util::pack::{pack_u8, pack_u16},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionCreate {
|
||||||
|
|
||||||
|
}
|
||||||
|
impl PacketSessionCreate {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionCreate {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()>
|
||||||
|
{
|
||||||
|
Ok(Self::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionCreateResponse {
|
||||||
|
pub status:u16,
|
||||||
|
pub token:SessionToken,
|
||||||
|
pub mode:u8,
|
||||||
|
}
|
||||||
|
impl PacketSessionCreateResponse {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
status:0,
|
||||||
|
token:SessionToken::default(),
|
||||||
|
mode:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionCreateResponse {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
pack_u16(self.status),
|
||||||
|
pack_u8(self.mode),
|
||||||
|
self.token.to_vec(),
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
}
|
67
server/src/protocol/packet/session_join.rs
Normal file
67
server/src/protocol/packet/session_join.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
use crate::{
|
||||||
|
app::session::SessionToken,
|
||||||
|
util::pack::{pack_u8, pack_u16},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionJoin {
|
||||||
|
pub token:SessionToken,
|
||||||
|
pub join:bool,
|
||||||
|
}
|
||||||
|
impl PacketSessionJoin {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
token:SessionToken::default(),
|
||||||
|
join:false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionJoin {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||||
|
{
|
||||||
|
let mut result = Self::new();
|
||||||
|
|
||||||
|
if data.len() - *index == 9 {
|
||||||
|
for i in 0..8 { result.token[i] = data[*index]; *index += 1; }
|
||||||
|
result.join = data[*index] != 0;
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionJoinResponse {
|
||||||
|
pub status:u16,
|
||||||
|
pub token:SessionToken,
|
||||||
|
pub mode:u8,
|
||||||
|
}
|
||||||
|
impl PacketSessionJoinResponse {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
status:0,
|
||||||
|
token:SessionToken::default(),
|
||||||
|
mode:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionJoinResponse {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
pack_u16(self.status),
|
||||||
|
pack_u8(self.mode),
|
||||||
|
self.token.to_vec(),
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
}
|
121
server/src/protocol/packet/session_list.rs
Normal file
121
server/src/protocol/packet/session_list.rs
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
use crate::{
|
||||||
|
app::session::SessionToken,
|
||||||
|
util::pack::{pack_u16, pack_u32, unpack_u16},
|
||||||
|
};
|
||||||
|
use game::{
|
||||||
|
game::GameState,
|
||||||
|
util::mask,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionList {
|
||||||
|
pub page:u16,
|
||||||
|
pub game_state:GameState,
|
||||||
|
pub is_player:bool,
|
||||||
|
pub is_live:bool,
|
||||||
|
}
|
||||||
|
impl PacketSessionList {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
page:0,
|
||||||
|
game_state:GameState::Joinable,
|
||||||
|
is_player:false,
|
||||||
|
is_live:false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionList {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||||
|
{
|
||||||
|
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 = match flags & mask(2, 0) as u16 {
|
||||||
|
1 => GameState::Joinable,
|
||||||
|
2 => GameState::Ongoing,
|
||||||
|
3 => GameState::Complete,
|
||||||
|
_ => GameState::None,
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionListResponseRecord {
|
||||||
|
pub token:SessionToken,
|
||||||
|
pub handles:[String; 2],
|
||||||
|
pub turn:u16,
|
||||||
|
pub last_move:[u8; 5],
|
||||||
|
pub viewers:u32,
|
||||||
|
pub player:bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionListResponse {
|
||||||
|
pub records:Vec<PacketSessionListResponseRecord>,
|
||||||
|
}
|
||||||
|
impl PacketSessionListResponse {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
records:Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionListResponse {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn encode(&self) -> Vec<u8>
|
||||||
|
{
|
||||||
|
let mut result = pack_u16(self.records.len() as u16);
|
||||||
|
|
||||||
|
for record in &self.records {
|
||||||
|
let mut chunk = record.token.to_vec();
|
||||||
|
|
||||||
|
// Dawn handle
|
||||||
|
let mut bytes = record.handles[0].as_bytes().to_vec();
|
||||||
|
chunk.append(&mut pack_u16(bytes.len() as u16));
|
||||||
|
if bytes.len() > 0 { chunk.append(&mut bytes); }
|
||||||
|
|
||||||
|
// Dusk handle
|
||||||
|
let mut bytes = record.handles[1].as_bytes().to_vec();
|
||||||
|
chunk.append(&mut pack_u16(bytes.len() as u16));
|
||||||
|
if bytes.len() > 0 { chunk.append(&mut bytes); }
|
||||||
|
|
||||||
|
// Turn number
|
||||||
|
chunk.append(&mut pack_u16(record.turn));
|
||||||
|
|
||||||
|
// Last move
|
||||||
|
chunk.append(&mut record.last_move.to_vec());
|
||||||
|
|
||||||
|
// Spectator count
|
||||||
|
chunk.append(&mut pack_u32(record.viewers));
|
||||||
|
|
||||||
|
// User is player
|
||||||
|
chunk.append(&mut vec![record.player as u8]);
|
||||||
|
|
||||||
|
result.append(&mut chunk);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
48
server/src/system/cache/mod.rs
vendored
48
server/src/system/cache/mod.rs
vendored
@ -19,8 +19,6 @@ impl WebCache {
|
|||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
|
||||||
let mut html = String::new();
|
let mut html = String::new();
|
||||||
let mut css = String::new();
|
|
||||||
let mut js = String::new();
|
|
||||||
let mut favicon = Vec::<u8>::new();
|
let mut favicon = Vec::<u8>::new();
|
||||||
|
|
||||||
// Cache html file
|
// Cache html file
|
||||||
@ -32,23 +30,43 @@ impl WebCache {
|
|||||||
Err(_) => { }
|
Err(_) => { }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache css file
|
// Cache js file
|
||||||
match File::open("www/.css") {
|
let css_path = std::path::Path::new("www/css/");
|
||||||
Ok(mut file) => {
|
let css: String = [
|
||||||
file.read_to_string(&mut css).ok();
|
"main.css",
|
||||||
css = minimize_whitespace(&css);
|
"util.css",
|
||||||
|
"ui.css",
|
||||||
|
"form.css",
|
||||||
|
"game.css",
|
||||||
|
].map(|path| {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
match File::open(css_path.join(path)) {
|
||||||
|
Ok(mut file) => { file.read_to_string(&mut buffer).ok(); }
|
||||||
|
Err(_) => { }
|
||||||
}
|
}
|
||||||
Err(_) => { }
|
buffer
|
||||||
}
|
}).concat();
|
||||||
|
|
||||||
// Cache js file
|
// Cache js file
|
||||||
match File::open("www/.js") {
|
let js_path = std::path::Path::new("www/js/");
|
||||||
Ok(mut file) => {
|
let js = [
|
||||||
file.read_to_string(&mut js).ok();
|
"const.js",
|
||||||
//js = minimize_whitespace(&js);
|
"util.js",
|
||||||
|
"game_asset.js",
|
||||||
|
"game.js",
|
||||||
|
"interface.js",
|
||||||
|
"ui.js",
|
||||||
|
"scene.js",
|
||||||
|
"system.js",
|
||||||
|
"main.js",
|
||||||
|
].map(|path| {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
match File::open(js_path.join(path)) {
|
||||||
|
Ok(mut file) => { file.read_to_string(&mut buffer).ok(); }
|
||||||
|
Err(_) => { }
|
||||||
}
|
}
|
||||||
Err(_) => { }
|
buffer
|
||||||
}
|
}).concat();
|
||||||
|
|
||||||
// Cache favicon file
|
// Cache favicon file
|
||||||
match File::open("www/favicon.png") {
|
match File::open("www/favicon.png") {
|
||||||
|
114
server/src/util/chain.rs
Normal file
114
server/src/util/chain.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
use pool::Pool;
|
||||||
|
|
||||||
|
struct Node<T> {
|
||||||
|
pub data:T,
|
||||||
|
pub prev:Option<usize>,
|
||||||
|
pub next:Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Chain<T> {
|
||||||
|
nodes:Pool<Node<T>>,
|
||||||
|
begin:Option<usize>,
|
||||||
|
end:Option<usize>,
|
||||||
|
}
|
||||||
|
impl<T> Chain<T> {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
nodes:Pool::new(),
|
||||||
|
begin:None,
|
||||||
|
end:None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(&mut self, data:T) -> usize
|
||||||
|
// Add an element to the top of the chain.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
let id = self.nodes.add(Node {
|
||||||
|
data:data,
|
||||||
|
prev:None,
|
||||||
|
next:self.begin,
|
||||||
|
});
|
||||||
|
self.begin = Some(id);
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn promote(&mut self, id:usize)
|
||||||
|
// Move an element to the top of the chain.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
let mut prev = None;
|
||||||
|
let mut next = None;
|
||||||
|
|
||||||
|
if let Some(node) = self.nodes.get_mut(id) {
|
||||||
|
prev = node.prev;
|
||||||
|
next = node.next;
|
||||||
|
node.prev = None;
|
||||||
|
node.next = self.begin;
|
||||||
|
}
|
||||||
|
self.begin = Some(id);
|
||||||
|
if self.begin == self.end { self.end = next; }
|
||||||
|
|
||||||
|
if let Some(pid) = prev {
|
||||||
|
if let Some(pnode) = self.nodes.get_mut(pid) {
|
||||||
|
pnode.next = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(nid) = next {
|
||||||
|
if let Some(nnode) = self.nodes.get_mut(nid) {
|
||||||
|
nnode.next = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id:usize) -> Option<&T>
|
||||||
|
{
|
||||||
|
if let Some(node) = self.nodes.get(id) {
|
||||||
|
Some(&node.data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&mut self, id:usize, data:T)
|
||||||
|
{
|
||||||
|
if let Some(node) = self.nodes.get_mut(id) {
|
||||||
|
node.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin(&self) -> Option<usize>
|
||||||
|
{
|
||||||
|
self.begin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self, id:usize) -> Option<usize>
|
||||||
|
{
|
||||||
|
if let Some(node) = self.nodes.get(id) {
|
||||||
|
node.next
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self)
|
||||||
|
// Remove the last element.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
let mut prev = None;
|
||||||
|
if let Some(end) = self.end {
|
||||||
|
if let Some(node) = self.nodes.get_mut(end) {
|
||||||
|
prev = node.prev;
|
||||||
|
}
|
||||||
|
self.nodes.remove(end).ok();
|
||||||
|
|
||||||
|
if let Some(id) = prev {
|
||||||
|
if let Some(node) = self.nodes.get_mut(id) {
|
||||||
|
node.next = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.end = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,3 +2,4 @@
|
|||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
|
mod chain; pub use chain::Chain;
|
||||||
|
@ -1,3 +1,18 @@
|
|||||||
|
pub fn pack_u8(value:u8) -> Vec<u8>
|
||||||
|
{
|
||||||
|
vec![value]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpack_u8(data:&Vec<u8>, index:&mut usize) -> u8
|
||||||
|
{
|
||||||
|
let mut result :u8 = 0;
|
||||||
|
if *index < data.len() {
|
||||||
|
result = data[*index];
|
||||||
|
*index += 1;
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pack_u16(value:u16) -> Vec<u8>
|
pub fn pack_u16(value:u16) -> Vec<u8>
|
||||||
{
|
{
|
||||||
vec![(value >> 8) as u8, (value & 0xFF) as u8]
|
vec![(value >> 8) as u8, (value & 0xFF) as u8]
|
||||||
|
344
www/.css
344
www/.css
@ -1,344 +0,0 @@
|
|||||||
*{
|
|
||||||
box-sizing:border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
html{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
body{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:row nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-start;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
overflow:hidden;
|
|
||||||
|
|
||||||
font-family:sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
body>nav{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:9rem;
|
|
||||||
height:100%;
|
|
||||||
|
|
||||||
background-color:#282828;
|
|
||||||
}
|
|
||||||
body>nav>button{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:2.5rem;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
text-align:left;
|
|
||||||
font-size:1.25rem;
|
|
||||||
cursor:pointer;
|
|
||||||
|
|
||||||
background-color:#282828;
|
|
||||||
color:#c0c0c0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
} body>nav>button:hover{
|
|
||||||
background-color:#383838;
|
|
||||||
color:#e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body>nav>header{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:3rem;
|
|
||||||
|
|
||||||
line-height:3rem;
|
|
||||||
text-align:center;
|
|
||||||
font-size:1.8rem;
|
|
||||||
font-weight:bold;
|
|
||||||
font-variant:small-caps;
|
|
||||||
|
|
||||||
color:#d0d0d0;
|
|
||||||
border-bottom:1px solid #404040;
|
|
||||||
}
|
|
||||||
|
|
||||||
main{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:column nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-start;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
|
|
||||||
flex-grow:1;
|
|
||||||
|
|
||||||
background-color:#202020;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>nav{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:row nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-start;
|
|
||||||
width:100%;
|
|
||||||
height:3rem;
|
|
||||||
|
|
||||||
background-color:#282828;
|
|
||||||
border-bottom:1px solid #404040;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>nav>section:first-child{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:row nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-start;
|
|
||||||
height:100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>nav>section:last-child{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:row nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-end;
|
|
||||||
height:100%;
|
|
||||||
flex-grow:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>nav>section>button{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:auto;
|
|
||||||
height:100%;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
text-align:left;
|
|
||||||
font-size:1.25rem;
|
|
||||||
cursor:pointer;
|
|
||||||
|
|
||||||
background-color:#282828;
|
|
||||||
color:#c0c0c0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
}
|
|
||||||
main>nav>section>button:hover{
|
|
||||||
background-color:#383838;
|
|
||||||
color:#e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main>nav>section>div{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:auto;
|
|
||||||
height:100%;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
line-height:3rem;
|
|
||||||
text-align:left;
|
|
||||||
font-size:1.25rem;
|
|
||||||
|
|
||||||
color:#e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.list table{
|
|
||||||
width:100%;
|
|
||||||
border-collapse:collapse;
|
|
||||||
}
|
|
||||||
main.list table tr{
|
|
||||||
height:2.5rem;
|
|
||||||
}
|
|
||||||
main.list table th{
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
text-align:center;
|
|
||||||
font-size:1.2rem;
|
|
||||||
font-weight:bold;
|
|
||||||
|
|
||||||
background-color:#383838;
|
|
||||||
color:#f0f0f0;
|
|
||||||
border-bottom:1px solid #404040;
|
|
||||||
}
|
|
||||||
main.list table td{
|
|
||||||
height:100%;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
text-align:center;
|
|
||||||
|
|
||||||
background-color:#303030;
|
|
||||||
color:#f0f0f0;
|
|
||||||
}
|
|
||||||
main.list table td:last-child{
|
|
||||||
display:flex;
|
|
||||||
flex-flow:row nowrap;
|
|
||||||
align-items:flex-start;
|
|
||||||
justify-content:flex-end;
|
|
||||||
flex-grow:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.list table td:last-child>button{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:auto;
|
|
||||||
height:100%;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
text-align:left;
|
|
||||||
font-size:1.25rem;
|
|
||||||
cursor:pointer;
|
|
||||||
|
|
||||||
background-color:#303030;
|
|
||||||
color:#e0e0e0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
}
|
|
||||||
main.list table td:last-child>button:hover{
|
|
||||||
background-color:#343434;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.game>canvas{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:100%;
|
|
||||||
padding:0;
|
|
||||||
margin:0;
|
|
||||||
|
|
||||||
background-color:#202020;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.game>div.sidemenu{
|
|
||||||
display:flex;
|
|
||||||
position:absolute;
|
|
||||||
bottom:0px;
|
|
||||||
right:1px;
|
|
||||||
z-index:10;
|
|
||||||
|
|
||||||
flex-flow:column nowrap;
|
|
||||||
|
|
||||||
width:auto;
|
|
||||||
height:auto;
|
|
||||||
|
|
||||||
border:0;
|
|
||||||
border-top-left-radius:1rem;
|
|
||||||
|
|
||||||
overflow:hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.game>div.sidemenu>button{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
padding:1rem;
|
|
||||||
margin:0 0 1px 0;
|
|
||||||
background-color:#202020;
|
|
||||||
color:#F0F0F0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
|
|
||||||
font-family:sans-serif;
|
|
||||||
font-size:1rem;
|
|
||||||
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
main.game>div.sidemenu>button:hover{
|
|
||||||
background-color:#404040;
|
|
||||||
}
|
|
||||||
main.game>div.sidemenu>button.warn:hover{
|
|
||||||
background-color:#602020;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section{
|
|
||||||
display:flex;
|
|
||||||
position:relative;
|
|
||||||
flex-flow:column nowrap;
|
|
||||||
align-items:center;
|
|
||||||
justify-content:center;
|
|
||||||
width:100%;
|
|
||||||
height:auto;
|
|
||||||
flex-grow:1;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section>div{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
max-width:30rem;
|
|
||||||
padding:0.5rem;
|
|
||||||
|
|
||||||
background-color:#303030;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section>div>table{
|
|
||||||
width:100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section>div>table label{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:auto;
|
|
||||||
height:auto;
|
|
||||||
padding:0 0.5rem 0 0.5rem;
|
|
||||||
|
|
||||||
text-align:right;
|
|
||||||
font-size:1.2rem;
|
|
||||||
|
|
||||||
color:#e0e0e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section>div>table input{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:2.5rem;
|
|
||||||
padding:0 1rem 0 1rem;
|
|
||||||
|
|
||||||
text-align:left;
|
|
||||||
font-size:1.15rem;
|
|
||||||
|
|
||||||
background-color:#202020;
|
|
||||||
color:#e0e0e0;
|
|
||||||
border:1px solid #303030;
|
|
||||||
outline:0;
|
|
||||||
}
|
|
||||||
main.form>section>div>table input.error{
|
|
||||||
border:1px solid #603030;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.form>section>div>button{
|
|
||||||
display:block;
|
|
||||||
position:relative;
|
|
||||||
width:100%;
|
|
||||||
height:2.5rem;
|
|
||||||
padding:0.5rem 1rem 0.5rem 1rem;
|
|
||||||
|
|
||||||
font-size:1.2rem;
|
|
||||||
cursor:pointer;
|
|
||||||
|
|
||||||
background-color:#303030;
|
|
||||||
color:#e0e0e0;
|
|
||||||
border:0;
|
|
||||||
outline:0;
|
|
||||||
}
|
|
||||||
main.form>section>div>button.error{
|
|
||||||
border:1px solid #603030;
|
|
||||||
}
|
|
||||||
main.form>section>div>button:hover{
|
|
||||||
background-color:#383838;
|
|
||||||
}
|
|
||||||
main.form>section>div>button:disabled{
|
|
||||||
background-color:#2c2c2c;
|
|
||||||
color:#606060;
|
|
||||||
cursor:pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.text-system{color:#909090;}
|
|
83
www/css/form.css
Normal file
83
www/css/form.css
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
main.form>section{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:column nowrap;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:center;
|
||||||
|
width:100%;
|
||||||
|
height:auto;
|
||||||
|
flex-grow:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
max-width:30rem;
|
||||||
|
padding:0.5rem;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table{
|
||||||
|
width:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table label{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:auto;
|
||||||
|
height:auto;
|
||||||
|
padding:0 0.5rem 0 0.5rem;
|
||||||
|
|
||||||
|
text-align:right;
|
||||||
|
font-size:1.2rem;
|
||||||
|
|
||||||
|
color:#e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>table input{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:2.5rem;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.15rem;
|
||||||
|
|
||||||
|
background-color:#202020;
|
||||||
|
color:#e0e0e0;
|
||||||
|
border:1px solid #303030;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main.form>section>div>table input.error{
|
||||||
|
border:1px solid #603030;
|
||||||
|
}
|
||||||
|
|
||||||
|
main.form>section>div>button{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:2.5rem;
|
||||||
|
padding:0.5rem 1rem 0.5rem 1rem;
|
||||||
|
|
||||||
|
font-size:1.2rem;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
color:#e0e0e0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main.form>section>div>button.error{
|
||||||
|
border:1px solid #603030;
|
||||||
|
}
|
||||||
|
main.form>section>div>button:hover{
|
||||||
|
background-color:#383838;
|
||||||
|
}
|
||||||
|
main.form>section>div>button:disabled{
|
||||||
|
background-color:#2c2c2c;
|
||||||
|
color:#606060;
|
||||||
|
cursor:pointer;
|
||||||
|
}
|
8
www/css/game.css
Normal file
8
www/css/game.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
main>canvas#game {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
background-color: #101010;
|
||||||
|
}
|
162
www/css/main.css
Normal file
162
www/css/main.css
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
*{
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:row nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-start;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
padding:0;
|
||||||
|
margin:0;
|
||||||
|
overflow:hidden;
|
||||||
|
|
||||||
|
font-family:sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
body>nav{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:column nowrap;
|
||||||
|
width:9rem;
|
||||||
|
height:100%;
|
||||||
|
|
||||||
|
background-color:#282828;
|
||||||
|
}
|
||||||
|
body>nav>section{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:auto;
|
||||||
|
}
|
||||||
|
body>nav>section:first-of-type{
|
||||||
|
flex-grow:1;
|
||||||
|
}
|
||||||
|
body>nav>section>button{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:2.5rem;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.25rem;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
|
background-color:#282828;
|
||||||
|
color:#c0c0c0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
} body>nav>section>button:hover{
|
||||||
|
background-color:#383838;
|
||||||
|
color:#e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body>nav>header{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:100%;
|
||||||
|
height:3rem;
|
||||||
|
|
||||||
|
line-height:3rem;
|
||||||
|
text-align:center;
|
||||||
|
font-size:1.8rem;
|
||||||
|
font-weight:bold;
|
||||||
|
font-variant:small-caps;
|
||||||
|
|
||||||
|
color:#d0d0d0;
|
||||||
|
border-bottom:1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
main{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:column nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-start;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
|
||||||
|
flex-grow:1;
|
||||||
|
|
||||||
|
background-color:#202020;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>nav{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:row nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-start;
|
||||||
|
width:100%;
|
||||||
|
height:3rem;
|
||||||
|
|
||||||
|
background-color:#282828;
|
||||||
|
border-bottom:1px solid #404040;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>nav>section:first-child{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:row nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-start;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>nav>section:last-child{
|
||||||
|
display:flex;
|
||||||
|
position:relative;
|
||||||
|
flex-flow:row nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-end;
|
||||||
|
height:100%;
|
||||||
|
flex-grow:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>nav>section>button{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:auto;
|
||||||
|
height:100%;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.25rem;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
|
background-color:#282828;
|
||||||
|
color:#c0c0c0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main>nav>section>button:hover{
|
||||||
|
background-color:#383838;
|
||||||
|
color:#e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>nav>section>div{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:auto;
|
||||||
|
height:100%;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
line-height:3rem;
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.25rem;
|
||||||
|
|
||||||
|
color:#e0e0e0;
|
||||||
|
}
|
54
www/css/ui.css
Normal file
54
www/css/ui.css
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
main>table.list{
|
||||||
|
width:100%;
|
||||||
|
border-collapse:collapse;
|
||||||
|
}
|
||||||
|
main>table.list tr{
|
||||||
|
height:2.5rem;
|
||||||
|
}
|
||||||
|
main>table.list th{
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:center;
|
||||||
|
font-size:1.2rem;
|
||||||
|
font-weight:bold;
|
||||||
|
|
||||||
|
background-color:#383838;
|
||||||
|
color:#f0f0f0;
|
||||||
|
border-bottom:1px solid #404040;
|
||||||
|
}
|
||||||
|
main>table.list td{
|
||||||
|
height:100%;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:center;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
color:#f0f0f0;
|
||||||
|
}
|
||||||
|
main>table.list td:last-child{
|
||||||
|
display:flex;
|
||||||
|
flex-flow:row nowrap;
|
||||||
|
align-items:flex-start;
|
||||||
|
justify-content:flex-end;
|
||||||
|
flex-grow:1;
|
||||||
|
}
|
||||||
|
|
||||||
|
main>table.list td:last-child>button{
|
||||||
|
display:block;
|
||||||
|
position:relative;
|
||||||
|
width:auto;
|
||||||
|
height:100%;
|
||||||
|
padding:0 1rem 0 1rem;
|
||||||
|
|
||||||
|
text-align:left;
|
||||||
|
font-size:1.25rem;
|
||||||
|
cursor:pointer;
|
||||||
|
|
||||||
|
background-color:#303030;
|
||||||
|
color:#e0e0e0;
|
||||||
|
border:0;
|
||||||
|
outline:0;
|
||||||
|
}
|
||||||
|
main>table.list td:last-child>button:hover{
|
||||||
|
background-color:#343434;
|
||||||
|
}
|
1
www/css/util.css
Normal file
1
www/css/util.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
span.text-system{color:#909090;}
|
40
www/js/const.js
Normal file
40
www/js/const.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
let MAIN = null;
|
||||||
|
let MENU = null;
|
||||||
|
let SCENE = null;
|
||||||
|
|
||||||
|
let CONNECTED = false;
|
||||||
|
let SOCKET = null;
|
||||||
|
let CONTEXT = {
|
||||||
|
Scene: null,
|
||||||
|
Auth: null,
|
||||||
|
Data: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Status = {
|
||||||
|
Ok: 0,
|
||||||
|
Error: 1,
|
||||||
|
NotImplement: 2,
|
||||||
|
|
||||||
|
BadHandle: 1,
|
||||||
|
BadSecret: 2,
|
||||||
|
BadCode: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OpCode = {
|
||||||
|
Register :0x0010,
|
||||||
|
Authenticate :0x0011,
|
||||||
|
Resume :0x0012,
|
||||||
|
Deauthenticate :0x0013,
|
||||||
|
|
||||||
|
SessionList :0x0020,
|
||||||
|
SessionCreate :0x0021,
|
||||||
|
SessionJoin :0x0022,
|
||||||
|
|
||||||
|
GameState :0x0030,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GameState = {
|
||||||
|
Joinable :0x00,
|
||||||
|
Ongoing :0x01,
|
||||||
|
Complete :0x02,
|
||||||
|
};
|
157
www/js/game.js
Normal file
157
www/js/game.js
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
class GamePieceMove {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class GamePiece {
|
||||||
|
constructor(name, assets, moves, promote_moves) {
|
||||||
|
this.name = name;
|
||||||
|
this.assets = assets;
|
||||||
|
this.moves = moves;
|
||||||
|
this.pmoves = promote_moves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let GAME_DATA = {
|
||||||
|
board: {
|
||||||
|
tiles: [ ],
|
||||||
|
},
|
||||||
|
|
||||||
|
pieces: [
|
||||||
|
new PieceDef("Militia「兵」", "♟︎", // ♟︎士
|
||||||
|
["asset/militia_dusk.svg", "asset/militia_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(5),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(2)
|
||||||
|
.add(4)
|
||||||
|
.add(5)
|
||||||
|
),
|
||||||
|
new PieceDef("Knight「騎」", "♞", // ♞馬
|
||||||
|
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(3)
|
||||||
|
.add(6)
|
||||||
|
.add(11)
|
||||||
|
.add(13)
|
||||||
|
.add(17),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(3)
|
||||||
|
.add(6)
|
||||||
|
.add(7)
|
||||||
|
.add(10)
|
||||||
|
.add(11)
|
||||||
|
.add(13)
|
||||||
|
.add(14)
|
||||||
|
.add(16)
|
||||||
|
.add(17)
|
||||||
|
),
|
||||||
|
new PieceDef("Lance「槍」", "♛", // ♛槍
|
||||||
|
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(0, true)
|
||||||
|
.add(1)
|
||||||
|
.add(5),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(0, true)
|
||||||
|
.add(1, true)
|
||||||
|
.add(2, true)
|
||||||
|
.add(3, true)
|
||||||
|
.add(4, true)
|
||||||
|
.add(5, true)
|
||||||
|
),
|
||||||
|
new PieceDef("Tower「楼」", "♖", // ♖高
|
||||||
|
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(3)
|
||||||
|
.add(5)
|
||||||
|
.add(6)
|
||||||
|
.add(11),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(2)
|
||||||
|
.add(3)
|
||||||
|
.add(4)
|
||||||
|
.add(5)
|
||||||
|
.add(6)
|
||||||
|
.add(8)
|
||||||
|
.add(9)
|
||||||
|
.add(11)
|
||||||
|
),
|
||||||
|
new PieceDef("Castle「城」", "♜", // ♜城
|
||||||
|
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(2)
|
||||||
|
.add(4)
|
||||||
|
.add(5)
|
||||||
|
.add(7)
|
||||||
|
.add(10),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(2)
|
||||||
|
.add(3)
|
||||||
|
.add(4)
|
||||||
|
.add(5)
|
||||||
|
.add(7, true)
|
||||||
|
.add(10, true)
|
||||||
|
),
|
||||||
|
new PieceDef("Dragon「竜」", "♝", // ♝竜
|
||||||
|
["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(6, true)
|
||||||
|
.add(7, true)
|
||||||
|
.add(8, true)
|
||||||
|
.add(9, true)
|
||||||
|
.add(10, true)
|
||||||
|
.add(11, true),
|
||||||
|
|
||||||
|
new Move()
|
||||||
|
.add(0, true)
|
||||||
|
.add(1, true)
|
||||||
|
.add(2, true)
|
||||||
|
.add(3, true)
|
||||||
|
.add(4, true)
|
||||||
|
.add(5, true)
|
||||||
|
.add(6, true)
|
||||||
|
.add(7, true)
|
||||||
|
.add(8, true)
|
||||||
|
.add(9, true)
|
||||||
|
.add(10, true)
|
||||||
|
.add(11, true)
|
||||||
|
),
|
||||||
|
new PieceDef("King「王」", "♚", // ♚王
|
||||||
|
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
||||||
|
new Move()
|
||||||
|
.add(0)
|
||||||
|
.add(1)
|
||||||
|
.add(2)
|
||||||
|
.add(3)
|
||||||
|
.add(4)
|
||||||
|
.add(5)
|
||||||
|
.add(7)
|
||||||
|
.add(10)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const GAME = {
|
||||||
|
init() {
|
||||||
|
GAME_DATA.board.tiles
|
||||||
|
},
|
||||||
|
};
|
0
www/js/game_asset.js
Normal file
0
www/js/game_asset.js
Normal file
71
www/js/interface.js
Normal file
71
www/js/interface.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
let INTERFACE_DATA = {
|
||||||
|
canvas:null,
|
||||||
|
context:null,
|
||||||
|
|
||||||
|
scale:1,
|
||||||
|
};
|
||||||
|
|
||||||
|
const INTERFACE = {
|
||||||
|
hover(event) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
click(event) {
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
resize() {
|
||||||
|
INTERFACE_DATA.canvas.width = INTERFACE_DATA.canvas.clientWidth;
|
||||||
|
INTERFACE_DATA.canvas.height = INTERFACE_DATA.canvas.clientHeight;
|
||||||
|
},
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
this.resize();
|
||||||
|
|
||||||
|
// Determine
|
||||||
|
let width = INTERFACE_DATA.canvas.width;
|
||||||
|
let height = INTERFACE_DATA.canvas.height;
|
||||||
|
let min_dimension = Math.min(width, height);
|
||||||
|
|
||||||
|
let scale = 1;
|
||||||
|
//let margin = INTERFACE_DATA.canvas.
|
||||||
|
|
||||||
|
|
||||||
|
// Draw indicator gradient if player's turn.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Draw tiles
|
||||||
|
for(let i = 0; i < GAME.board.tiles.length; ++i) {
|
||||||
|
// Draw background
|
||||||
|
|
||||||
|
// Draw piece
|
||||||
|
|
||||||
|
// Draw
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Draw player pool
|
||||||
|
|
||||||
|
|
||||||
|
// Draw opponent pool
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
init() {
|
||||||
|
INTERFACE_DATA.canvas = document.getElementById("game");
|
||||||
|
if(canvas !== undefined) {
|
||||||
|
INTERFACE_DATA.context = canvas.getContext("2d");
|
||||||
|
|
||||||
|
canvas.addEventListener("mousemove", INTERFACE.hover);
|
||||||
|
canvas.addEventListener("mousedown", INTERFACE.click);
|
||||||
|
canvas.addEventListener("resize", INTERFACE.draw);
|
||||||
|
this.draw();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
uninit() {
|
||||||
|
INTERFACE_DATA.canvas = null;
|
||||||
|
INTERFACE_DATA.context = null;
|
||||||
|
},
|
||||||
|
};
|
4
www/js/main.js
Normal file
4
www/js/main.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
SCENE = SCENES.Offline;
|
||||||
|
LOAD(SCENES.Init);
|
||||||
|
});
|
@ -1,173 +1,6 @@
|
|||||||
let MAIN = null;
|
|
||||||
let MENU = null;
|
|
||||||
let SCENE = null;
|
|
||||||
|
|
||||||
let CONNECTED = false;
|
|
||||||
let SOCKET = null;
|
|
||||||
let CONTEXT = {
|
|
||||||
Scene: null,
|
|
||||||
Auth: null,
|
|
||||||
Data: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Status = {
|
|
||||||
Ok: 0,
|
|
||||||
Error: 1,
|
|
||||||
NotImplement: 2,
|
|
||||||
|
|
||||||
BadHandle: 1,
|
|
||||||
BadSecret: 2,
|
|
||||||
BadCode: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const OpCode = {
|
|
||||||
Register :0x0010,
|
|
||||||
Authenticate :0x0011,
|
|
||||||
Resume :0x0012,
|
|
||||||
Deauthenticate :0x0013,
|
|
||||||
};
|
|
||||||
|
|
||||||
class Message {
|
|
||||||
constructor(code, data) {
|
|
||||||
this.code = code;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PACK = {
|
|
||||||
u8:(value) => {
|
|
||||||
return new Uint8Array([ value & 0xFF ]);
|
|
||||||
},
|
|
||||||
u16:(value) => {
|
|
||||||
return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]);
|
|
||||||
},
|
|
||||||
u32:(value) => {
|
|
||||||
return new Uint8Array([
|
|
||||||
(value >> 24) & 0xFF,
|
|
||||||
(value >> 16) & 0xFF,
|
|
||||||
(value >> 8) & 0xFF,
|
|
||||||
value & 0xFF
|
|
||||||
]);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const UI = {
|
|
||||||
text:(value) => {
|
|
||||||
return document.createTextNode(value);
|
|
||||||
},
|
|
||||||
|
|
||||||
button:(text, callback) => {
|
|
||||||
let button = document.createElement("button");
|
|
||||||
button.innerText = text;
|
|
||||||
if(callback !== null) { button.addEventListener("click", callback); }
|
|
||||||
return button;
|
|
||||||
},
|
|
||||||
|
|
||||||
textbox:(id, placeholder) => {
|
|
||||||
let input = document.createElement("input");
|
|
||||||
input.setAttribute("type", "text");
|
|
||||||
if(id !== null) { input.setAttribute("id", id); }
|
|
||||||
input.setAttribute("placeholder", placeholder);
|
|
||||||
return input;
|
|
||||||
},
|
|
||||||
|
|
||||||
password:(id) => {
|
|
||||||
let input = document.createElement("input");
|
|
||||||
input.setAttribute("type", "password");
|
|
||||||
if(id !== null) { input.setAttribute("id", id); }
|
|
||||||
return input;
|
|
||||||
},
|
|
||||||
|
|
||||||
label:(name, id) => {
|
|
||||||
let label = document.createElement("label");
|
|
||||||
label.setAttribute("for", id);
|
|
||||||
label.innerText = name;
|
|
||||||
return label;
|
|
||||||
},
|
|
||||||
|
|
||||||
div:(children) => {
|
|
||||||
let div = document.createElement("div");
|
|
||||||
for(child of children) { div.appendChild(child); }
|
|
||||||
return div;
|
|
||||||
},
|
|
||||||
|
|
||||||
table:(header, rows) => {
|
|
||||||
let table = document.createElement("table");
|
|
||||||
let tbody = document.createElement("tbody");
|
|
||||||
|
|
||||||
if(header !== null) {
|
|
||||||
let row = document.createElement("tr");
|
|
||||||
for(head of header) {
|
|
||||||
let cell = document.createElement("th");
|
|
||||||
cell.innerText = head;
|
|
||||||
row.appendChild(cell);
|
|
||||||
}
|
|
||||||
tbody.appendChild(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(row of rows) {
|
|
||||||
let tr = document.createElement("tr");
|
|
||||||
for(node of row) {
|
|
||||||
let cell = document.createElement("td");
|
|
||||||
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
|
|
||||||
else { cell.appendChild(node); }
|
|
||||||
tr.appendChild(cell);
|
|
||||||
}
|
|
||||||
tbody.appendChild(tr);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.appendChild(tbody);
|
|
||||||
return table;
|
|
||||||
},
|
|
||||||
|
|
||||||
mainnav:(left_children, right_children) => {
|
|
||||||
let header = document.createElement("nav");
|
|
||||||
let left = document.createElement("section");
|
|
||||||
|
|
||||||
if(CONTEXT.Auth === null) {
|
|
||||||
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) }));
|
|
||||||
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) }));
|
|
||||||
}
|
|
||||||
|
|
||||||
for(child of left_children) { left.appendChild(child); }
|
|
||||||
|
|
||||||
let right = document.createElement("section");
|
|
||||||
for(child of right_children) { right.appendChild(child); }
|
|
||||||
|
|
||||||
header.appendChild(left);
|
|
||||||
header.appendChild(right);
|
|
||||||
|
|
||||||
MAIN.appendChild(header);
|
|
||||||
},
|
|
||||||
|
|
||||||
mainmenu:() => {
|
|
||||||
if(SOCKET !== null) {
|
|
||||||
MENU.appendChild(UI.button("Online", () => { LOAD(SCENES.Online); }));
|
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
MENU.appendChild(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
|
||||||
MENU.appendChild(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
|
||||||
}
|
|
||||||
MENU.appendChild(UI.button("Live", () => { LOAD(SCENES.Live); }));
|
|
||||||
MENU.appendChild(UI.button("History", () => { LOAD(SCENES.History); }));
|
|
||||||
MENU.appendChild(UI.button("Guide", () => { LOAD(SCENES.Guide); }));
|
|
||||||
MENU.appendChild(UI.button("About", () => { LOAD(SCENES.About); }));
|
|
||||||
|
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
MENU.appendChild(UI.button("Logout", () => {
|
|
||||||
MESSAGE_COMPOSE([
|
|
||||||
PACK.u16(OpCode.Deauthenticate),
|
|
||||||
]);
|
|
||||||
CONTEXT.Auth = null;
|
|
||||||
LOAD(SCENE);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const SCENES = {
|
const SCENES = {
|
||||||
Init:{
|
Init:{
|
||||||
load:() => {
|
load() {
|
||||||
LOAD_OFFLINE();
|
LOAD_OFFLINE();
|
||||||
CONTEXT.Scene = SCENES.Online;
|
CONTEXT.Scene = SCENES.Online;
|
||||||
RECONNECT();
|
RECONNECT();
|
||||||
@ -176,14 +9,16 @@ const SCENES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Offline:{
|
Offline:{
|
||||||
load:() => {
|
load() {
|
||||||
MENU.appendChild(UI.button("Reconnect", () => { RECONNECT(); }))
|
UI.nav([
|
||||||
|
UI.button("Reconnect", () => { RECONNECT(); })
|
||||||
|
], []);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Register:{
|
Register:{
|
||||||
load:() => {
|
load() {
|
||||||
if(CONTEXT.Auth !== null) return false;
|
if(CONTEXT.Auth !== null) return false;
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], []);
|
||||||
@ -238,7 +73,7 @@ const SCENES = {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
message:(code, data) => {
|
message(code, data) {
|
||||||
if(code == OpCode.Register && data !== null) {
|
if(code == OpCode.Register && data !== null) {
|
||||||
let submit = document.getElementById("submit");
|
let submit = document.getElementById("submit");
|
||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
@ -269,7 +104,7 @@ const SCENES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Authenticate:{
|
Authenticate:{
|
||||||
load:() => {
|
load() {
|
||||||
if(CONTEXT.Auth !== null) return false;
|
if(CONTEXT.Auth !== null) return false;
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], []);
|
||||||
@ -281,7 +116,7 @@ const SCENES = {
|
|||||||
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
[ UI.label("Secret", "secret"), UI.password("secret") ],
|
||||||
]));
|
]));
|
||||||
|
|
||||||
let button = UI.button("Register", (event) => {
|
let button = UI.button("Login", (event) => {
|
||||||
let handle = document.getElementById("handle");
|
let handle = document.getElementById("handle");
|
||||||
let secret = document.getElementById("secret");
|
let secret = document.getElementById("secret");
|
||||||
|
|
||||||
@ -317,7 +152,7 @@ const SCENES = {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
message:(code, data) => {
|
message(code, data) {
|
||||||
if(code == OpCode.Authenticate && data !== null) {
|
if(code == OpCode.Authenticate && data !== null) {
|
||||||
let submit = document.getElementById("submit");
|
let submit = document.getElementById("submit");
|
||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
@ -338,12 +173,19 @@ const SCENES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Online:{
|
Online:{
|
||||||
load:() => {
|
load() {
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
|
CONTEXT.data = {
|
||||||
|
page:0,
|
||||||
|
records:[],
|
||||||
|
};
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
left_buttons.push(UI.button("Start", null));
|
left_buttons.push(UI.button("Start", () => {
|
||||||
|
MESSAGE_SESSION_START();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -352,42 +194,33 @@ const SCENES = {
|
|||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
|
UI.button("Refresh", null),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
table.setAttribute("id", "content");
|
table.setAttribute("id", "content");
|
||||||
|
table.setAttribute("class", "list");
|
||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
MAIN.setAttribute("class", "list");
|
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
let request = new Uint8Array();
|
MESSAGE_SESSION_LIST(0, 0, false, false);
|
||||||
|
|
||||||
//SERVER.send()
|
|
||||||
|
|
||||||
SCENE.message(0, 0, null);
|
|
||||||
},
|
},
|
||||||
message:(code, data) => {
|
message(code, data) {
|
||||||
let table = document.getElementById("content");
|
let table = document.getElementById("content");
|
||||||
MAIN.removeChild(table);
|
UI.clear(table);
|
||||||
|
|
||||||
let rows = [
|
if(data !== null) {
|
||||||
[ UI.text("Player1"), UI.text("Player2"), UI.text("0"), UI.text("Ha1-D ◈ Ba1-M"), UI.text("0"), [ UI.button("Join", null), UI.button("Spectate", null) ] ],
|
table.appendChild(UI.session_table(data.records));
|
||||||
];
|
}
|
||||||
|
|
||||||
MAIN.appendChild(UI.table(
|
|
||||||
[ "Dawn", "Dusk", "Turn", "Move", "Spectators", "" ],
|
|
||||||
rows,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Continue:{
|
Continue:{
|
||||||
load:() => {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(CONTEXT.Auth === null) return false;
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
@ -414,13 +247,13 @@ const SCENES = {
|
|||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Join:{
|
Join:{
|
||||||
load:() => {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(CONTEXT.Auth === null) return false;
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
@ -447,13 +280,13 @@ const SCENES = {
|
|||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Live:{
|
Live:{
|
||||||
load:() => {
|
load() {
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
@ -479,13 +312,13 @@ const SCENES = {
|
|||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
History:{
|
History:{
|
||||||
load:() => {
|
load() {
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -506,190 +339,66 @@ const SCENES = {
|
|||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Guide:{
|
Guide:{
|
||||||
load:() => {
|
load() {
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], []);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
About:{
|
About:{
|
||||||
load:() => {
|
load() {
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], []);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Game:{
|
Game:{
|
||||||
load:() => {
|
load() {
|
||||||
MENU.appendChild(UI.button("Back", () => { LOAD(SCENES.Online) }));
|
UI.nav([
|
||||||
MENU.appendChild(UI.button("Retire", () => { }));
|
UI.button("Rotate", () => { }),
|
||||||
|
], [
|
||||||
|
UI.button("Back", () => { LOAD(SCENES.Online) }),
|
||||||
|
UI.button("Retire", () => { }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let canvas = document.createElement("canvas");
|
||||||
|
canvas.setAttribute("id", "game");
|
||||||
|
|
||||||
|
MAIN.appendChild(canvas);
|
||||||
|
|
||||||
|
INTERFACE.init();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
unload:() => {
|
unload() {
|
||||||
|
|
||||||
},
|
},
|
||||||
refresh:() => {
|
refresh() {
|
||||||
|
|
||||||
|
},
|
||||||
|
message() {
|
||||||
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function RECONNECT() {
|
|
||||||
if(SOCKET === null) {
|
|
||||||
console.log("Websocket connecting..");
|
|
||||||
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
|
||||||
SOCKET.binaryType = "arraybuffer";
|
|
||||||
SOCKET.addEventListener("error", (event) => {
|
|
||||||
SOCKET = null;
|
|
||||||
LOAD(SCENES.Offline)
|
|
||||||
});
|
|
||||||
SOCKET.addEventListener("open", (event) => {
|
|
||||||
if(SOCKET.readyState === WebSocket.OPEN) {
|
|
||||||
console.log("Websocket connected.");
|
|
||||||
|
|
||||||
SOCKET.addEventListener("message", MESSAGE);
|
|
||||||
|
|
||||||
SOCKET.addEventListener("close", (event) => {
|
|
||||||
console.log("Websocket closed.");
|
|
||||||
SOCKET = null;
|
|
||||||
RECONNECT();
|
|
||||||
});
|
|
||||||
|
|
||||||
RESUME();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function RESUME() {
|
|
||||||
LOAD(CONTEXT.Scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
function REBUILD() {
|
|
||||||
MENU = document.createElement("nav");
|
|
||||||
let title = document.createElement("header");
|
|
||||||
title.innerText = "Omen";
|
|
||||||
MENU.appendChild(title);
|
|
||||||
|
|
||||||
MAIN = document.createElement("main");
|
|
||||||
|
|
||||||
document.body.appendChild(MENU);
|
|
||||||
document.body.appendChild(MAIN);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MESSAGE(event) {
|
|
||||||
console.log("Message received.");
|
|
||||||
|
|
||||||
if(SCENE.message !== undefined) {
|
|
||||||
let bytes = new Uint8Array(event.data);
|
|
||||||
let code = 0;
|
|
||||||
let index = 2;
|
|
||||||
let data = null;
|
|
||||||
|
|
||||||
if(bytes.length >= 2) {
|
|
||||||
code = (bytes[0] << 8) + bytes[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(code) {
|
|
||||||
case OpCode.Register: {
|
|
||||||
console.log("Register response.");
|
|
||||||
|
|
||||||
if(bytes.length == 28) {
|
|
||||||
console.log("Good size");
|
|
||||||
data = {
|
|
||||||
status:(bytes[2] << 8) + bytes[3],
|
|
||||||
token:new Uint8Array(),
|
|
||||||
secret:new Uint8Array(),
|
|
||||||
};
|
|
||||||
index += 2;
|
|
||||||
|
|
||||||
for(let i = 0; i < 8; ++i) {
|
|
||||||
data.token += bytes[index++];
|
|
||||||
}
|
|
||||||
for(let i = 0; i < 16; ++i) {
|
|
||||||
data.secret += bytes[index++];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Register bad length:" + bytes.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case OpCode.Authenticate: {
|
|
||||||
console.log("Authenticate response.");
|
|
||||||
|
|
||||||
if(bytes.length == 28) {
|
|
||||||
console.log("Good size");
|
|
||||||
data = {
|
|
||||||
status:(bytes[2] << 8) + bytes[3],
|
|
||||||
token:new Uint8Array(),
|
|
||||||
secret:new Uint8Array(),
|
|
||||||
};
|
|
||||||
index += 2;
|
|
||||||
|
|
||||||
for(let i = 0; i < 8; ++i) {
|
|
||||||
data.token += bytes[index++];
|
|
||||||
}
|
|
||||||
for(let i = 0; i < 16; ++i) {
|
|
||||||
data.secret += bytes[index++];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log("Authenticate bad length:" + bytes.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case OpCode.Resume: {
|
|
||||||
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case OpCode.Deauthenticate: {
|
|
||||||
|
|
||||||
} break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SCENE.message(code, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function MESSAGE_COMPOSE(data) {
|
|
||||||
if(SOCKET !== null) {
|
|
||||||
let length = 0;
|
|
||||||
for(let i = 0; i < data.length; ++i) {
|
|
||||||
length += data[i].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
let raw = new Uint8Array(length);
|
|
||||||
length = 0;
|
|
||||||
for(let i = 0; i < data.length; ++i) {
|
|
||||||
raw.set(data[i], length);
|
|
||||||
length += data[i].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
SOCKET.send(raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function LOAD(scene) {
|
function LOAD(scene) {
|
||||||
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||||
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
UI.rebuild();
|
||||||
REBUILD();
|
|
||||||
SCENE = scene;
|
SCENE = scene;
|
||||||
CONTEXT.Scene = SCENE;
|
CONTEXT.Scene = SCENE;
|
||||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||||
@ -698,12 +407,7 @@ function LOAD(scene) {
|
|||||||
function LOAD_OFFLINE() {
|
function LOAD_OFFLINE() {
|
||||||
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
if(SCENE.unload !== undefined) { SCENE.unload(); }
|
||||||
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
while(document.body.lastChild !== null) { document.body.removeChild(document.body.lastChild); }
|
||||||
REBUILD();
|
UI.rebuild();
|
||||||
SCENE = SCENES.Offline;
|
SCENE = SCENES.Offline;
|
||||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
SCENE = SCENES.Offline;
|
|
||||||
LOAD(SCENES.Init);
|
|
||||||
});
|
|
255
www/js/system.js
Normal file
255
www/js/system.js
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
function RECONNECT() {
|
||||||
|
if(SOCKET === null) {
|
||||||
|
console.log("Websocket connecting..");
|
||||||
|
SOCKET = new WebSocket("wss://omen.kirisame.com:38612");
|
||||||
|
SOCKET.binaryType = "arraybuffer";
|
||||||
|
SOCKET.addEventListener("error", (event) => {
|
||||||
|
SOCKET = null;
|
||||||
|
LOAD_OFFLINE()
|
||||||
|
});
|
||||||
|
SOCKET.addEventListener("open", (event) => {
|
||||||
|
if(SOCKET.readyState === WebSocket.OPEN) {
|
||||||
|
console.log("Websocket connected.");
|
||||||
|
|
||||||
|
SOCKET.addEventListener("message", MESSAGE);
|
||||||
|
|
||||||
|
SOCKET.addEventListener("close", (event) => {
|
||||||
|
console.log("Websocket closed.");
|
||||||
|
SOCKET = null;
|
||||||
|
RECONNECT();
|
||||||
|
});
|
||||||
|
|
||||||
|
RESUME();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
RESUME();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function RESUME() {
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
CONTEXT.Auth.token,
|
||||||
|
CONTEXT.Auth.secret,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
LOAD(CONTEXT.Scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MESSAGE(event) {
|
||||||
|
console.log("Message received.");
|
||||||
|
|
||||||
|
if(SCENE.message !== undefined) {
|
||||||
|
let bytes = new Uint8Array(event.data);
|
||||||
|
let code = 0;
|
||||||
|
let index = 2;
|
||||||
|
let data = null;
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
if(bytes.length >= 2) {
|
||||||
|
code = (bytes[0] << 8) + bytes[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(code) {
|
||||||
|
case OpCode.Register: {
|
||||||
|
console.log("RECV Register");
|
||||||
|
|
||||||
|
if(bytes.length - index == 26) {
|
||||||
|
data = {
|
||||||
|
status:(bytes[2] << 8) + bytes[3],
|
||||||
|
token:new Uint8Array(8),
|
||||||
|
secret:new Uint8Array(16),
|
||||||
|
};
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
for(let i = 0; i < 8; ++i) {
|
||||||
|
data.token[i] = bytes[index++];
|
||||||
|
}
|
||||||
|
for(let i = 0; i < 16; ++i) {
|
||||||
|
data.secret[i] = bytes[index++];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Register packet bad length:" + bytes.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Authenticate: {
|
||||||
|
console.log("RECV Authenticate");
|
||||||
|
|
||||||
|
if(bytes.length - index == 26) {
|
||||||
|
data = {
|
||||||
|
status:(bytes[2] << 8) + bytes[3],
|
||||||
|
token:new Uint8Array(),
|
||||||
|
secret:new Uint8Array(),
|
||||||
|
};
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
for(let i = 0; i < 8; ++i) {
|
||||||
|
data.token += bytes[index++];
|
||||||
|
}
|
||||||
|
for(let i = 0; i < 16; ++i) {
|
||||||
|
data.secret += bytes[index++];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Authenticate packet bad length:" + bytes.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Resume: {
|
||||||
|
console.log("RECV Resume");
|
||||||
|
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
if(result.data != Status.Ok) {
|
||||||
|
CONTEXT.Auth = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOAD(CONTEXT.Scene);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.Deauthenticate: {
|
||||||
|
console.log("RECV Deauthenticate");
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.SessionList: {
|
||||||
|
console.log("RECV Session list");
|
||||||
|
|
||||||
|
if(bytes.length - index >= 2) {
|
||||||
|
data = {
|
||||||
|
records: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
let count = result.data;
|
||||||
|
|
||||||
|
for(let i = 0; i < count; ++i) {
|
||||||
|
let record = {
|
||||||
|
token: new Uint8Array(8),
|
||||||
|
dawn: "",
|
||||||
|
dusk: "",
|
||||||
|
turn: 0,
|
||||||
|
move: "",
|
||||||
|
viewers: 0,
|
||||||
|
player: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(index <= bytes.length + 8) {
|
||||||
|
for(let i = 0; i < 8; ++i) {
|
||||||
|
record.token[i] = bytes[index];
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = UNPACK.string(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
record.dawn = result.data;
|
||||||
|
|
||||||
|
result = UNPACK.string(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
record.dusk = result.data;
|
||||||
|
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
record.turn = result.data;
|
||||||
|
|
||||||
|
if(index <= bytes.length + 5) {
|
||||||
|
let move = new Uint8Array(5);
|
||||||
|
for(let i = 0; i < 5; ++i) {
|
||||||
|
move[i] = bytes[index];
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
record.move = UNPACK.move(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = UNPACK.u32(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
record.viewers = result.data;
|
||||||
|
|
||||||
|
record.player = bytes[index++] != 0;
|
||||||
|
|
||||||
|
data.records.push(record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.SessionCreate:
|
||||||
|
case OpCode.SessionJoin: {
|
||||||
|
if(bytes.length - index == 11) {
|
||||||
|
data = {
|
||||||
|
token:new Uint8Array(8),
|
||||||
|
mode:2,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = UNPACK.u16(data, index);
|
||||||
|
index = result.index;
|
||||||
|
let status = result.data;
|
||||||
|
|
||||||
|
result = UNPACK.u8(data, index);
|
||||||
|
index = result.index;
|
||||||
|
data.mode = result.data;
|
||||||
|
|
||||||
|
for(let i = 0; i < 8; ++i) { data.token[i] = data[index++]; }
|
||||||
|
|
||||||
|
if(status == Status.Ok) {
|
||||||
|
LOAD(SCENES.Game);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(SCENE.message !== undefined) { SCENE.message(code, data) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function MESSAGE_COMPOSE(data) {
|
||||||
|
if(SOCKET !== null) {
|
||||||
|
let length = 0;
|
||||||
|
for(let i = 0; i < data.length; ++i) {
|
||||||
|
length += data[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw = new Uint8Array(length);
|
||||||
|
length = 0;
|
||||||
|
for(let i = 0; i < data.length; ++i) {
|
||||||
|
raw.set(data[i], length);
|
||||||
|
length += data[i].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
SOCKET.send(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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([
|
||||||
|
PACK.u16(OpCode.SessionList),
|
||||||
|
PACK.u16(flags),
|
||||||
|
PACK.u16(page),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MESSAGE_SESSION_START() {
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.SessionCreate),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MESSAGE_SESSION_JOIN(token, player) {
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.SessionJoin),
|
||||||
|
token,
|
||||||
|
PACK.u8(player),
|
||||||
|
]);
|
||||||
|
}
|
205
www/js/ui.js
Normal file
205
www/js/ui.js
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
const UI = {
|
||||||
|
text(value) {
|
||||||
|
return document.createTextNode(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
button(text, callback) {
|
||||||
|
let button = document.createElement("button");
|
||||||
|
button.innerText = text;
|
||||||
|
if(callback !== null) { button.addEventListener("click", callback); }
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
|
textbox(id, placeholder) {
|
||||||
|
let input = document.createElement("input");
|
||||||
|
input.setAttribute("type", "text");
|
||||||
|
if(id !== null) { input.setAttribute("id", id); }
|
||||||
|
input.setAttribute("placeholder", placeholder);
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
password(id) {
|
||||||
|
let input = document.createElement("input");
|
||||||
|
input.setAttribute("type", "password");
|
||||||
|
if(id !== null) { input.setAttribute("id", id); }
|
||||||
|
return input;
|
||||||
|
},
|
||||||
|
|
||||||
|
label(name, id) {
|
||||||
|
let label = document.createElement("label");
|
||||||
|
label.setAttribute("for", id);
|
||||||
|
label.innerText = name;
|
||||||
|
return label;
|
||||||
|
},
|
||||||
|
|
||||||
|
span(children, attr_class) {
|
||||||
|
let span = document.createElement("span");
|
||||||
|
if(attr_class !== undefined) { span.setAttribute("class", attr_class); }
|
||||||
|
for(child of children) { span.appendChild(child); }
|
||||||
|
return span;
|
||||||
|
},
|
||||||
|
|
||||||
|
div(children, attr_class) {
|
||||||
|
let div = document.createElement("div");
|
||||||
|
if(attr_class !== undefined) { div.setAttribute("class", attr_class); }
|
||||||
|
for(child of children) { div.appendChild(child); }
|
||||||
|
return div;
|
||||||
|
},
|
||||||
|
|
||||||
|
table_content(header, rows) {
|
||||||
|
let tbody = document.createElement("tbody");
|
||||||
|
|
||||||
|
if(header !== null) {
|
||||||
|
let row = document.createElement("tr");
|
||||||
|
for(head of header) {
|
||||||
|
let cell = document.createElement("th");
|
||||||
|
cell.innerText = head;
|
||||||
|
row.appendChild(cell);
|
||||||
|
}
|
||||||
|
tbody.appendChild(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(row of rows) {
|
||||||
|
let tr = document.createElement("tr");
|
||||||
|
for(node of row) {
|
||||||
|
let cell = document.createElement("td");
|
||||||
|
if(Array.isArray(node)) { for(item of node) { cell.appendChild(item); } }
|
||||||
|
else { cell.appendChild(node); }
|
||||||
|
tr.appendChild(cell);
|
||||||
|
}
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tbody;
|
||||||
|
},
|
||||||
|
|
||||||
|
table(header, rows) {
|
||||||
|
let table = document.createElement("table");
|
||||||
|
table.appendChild(this.table_content(header, rows));
|
||||||
|
return table;
|
||||||
|
},
|
||||||
|
|
||||||
|
mainnav(left_children, right_children) {
|
||||||
|
let header = document.createElement("nav");
|
||||||
|
let left = document.createElement("section");
|
||||||
|
|
||||||
|
if(CONTEXT.Auth === null) {
|
||||||
|
left.appendChild(UI.button("Register", () => { LOAD(SCENES.Register) }));
|
||||||
|
left.appendChild(UI.button("Log In", () => { LOAD(SCENES.Authenticate) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(child of left_children) { left.appendChild(child); }
|
||||||
|
|
||||||
|
let right = document.createElement("section");
|
||||||
|
for(child of right_children) { right.appendChild(child); }
|
||||||
|
|
||||||
|
header.appendChild(left);
|
||||||
|
header.appendChild(right);
|
||||||
|
|
||||||
|
MAIN.appendChild(header);
|
||||||
|
},
|
||||||
|
|
||||||
|
nav(top, bottom) {
|
||||||
|
let section = document.createElement("section");
|
||||||
|
for(node of top) { section.appendChild(node); }
|
||||||
|
MENU.appendChild(section);
|
||||||
|
|
||||||
|
section = document.createElement("section");
|
||||||
|
for(node of bottom) { section.appendChild(node); }
|
||||||
|
MENU.appendChild(section);
|
||||||
|
},
|
||||||
|
|
||||||
|
mainmenu() {
|
||||||
|
if(SOCKET !== null) {
|
||||||
|
let top = [ ];
|
||||||
|
let bottom = [ ];
|
||||||
|
|
||||||
|
top.push(UI.button("Online", () => { LOAD(SCENES.Online); }));
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
top.push(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
||||||
|
top.push(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
||||||
|
}
|
||||||
|
top.push(UI.button("Live", () => { LOAD(SCENES.Live); }));
|
||||||
|
top.push(UI.button("History", () => { LOAD(SCENES.History); }));
|
||||||
|
top.push(UI.button("Guide", () => { LOAD(SCENES.Guide); }));
|
||||||
|
top.push(UI.button("About", () => { LOAD(SCENES.About); }));
|
||||||
|
|
||||||
|
if(CONTEXT.Auth !== null) {
|
||||||
|
bottom.push(UI.button("Logout", () => {
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.Deauthenticate),
|
||||||
|
]);
|
||||||
|
CONTEXT.Auth = null;
|
||||||
|
LOAD(SCENE);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.nav(top, bottom);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
session_table(records) {
|
||||||
|
let rows = [ ];
|
||||||
|
|
||||||
|
for(let r = 0; r < records.length; ++r) {
|
||||||
|
let buttons = [ ];
|
||||||
|
let join_callback = function() {
|
||||||
|
MESSAGE_SESSION_JOIN(this.token, true);
|
||||||
|
};
|
||||||
|
join_callback = join_callback.bind({token: records[r].token});
|
||||||
|
|
||||||
|
let spectate_callback = function() {
|
||||||
|
MESSAGE_SESSION_JOIN(this.token, false);
|
||||||
|
};
|
||||||
|
spectate_callback = spectate_callback.bind({token: records[r].token});
|
||||||
|
|
||||||
|
if(records[r].player) {
|
||||||
|
buttons.push(UI.button("Resume", join_callback));
|
||||||
|
} else {
|
||||||
|
if(CONTEXT.Auth !== null && (records[r].dawn == "" || records[r].dusk == "")) {
|
||||||
|
buttons.push(UI.button("Join", join_callback));
|
||||||
|
}
|
||||||
|
buttons.push(UI.button("Spectate", spectate_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),
|
||||||
|
UI.text(records[r].viewers),
|
||||||
|
buttons,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let tbody = UI.table_content(
|
||||||
|
[ "Dawn", "Dusk", "Turn", "Spectators", "" ],
|
||||||
|
rows,
|
||||||
|
);
|
||||||
|
|
||||||
|
return tbody;
|
||||||
|
},
|
||||||
|
|
||||||
|
clear(dom) {
|
||||||
|
while(dom.lastChild !== null) { dom.removeChild(document.body.lastChild); }
|
||||||
|
},
|
||||||
|
|
||||||
|
rebuild() {
|
||||||
|
this.clear(document.body);
|
||||||
|
|
||||||
|
MENU = document.createElement("nav");
|
||||||
|
let title = document.createElement("header");
|
||||||
|
title.innerText = "Omen";
|
||||||
|
MENU.appendChild(title);
|
||||||
|
|
||||||
|
MAIN = document.createElement("main");
|
||||||
|
|
||||||
|
document.body.appendChild(MENU);
|
||||||
|
document.body.appendChild(MAIN);
|
||||||
|
},
|
||||||
|
};
|
141
www/js/util.js
Normal file
141
www/js/util.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
const PACK = {
|
||||||
|
u8(value) {
|
||||||
|
return new Uint8Array([ value & 0xFF ]);
|
||||||
|
},
|
||||||
|
u16(value) {
|
||||||
|
return new Uint8Array([ (value >> 8) & 0xFF, value & 0xFF ]);
|
||||||
|
},
|
||||||
|
u32(value) {
|
||||||
|
return new Uint8Array([
|
||||||
|
(value >> 24) & 0xFF,
|
||||||
|
(value >> 16) & 0xFF,
|
||||||
|
(value >> 8) & 0xFF,
|
||||||
|
value & 0xFF
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const UNPACK = {
|
||||||
|
u8(data, index) {
|
||||||
|
let result = 0;
|
||||||
|
if(index + 1 <= data.length) {
|
||||||
|
result = data[index];
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
return { data: result, index: index };
|
||||||
|
},
|
||||||
|
u16(data, index) {
|
||||||
|
let result = 0;
|
||||||
|
if(index + 2 <= data.length) {
|
||||||
|
result = (data[index] << 8) + data[index + 1];
|
||||||
|
index += 2;
|
||||||
|
}
|
||||||
|
return { data: result, index: index };
|
||||||
|
},
|
||||||
|
u32(data, index) {
|
||||||
|
let result = 0;
|
||||||
|
if(index + 4 <= data.length) {
|
||||||
|
result = (data[index] << 24)
|
||||||
|
+ (data[index + 1] << 16)
|
||||||
|
+ (data[index + 1] << 8)
|
||||||
|
+ data[index + 1];
|
||||||
|
index += 4;
|
||||||
|
}
|
||||||
|
return { data: result, index: index };
|
||||||
|
},
|
||||||
|
string(data, index) {
|
||||||
|
let result = UNPACK.u16(data, index);
|
||||||
|
index = result.index;
|
||||||
|
let length = result.data;
|
||||||
|
|
||||||
|
let dec = new TextDecoder();
|
||||||
|
|
||||||
|
let result_str = "";
|
||||||
|
if(index + length <= data.length) {
|
||||||
|
let bytes = new Uint8Array(length);
|
||||||
|
for(let i = 0; i < length; ++i) {
|
||||||
|
bytes[i] = data[index + i];
|
||||||
|
}
|
||||||
|
index += length;
|
||||||
|
|
||||||
|
result_str = dec.decode(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: result_str, index: index };
|
||||||
|
},
|
||||||
|
move(bytes) {
|
||||||
|
function piece_by_id(id) {
|
||||||
|
switch(id) {
|
||||||
|
case 0: return "";
|
||||||
|
case 1: return "M";
|
||||||
|
case 2: return "N";
|
||||||
|
case 3: return "L";
|
||||||
|
case 4: return "T";
|
||||||
|
case 5: return "C";
|
||||||
|
case 6: return "D";
|
||||||
|
case 7: return "K";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** From [6]
|
||||||
|
** To [6]
|
||||||
|
** Piece [3]
|
||||||
|
** Take [3]
|
||||||
|
**
|
||||||
|
*/
|
||||||
|
|
||||||
|
let from = (bytes[0] & 0xFC) >> 2;
|
||||||
|
let to = ((bytes[0] & 0x03) << 4) | ((bytes[1] & 0xC0) >> 6);
|
||||||
|
let piece = piece_by_id((bytes[1] & 0x38) >> 3);
|
||||||
|
let take = piece_by_id(bytes[1] & 0x07);
|
||||||
|
let source = (bytes[2] & 0x80) >> 7;
|
||||||
|
switch((bytes[2] & 0x60) >> 5) {
|
||||||
|
case 0: state = "";
|
||||||
|
case 1: state = "◇"; break;
|
||||||
|
case 2: state = "◈"; break;
|
||||||
|
case 3: state = "◆"; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let str = "";
|
||||||
|
if(state.length > 0) {
|
||||||
|
if(source == 1) {
|
||||||
|
str = "" + piece + " " + state + " " + to;
|
||||||
|
} else {
|
||||||
|
str = "" + from + " " + piece + " " + state + " " + to + " " + take;
|
||||||
|
}
|
||||||
|
if(take != "") { str += " " + take; }
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const BITWISE = {
|
||||||
|
lsb(x) {
|
||||||
|
return x & -x;
|
||||||
|
},
|
||||||
|
|
||||||
|
ffs(x) {
|
||||||
|
return 31 - Math.clz32(x & -x);
|
||||||
|
},
|
||||||
|
|
||||||
|
count(mask) {
|
||||||
|
// source: https://graphics.stanford.edu/~seander/bithacks.html
|
||||||
|
mask = mask|0;
|
||||||
|
mask = mask - ((mask >> 1) & 0x55555555);
|
||||||
|
mask = (mask & 0x33333333) + ((mask >> 2) & 0x33333333);
|
||||||
|
return ((mask + (mask >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const MATH = {
|
||||||
|
sign(a)
|
||||||
|
{
|
||||||
|
return 1 - ((a < 0) << 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
mod(a, b)
|
||||||
|
{
|
||||||
|
return ((a % b) + b) % b
|
||||||
|
},
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user