Add game interface.
This commit is contained in:
parent
ee12d74c67
commit
7ba9ce7ba0
@ -20,7 +20,6 @@ pub struct App {
|
||||
pub connections:Pool<Connection>,
|
||||
|
||||
pub users:Pool<User>,
|
||||
pub user_next:u32,
|
||||
pub user_id:Sparse<usize>,
|
||||
pub user_handle:Trie<u32>,
|
||||
pub salts:Sparse<[u8; 16]>,
|
||||
@ -48,25 +47,58 @@ impl App {
|
||||
let handle_count = filesystem.handle_count()?;
|
||||
for id in 0..handle_count {
|
||||
let (handle, user_id) = filesystem.handle_fetch(id as u32).unwrap();
|
||||
println!("got: {} = {}", handle, user_id);
|
||||
user_handle.set(handle.as_bytes(), user_id);
|
||||
}
|
||||
|
||||
// Load users
|
||||
println!("Load users..");
|
||||
let mut users = Pool::new();
|
||||
let mut user_id = Sparse::new();
|
||||
let user_count = filesystem.user_count()?;
|
||||
for id in 0..user_count {
|
||||
let user = filesystem.user_fetch(id as u32).unwrap();
|
||||
let user_local_id = users.add(user);
|
||||
user_id.set(user_local_id as isize, id);
|
||||
}
|
||||
|
||||
// Load sessions
|
||||
println!("Load sessions..");
|
||||
let mut sessions = Trie::new();
|
||||
let mut times = Vec::<(u64, SessionToken)>::new();
|
||||
let session_count = filesystem.session_count()?;
|
||||
for id in 0..session_count {
|
||||
let session = filesystem.session_fetch(id as u32).unwrap();
|
||||
|
||||
times.push((session.time, session.token.clone()));
|
||||
|
||||
sessions.set(&session.token.clone(), session);
|
||||
}
|
||||
|
||||
// Organize sessions by most recent
|
||||
let mut session_time = Chain::new();
|
||||
times.sort_by(|(a, _), (b, _)| {
|
||||
if a < b { std::cmp::Ordering::Greater } else { std::cmp::Ordering::Less }
|
||||
});
|
||||
for (_, token) in times {
|
||||
let id = session_time.add(token);
|
||||
sessions.get_mut(&token).unwrap().chain_id = id;
|
||||
}
|
||||
|
||||
println!("Done.");
|
||||
Ok(Self {
|
||||
filesystem:filesystem,
|
||||
|
||||
connections:Pool::new(),
|
||||
|
||||
users:Pool::new(),
|
||||
user_next:0,
|
||||
user_id:Sparse::new(),
|
||||
user_handle:Trie::new(),
|
||||
users,
|
||||
user_id,
|
||||
user_handle,
|
||||
salts,
|
||||
|
||||
auths:Trie::new(),
|
||||
sessions:Trie::new(),
|
||||
sessions,
|
||||
|
||||
session_time:Chain::new(),
|
||||
session_time,
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
|
@ -9,7 +9,8 @@ pub struct Viewer {
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub key:SessionToken,
|
||||
pub id:u32,
|
||||
pub token:SessionToken,
|
||||
pub secret:SessionSecret,
|
||||
|
||||
pub game:Game,
|
||||
@ -18,5 +19,6 @@ pub struct Session {
|
||||
pub p_dusk:Viewer,
|
||||
pub viewers:Vec<Viewer>,
|
||||
|
||||
pub time:u64,
|
||||
pub chain_id:usize,
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub struct User {
|
||||
pub id:u32,
|
||||
pub handle_id:u32,
|
||||
pub handle:String,
|
||||
pub secret:Vec<u8>,
|
||||
pub na_key:u32,
|
||||
|
@ -94,23 +94,28 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
let salt_id = app.filesystem.salt_store(salt).unwrap();
|
||||
app.salts.set(salt_id as isize, salt);
|
||||
|
||||
if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
||||
let user_id = app.user_next;
|
||||
app.user_next += 1;
|
||||
|
||||
// Create user entry
|
||||
let user_pos = app.users.add(User {
|
||||
id:user_id,
|
||||
handle:request.handle.clone(),
|
||||
secret:hash,
|
||||
na_key:salt_id,
|
||||
});
|
||||
if let Ok(secret) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
||||
let user_id = app.filesystem.user_count().unwrap() as u32;
|
||||
|
||||
// Register user pool id and handle
|
||||
app.filesystem.handle_store(&request.handle, user_id).ok();
|
||||
app.user_id.set(user_id as isize, user_pos);
|
||||
let handle_id = app.filesystem.handle_store(&request.handle, user_id).unwrap();
|
||||
app.user_handle.set(request.handle.as_bytes(), user_id);
|
||||
|
||||
let user_data = User {
|
||||
id:user_id,
|
||||
handle_id,
|
||||
handle:request.handle.clone(),
|
||||
secret,
|
||||
na_key:salt_id,
|
||||
};
|
||||
|
||||
app.filesystem.user_store(&user_data).ok();
|
||||
|
||||
// Create user entry
|
||||
let user_pos = app.users.add(user_data);
|
||||
|
||||
app.user_id.set(user_id as isize, user_pos);
|
||||
|
||||
println!("Registered user '{}' @ {} with id {}", request.handle, user_pos, user_id);
|
||||
|
||||
// Generate authentication token and secret
|
||||
@ -296,7 +301,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
} else { String::new() };
|
||||
|
||||
response.records.push(PacketSessionListResponseRecord {
|
||||
token:session.key,
|
||||
token:session.token,
|
||||
handles:[
|
||||
dawn_handle,
|
||||
dusk_handle,
|
||||
@ -337,9 +342,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
rng.fill(&mut secret).ok();
|
||||
|
||||
let chain_id = app.session_time.add(token);
|
||||
app.sessions.set(&token, Session {
|
||||
key:token,
|
||||
secret:secret,
|
||||
|
||||
let mut session = Session {
|
||||
id:0,
|
||||
token,
|
||||
secret,
|
||||
game:game::Game::new(),
|
||||
p_dawn:Viewer {
|
||||
connection:None,//Some(qr.id),
|
||||
@ -350,8 +357,13 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
user:None,
|
||||
},
|
||||
viewers:Vec::new(),
|
||||
chain_id:chain_id,
|
||||
});
|
||||
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
|
||||
chain_id,
|
||||
};
|
||||
|
||||
session.id = app.filesystem.session_store(&session).unwrap();
|
||||
|
||||
app.sessions.set(&token, session);
|
||||
app.session_time.set(chain_id, token);
|
||||
|
||||
// Set player to Dawn.
|
||||
@ -397,6 +409,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
false
|
||||
} {
|
||||
println!("Add user to session.");
|
||||
|
||||
app.filesystem.session_update(session.id, session).ok();
|
||||
response.status = STATUS_OK;
|
||||
}
|
||||
} else {
|
||||
@ -418,6 +432,14 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||
Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response)))
|
||||
}
|
||||
|
||||
// SessionRetire
|
||||
|
||||
// SessionLeave
|
||||
|
||||
// GameState
|
||||
|
||||
// GamePlay
|
||||
|
||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
||||
}
|
||||
}
|
||||
|
77
server/src/protocol/packet/game_play.rs
Normal file
77
server/src/protocol/packet/game_play.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::util::pack::{pack_u16, unpack_u16};
|
||||
|
||||
use super::Packet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGamePlay {
|
||||
pub handle:String,
|
||||
pub secret:String,
|
||||
}
|
||||
impl PacketGamePlay {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
handle:String::new(),
|
||||
secret:String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGamePlay {
|
||||
type Data = Self;
|
||||
|
||||
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||
{
|
||||
let mut result = Self::new();
|
||||
|
||||
let mut length = unpack_u16(data, index) as usize;
|
||||
if data.len() >= *index + length {
|
||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
||||
Ok(text) => {
|
||||
*index += length;
|
||||
result.handle = text;
|
||||
|
||||
length = unpack_u16(data, index) as usize;
|
||||
if data.len() >= *index + length {
|
||||
|
||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
||||
Ok(text) => {
|
||||
*index += length;
|
||||
result.secret = text;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGamePlayResponse {
|
||||
pub status:u16,
|
||||
}
|
||||
impl PacketGamePlayResponse {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
status:0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGamePlayResponse {
|
||||
type Data = Self;
|
||||
|
||||
fn encode(&self) -> Vec<u8>
|
||||
{
|
||||
[
|
||||
pack_u16(self.status),
|
||||
].concat()
|
||||
}
|
||||
}
|
55
server/src/protocol/packet/game_state.rs
Normal file
55
server/src/protocol/packet/game_state.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use crate::{
|
||||
app::session::SessionToken,
|
||||
util::pack::{pack_u16, unpack_u16},
|
||||
};
|
||||
|
||||
use super::Packet;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGameState {
|
||||
pub token:SessionToken,
|
||||
}
|
||||
impl PacketGameState {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
token:SessionToken::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGameState {
|
||||
type Data = Self;
|
||||
|
||||
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||
{
|
||||
let mut result = Self::new();
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PacketGameStateResponse {
|
||||
pub status:u16,
|
||||
}
|
||||
impl PacketGameStateResponse {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
status:0,
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketGameStateResponse {
|
||||
type Data = Self;
|
||||
|
||||
fn encode(&self) -> Vec<u8>
|
||||
{
|
||||
[
|
||||
pack_u16(self.status),
|
||||
|
||||
].concat()
|
||||
}
|
||||
}
|
@ -7,6 +7,11 @@ 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 session_retire; pub use session_retire::*;
|
||||
mod session_leave; pub use session_leave::*;
|
||||
|
||||
mod game_state; pub use game_state::*;
|
||||
mod game_play; pub use game_play::*;
|
||||
|
||||
mod prelude {
|
||||
pub trait Packet {
|
||||
|
0
server/src/protocol/packet/session_leave.rs
Normal file
0
server/src/protocol/packet/session_leave.rs
Normal file
0
server/src/protocol/packet/session_retire.rs
Normal file
0
server/src/protocol/packet/session_retire.rs
Normal file
@ -2,23 +2,31 @@ use std::{
|
||||
fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
|
||||
};
|
||||
|
||||
use game::Game;
|
||||
|
||||
use crate::{
|
||||
app::session::Session,
|
||||
app::user::User,
|
||||
util::pack::{pack_u32, unpack_u32}
|
||||
app::{
|
||||
session::{self, Session, SessionToken, SessionSecret},
|
||||
user::User,
|
||||
},
|
||||
util::pack::*,
|
||||
};
|
||||
|
||||
const HANDLE_BUCKET_MASK :u32 = 0xFF;
|
||||
const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1;
|
||||
|
||||
const GENERIC_CONFIG :&str = "c.bin";
|
||||
const GENERIC_INDEX :&str = "i.bin";
|
||||
const GENERIC_HISTORY :&str = "h.bin";
|
||||
|
||||
const DIR_DATA :&str = "data";
|
||||
const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA);
|
||||
const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA);
|
||||
const DIR_USER :&str = const_format::formatcp!("{}/u", DIR_DATA);
|
||||
|
||||
const INDEX_HANDLE :&str = const_format::formatcp!("{}/i.bin", DIR_HANDLE);
|
||||
const INDEX_SESSION :&str = const_format::formatcp!("{}/i.bin", DIR_SESSION);
|
||||
const INDEX_USER :&str = const_format::formatcp!("{}/i.bin", DIR_USER);
|
||||
const INDEX_HANDLE :&str = const_format::formatcp!("{d}/{f}", d= DIR_HANDLE, f= GENERIC_INDEX);
|
||||
const INDEX_SESSION :&str = const_format::formatcp!("{d}/{f}", d= DIR_SESSION, f= GENERIC_INDEX);
|
||||
const INDEX_USER :&str = const_format::formatcp!("{d}/{f}", d= DIR_USER, f= GENERIC_INDEX);
|
||||
|
||||
pub const FILE_SALT :&str = const_format::formatcp!("{}/x.bin", DIR_DATA);
|
||||
|
||||
@ -39,7 +47,7 @@ impl FileSystem {
|
||||
|
||||
// Initialize filesystem if does not exist.
|
||||
//
|
||||
// Note: does not currently check for corruption.
|
||||
// Notice: does not currently check for corruption.
|
||||
//
|
||||
if !Path::new(DIR_DATA).exists() {
|
||||
fs::create_dir(DIR_DATA)?;
|
||||
@ -82,17 +90,219 @@ impl FileSystem {
|
||||
}
|
||||
|
||||
|
||||
pub fn session_store(&mut self, _session:&Session) -> Result<(),()>
|
||||
pub fn session_store(&mut self, session:&Session) -> Result<u32,()>
|
||||
{
|
||||
let size = self.session_count()? as u32;
|
||||
|
||||
// Update size record
|
||||
self.index_session.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
self.index_session.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||
|
||||
match self.session_update(size, session) {
|
||||
Ok(_) => {
|
||||
let bucket_index = size & !HANDLE_BUCKET_MASK;
|
||||
let dir_index = size & HANDLE_BUCKET_MASK;
|
||||
|
||||
let bucket_path = Path::new(DIR_SESSION)
|
||||
.join(format!("{:08x}", bucket_index))
|
||||
.join(format!("{:08x}", dir_index));
|
||||
fs::write(bucket_path.join(GENERIC_HISTORY), Vec::<u8>::new()).map_err(|_| ())?;
|
||||
Ok(size)
|
||||
}
|
||||
Err(_) => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn session_update(&mut self, id:u32, session:&Session) -> Result<(),()>
|
||||
{
|
||||
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
||||
let dir_index = id & HANDLE_BUCKET_MASK;
|
||||
|
||||
// Create bucket file if not exists
|
||||
let bucket_path = Path::new(DIR_SESSION)
|
||||
.join(format!("{:08x}", bucket_index))
|
||||
.join(format!("{:08x}", dir_index));
|
||||
if !bucket_path.exists() {
|
||||
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?;
|
||||
}
|
||||
|
||||
// Open bucket file for record
|
||||
if let Ok(mut file) = File::options().write(true).create(true).open(bucket_path.join(GENERIC_CONFIG)) {
|
||||
|
||||
// Write session information
|
||||
file.write(&session.token).map_err(|_| ())?;
|
||||
file.write(&session.secret).map_err(|_| ())?;
|
||||
file.write(&pack_u8(session.p_dawn.user.is_some() as u8)).map_err(|_| ())?;
|
||||
file.write(&pack_u32(session.p_dawn.user.unwrap_or(0))).map_err(|_| ())?;
|
||||
file.write(&pack_u8(session.p_dusk.user.is_some() as u8)).map_err(|_| ())?;
|
||||
file.write(&pack_u32(session.p_dusk.user.unwrap_or(0))).map_err(|_| ())?;
|
||||
file.write(&pack_u64(session.time)).map_err(|_| ())?;
|
||||
|
||||
Ok(())
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn session_fetch(&mut self, id:u32) -> Result<Session,()>
|
||||
{
|
||||
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
||||
let dir_index = id & HANDLE_BUCKET_MASK;
|
||||
|
||||
// Create bucket file if not exists
|
||||
let bucket_path = Path::new(DIR_SESSION)
|
||||
.join(format!("{:08x}", bucket_index))
|
||||
.join(format!("{:08x}", dir_index));
|
||||
if !bucket_path.exists() {
|
||||
fs::create_dir_all(bucket_path.clone()).map_err(|_| ())?;
|
||||
}
|
||||
|
||||
// Open bucket file for record
|
||||
if let Ok(mut file) = File::options().read(true).open(bucket_path.join(GENERIC_CONFIG)) {
|
||||
|
||||
// Extract session information
|
||||
let mut buffer_u8 = [0u8; 1];
|
||||
let mut buffer_u32 = [0u8; 4];
|
||||
let mut buffer_u64 = [0u8; 8];
|
||||
|
||||
let mut token = SessionToken::default();
|
||||
let mut secret = SessionSecret::default();
|
||||
|
||||
file.read_exact(&mut token).map_err(|_| ())?;
|
||||
file.read_exact(&mut secret).map_err(|_| ())?;
|
||||
|
||||
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||
let dawn = if unpack_u8(&buffer_u8, &mut 0) != 0 {
|
||||
Some(unpack_u32(&buffer_u32, &mut 0))
|
||||
} else { None };
|
||||
|
||||
file.read_exact(&mut buffer_u8).map_err(|_| ())?;
|
||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||
let dusk = if unpack_u8(&buffer_u8, &mut 0) != 0 {
|
||||
Some(unpack_u32(&buffer_u32, &mut 0))
|
||||
} else { None };
|
||||
|
||||
file.read_exact(&mut buffer_u64).map_err(|_| ())?;
|
||||
let time = unpack_u64(&buffer_u64, &mut 0);
|
||||
|
||||
Ok(Session {
|
||||
id,
|
||||
token,
|
||||
secret,
|
||||
game:Game::new(),
|
||||
p_dawn:session::Viewer {
|
||||
connection:None,
|
||||
user:dawn,
|
||||
},
|
||||
p_dusk:session::Viewer {
|
||||
connection:None,
|
||||
user:dusk,
|
||||
},
|
||||
viewers:Vec::new(),
|
||||
time,
|
||||
chain_id:0,
|
||||
})
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn session_history_push(&mut self, _id:u32, _history:()) -> Result<(),()>
|
||||
{
|
||||
Err(())
|
||||
}
|
||||
|
||||
|
||||
pub fn user_store(&mut self, _user:&User) -> Result<(),()>
|
||||
pub fn session_history_fetch(&mut self, _id:u32) -> Result<Vec<()>,()>
|
||||
{
|
||||
Err(())
|
||||
}
|
||||
|
||||
pub fn session_count(&mut self) -> Result<usize, ()>
|
||||
// Get number of salts in store.
|
||||
//
|
||||
{
|
||||
Self::get_header_size(&mut self.index_session)
|
||||
}
|
||||
|
||||
|
||||
pub fn user_store(&mut self, user:&User) -> Result<u32,()>
|
||||
{
|
||||
let size = self.user_count()? as u32;
|
||||
|
||||
let bucket_index = size & !HANDLE_BUCKET_MASK;
|
||||
let file_index = size & HANDLE_BUCKET_MASK;
|
||||
|
||||
// Update size record
|
||||
self.index_user.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
self.index_user.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||
|
||||
// Create bucket file if not exists
|
||||
let bucket_path = Path::new(DIR_USER).join(format!("{:08x}", bucket_index));
|
||||
if !bucket_path.exists() {
|
||||
fs::create_dir(bucket_path.clone()).map_err(|_| ())?;
|
||||
}
|
||||
|
||||
// Open bucket file for record
|
||||
let file_path = bucket_path.join(format!("{:08x}.bin", file_index));
|
||||
if let Ok(mut file) = File::options().write(true).create(true).open(file_path) {
|
||||
|
||||
// Write user information
|
||||
file.write(&pack_u32(user.handle_id)).map_err(|_| ())?;
|
||||
file.write(&pack_u32(user.na_key)).map_err(|_| ())?;
|
||||
file.write(&pack_u16(user.secret.len() as u16)).map_err(|_| ())?;
|
||||
file.write(&user.secret).map_err(|_| ())?;
|
||||
|
||||
Ok(size)
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn user_fetch(&mut self, id:u32) -> Result<User,()>
|
||||
// Retrieve a salt from store.
|
||||
//
|
||||
{
|
||||
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
||||
let file_index = id & HANDLE_BUCKET_MASK;
|
||||
|
||||
// Open bucket file for record
|
||||
let file_path = Path::new(DIR_USER)
|
||||
.join(format!("{:08x}", bucket_index))
|
||||
.join(format!("{:08x}.bin", file_index));
|
||||
|
||||
if let Ok(mut file) = File::options().read(true).open(file_path) {
|
||||
file.seek(SeekFrom::Start(0)).map_err(|_| ())?;
|
||||
|
||||
// Extract user information
|
||||
let mut buffer_u16 = [0u8; 2];
|
||||
let mut buffer_u32 = [0u8; 4];
|
||||
|
||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||
let handle_id = unpack_u32(&buffer_u32, &mut 0);
|
||||
|
||||
file.read_exact(&mut buffer_u32).map_err(|_| ())?;
|
||||
let na_key = unpack_u32(&buffer_u32, &mut 0);
|
||||
|
||||
file.read_exact(&mut buffer_u16).map_err(|_| ())?;
|
||||
let secret_length = unpack_u16(&buffer_u16, &mut 0);
|
||||
|
||||
let mut secret = vec![0u8; secret_length as usize];
|
||||
file.read_exact(&mut secret).map_err(|_| ())?;
|
||||
|
||||
let (handle, _) = self.handle_fetch(handle_id)?;
|
||||
|
||||
Ok(User {
|
||||
id,
|
||||
handle_id,
|
||||
handle,
|
||||
secret,
|
||||
na_key,
|
||||
})
|
||||
} else { Err(()) }
|
||||
}
|
||||
|
||||
pub fn user_count(&mut self) -> Result<usize, ()>
|
||||
// Get number of salts in store.
|
||||
//
|
||||
{
|
||||
Self::get_header_size(&mut self.index_user)
|
||||
}
|
||||
|
||||
|
||||
pub fn handle_store(&mut self, handle:&String, user_id:u32) -> Result<u32,()>
|
||||
// Add a salt to store.
|
||||
@ -111,7 +321,7 @@ impl FileSystem {
|
||||
self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||
|
||||
// Create bucket file if not exists
|
||||
let bucket_path = Path::new(DIR_HANDLE).join(format!("{:x}.bin", bucket_index));
|
||||
let bucket_path = Path::new(DIR_HANDLE).join(format!("{:08x}.bin", bucket_index));
|
||||
if !bucket_path.exists() {
|
||||
fs::write(bucket_path.clone(), vec![0u8; 12 * HANDLE_BUCKET_SIZE as usize]).map_err(|_| ())?;
|
||||
}
|
||||
@ -153,7 +363,7 @@ impl FileSystem {
|
||||
let record_offset = 12 * record_index as u64;
|
||||
|
||||
let path = Path::new(DIR_HANDLE)
|
||||
.join(format!("{:x}.bin", bucket_index));
|
||||
.join(format!("{:08x}.bin", bucket_index));
|
||||
|
||||
// Read bucket file for record
|
||||
if let Ok(mut file) = File::open(path) {
|
||||
|
@ -5,7 +5,7 @@ pub fn pack_u8(value:u8) -> Vec<u8>
|
||||
|
||||
pub fn unpack_u8(data:&[u8], index:&mut usize) -> u8
|
||||
{
|
||||
let mut result :u8 = 0;
|
||||
let mut result = 0u8;
|
||||
if *index < data.len() {
|
||||
result = data[*index];
|
||||
*index += 1;
|
||||
@ -20,7 +20,7 @@ pub fn pack_u16(value:u16) -> Vec<u8>
|
||||
|
||||
pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
|
||||
{
|
||||
let mut result :u16 = 0;
|
||||
let mut result = 0u16;
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u16) << 8;
|
||||
*index += 1;
|
||||
@ -44,22 +44,40 @@ pub fn pack_u32(value:u32) -> Vec<u8>
|
||||
|
||||
pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32
|
||||
{
|
||||
let mut result :u32 = 0;
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 24;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 16;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 8;
|
||||
*index += 1;
|
||||
}
|
||||
let mut result = 0u32;
|
||||
for _ in 0..4 {
|
||||
result <<= 8;
|
||||
if *index < data.len() {
|
||||
result |= data[*index] as u32;
|
||||
*index += 1;
|
||||
} else { break; }
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn pack_u64(value:u64) -> Vec<u8>
|
||||
{
|
||||
vec![
|
||||
(value >> 56) as u8,
|
||||
(value >> 48) as u8,
|
||||
(value >> 40) as u8,
|
||||
(value >> 32) as u8,
|
||||
(value >> 24) as u8,
|
||||
(value >> 16) as u8,
|
||||
(value >> 8) as u8,
|
||||
(value & 0xFF) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn unpack_u64(data:&[u8], index:&mut usize) -> u64
|
||||
{
|
||||
let mut result = 0u64;
|
||||
for _ in 0..8 {
|
||||
result <<= 8;
|
||||
if *index < data.len() {
|
||||
result |= data[*index] as u64;
|
||||
*index += 1;
|
||||
} else { break; }
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ main>canvas#game {
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
|
||||
background-color: #101010;
|
||||
}
|
||||
|
358
www/js/game.js
358
www/js/game.js
@ -1,91 +1,254 @@
|
||||
const GAME_CONST = {
|
||||
PLAYER_DAWN: 0,
|
||||
PLAYER_DUSK: 1,
|
||||
const GAME = { };
|
||||
let GAME_DATA = null;
|
||||
|
||||
SOURCE_BOARD: 0,
|
||||
SOURCE_POOL: 1,
|
||||
GAME.Board = class {
|
||||
constructor() {
|
||||
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME.Tile()); }
|
||||
this.pieces = [ ];
|
||||
this.dawn = new GAME.Player();
|
||||
this.dusk = new GAME.Player();
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.pieces = [ ];
|
||||
|
||||
// Describe Dawn layout
|
||||
let layout = [
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(0, 1) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(1, 1) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(2, 2) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(3, 2) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(4, 3) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(5, 3) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(6, 4) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(7, 4) },
|
||||
{ piece:GAME.Const.PieceId.Militia, hex:new MATH.Vec2(8, 5) },
|
||||
|
||||
{ piece:GAME.Const.PieceId.Lance, hex:new MATH.Vec2(0, 0) },
|
||||
{ piece:GAME.Const.PieceId.Lance, hex:new MATH.Vec2(8, 4) },
|
||||
|
||||
{ piece:GAME.Const.PieceId.Knight, hex:new MATH.Vec2(1, 0) },
|
||||
{ piece:GAME.Const.PieceId.Knight, hex:new MATH.Vec2(7, 3) },
|
||||
|
||||
{ piece:GAME.Const.PieceId.Castle, hex:new MATH.Vec2(2, 0) },
|
||||
{ piece:GAME.Const.PieceId.Castle, hex:new MATH.Vec2(6, 2) },
|
||||
|
||||
{ piece:GAME.Const.PieceId.Tower, hex:new MATH.Vec2(3, 0) },
|
||||
{ piece:GAME.Const.PieceId.Tower, hex:new MATH.Vec2(5, 1) },
|
||||
|
||||
{ piece:GAME.Const.PieceId.Dragon, hex:new MATH.Vec2(4, 1) },
|
||||
{ piece:GAME.Const.PieceId.Omen, hex:new MATH.Vec2(4, 0) },
|
||||
];
|
||||
|
||||
// Add Dawn pieces
|
||||
for(let piece of layout) {
|
||||
this.place_piece(piece.piece, GAME.Const.Player.Dawn, piece.hex);
|
||||
}
|
||||
|
||||
// Add Dusk pieces
|
||||
for(let piece of layout) {
|
||||
let hex = new MATH.Vec2(8 - piece.hex.x, 8 - piece.hex.y);
|
||||
this.place_piece(piece.piece, GAME.Const.Player.Dusk, hex);
|
||||
}
|
||||
}
|
||||
|
||||
place_piece(piece, player, hex) {
|
||||
let game_piece = new GAME.Piece(piece, player);
|
||||
game_piece.tile = this.hex_to_tile(hex);
|
||||
this.tiles[game_piece.tile].piece = this.pieces.length;
|
||||
this.pieces.push(game_piece);
|
||||
}
|
||||
|
||||
hex_to_tile(hex) {
|
||||
let a = ((hex.x + 4) * (hex.x + 5) / 2) - 10;
|
||||
let b = (hex.x > 4) * ((hex.x - 4) + ((hex.x - 5) * (hex.x - 4)));
|
||||
return a - b + hex.y;
|
||||
}
|
||||
|
||||
tile_to_hex(tile) {
|
||||
const ROWS = [ 0, 5, 11, 18, 26, 35, 43, 50, 56, 61 ];
|
||||
let column = 0;
|
||||
while(tile >= ROWS[column + 1]) { column += 1; }
|
||||
let row = tile - ROWS[column] + ((column > 4) * (column - 4));
|
||||
return new MATH.Vec2(column, row);
|
||||
}
|
||||
};
|
||||
|
||||
const GAME_CLASS = {
|
||||
Board: class {
|
||||
constructor() {
|
||||
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME_CLASS.Tile()); }
|
||||
this.dawn = new GAME_CLASS.Player();
|
||||
}
|
||||
},
|
||||
|
||||
Player: class {
|
||||
GAME.Player = class {
|
||||
constructor() {
|
||||
this.handle = "";
|
||||
this.pool = new GAME_CLASS.Pool();
|
||||
this.pool = new GAME.Pool();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Pool: class {
|
||||
GAME.Pool = class {
|
||||
constructor() {
|
||||
this.pieces = [ ]; for(let i = 0; i < 6; ++i) { this.pieces.push(0); }
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Tile: class {
|
||||
GAME.Tile = class {
|
||||
constructor() {
|
||||
this.piece = 0;
|
||||
this.piece = null;
|
||||
this.threaten = { dawn: 0, dusk: 0 };
|
||||
this.checking = { dawn: false, dusk: false };
|
||||
}
|
||||
},
|
||||
|
||||
Move: class {
|
||||
reset() {
|
||||
this.threaten = { dawn: 0, dusk: 0 };
|
||||
this.checking = { dawn: false, dusk: false };
|
||||
}
|
||||
};
|
||||
|
||||
GAME.Move = class {
|
||||
constructor(source, from, to) {
|
||||
this.source = source;
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
GamePiece: class {
|
||||
GAME.GamePiece = class {
|
||||
constructor(name, assets, moves, promote_moves) {
|
||||
this.name = name;
|
||||
this.assets = assets;
|
||||
this.moves = moves;
|
||||
this.pmoves = promote_moves;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Piece: class {
|
||||
GAME.PieceMovement = class {
|
||||
constructor() {
|
||||
this.direction = 0;
|
||||
this.stride = 0;
|
||||
}
|
||||
|
||||
add(direction) {
|
||||
this.direction |= 1 << direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
add_stride(direction) {
|
||||
this.direction |= 1 << direction;
|
||||
this.stride |= 1 << direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
rotate() {
|
||||
let copy = new GAME.PieceMovement();
|
||||
copy.direction = BITWISE.rotate_blocks(this.direction);
|
||||
copy.stride = BITWISE.rotate_blocks(this.stride);
|
||||
return copy;
|
||||
}
|
||||
};
|
||||
|
||||
GAME.Piece = class {
|
||||
constructor(piece, player) {
|
||||
this.piece = piece;
|
||||
this.player = player;
|
||||
this.promoted = false;
|
||||
|
||||
this.tile = 0;
|
||||
this.blocking = 0;
|
||||
}
|
||||
};
|
||||
|
||||
GAME.Game = class {
|
||||
constructor() {
|
||||
this.turn = 0;
|
||||
this.board = new GAME.Board();
|
||||
}
|
||||
|
||||
update_board() {
|
||||
// Reset tiles
|
||||
for(tile of this.board.tiles) { tile.reset(); }
|
||||
|
||||
// Determine threaten, check, and blocking for each piece
|
||||
for(piece of this.board.pieces) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
process(move) {
|
||||
|
||||
|
||||
// Recalculate new board state.
|
||||
GAME.update_board();
|
||||
}
|
||||
|
||||
validate(move) {
|
||||
|
||||
}
|
||||
|
||||
get_move_tiles(piece_id) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
GAME.Const = {
|
||||
Player: {
|
||||
Dawn: 0,
|
||||
Dusk: 1,
|
||||
},
|
||||
|
||||
Game: class {
|
||||
constructor() {
|
||||
this.board = new GAME_CLASS.Board();
|
||||
this.pieces = [
|
||||
new GAME_CLASS.GamePiece("Militia",
|
||||
Source: {
|
||||
Board: 0,
|
||||
Pool: 1,
|
||||
},
|
||||
|
||||
Direction: [
|
||||
new MATH.Vec2(0, 1),
|
||||
new MATH.Vec2(1, 1),
|
||||
new MATH.Vec2(1, 0),
|
||||
new MATH.Vec2(0, -1),
|
||||
new MATH.Vec2(-1, -1),
|
||||
new MATH.Vec2(-1, 0),
|
||||
|
||||
new MATH.Vec2(1, 2),
|
||||
new MATH.Vec2(2, 1),
|
||||
new MATH.Vec2(1, -1),
|
||||
new MATH.Vec2(-1, -2),
|
||||
new MATH.Vec2(-2, -1),
|
||||
new MATH.Vec2(-1, 1),
|
||||
],
|
||||
|
||||
PieceId: {
|
||||
Militia: 0,
|
||||
Knight: 1,
|
||||
Lance: 2,
|
||||
Tower: 3,
|
||||
Castle: 4,
|
||||
Dragon: 5,
|
||||
Omen: 6,
|
||||
},
|
||||
|
||||
Piece: [
|
||||
new GAME.GamePiece(
|
||||
"Militia",
|
||||
["asset/militia_dusk.svg", "asset/militia_dawn.svg"],
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(5),
|
||||
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(2)
|
||||
.add(4)
|
||||
.add(5)
|
||||
.add(5),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Knight",
|
||||
new GAME.GamePiece(
|
||||
"Knight",
|
||||
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(3)
|
||||
.add(6)
|
||||
.add(11)
|
||||
.add(13)
|
||||
.add(17),
|
||||
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(3)
|
||||
.add(6)
|
||||
.add(7)
|
||||
@ -94,34 +257,34 @@ const GAME_CLASS = {
|
||||
.add(13)
|
||||
.add(14)
|
||||
.add(16)
|
||||
.add(17)
|
||||
.add(17),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Lance",
|
||||
new GAME.GamePiece(
|
||||
"Lance",
|
||||
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
||||
new Move()
|
||||
.add(0, true)
|
||||
new GAME.PieceMovement()
|
||||
.add_stride(0)
|
||||
.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 GAME.PieceMovement()
|
||||
.add_stride(0)
|
||||
.add_stride(1)
|
||||
.add_stride(2)
|
||||
.add_stride(3)
|
||||
.add_stride(4)
|
||||
.add_stride(5),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Tower",
|
||||
new GAME.GamePiece(
|
||||
"Tower",
|
||||
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(3)
|
||||
.add(5)
|
||||
.add(6)
|
||||
.add(11),
|
||||
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(2)
|
||||
@ -131,11 +294,12 @@ const GAME_CLASS = {
|
||||
.add(6)
|
||||
.add(8)
|
||||
.add(9)
|
||||
.add(11)
|
||||
.add(11),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Castle",
|
||||
new GAME.GamePiece(
|
||||
"Castle",
|
||||
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(2)
|
||||
@ -143,44 +307,44 @@ const GAME_CLASS = {
|
||||
.add(5)
|
||||
.add(7)
|
||||
.add(10),
|
||||
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(2)
|
||||
.add(3)
|
||||
.add(4)
|
||||
.add(5)
|
||||
.add(7, true)
|
||||
.add(10, true)
|
||||
.add_stride(7)
|
||||
.add_stride(10),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Dragon",
|
||||
new GAME.GamePiece(
|
||||
"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 GAME.PieceMovement()
|
||||
.add_stride(6)
|
||||
.add_stride(7)
|
||||
.add_stride(8)
|
||||
.add_stride(9)
|
||||
.add_stride(10)
|
||||
.add_stride(11),
|
||||
new GAME.PieceMovement()
|
||||
.add_stride(0)
|
||||
.add_stride(1)
|
||||
.add_stride(2)
|
||||
.add_stride(3)
|
||||
.add_stride(4)
|
||||
.add_stride(5)
|
||||
.add_stride(6)
|
||||
.add_stride(7)
|
||||
.add_stride(8)
|
||||
.add_stride(9)
|
||||
.add_stride(10)
|
||||
.add_stride(11),
|
||||
),
|
||||
new GAME_CLASS.GamePiece("Omen",
|
||||
new GAME.GamePiece(
|
||||
"Omen",
|
||||
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
||||
new Move()
|
||||
new GAME.PieceMovement()
|
||||
.add(0)
|
||||
.add(1)
|
||||
.add(2)
|
||||
@ -188,25 +352,11 @@ const GAME_CLASS = {
|
||||
.add(4)
|
||||
.add(5)
|
||||
.add(7)
|
||||
.add(10)
|
||||
.add(10),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
|
||||
let GAME_DATA = null;
|
||||
|
||||
const GAME = {
|
||||
init() {
|
||||
GAME_DATA = new GAME_CLASS.Game();
|
||||
},
|
||||
|
||||
process(move) {
|
||||
|
||||
},
|
||||
|
||||
validate(move) {
|
||||
|
||||
},
|
||||
GAME.init = () => {
|
||||
GAME_DATA = new GAME.Game();
|
||||
};
|
||||
|
@ -1,11 +1,32 @@
|
||||
let INTERFACE_DATA = {
|
||||
canvas:null,
|
||||
context:null,
|
||||
|
||||
scale:1,
|
||||
};
|
||||
let INTERFACE_DATA = null;
|
||||
|
||||
const INTERFACE = {
|
||||
Color: {
|
||||
Background: "#101010",
|
||||
Text: "#c0c0c0",
|
||||
|
||||
TileBorder: "#606060",
|
||||
TileLight: "#383838",
|
||||
TileMedium: "#242424",
|
||||
TileDark: "#101010",
|
||||
|
||||
Dawn: "#ffe082",
|
||||
DawnDark: "#ff6d00",
|
||||
|
||||
Dusk: "#f6a1bd",
|
||||
DuskDark: "#c51162",
|
||||
|
||||
HintHover: "#71a1e8",
|
||||
HintSelect: "#4a148c",
|
||||
HintValid: "#1a237e",
|
||||
HintAllowed: "#6a1b9a",
|
||||
HintInvalid: "b71c1c",
|
||||
HintPlay: "#083242",
|
||||
HintWarn: "#054719",
|
||||
HintCheck: "#C62828",
|
||||
|
||||
},
|
||||
|
||||
hover(event) {
|
||||
|
||||
},
|
||||
@ -14,66 +35,330 @@ const INTERFACE = {
|
||||
|
||||
},
|
||||
|
||||
resize() {
|
||||
INTERFACE_DATA.canvas.width = INTERFACE_DATA.canvas.clientWidth;
|
||||
INTERFACE_DATA.canvas.height = INTERFACE_DATA.canvas.clientHeight;
|
||||
},
|
||||
|
||||
draw() {
|
||||
this.resize();
|
||||
let canvas = INTERFACE_DATA.canvas;
|
||||
let ctx = INTERFACE_DATA.context;
|
||||
|
||||
// Determine
|
||||
let width = INTERFACE_DATA.canvas.width;
|
||||
let height = INTERFACE_DATA.canvas.height;
|
||||
let min_dimension = Math.min(width, height);
|
||||
// Determine interface configuration
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
|
||||
let scale = 1;
|
||||
//let margin = INTERFACE_DATA.canvas.
|
||||
let width = canvas.width;
|
||||
let height = canvas.height;
|
||||
|
||||
// Interface margin
|
||||
let gui_margin = Math.floor(Math.min(width, height) / 100);
|
||||
|
||||
// Interface width, height, and scale
|
||||
let gui_width = width - (gui_margin * 2);
|
||||
let gui_height = height - (gui_margin * 2);
|
||||
|
||||
if(gui_width < gui_height * INTERFACE.Ratio) {
|
||||
gui_height = Math.floor(gui_width / INTERFACE.Ratio);
|
||||
} else {
|
||||
gui_width = Math.floor(gui_height * INTERFACE.Ratio);
|
||||
}
|
||||
let gui_scale = gui_height * INTERFACE.Scale;
|
||||
|
||||
// Boundries to center interface
|
||||
let gui_offset = new MATH.Vec2(
|
||||
(width - gui_width) / 2,
|
||||
(height - gui_height) / 2,
|
||||
);
|
||||
|
||||
|
||||
// Draw indicator gradient if player's turn.
|
||||
|
||||
// Draw background
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
ctx.fillStyle = INTERFACE.Color.Background;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
|
||||
// Draw tiles
|
||||
for(let i = 0; i < GAME.board.tiles.length; ++i) {
|
||||
// Draw background
|
||||
let radius = INTERFACE.Radius * gui_scale;
|
||||
let basis_x = gui_offset.x + radius;
|
||||
let basis_y = gui_offset.y + (13 * gui_scale);
|
||||
|
||||
const TILE_SCALE = 0.9;
|
||||
ctx.lineWidth = gui_scale * 0.06;
|
||||
|
||||
let draw = new INTERFACE.Draw(ctx, TILE_SCALE * gui_scale);
|
||||
|
||||
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
|
||||
let tile = GAME_DATA.board.tiles[i];
|
||||
|
||||
let coord = GAME_DATA.board.tile_to_hex(i);
|
||||
let gui_x = basis_x + (1.5 * radius * coord.x);
|
||||
let gui_y = basis_y - (2 * gui_scale * coord.y) + (gui_scale * coord.x);
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(gui_x, gui_y);
|
||||
|
||||
// Draw background.
|
||||
// Select indicator color or default to tile color.
|
||||
if(true) {
|
||||
switch(MATH.mod(coord.x + coord.y, 3)) {
|
||||
case 0: ctx.fillStyle = INTERFACE.Color.TileMedium; break;
|
||||
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
|
||||
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.fill();
|
||||
|
||||
// Draw tile content
|
||||
if(tile.piece !== null) {
|
||||
let piece = GAME_DATA.board.pieces[tile.piece];
|
||||
|
||||
// Draw border
|
||||
if(piece.player == GAME.Const.Player.Dawn) { ctx.strokeStyle = INTERFACE.Color.DawnDark; }
|
||||
else { ctx.strokeStyle = INTERFACE.Color.DuskDark; }
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.stroke();
|
||||
|
||||
// Draw border hints
|
||||
draw.hints(piece);
|
||||
|
||||
// Draw piece
|
||||
|
||||
// Draw
|
||||
} else {
|
||||
// Draw standard border
|
||||
ctx.strokeStyle = INTERFACE.Color.TileBorder;
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
// Draw player pool
|
||||
for(let i = 0; i < 6; ++i) {
|
||||
let gui_x = basis_x + (radius * 14);
|
||||
let gui_y = basis_y - (9 - (2 * i)) * gui_scale;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(gui_x, gui_y);
|
||||
|
||||
// Draw background if indicator is present.
|
||||
if(true) {
|
||||
ctx.fillStyle = INTERFACE.Color.TileDark;
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Draw border
|
||||
ctx.strokeStyle = INTERFACE.Color.Dawn;
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Draw opponent pool
|
||||
for(let i = 0; i < 6; ++i) {
|
||||
let gui_x = basis_x + (radius * 15.5);
|
||||
let gui_y = basis_y - (8 - (2 * i)) * gui_scale;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(gui_x, gui_y);
|
||||
|
||||
// Draw background.
|
||||
// Select indicator color or default to tile color.
|
||||
if(true) {
|
||||
ctx.fillStyle = INTERFACE.Color.TileDark;
|
||||
} else {
|
||||
|
||||
}
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.fill();
|
||||
|
||||
// Draw border
|
||||
ctx.strokeStyle = INTERFACE.Color.Dusk;
|
||||
ctx.beginPath();
|
||||
draw.hex();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
// Draw informational text
|
||||
ctx.font = Math.ceil(gui_scale / 24) + "px sans-serif";
|
||||
|
||||
|
||||
/*
|
||||
// Dawn handle
|
||||
ctx.fillStyle = INTERFACE.Color.Text;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(GAME_DATA.turn, width / 2, gui_margin);
|
||||
|
||||
|
||||
// Dusk handle
|
||||
ctx.fillStyle = INTERFACE.Color.Text;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
|
||||
*/
|
||||
|
||||
// Number of moves
|
||||
ctx.fillStyle = INTERFACE.Color.Text;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.textAlign = "right";
|
||||
ctx.fillText(GAME_DATA.turn, width - gui_margin, gui_margin);
|
||||
|
||||
// Game state message
|
||||
if(INTERFACE_DATA.message !== null) {
|
||||
ctx.fillStyle = INTERFACE.Color.Text;
|
||||
ctx.textBaseline = "bottom";
|
||||
ctx.textAlign = "left";
|
||||
ctx.fillText(INTERFACE_DATA.message, gui_margin, height - gui_margin);
|
||||
}
|
||||
},
|
||||
|
||||
init() {
|
||||
Draw: class {
|
||||
constructor(ctx, scale) {
|
||||
this.ctx = ctx;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
hex() {
|
||||
this.ctx.moveTo(INTERFACE.HexVertex[5].x * this.scale, INTERFACE.HexVertex[5].y * this.scale);
|
||||
for(let i = 0; i < INTERFACE.HexVertex.length; ++i) {
|
||||
this.ctx.lineTo(INTERFACE.HexVertex[i].x * this.scale, INTERFACE.HexVertex[i].y * this.scale);
|
||||
}
|
||||
}
|
||||
|
||||
hints(piece) {
|
||||
let descriptor = GAME.Const.Piece[piece.piece];
|
||||
let moves = descriptor.moves;
|
||||
if(piece.promoted) { moves = descriptor.pmoves; }
|
||||
if(piece.player == GAME.Const.Player.Dusk) { moves = moves.rotate(); }
|
||||
moves = moves.direction;
|
||||
|
||||
for(let mask = BITWISE.lsb(moves); moves > 0; mask = BITWISE.lsb(moves)) {
|
||||
let move = BITWISE.ffs(mask);
|
||||
let nmove = move % 12;
|
||||
|
||||
this.ctx.strokeStyle = (piece.player == GAME.Const.Player.Dawn)? INTERFACE.Color.Dawn : INTERFACE.Color.Dusk;
|
||||
this.ctx.beginPath();
|
||||
|
||||
// draw edge marker
|
||||
if(nmove < 6) {
|
||||
let fr = INTERFACE.HexVertex[nmove];
|
||||
let to = INTERFACE.HexVertex[(nmove + 1) % 6];
|
||||
|
||||
let dx = (to.x - fr.x) / 3.0;
|
||||
let dy = (to.y - fr.y) / 3.0;
|
||||
|
||||
let fqx = fr.x + dx;
|
||||
let fqy = fr.y + dy;
|
||||
|
||||
let tqx = fr.x + (2 * dx);
|
||||
let tqy = fr.y + (2 * dy);
|
||||
|
||||
this.ctx.moveTo(fqx * this.scale, fqy * this.scale);
|
||||
this.ctx.lineTo(tqx * this.scale, tqy * this.scale);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
// draw vertex marker
|
||||
else {
|
||||
let fr = INTERFACE.HexVertex[nmove % 6];
|
||||
let mid = INTERFACE.HexVertex[(nmove + 1) % 6];
|
||||
let to = INTERFACE.HexVertex[(nmove + 2) % 6];
|
||||
|
||||
let dx1 = (mid.x - fr.x) / 3.0;
|
||||
let dy1 = (mid.y - fr.y) / 3.0;
|
||||
|
||||
let dx2 = (to.x - mid.x) / 3.0;
|
||||
let dy2 = (to.y - mid.y) / 3.0;
|
||||
|
||||
let fqx = mid.x - dx1;
|
||||
let fqy = mid.y - dy1;
|
||||
|
||||
let tqx = mid.x + dx2;
|
||||
let tqy = mid.y + dy2;
|
||||
|
||||
this.ctx.moveTo(fqx * this.scale, fqy * this.scale);
|
||||
this.ctx.lineTo(mid.x * this.scale, mid.y * this.scale);
|
||||
this.ctx.lineTo(tqx * this.scale, tqy * this.scale);
|
||||
this.ctx.stroke();
|
||||
}
|
||||
moves &= ~mask;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
init(player_id) {
|
||||
GAME.init();
|
||||
|
||||
INTERFACE_DATA.canvas = document.getElementById("game");
|
||||
let canvas = INTERFACE_DATA.canvas;
|
||||
INTERFACE_DATA = {
|
||||
canvas: document.getElementById("game"),
|
||||
context: null,
|
||||
|
||||
player_id: player_id,
|
||||
|
||||
message: null,
|
||||
};
|
||||
|
||||
let canvas = INTERFACE_DATA.canvas;
|
||||
if(canvas !== undefined) {
|
||||
INTERFACE_DATA.context = canvas.getContext("2d");
|
||||
|
||||
canvas.addEventListener("mousemove", INTERFACE.hover);
|
||||
canvas.addEventListener("mousedown", INTERFACE.click);
|
||||
canvas.addEventListener("resize", INTERFACE.draw);
|
||||
window.addEventListener("resize", INTERFACE.draw);
|
||||
this.draw();
|
||||
}
|
||||
},
|
||||
|
||||
uninit() {
|
||||
INTERFACE_DATA.canvas = null;
|
||||
INTERFACE_DATA.context = null;
|
||||
MAIN.removeChild(INTERFACE_DATA.canvas);
|
||||
INTERFACE_DATA = null;
|
||||
},
|
||||
|
||||
message(data) {
|
||||
message(code, data) {
|
||||
switch(code) {
|
||||
case OpCode.GameState: {
|
||||
// Build game state
|
||||
|
||||
INTERFACE.draw();
|
||||
} break;
|
||||
|
||||
case OpCode.GamePlay: {
|
||||
// Apply play to board
|
||||
GAME_DATA.turn += 1;
|
||||
|
||||
INTERFACE.draw();
|
||||
} break;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
INTERFACE.Radius = 2.0 / Math.sqrt(3.0);
|
||||
INTERFACE.HalfRadius = 1.0 / Math.sqrt(3.0);
|
||||
INTERFACE.Scale = 1 / 18;
|
||||
INTERFACE.Ratio = (17.5 * INTERFACE.Radius) * INTERFACE.Scale;
|
||||
INTERFACE.HexVertex = [
|
||||
// top face
|
||||
new MATH.Vec2(-INTERFACE.HalfRadius, -1),
|
||||
// top-right face
|
||||
new MATH.Vec2(INTERFACE.HalfRadius, -1),
|
||||
// bottom-right face
|
||||
new MATH.Vec2(INTERFACE.Radius, 0),
|
||||
// bottom face
|
||||
new MATH.Vec2(INTERFACE.HalfRadius, 1),
|
||||
// bottom-left face
|
||||
new MATH.Vec2(-INTERFACE.HalfRadius, 1),
|
||||
// top-left face
|
||||
new MATH.Vec2(-INTERFACE.Radius, 0),
|
||||
];
|
||||
|
@ -432,6 +432,7 @@ const SCENES = {
|
||||
return true;
|
||||
},
|
||||
unload() {
|
||||
INTERFACE.uninit();
|
||||
MESSAGE_COMPOSE([
|
||||
PACK.u16(OpCode.SessionLeave),
|
||||
]);
|
||||
|
@ -125,17 +125,49 @@ const BITWISE = {
|
||||
mask = mask - ((mask >> 1) & 0x55555555);
|
||||
mask = (mask & 0x33333333) + ((mask >> 2) & 0x33333333);
|
||||
return ((mask + (mask >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
|
||||
}
|
||||
},
|
||||
|
||||
rotate_blocks(mask)
|
||||
{
|
||||
const r1 = 0x00003F; // first 6 bits
|
||||
const r2 = 0x000FC0; // second 6 bits
|
||||
const r3 = 0x03F000; // third 6 bits
|
||||
|
||||
let v1 = (r1 & mask) << 3;
|
||||
let v2 = (r2 & mask) << 3;
|
||||
let v3 = (r3 & mask) << 3;
|
||||
|
||||
v1 = (v1 & r1) | ((v1 & ~r1) >> 6);
|
||||
v2 = (v2 & r2) | ((v2 & ~r2) >> 6);
|
||||
v3 = (v3 & r3) | ((v3 & ~r3) >> 6);
|
||||
|
||||
return v1 | v2 | v3;
|
||||
},
|
||||
};
|
||||
|
||||
const MATH = {
|
||||
sign(a)
|
||||
{
|
||||
Vec2: class {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
},
|
||||
|
||||
sign(a) {
|
||||
return 1 - ((a < 0) << 1);
|
||||
},
|
||||
|
||||
mod(a, b)
|
||||
{
|
||||
mod(a, b) {
|
||||
return ((a % b) + b) % b
|
||||
},
|
||||
};
|
||||
|
||||
const COLOR = {
|
||||
rgba(r, g, b, a) {
|
||||
let ur = Math.floor(r * 255);
|
||||
let ug = Math.floor(r * 255);
|
||||
let ub = Math.floor(r * 255);
|
||||
let ua = Math.floor(r * 255);
|
||||
return "rgba(" + ur + "," + ug + "," + ub + "," + ua + ")";
|
||||
},
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user