Add game interface.
This commit is contained in:
parent
ee12d74c67
commit
7ba9ce7ba0
@ -20,7 +20,6 @@ pub struct App {
|
|||||||
pub connections:Pool<Connection>,
|
pub connections:Pool<Connection>,
|
||||||
|
|
||||||
pub users:Pool<User>,
|
pub users:Pool<User>,
|
||||||
pub user_next:u32,
|
|
||||||
pub user_id:Sparse<usize>,
|
pub user_id:Sparse<usize>,
|
||||||
pub user_handle:Trie<u32>,
|
pub user_handle:Trie<u32>,
|
||||||
pub salts:Sparse<[u8; 16]>,
|
pub salts:Sparse<[u8; 16]>,
|
||||||
@ -48,25 +47,58 @@ impl App {
|
|||||||
let handle_count = filesystem.handle_count()?;
|
let handle_count = filesystem.handle_count()?;
|
||||||
for id in 0..handle_count {
|
for id in 0..handle_count {
|
||||||
let (handle, user_id) = filesystem.handle_fetch(id as u32).unwrap();
|
let (handle, user_id) = filesystem.handle_fetch(id as u32).unwrap();
|
||||||
println!("got: {} = {}", handle, user_id);
|
|
||||||
user_handle.set(handle.as_bytes(), 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 {
|
Ok(Self {
|
||||||
filesystem:filesystem,
|
filesystem:filesystem,
|
||||||
|
|
||||||
connections:Pool::new(),
|
connections:Pool::new(),
|
||||||
|
|
||||||
users:Pool::new(),
|
users,
|
||||||
user_next:0,
|
user_id,
|
||||||
user_id:Sparse::new(),
|
user_handle,
|
||||||
user_handle:Trie::new(),
|
|
||||||
salts,
|
salts,
|
||||||
|
|
||||||
auths:Trie::new(),
|
auths:Trie::new(),
|
||||||
sessions:Trie::new(),
|
sessions,
|
||||||
|
|
||||||
session_time:Chain::new(),
|
session_time,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
|
@ -9,7 +9,8 @@ pub struct Viewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub key:SessionToken,
|
pub id:u32,
|
||||||
|
pub token:SessionToken,
|
||||||
pub secret:SessionSecret,
|
pub secret:SessionSecret,
|
||||||
|
|
||||||
pub game:Game,
|
pub game:Game,
|
||||||
@ -18,5 +19,6 @@ pub struct Session {
|
|||||||
pub p_dusk:Viewer,
|
pub p_dusk:Viewer,
|
||||||
pub viewers:Vec<Viewer>,
|
pub viewers:Vec<Viewer>,
|
||||||
|
|
||||||
|
pub time:u64,
|
||||||
pub chain_id:usize,
|
pub chain_id:usize,
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
pub struct User {
|
pub struct User {
|
||||||
pub id:u32,
|
pub id:u32,
|
||||||
|
pub handle_id:u32,
|
||||||
pub handle:String,
|
pub handle:String,
|
||||||
pub secret:Vec<u8>,
|
pub secret:Vec<u8>,
|
||||||
pub na_key:u32,
|
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();
|
let salt_id = app.filesystem.salt_store(salt).unwrap();
|
||||||
app.salts.set(salt_id as isize, salt);
|
app.salts.set(salt_id as isize, salt);
|
||||||
|
|
||||||
if let Ok(hash) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
if let Ok(secret) = argon2::hash_raw(&request.secret, &salt, &argon_config) {
|
||||||
let user_id = app.user_next;
|
let user_id = app.filesystem.user_count().unwrap() as u32;
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register user pool id and handle
|
// Register user pool id and handle
|
||||||
app.filesystem.handle_store(&request.handle, user_id).ok();
|
let handle_id = app.filesystem.handle_store(&request.handle, user_id).unwrap();
|
||||||
app.user_id.set(user_id as isize, user_pos);
|
|
||||||
app.user_handle.set(request.handle.as_bytes(), user_id);
|
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);
|
println!("Registered user '{}' @ {} with id {}", request.handle, user_pos, user_id);
|
||||||
|
|
||||||
// Generate authentication token and secret
|
// Generate authentication token and secret
|
||||||
@ -296,7 +301,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
} else { String::new() };
|
} else { String::new() };
|
||||||
|
|
||||||
response.records.push(PacketSessionListResponseRecord {
|
response.records.push(PacketSessionListResponseRecord {
|
||||||
token:session.key,
|
token:session.token,
|
||||||
handles:[
|
handles:[
|
||||||
dawn_handle,
|
dawn_handle,
|
||||||
dusk_handle,
|
dusk_handle,
|
||||||
@ -337,9 +342,11 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
rng.fill(&mut secret).ok();
|
rng.fill(&mut secret).ok();
|
||||||
|
|
||||||
let chain_id = app.session_time.add(token);
|
let chain_id = app.session_time.add(token);
|
||||||
app.sessions.set(&token, Session {
|
|
||||||
key:token,
|
let mut session = Session {
|
||||||
secret:secret,
|
id:0,
|
||||||
|
token,
|
||||||
|
secret,
|
||||||
game:game::Game::new(),
|
game:game::Game::new(),
|
||||||
p_dawn:Viewer {
|
p_dawn:Viewer {
|
||||||
connection:None,//Some(qr.id),
|
connection:None,//Some(qr.id),
|
||||||
@ -350,8 +357,13 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
user:None,
|
user:None,
|
||||||
},
|
},
|
||||||
viewers:Vec::new(),
|
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);
|
app.session_time.set(chain_id, token);
|
||||||
|
|
||||||
// Set player to Dawn.
|
// Set player to Dawn.
|
||||||
@ -397,6 +409,8 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
false
|
false
|
||||||
} {
|
} {
|
||||||
println!("Add user to session.");
|
println!("Add user to session.");
|
||||||
|
|
||||||
|
app.filesystem.session_update(session.id, session).ok();
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
}
|
}
|
||||||
} else {
|
} 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)))
|
Some(QRPacket::new(qr.id, QRPacketData::RSessionJoin(response)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SessionRetire
|
||||||
|
|
||||||
|
// SessionLeave
|
||||||
|
|
||||||
|
// GameState
|
||||||
|
|
||||||
|
// GamePlay
|
||||||
|
|
||||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
_ => { 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_list; pub use session_list::*;
|
||||||
mod session_create; pub use session_create::*;
|
mod session_create; pub use session_create::*;
|
||||||
mod session_join; pub use session_join::*;
|
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 {
|
mod prelude {
|
||||||
pub trait Packet {
|
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
|
fs::{self, File}, io::{Read, Seek, SeekFrom, Write}, path::Path
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use game::Game;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::session::Session,
|
app::{
|
||||||
app::user::User,
|
session::{self, Session, SessionToken, SessionSecret},
|
||||||
util::pack::{pack_u32, unpack_u32}
|
user::User,
|
||||||
|
},
|
||||||
|
util::pack::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
const HANDLE_BUCKET_MASK :u32 = 0xFF;
|
const HANDLE_BUCKET_MASK :u32 = 0xFF;
|
||||||
const HANDLE_BUCKET_SIZE :u32 = HANDLE_BUCKET_MASK + 1;
|
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_DATA :&str = "data";
|
||||||
const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA);
|
const DIR_HANDLE :&str = const_format::formatcp!("{}/h", DIR_DATA);
|
||||||
const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA);
|
const DIR_SESSION :&str = const_format::formatcp!("{}/s", DIR_DATA);
|
||||||
const DIR_USER :&str = const_format::formatcp!("{}/u", 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_HANDLE :&str = const_format::formatcp!("{d}/{f}", d= DIR_HANDLE, f= GENERIC_INDEX);
|
||||||
const INDEX_SESSION :&str = const_format::formatcp!("{}/i.bin", DIR_SESSION);
|
const INDEX_SESSION :&str = const_format::formatcp!("{d}/{f}", d= DIR_SESSION, f= GENERIC_INDEX);
|
||||||
const INDEX_USER :&str = const_format::formatcp!("{}/i.bin", DIR_USER);
|
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);
|
pub const FILE_SALT :&str = const_format::formatcp!("{}/x.bin", DIR_DATA);
|
||||||
|
|
||||||
@ -39,7 +47,7 @@ impl FileSystem {
|
|||||||
|
|
||||||
// Initialize filesystem if does not exist.
|
// 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() {
|
if !Path::new(DIR_DATA).exists() {
|
||||||
fs::create_dir(DIR_DATA)?;
|
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(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn session_history_fetch(&mut self, _id:u32) -> Result<Vec<()>,()>
|
||||||
pub fn user_store(&mut self, _user:&User) -> Result<(),()>
|
|
||||||
{
|
{
|
||||||
Err(())
|
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,()>
|
pub fn handle_store(&mut self, handle:&String, user_id:u32) -> Result<u32,()>
|
||||||
// Add a salt to store.
|
// Add a salt to store.
|
||||||
@ -111,7 +321,7 @@ impl FileSystem {
|
|||||||
self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
self.index_handle.write(&pack_u32(size + 1)).map_err(|_| ())?;
|
||||||
|
|
||||||
// Create bucket file if not exists
|
// 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() {
|
if !bucket_path.exists() {
|
||||||
fs::write(bucket_path.clone(), vec![0u8; 12 * HANDLE_BUCKET_SIZE as usize]).map_err(|_| ())?;
|
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 record_offset = 12 * record_index as u64;
|
||||||
|
|
||||||
let path = Path::new(DIR_HANDLE)
|
let path = Path::new(DIR_HANDLE)
|
||||||
.join(format!("{:x}.bin", bucket_index));
|
.join(format!("{:08x}.bin", bucket_index));
|
||||||
|
|
||||||
// Read bucket file for record
|
// Read bucket file for record
|
||||||
if let Ok(mut file) = File::open(path) {
|
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
|
pub fn unpack_u8(data:&[u8], index:&mut usize) -> u8
|
||||||
{
|
{
|
||||||
let mut result :u8 = 0;
|
let mut result = 0u8;
|
||||||
if *index < data.len() {
|
if *index < data.len() {
|
||||||
result = data[*index];
|
result = data[*index];
|
||||||
*index += 1;
|
*index += 1;
|
||||||
@ -20,7 +20,7 @@ pub fn pack_u16(value:u16) -> Vec<u8>
|
|||||||
|
|
||||||
pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
|
pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
|
||||||
{
|
{
|
||||||
let mut result :u16 = 0;
|
let mut result = 0u16;
|
||||||
if *index < data.len() {
|
if *index < data.len() {
|
||||||
result = (data[*index] as u16) << 8;
|
result = (data[*index] as u16) << 8;
|
||||||
*index += 1;
|
*index += 1;
|
||||||
@ -44,22 +44,40 @@ pub fn pack_u32(value:u32) -> Vec<u8>
|
|||||||
|
|
||||||
pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32
|
pub fn unpack_u32(data:&[u8], index:&mut usize) -> u32
|
||||||
{
|
{
|
||||||
let mut result :u32 = 0;
|
let mut result = 0u32;
|
||||||
if *index < data.len() {
|
for _ in 0..4 {
|
||||||
result = (data[*index] as u32) << 24;
|
result <<= 8;
|
||||||
*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;
|
|
||||||
}
|
|
||||||
if *index < data.len() {
|
if *index < data.len() {
|
||||||
result |= data[*index] as u32;
|
result |= data[*index] as u32;
|
||||||
*index += 1;
|
*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
|
result
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ main>canvas#game {
|
|||||||
display: block;
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex-grow: 1;
|
height: 100%;
|
||||||
|
|
||||||
background-color: #101010;
|
background-color: #101010;
|
||||||
}
|
}
|
||||||
|
358
www/js/game.js
358
www/js/game.js
@ -1,91 +1,254 @@
|
|||||||
const GAME_CONST = {
|
const GAME = { };
|
||||||
PLAYER_DAWN: 0,
|
let GAME_DATA = null;
|
||||||
PLAYER_DUSK: 1,
|
|
||||||
|
|
||||||
SOURCE_BOARD: 0,
|
GAME.Board = class {
|
||||||
SOURCE_POOL: 1,
|
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 = {
|
GAME.Player = 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 {
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.handle = "";
|
this.handle = "";
|
||||||
this.pool = new GAME_CLASS.Pool();
|
this.pool = new GAME.Pool();
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
Pool: class {
|
GAME.Pool = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.pieces = [ ]; for(let i = 0; i < 6; ++i) { this.pieces.push(0); }
|
this.pieces = [ ]; for(let i = 0; i < 6; ++i) { this.pieces.push(0); }
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
Tile: class {
|
GAME.Tile = class {
|
||||||
constructor() {
|
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) {
|
constructor(source, from, to) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.to = to;
|
this.to = to;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
|
|
||||||
GamePiece: class {
|
GAME.GamePiece = class {
|
||||||
constructor(name, assets, moves, promote_moves) {
|
constructor(name, assets, moves, promote_moves) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.assets = assets;
|
this.assets = assets;
|
||||||
this.moves = moves;
|
this.moves = moves;
|
||||||
this.pmoves = promote_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) {
|
constructor(piece, player) {
|
||||||
this.piece = piece;
|
this.piece = piece;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
this.promoted = false;
|
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 {
|
Source: {
|
||||||
constructor() {
|
Board: 0,
|
||||||
this.board = new GAME_CLASS.Board();
|
Pool: 1,
|
||||||
this.pieces = [
|
},
|
||||||
new GAME_CLASS.GamePiece("Militia",
|
|
||||||
|
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"],
|
["asset/militia_dusk.svg", "asset/militia_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(5),
|
.add(5),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(2)
|
.add(2)
|
||||||
.add(4)
|
.add(4)
|
||||||
.add(5)
|
.add(5),
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Knight",
|
new GAME.GamePiece(
|
||||||
|
"Knight",
|
||||||
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(3)
|
.add(3)
|
||||||
.add(6)
|
.add(6)
|
||||||
.add(11)
|
.add(11)
|
||||||
.add(13)
|
.add(13)
|
||||||
.add(17),
|
.add(17),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
|
||||||
.add(3)
|
.add(3)
|
||||||
.add(6)
|
.add(6)
|
||||||
.add(7)
|
.add(7)
|
||||||
@ -94,34 +257,34 @@ const GAME_CLASS = {
|
|||||||
.add(13)
|
.add(13)
|
||||||
.add(14)
|
.add(14)
|
||||||
.add(16)
|
.add(16)
|
||||||
.add(17)
|
.add(17),
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Lance",
|
new GAME.GamePiece(
|
||||||
|
"Lance",
|
||||||
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(0, true)
|
.add_stride(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(5),
|
.add(5),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
.add_stride(0)
|
||||||
.add(0, true)
|
.add_stride(1)
|
||||||
.add(1, true)
|
.add_stride(2)
|
||||||
.add(2, true)
|
.add_stride(3)
|
||||||
.add(3, true)
|
.add_stride(4)
|
||||||
.add(4, true)
|
.add_stride(5),
|
||||||
.add(5, true)
|
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Tower",
|
new GAME.GamePiece(
|
||||||
|
"Tower",
|
||||||
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(3)
|
.add(3)
|
||||||
.add(5)
|
.add(5)
|
||||||
.add(6)
|
.add(6)
|
||||||
.add(11),
|
.add(11),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(2)
|
.add(2)
|
||||||
@ -131,11 +294,12 @@ const GAME_CLASS = {
|
|||||||
.add(6)
|
.add(6)
|
||||||
.add(8)
|
.add(8)
|
||||||
.add(9)
|
.add(9)
|
||||||
.add(11)
|
.add(11),
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Castle",
|
new GAME.GamePiece(
|
||||||
|
"Castle",
|
||||||
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(2)
|
.add(2)
|
||||||
@ -143,44 +307,44 @@ const GAME_CLASS = {
|
|||||||
.add(5)
|
.add(5)
|
||||||
.add(7)
|
.add(7)
|
||||||
.add(10),
|
.add(10),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(2)
|
.add(2)
|
||||||
.add(3)
|
.add(3)
|
||||||
.add(4)
|
.add(4)
|
||||||
.add(5)
|
.add(5)
|
||||||
.add(7, true)
|
.add_stride(7)
|
||||||
.add(10, true)
|
.add_stride(10),
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Dragon",
|
new GAME.GamePiece(
|
||||||
|
"Dragon",
|
||||||
["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"],
|
["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(6, true)
|
.add_stride(6)
|
||||||
.add(7, true)
|
.add_stride(7)
|
||||||
.add(8, true)
|
.add_stride(8)
|
||||||
.add(9, true)
|
.add_stride(9)
|
||||||
.add(10, true)
|
.add_stride(10)
|
||||||
.add(11, true),
|
.add_stride(11),
|
||||||
|
new GAME.PieceMovement()
|
||||||
new Move()
|
.add_stride(0)
|
||||||
.add(0, true)
|
.add_stride(1)
|
||||||
.add(1, true)
|
.add_stride(2)
|
||||||
.add(2, true)
|
.add_stride(3)
|
||||||
.add(3, true)
|
.add_stride(4)
|
||||||
.add(4, true)
|
.add_stride(5)
|
||||||
.add(5, true)
|
.add_stride(6)
|
||||||
.add(6, true)
|
.add_stride(7)
|
||||||
.add(7, true)
|
.add_stride(8)
|
||||||
.add(8, true)
|
.add_stride(9)
|
||||||
.add(9, true)
|
.add_stride(10)
|
||||||
.add(10, true)
|
.add_stride(11),
|
||||||
.add(11, true)
|
|
||||||
),
|
),
|
||||||
new GAME_CLASS.GamePiece("Omen",
|
new GAME.GamePiece(
|
||||||
|
"Omen",
|
||||||
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
||||||
new Move()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
.add(2)
|
.add(2)
|
||||||
@ -188,25 +352,11 @@ const GAME_CLASS = {
|
|||||||
.add(4)
|
.add(4)
|
||||||
.add(5)
|
.add(5)
|
||||||
.add(7)
|
.add(7)
|
||||||
.add(10)
|
.add(10),
|
||||||
),
|
),
|
||||||
];
|
],
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let GAME_DATA = null;
|
GAME.init = () => {
|
||||||
|
GAME_DATA = new GAME.Game();
|
||||||
const GAME = {
|
|
||||||
init() {
|
|
||||||
GAME_DATA = new GAME_CLASS.Game();
|
|
||||||
},
|
|
||||||
|
|
||||||
process(move) {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
validate(move) {
|
|
||||||
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,32 @@
|
|||||||
let INTERFACE_DATA = {
|
let INTERFACE_DATA = null;
|
||||||
canvas:null,
|
|
||||||
context:null,
|
|
||||||
|
|
||||||
scale:1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const INTERFACE = {
|
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) {
|
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() {
|
draw() {
|
||||||
this.resize();
|
let canvas = INTERFACE_DATA.canvas;
|
||||||
|
let ctx = INTERFACE_DATA.context;
|
||||||
|
|
||||||
// Determine
|
// Determine interface configuration
|
||||||
let width = INTERFACE_DATA.canvas.width;
|
canvas.width = canvas.clientWidth;
|
||||||
let height = INTERFACE_DATA.canvas.height;
|
canvas.height = canvas.clientHeight;
|
||||||
let min_dimension = Math.min(width, height);
|
|
||||||
|
|
||||||
let scale = 1;
|
let width = canvas.width;
|
||||||
//let margin = INTERFACE_DATA.canvas.
|
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
|
// Draw tiles
|
||||||
for(let i = 0; i < GAME.board.tiles.length; ++i) {
|
let radius = INTERFACE.Radius * gui_scale;
|
||||||
// Draw background
|
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 piece
|
||||||
|
|
||||||
// Draw
|
} else {
|
||||||
|
// Draw standard border
|
||||||
|
ctx.strokeStyle = INTERFACE.Color.TileBorder;
|
||||||
|
ctx.beginPath();
|
||||||
|
draw.hex();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Draw player pool
|
// 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
|
// 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();
|
GAME.init();
|
||||||
|
|
||||||
INTERFACE_DATA.canvas = document.getElementById("game");
|
INTERFACE_DATA = {
|
||||||
let canvas = INTERFACE_DATA.canvas;
|
canvas: document.getElementById("game"),
|
||||||
|
context: null,
|
||||||
|
|
||||||
|
player_id: player_id,
|
||||||
|
|
||||||
|
message: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
let canvas = INTERFACE_DATA.canvas;
|
||||||
if(canvas !== undefined) {
|
if(canvas !== undefined) {
|
||||||
INTERFACE_DATA.context = canvas.getContext("2d");
|
INTERFACE_DATA.context = canvas.getContext("2d");
|
||||||
|
|
||||||
canvas.addEventListener("mousemove", INTERFACE.hover);
|
canvas.addEventListener("mousemove", INTERFACE.hover);
|
||||||
canvas.addEventListener("mousedown", INTERFACE.click);
|
canvas.addEventListener("mousedown", INTERFACE.click);
|
||||||
canvas.addEventListener("resize", INTERFACE.draw);
|
window.addEventListener("resize", INTERFACE.draw);
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
uninit() {
|
uninit() {
|
||||||
INTERFACE_DATA.canvas = null;
|
MAIN.removeChild(INTERFACE_DATA.canvas);
|
||||||
INTERFACE_DATA.context = null;
|
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;
|
return true;
|
||||||
},
|
},
|
||||||
unload() {
|
unload() {
|
||||||
|
INTERFACE.uninit();
|
||||||
MESSAGE_COMPOSE([
|
MESSAGE_COMPOSE([
|
||||||
PACK.u16(OpCode.SessionLeave),
|
PACK.u16(OpCode.SessionLeave),
|
||||||
]);
|
]);
|
||||||
|
@ -125,17 +125,49 @@ const BITWISE = {
|
|||||||
mask = mask - ((mask >> 1) & 0x55555555);
|
mask = mask - ((mask >> 1) & 0x55555555);
|
||||||
mask = (mask & 0x33333333) + ((mask >> 2) & 0x33333333);
|
mask = (mask & 0x33333333) + ((mask >> 2) & 0x33333333);
|
||||||
return ((mask + (mask >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
|
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 = {
|
const MATH = {
|
||||||
sign(a)
|
Vec2: class {
|
||||||
{
|
constructor(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
sign(a) {
|
||||||
return 1 - ((a < 0) << 1);
|
return 1 - ((a < 0) << 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
mod(a, b)
|
mod(a, b) {
|
||||||
{
|
|
||||||
return ((a % b) + b) % 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