Implement game mechanics.
This commit is contained in:
parent
e3fd0c703a
commit
adf3cdef10
@ -4,6 +4,4 @@ version = "0.1.0"
|
|||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2.92"
|
pool = { git = "https://git.tsukiyo.org/Utility/pool" }
|
||||||
web-sys = "0.3.69"
|
|
||||||
js-sys = "0.3.69"
|
|
||||||
|
@ -1,31 +1,104 @@
|
|||||||
use crate::piece::{Piece, PIECE_NONE};
|
use crate::{
|
||||||
|
consts::*,
|
||||||
|
piece::*,
|
||||||
|
util::Hex,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Column {
|
||||||
|
pub militia:[bool; 2],
|
||||||
|
pub extent:[u8; 2],
|
||||||
|
}
|
||||||
|
impl Column {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
militia:[false; 2],
|
||||||
|
extent:[0, 8],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Tile {
|
pub struct Tile {
|
||||||
piece:u8,
|
pub piece:Option<u8>,
|
||||||
|
pub threat:[bool; 2],
|
||||||
}
|
}
|
||||||
impl Tile {
|
impl Tile {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
piece:PIECE_NONE,
|
piece:None,
|
||||||
|
threat:[false; 2],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone)]
|
||||||
pub struct Board {
|
pub struct Board {
|
||||||
tiles:[Tile; 61],
|
pub tiles:[Tile; 61],
|
||||||
pieces:[Piece; 38],
|
pub columns:[Column; 9],
|
||||||
pool:[[usize; 6]; 2],
|
pub pieces:[Option<Piece>; 38],
|
||||||
}
|
}
|
||||||
impl Board {
|
impl Board {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
tiles:[Tile::new(); 61],
|
tiles:[Tile::new(); 61],
|
||||||
pieces:[Piece::new(); 38],
|
columns:[Column::new(); 9],
|
||||||
pool:[[0; 6]; 2],
|
pieces:[None; 38],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
let layout = [
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(0, 1)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(1, 1)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(2, 2)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(3, 2)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(4, 3)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(5, 3)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(6, 4)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(7, 4)),
|
||||||
|
(Piece::new(PIECE_MILITIA, PLAYER_DAWN), Hex::from_hex(8, 5)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_LANCE, PLAYER_DAWN), Hex::from_hex(0, 0)),
|
||||||
|
(Piece::new(PIECE_LANCE, PLAYER_DAWN), Hex::from_hex(8, 4)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_KNIGHT, PLAYER_DAWN), Hex::from_hex(1, 0)),
|
||||||
|
(Piece::new(PIECE_KNIGHT, PLAYER_DAWN), Hex::from_hex(7, 3)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_CASTLE, PLAYER_DAWN), Hex::from_hex(2, 0)),
|
||||||
|
(Piece::new(PIECE_CASTLE, PLAYER_DAWN), Hex::from_hex(6, 2)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_TOWER, PLAYER_DAWN), Hex::from_hex(3, 0)),
|
||||||
|
(Piece::new(PIECE_TOWER, PLAYER_DAWN), Hex::from_hex(5, 1)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_DRAGON, PLAYER_DAWN), Hex::from_hex(4, 1)),
|
||||||
|
|
||||||
|
(Piece::new(PIECE_OMEN, PLAYER_DAWN), Hex::from_hex(4, 0)),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (piece, hex) in &layout {
|
||||||
|
self.set_piece(piece.clone(), hex.tile());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (piece, hex) in &layout {
|
||||||
|
let mut piece = piece.clone();
|
||||||
|
piece.player = 1;
|
||||||
|
let hex = Hex::from_hex(8 - hex.x(), 8 - hex.y());
|
||||||
|
self.set_piece(piece, hex.tile());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_piece(&mut self, mut piece:Piece, tile:u8) -> u8
|
||||||
|
{
|
||||||
|
let mut index = 0;
|
||||||
|
while self.pieces[index as usize].is_some() { index += 1; }
|
||||||
|
|
||||||
|
piece.tile = tile;
|
||||||
|
self.tiles[tile as usize].piece = Some(index);
|
||||||
|
self.pieces[index as usize] = Some(piece);
|
||||||
|
index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
12
game/src/consts.rs
Normal file
12
game/src/consts.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
pub const PLAYER_DAWN :u8 = 0;
|
||||||
|
pub const PLAYER_DUSK :u8 = 1;
|
||||||
|
|
||||||
|
pub const PIECES_COUNT :usize = 7;
|
||||||
|
pub const PIECE_NONE :u8 = PIECES_COUNT as u8 + 1;
|
||||||
|
pub const PIECE_MILITIA :u8 = 0;
|
||||||
|
pub const PIECE_KNIGHT :u8 = 1;
|
||||||
|
pub const PIECE_LANCE :u8 = 2;
|
||||||
|
pub const PIECE_TOWER :u8 = 3;
|
||||||
|
pub const PIECE_CASTLE :u8 = 4;
|
||||||
|
pub const PIECE_DRAGON :u8 = 5;
|
||||||
|
pub const PIECE_OMEN :u8 = 6;
|
@ -1,4 +1,9 @@
|
|||||||
use crate::board::Board;
|
use crate::{
|
||||||
|
//consts::*,
|
||||||
|
board::Board,
|
||||||
|
history::Play,
|
||||||
|
piece::Piece,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Clone, Copy, PartialEq)]
|
||||||
pub enum GameState {
|
pub enum GameState {
|
||||||
@ -9,15 +14,98 @@ pub enum GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Game {
|
pub struct Game {
|
||||||
|
pub turn:u16,
|
||||||
pub state:GameState,
|
pub state:GameState,
|
||||||
pub board:Board,
|
pub board:Board,
|
||||||
|
pub pool:[[u8; 6]; 2],
|
||||||
|
|
||||||
|
pub history:Vec<Play>,
|
||||||
}
|
}
|
||||||
impl Game {
|
impl Game {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
state:GameState::Joinable,
|
state:GameState::Joinable,
|
||||||
|
|
||||||
|
turn:0,
|
||||||
|
|
||||||
board:Board::new(),
|
board:Board::new(),
|
||||||
|
pool:[[0; 6]; 2],
|
||||||
|
|
||||||
|
history:Vec::new(),
|
||||||
|
}.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn init(mut self) -> Self
|
||||||
|
{
|
||||||
|
self.board.init();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process(&mut self, play:Play) -> Result<(),()>
|
||||||
|
{
|
||||||
|
let player = (self.turn & 1) as u8;
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO:
|
||||||
|
// - Check for piece promotion.
|
||||||
|
// - Validate plays against move sets and specific rules.
|
||||||
|
// - Determine game state (check, checkmate).
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// Move piece on board.
|
||||||
|
if if play.source == 0 {
|
||||||
|
if let Some(pid) = self.board.tiles[play.from as usize].piece {
|
||||||
|
if let Some(mut piece) = self.board.pieces[pid as usize] {
|
||||||
|
let mut swap = false;
|
||||||
|
|
||||||
|
if let Some(tid) = self.board.tiles[play.to as usize].piece {
|
||||||
|
if let Some(target) = &mut self.board.pieces[tid as usize] {
|
||||||
|
|
||||||
|
// Check for piece swap.
|
||||||
|
if piece.player == target.player {
|
||||||
|
println!("swap");
|
||||||
|
swap = true;
|
||||||
|
target.tile = play.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add captured piece to pool.
|
||||||
|
else {
|
||||||
|
self.pool[piece.player as usize][target.class as usize] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy piece if captured.
|
||||||
|
if !swap { self.board.pieces[tid as usize] = None; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if swap {
|
||||||
|
self.board.tiles[play.from as usize].piece = self.board.tiles[play.to as usize].piece;
|
||||||
|
} else {
|
||||||
|
self.board.tiles[play.from as usize].piece = None;
|
||||||
|
}
|
||||||
|
self.board.tiles[play.to as usize].piece = Some(pid);
|
||||||
|
piece.tile = play.to;
|
||||||
|
|
||||||
|
self.board.pieces[pid as usize] = Some(piece);
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
} else { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place piece from pool.
|
||||||
|
else {
|
||||||
|
if self.pool[player as usize][play.from as usize] > 0 && self.board.tiles[play.to as usize].piece.is_none() {
|
||||||
|
self.pool[player as usize][play.from as usize] -= 1;
|
||||||
|
let piece = Piece::new(play.from, player);
|
||||||
|
self.board.set_piece(piece, play.to);
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
|
} {
|
||||||
|
self.turn += 1;
|
||||||
|
Ok(())
|
||||||
|
} else { Err(()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,16 @@
|
|||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum PlayType {
|
|
||||||
Move = 0,
|
|
||||||
Place = 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum PlayState {
|
|
||||||
Normal = 0,
|
|
||||||
Check = 1,
|
|
||||||
Checkmate = 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Play {
|
pub struct Play {
|
||||||
form:PlayType,
|
pub source:u8,
|
||||||
from:u8,
|
pub from:u8,
|
||||||
to:u8,
|
pub to:u8,
|
||||||
piece:u8,
|
}
|
||||||
capture:u8,
|
impl Play {
|
||||||
state:PlayState,
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
source:0,
|
||||||
|
from:0,
|
||||||
|
to:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
pub mod consts;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod piece;
|
pub mod piece;
|
||||||
pub mod board;
|
pub mod board;
|
||||||
|
@ -1,119 +1,135 @@
|
|||||||
use crate::util::bit;
|
use crate::{
|
||||||
|
consts::*,
|
||||||
|
util::bit,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct MoveSet {
|
pub struct MoveSet {
|
||||||
direction:u32,
|
pub direction:u32,
|
||||||
distance:u32,
|
pub stride:u32,
|
||||||
|
}
|
||||||
|
impl MoveSet {
|
||||||
|
pub fn rotate(&self) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
direction:0,
|
||||||
|
stride:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct PieceClass {
|
pub struct PieceClass {
|
||||||
name:&'static str,
|
pub name:&'static str,
|
||||||
moves:MoveSet,
|
pub moves:MoveSet,
|
||||||
pmoves:MoveSet,
|
pub pmoves:MoveSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const PIECES_COUNT :usize = 7;
|
|
||||||
pub const PIECE_NONE :u8 = PIECES_COUNT as u8 + 1;
|
|
||||||
pub const PIECE_MILITIA :u8 = 0;
|
|
||||||
pub const PIECE_KNIGHT :u8 = 1;
|
|
||||||
pub const PIECE_LANCE :u8 = 2;
|
|
||||||
pub const PIECE_TOWER :u8 = 3;
|
|
||||||
pub const PIECE_CASTLE :u8 = 4;
|
|
||||||
pub const PIECE_DRAGON :u8 = 5;
|
|
||||||
pub const PIECE_KING :u8 = 6;
|
|
||||||
|
|
||||||
pub const PIECES :[PieceClass; PIECES_COUNT] = [
|
pub const PIECES :[PieceClass; PIECES_COUNT] = [
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Militia",
|
name: "Militia",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:bit(0) | bit(1) | bit(5),
|
direction:bit(0) | bit(1) | bit(5),
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet{
|
pmoves: MoveSet{
|
||||||
direction:bit(0) | bit(1) | bit(2) | bit(4) | bit(5),
|
direction:bit(0) | bit(1) | bit(2) | bit(4) | bit(5),
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Knight",
|
name: "Knight",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:bit(3) | bit(6) | bit(11) | bit(13) | bit(17),
|
direction:bit(3) | bit(6) | bit(11) | bit(13) | bit(17),
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet{
|
pmoves: MoveSet{
|
||||||
direction:bit(3) | bit(6) | bit(7) | bit(10) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17),
|
direction:bit(3) | bit(6) | bit(7) | bit(10) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17),
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Lance",
|
name: "Lance",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet {
|
pmoves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Tower",
|
name: "Tower",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet {
|
pmoves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Castle",
|
name: "Castle",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet {
|
pmoves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "Dragon",
|
name: "Dragon",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet {
|
pmoves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PieceClass {
|
PieceClass {
|
||||||
name: "King",
|
name: "King",
|
||||||
moves: MoveSet {
|
moves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
pmoves: MoveSet {
|
pmoves: MoveSet {
|
||||||
direction:0,
|
direction:0,
|
||||||
distance:0,
|
stride:0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Piece {
|
pub struct Piece {
|
||||||
class:u8,
|
pub class:u8,
|
||||||
promoted:bool,
|
pub promoted:bool,
|
||||||
|
pub player:u8,
|
||||||
|
pub tile:u8,
|
||||||
}
|
}
|
||||||
impl Piece {
|
impl Piece {
|
||||||
pub fn new() -> Self
|
pub fn new(class:u8, player:u8) -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
class:PIECE_NONE,
|
class,
|
||||||
promoted:false,
|
promoted:false,
|
||||||
|
player,
|
||||||
|
tile:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_at(class:u8, player:u8, tile:u8) -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
class,
|
||||||
|
promoted:false,
|
||||||
|
player,
|
||||||
|
tile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
47
game/src/util/hex.rs
Normal file
47
game/src/util/hex.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const ROWS :[u8; 10] = [ 0, 5, 11, 18, 26, 35, 43, 50, 56, 61 ];
|
||||||
|
|
||||||
|
pub struct Hex {
|
||||||
|
x:u8,
|
||||||
|
y:u8,
|
||||||
|
tile:u8,
|
||||||
|
}
|
||||||
|
impl Hex {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
x:0,
|
||||||
|
y:0,
|
||||||
|
tile:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_hex(x:u8, y:u8) -> Self
|
||||||
|
{
|
||||||
|
let x = x as i32;
|
||||||
|
let y = y as i32;
|
||||||
|
let a = ((x + 4) * (x + 5) / 2) - 10;
|
||||||
|
let b = (x > 4) as i32 * ((x - 4) + ((x - 5) * (x - 4)));
|
||||||
|
let tile = a - b + y;
|
||||||
|
Self {
|
||||||
|
x:x as u8,
|
||||||
|
y:y as u8,
|
||||||
|
tile:tile as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_tile(tile:u8) -> Self
|
||||||
|
{
|
||||||
|
let mut x = 0;
|
||||||
|
while tile >= ROWS[x as usize + 1] { x += 1; }
|
||||||
|
let y = tile - ROWS[x as usize] + ((x > 4) as u8 * (x - 4));
|
||||||
|
Self {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
tile,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn x(&self) -> u8 { self.x }
|
||||||
|
pub fn y(&self) -> u8 { self.y }
|
||||||
|
pub fn tile(&self) -> u8 { self.tile }
|
||||||
|
}
|
@ -1 +1,2 @@
|
|||||||
mod binary; pub use binary::*;
|
mod binary; pub use binary::*;
|
||||||
|
mod hex; pub use hex::*;
|
||||||
|
@ -136,8 +136,9 @@ Res_GameState {
|
|||||||
|
|
||||||
# 0031 GAME_PLAY
|
# 0031 GAME_PLAY
|
||||||
Req_GamePlay {
|
Req_GamePlay {
|
||||||
token :block<8> # session token
|
turn :block<2>
|
||||||
}
|
move :block<2>
|
||||||
Res_GamePlay {
|
# from : 0[6]
|
||||||
|
# to : 6[6]
|
||||||
}
|
}
|
||||||
|
Res_GamePlay = Req_GamePlay
|
||||||
|
@ -5,7 +5,10 @@ use hyper::upgrade::Upgraded;
|
|||||||
use hyper_util::rt::TokioIo;
|
use hyper_util::rt::TokioIo;
|
||||||
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
|
use tokio_tungstenite::{tungstenite::Message, WebSocketStream};
|
||||||
|
|
||||||
use crate::app::authentication::AuthToken;
|
use crate::app::{
|
||||||
|
authentication::AuthToken,
|
||||||
|
session::SessionToken,
|
||||||
|
};
|
||||||
|
|
||||||
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
|
type StreamType = Arc<RwLock<SplitSink<WebSocketStream<TokioIo<Upgraded>>, Message>>>;
|
||||||
|
|
||||||
@ -13,4 +16,5 @@ pub struct Connection {
|
|||||||
pub bus:u32,
|
pub bus:u32,
|
||||||
pub stream:StreamType,
|
pub stream:StreamType,
|
||||||
pub auth:Option<AuthToken>,
|
pub auth:Option<AuthToken>,
|
||||||
|
pub session:Option<SessionToken>,
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ use trie::Trie;
|
|||||||
use crate::{
|
use crate::{
|
||||||
system::filesystem::FileSystem,
|
system::filesystem::FileSystem,
|
||||||
util::Chain,
|
util::Chain,
|
||||||
|
protocol::QRPacket,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod connection; use connection::Connection;
|
pub mod connection; use connection::Connection;
|
||||||
@ -104,4 +105,84 @@ impl App {
|
|||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_user_by_id(&self, id:u32) -> Option<&User>
|
||||||
|
{
|
||||||
|
if let Some(uid) = self.user_id.get(id as isize) {
|
||||||
|
self.users.get(*uid)
|
||||||
|
} else { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_response(&mut self, response:QRPacket)
|
||||||
|
{
|
||||||
|
use crate::protocol::*;
|
||||||
|
use tokio_tungstenite::tungstenite::Message;
|
||||||
|
use futures::SinkExt;
|
||||||
|
|
||||||
|
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
crate::util::pack::pack_u16(code),
|
||||||
|
data,
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
if match response.data { QRPacketData::None => false, _ => true } {
|
||||||
|
if let Some(conn) = self.connections.get_mut(response.id as usize) {
|
||||||
|
let mut socket = conn.stream.write().await;
|
||||||
|
|
||||||
|
match response.data {
|
||||||
|
QRPacketData::RRegister(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_REGISTER, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RAuth(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_AUTH, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RAuthResume(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_AUTH_RESUME, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RSessionList(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_SESSION_LIST, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RSessionCreate(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_SESSION_CREATE, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RSessionJoin(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_SESSION_JOIN, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::RGameState(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_GAME_STATE, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
QRPacketData::QGamePlay(response) => {
|
||||||
|
socket.send(Message::Binary(
|
||||||
|
encode_response(CODE_GAME_PLAY, response.encode())
|
||||||
|
)).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ use game::Game;
|
|||||||
pub type SessionToken = [u8; 8];
|
pub type SessionToken = [u8; 8];
|
||||||
pub type SessionSecret = [u8; 8];
|
pub type SessionSecret = [u8; 8];
|
||||||
|
|
||||||
pub struct Viewer {
|
pub struct Player {
|
||||||
pub connection:Option<u32>,
|
|
||||||
pub user:Option<u32>,
|
pub user:Option<u32>,
|
||||||
|
pub connections:Vec<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
@ -15,10 +15,36 @@ pub struct Session {
|
|||||||
|
|
||||||
pub game:Game,
|
pub game:Game,
|
||||||
|
|
||||||
pub p_dawn:Viewer,
|
pub p_dawn:Player,
|
||||||
pub p_dusk:Viewer,
|
pub p_dusk:Player,
|
||||||
pub viewers:Vec<Viewer>,
|
pub connections:Vec<u32>,
|
||||||
|
|
||||||
pub time:u64,
|
pub time:u64,
|
||||||
pub chain_id:usize,
|
pub chain_id:usize,
|
||||||
}
|
}
|
||||||
|
impl Session {
|
||||||
|
pub fn get_connections(&self) -> Vec<u32>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
self.p_dawn.connections.clone(),
|
||||||
|
self.p_dusk.connections.clone(),
|
||||||
|
self.connections.clone(),
|
||||||
|
].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_connection(&mut self, source:u8, id:u32)
|
||||||
|
{
|
||||||
|
let connections = match source {
|
||||||
|
0 => &mut self.p_dawn.connections,
|
||||||
|
1 => &mut self.p_dusk.connections,
|
||||||
|
_ => &mut self.connections,
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 0..connections.len() {
|
||||||
|
if connections[i] == id {
|
||||||
|
connections.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -28,66 +28,6 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
|
|
||||||
println!("Serving: {}", request.uri().path());
|
println!("Serving: {}", request.uri().path());
|
||||||
|
|
||||||
match request.uri().path() {
|
|
||||||
"/.css" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "text/css")
|
|
||||||
.header(CACHE_CONTROL, "no-cache")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.css()))).unwrap()),
|
|
||||||
|
|
||||||
"/.js" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "text/javascript")
|
|
||||||
.header(CACHE_CONTROL, "no-cache")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.js()))).unwrap()),
|
|
||||||
|
|
||||||
"/favicon.png" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/png")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.favicon()))).unwrap()),
|
|
||||||
|
|
||||||
// Assets
|
|
||||||
"/asset/omen_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 6)))).unwrap()),
|
|
||||||
"/asset/dragon_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 5)))).unwrap()),
|
|
||||||
"/asset/castle_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 4)))).unwrap()),
|
|
||||||
"/asset/tower_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 3)))).unwrap()),
|
|
||||||
"/asset/lance_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 2)))).unwrap()),
|
|
||||||
"/asset/knight_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 1)))).unwrap()),
|
|
||||||
"/asset/militia_dawn.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(0, 0)))).unwrap()),
|
|
||||||
"/asset/omen_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 6)))).unwrap()),
|
|
||||||
"/asset/dragon_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 5)))).unwrap()),
|
|
||||||
"/asset/castle_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 4)))).unwrap()),
|
|
||||||
"/asset/tower_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 3)))).unwrap()),
|
|
||||||
"/asset/lance_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 2)))).unwrap()),
|
|
||||||
"/asset/knight_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 1)))).unwrap()),
|
|
||||||
"/asset/militia_dusk.svg" => Ok(Response::builder()
|
|
||||||
.header(CONTENT_TYPE, "image/svg")
|
|
||||||
.body(Full::new(Bytes::from(args.cache.asset(1, 0)))).unwrap()),
|
|
||||||
|
|
||||||
_ => {
|
|
||||||
if hyper_tungstenite::is_upgrade_request(&request) {
|
if hyper_tungstenite::is_upgrade_request(&request) {
|
||||||
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
if let Ok((response, websocket)) = hyper_tungstenite::upgrade(&mut request, None) {
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
@ -105,10 +45,20 @@ async fn service_http(mut request:hyper::Request<hyper::body::Incoming>, args:Ht
|
|||||||
.unwrap())
|
.unwrap())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(Response::builder()
|
match request.uri().path() {
|
||||||
|
"/" => Ok(Response::builder()
|
||||||
.header(CONTENT_TYPE, "text/html")
|
.header(CONTENT_TYPE, "text/html")
|
||||||
.header(CACHE_CONTROL, "no-cache")
|
.header(CACHE_CONTROL, "no-cache")
|
||||||
.body(Full::new(Bytes::from(args.cache.html()))).unwrap())
|
.body(Full::new(Bytes::from(args.cache.fetch("/.html").unwrap().data))).unwrap()),
|
||||||
|
|
||||||
|
_ => match args.cache.fetch(request.uri().path()) {
|
||||||
|
Some(data) => Ok(Response::builder()
|
||||||
|
.header(CONTENT_TYPE, &data.mime)
|
||||||
|
.header(CACHE_CONTROL, "no-cache")
|
||||||
|
.body(Full::new(Bytes::from(data.data))).unwrap()),
|
||||||
|
None => Ok(Response::builder()
|
||||||
|
.status(404)
|
||||||
|
.body(Full::new(Bytes::new())).unwrap())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -170,7 +120,53 @@ async fn main()
|
|||||||
// Initialize HTTPS service.
|
// Initialize HTTPS service.
|
||||||
match b_main.connect() {
|
match b_main.connect() {
|
||||||
Ok(bus) => {
|
Ok(bus) => {
|
||||||
let cache = WebCache::init();
|
let cache = WebCache::new();
|
||||||
|
cache.cache_file("text/html", "/.html", "www/.html").ok();
|
||||||
|
cache.cache_whitespace_minimize("/.html").ok();
|
||||||
|
cache.cache_file_group("text/css", "/.css", &[
|
||||||
|
"www/css/main.css",
|
||||||
|
"www/css/util.css",
|
||||||
|
"www/css/ui.css",
|
||||||
|
"www/css/form.css",
|
||||||
|
"www/css/game.css",
|
||||||
|
]).ok();
|
||||||
|
cache.cache_file_group("text/javascript", "/.js", &[
|
||||||
|
"www/js/const.js",
|
||||||
|
"www/js/util.js",
|
||||||
|
"www/js/game_asset.js",
|
||||||
|
"www/js/game.js",
|
||||||
|
"www/js/interface.js",
|
||||||
|
"www/js/ui.js",
|
||||||
|
"www/js/scene.js",
|
||||||
|
"www/js/system.js",
|
||||||
|
"www/js/main.js",
|
||||||
|
]).ok();
|
||||||
|
cache.cache_file("image/png", "/favicon.png", "www/asset/favicon.png").ok();
|
||||||
|
|
||||||
|
let asset_path = std::path::Path::new("www/asset");
|
||||||
|
for asset in [
|
||||||
|
"promote.svg",
|
||||||
|
|
||||||
|
"omen_dawn.svg",
|
||||||
|
"dragon_dawn.svg",
|
||||||
|
"castle_dawn.svg",
|
||||||
|
"tower_dawn.svg",
|
||||||
|
"lance_dawn.svg",
|
||||||
|
"knight_dawn.svg",
|
||||||
|
"militia_dawn.svg",
|
||||||
|
|
||||||
|
"omen_dusk.svg",
|
||||||
|
"dragon_dusk.svg",
|
||||||
|
"castle_dusk.svg",
|
||||||
|
"tower_dusk.svg",
|
||||||
|
"lance_dusk.svg",
|
||||||
|
"knight_dusk.svg",
|
||||||
|
"militia_dusk.svg",
|
||||||
|
] {
|
||||||
|
if cache.cache_file("image/svg+xml", &format!("/asset/{}", asset), asset_path.join(asset)).is_err() {
|
||||||
|
println!("error: failed to load: {}", asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut server = TlsServer::new();
|
let mut server = TlsServer::new();
|
||||||
if server.add_cert("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").await.is_ok() {
|
if server.add_cert("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").await.is_ok() {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
use tokio_tungstenite::tungstenite::Message;
|
|
||||||
use futures::SinkExt;
|
|
||||||
|
|
||||||
use bus::Bus;
|
use bus::Bus;
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
@ -10,19 +7,11 @@ use crate::{
|
|||||||
connection::Connection,
|
connection::Connection,
|
||||||
},
|
},
|
||||||
protocol,
|
protocol,
|
||||||
util::pack::pack_u16,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn encode_response(code:u16, data:Vec<u8>) -> Vec<u8>
|
|
||||||
{
|
|
||||||
[
|
|
||||||
pack_u16(code),
|
|
||||||
data,
|
|
||||||
].concat()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
||||||
{
|
{
|
||||||
|
use futures::SinkExt;
|
||||||
use protocol::*;
|
use protocol::*;
|
||||||
use ring::rand::{SecureRandom, SystemRandom};
|
use ring::rand::{SecureRandom, SystemRandom};
|
||||||
|
|
||||||
@ -34,7 +23,10 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
let qr = packet.data;
|
let qr = packet.data;
|
||||||
|
|
||||||
let mut user_id = None;
|
let mut user_id = None;
|
||||||
|
let mut session_id = None;
|
||||||
if let Some(conn) = app.connections.get(qr.id as usize) {
|
if let Some(conn) = app.connections.get(qr.id as usize) {
|
||||||
|
session_id = conn.session;
|
||||||
|
|
||||||
if let Some(auth_id) = conn.auth {
|
if let Some(auth_id) = conn.auth {
|
||||||
if let Some(auth) = app.auths.get(&auth_id) {
|
if let Some(auth) = app.auths.get(&auth_id) {
|
||||||
user_id = Some(auth.user);
|
user_id = Some(auth.user);
|
||||||
@ -48,6 +40,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
bus: request.bus_id,
|
bus: request.bus_id,
|
||||||
stream: request.stream,
|
stream: request.stream,
|
||||||
auth: None,
|
auth: None,
|
||||||
|
session: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
println!("Connect: {}", id);
|
println!("Connect: {}", id);
|
||||||
@ -60,9 +53,19 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
QRPacketData::QDisconn => {
|
QRPacketData::QDisconn => {
|
||||||
// Close socket and remove connection if valid
|
// Uninitialize connection
|
||||||
//
|
|
||||||
if if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
if if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
|
||||||
|
// Disassociate session if present
|
||||||
|
if let Some(session_token) = conn.session {
|
||||||
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
||||||
|
if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); }
|
||||||
|
else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); }
|
||||||
|
else { session.remove_connection(2, qr.id); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close socket
|
||||||
let mut socket = conn.stream.write().await;
|
let mut socket = conn.stream.write().await;
|
||||||
socket.close().await.ok();
|
socket.close().await.ok();
|
||||||
true
|
true
|
||||||
@ -279,25 +282,21 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
let mut valid = request.game_state == GameState::None || request.game_state == session.game.state;
|
let mut valid = request.game_state == GameState::None || request.game_state == session.game.state;
|
||||||
valid &= request.game_state != GameState::Joinable || session.p_dawn.user.is_none() || session.p_dusk.user.is_none();
|
valid &= request.game_state != GameState::Joinable || session.p_dawn.user.is_none() || session.p_dusk.user.is_none();
|
||||||
valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id;
|
valid &= !request.is_player || session.p_dawn.user == user_id || session.p_dusk.user == user_id;
|
||||||
valid &= !request.is_live || (session.p_dawn.connection.is_some() && session.p_dusk.connection.is_some());
|
valid &= !request.is_live || (session.p_dawn.connections.len() > 0 && session.p_dusk.connections.len() > 0);
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
let is_player = user_id.is_some() && (session.p_dawn.user == user_id || session.p_dusk.user == user_id);
|
let is_player = user_id.is_some() && (session.p_dawn.user == user_id || session.p_dusk.user == user_id);
|
||||||
|
|
||||||
let dawn_handle = if let Some(uid) = session.p_dawn.user {
|
let dawn_handle = if let Some(uid) = session.p_dawn.user {
|
||||||
if let Some(cuid) = app.user_id.get(uid as isize) {
|
if let Some(user) = app.get_user_by_id(uid) {
|
||||||
if let Some(user) = app.users.get(*cuid) {
|
|
||||||
user.handle.clone()
|
user.handle.clone()
|
||||||
} else { String::new() }
|
} else { String::new() }
|
||||||
} else { String::new() }
|
|
||||||
} else { String::new() };
|
} else { String::new() };
|
||||||
|
|
||||||
let dusk_handle = if let Some(uid) = session.p_dusk.user {
|
let dusk_handle = if let Some(uid) = session.p_dusk.user {
|
||||||
if let Some(cuid) = app.user_id.get(uid as isize) {
|
if let Some(user) = app.get_user_by_id(uid) {
|
||||||
if let Some(user) = app.users.get(*cuid) {
|
|
||||||
user.handle.clone()
|
user.handle.clone()
|
||||||
} else { String::new() }
|
} else { String::new() }
|
||||||
} else { String::new() }
|
|
||||||
} else { String::new() };
|
} else { String::new() };
|
||||||
|
|
||||||
response.records.push(PacketSessionListResponseRecord {
|
response.records.push(PacketSessionListResponseRecord {
|
||||||
@ -306,9 +305,9 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
dawn_handle,
|
dawn_handle,
|
||||||
dusk_handle,
|
dusk_handle,
|
||||||
],
|
],
|
||||||
turn:0,
|
turn:session.game.turn,
|
||||||
last_move:[0; 3],
|
last_move:[0; 3],
|
||||||
viewers:0,
|
viewers:session.connections.len() as u32,
|
||||||
player:is_player,
|
player:is_player,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -348,19 +347,23 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
token,
|
token,
|
||||||
secret,
|
secret,
|
||||||
game:game::Game::new(),
|
game:game::Game::new(),
|
||||||
p_dawn:Viewer {
|
p_dawn:Player {
|
||||||
connection:None,//Some(qr.id),
|
|
||||||
user:Some(uid),
|
user:Some(uid),
|
||||||
|
connections:vec![qr.id],
|
||||||
},
|
},
|
||||||
p_dusk:Viewer {
|
p_dusk:Player {
|
||||||
connection:None,
|
|
||||||
user:None,
|
user:None,
|
||||||
|
connections:Vec::new(),
|
||||||
},
|
},
|
||||||
viewers:Vec::new(),
|
connections:Vec::new(),
|
||||||
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
|
time:std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs() as u64,
|
||||||
chain_id,
|
chain_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
conn.session = Some(session.token);
|
||||||
|
}
|
||||||
|
|
||||||
session.id = app.filesystem.session_store(&session).unwrap();
|
session.id = app.filesystem.session_store(&session).unwrap();
|
||||||
|
|
||||||
app.sessions.set(&token, session);
|
app.sessions.set(&token, session);
|
||||||
@ -381,20 +384,21 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
|
|
||||||
let mut response = PacketSessionJoinResponse::new();
|
let mut response = PacketSessionJoinResponse::new();
|
||||||
response.status = STATUS_ERROR;
|
response.status = STATUS_ERROR;
|
||||||
|
response.token = request.token;
|
||||||
|
|
||||||
// Verify that session exists.
|
// Verify that session exists
|
||||||
if let Some(session) = app.sessions.get_mut(&request.token) {
|
if let Some(session) = app.sessions.get_mut(&request.token) {
|
||||||
|
|
||||||
// Join game as player.
|
// Join game as player
|
||||||
if request.join {
|
if if request.join {
|
||||||
|
|
||||||
// Verify client is authenticated.
|
// Verify client is authenticated
|
||||||
if let Some(uid) = user_id {
|
if let Some(uid) = user_id {
|
||||||
|
|
||||||
// User must not already be player.
|
// User must not already be player
|
||||||
if session.p_dawn.user != user_id && session.p_dusk.user != user_id {
|
if session.p_dawn.user != user_id && session.p_dusk.user != user_id {
|
||||||
|
|
||||||
// Add user to empty player slot.
|
// Add user to empty player slot
|
||||||
if if session.p_dawn.user.is_none() {
|
if if session.p_dawn.user.is_none() {
|
||||||
session.p_dawn.user = Some(uid);
|
session.p_dawn.user = Some(uid);
|
||||||
response.mode = 0;
|
response.mode = 0;
|
||||||
@ -404,7 +408,7 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
response.mode = 1;
|
response.mode = 1;
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// Session is not empty.
|
// Session is not empty
|
||||||
response.status = STATUS_ERROR;
|
response.status = STATUS_ERROR;
|
||||||
false
|
false
|
||||||
} {
|
} {
|
||||||
@ -412,20 +416,38 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
|
|
||||||
app.filesystem.session_update(session.id, session).ok();
|
app.filesystem.session_update(session.id, session).ok();
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
|
true
|
||||||
|
} else { false }
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
// Resume session for player connection
|
||||||
|
else {
|
||||||
println!("User resumes session.");
|
println!("User resumes session.");
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
response.mode = (session.p_dusk.user == user_id) as u8;
|
response.mode = (session.p_dusk.user == user_id) as u8;
|
||||||
|
true
|
||||||
}
|
}
|
||||||
} else { response.status = STATUS_NOAUTH; }
|
} else { response.status = STATUS_NOAUTH; false }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Join game as spectator.
|
// Join game as spectator.
|
||||||
else {
|
else {
|
||||||
println!("User spectates session.");
|
println!("User spectates session.");
|
||||||
|
|
||||||
response.status = STATUS_OK;
|
response.status = STATUS_OK;
|
||||||
response.mode = 2;
|
response.mode = 2;
|
||||||
|
true
|
||||||
|
} {
|
||||||
|
// Associate session and connection on join
|
||||||
|
if let Some(conn) = app.connections.get_mut(qr.id as usize) {
|
||||||
|
conn.session = Some(session.token);
|
||||||
|
}
|
||||||
|
match response.mode {
|
||||||
|
0 => session.p_dawn.connections.push(qr.id),
|
||||||
|
1 => session.p_dusk.connections.push(qr.id),
|
||||||
|
2 => session.connections.push(qr.id),
|
||||||
|
_ => { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,62 +455,131 @@ pub async fn thread_system(mut app:App, bus:Bus<protocol::QRPacket>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SessionRetire
|
// SessionRetire
|
||||||
|
QRPacketData::QSessionRetire(_request) => {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Not implemented
|
||||||
|
//
|
||||||
|
|
||||||
|
Some(QRPacket::new(0, QRPacketData::None))
|
||||||
|
}
|
||||||
|
|
||||||
// SessionLeave
|
// SessionLeave
|
||||||
|
QRPacketData::QSessionLeave => {
|
||||||
|
println!("Request: Session Leave");
|
||||||
|
|
||||||
|
// Verify that session exists.
|
||||||
|
if let Some(session_token) = session_id {
|
||||||
|
if let Some(session) = app.sessions.get_mut(&session_token) {
|
||||||
|
if user_id == session.p_dawn.user { session.remove_connection(0, qr.id); }
|
||||||
|
else if user_id == session.p_dusk.user { session.remove_connection(1, qr.id); }
|
||||||
|
else { session.remove_connection(2, qr.id); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(QRPacket::new(0, QRPacketData::None))
|
||||||
|
}
|
||||||
|
|
||||||
// GameState
|
// GameState
|
||||||
|
QRPacketData::QGameState(request) => {
|
||||||
|
let mut response = PacketGameStateResponse::new();
|
||||||
|
|
||||||
|
println!("Request: Game State");
|
||||||
|
|
||||||
|
if let Some(session) = app.sessions.get(&request.token) {
|
||||||
|
response.status = STATUS_OK;
|
||||||
|
response.turn = session.game.turn;
|
||||||
|
|
||||||
|
// Get Dawn handle
|
||||||
|
if let Some(id) = session.p_dawn.user {
|
||||||
|
if let Some(user) = app.get_user_by_id(id) {
|
||||||
|
response.dawn_handle = user.handle.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Dusk handle
|
||||||
|
if let Some(id) = session.p_dusk.user {
|
||||||
|
if let Some(user) = app.get_user_by_id(id) {
|
||||||
|
response.dusk_handle = user.handle.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get pool sizes
|
||||||
|
response.dawn_pool = session.game.pool[0];
|
||||||
|
response.dusk_pool = session.game.pool[1];
|
||||||
|
|
||||||
|
// Get list of pieces
|
||||||
|
for i in 0..session.game.board.pieces.len() {
|
||||||
|
response.pieces[i] = if let Some(piece) = &session.game.board.pieces[i] {
|
||||||
|
PacketGameStateResponsePiece {
|
||||||
|
valid:true,
|
||||||
|
piece:piece.class,
|
||||||
|
promoted:piece.promoted,
|
||||||
|
player:piece.player,
|
||||||
|
tile:piece.tile,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
PacketGameStateResponsePiece::new()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.status = STATUS_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::RGameState(response)))
|
||||||
|
}
|
||||||
|
|
||||||
// GamePlay
|
// GamePlay
|
||||||
|
QRPacketData::QGamePlay(mut request) => {
|
||||||
|
println!("Request: Game Play");
|
||||||
|
|
||||||
|
request.status = STATUS_ERROR;
|
||||||
|
let mut packets = Vec::<QRPacket>::new();
|
||||||
|
|
||||||
|
if let Some(sid) = session_id {
|
||||||
|
if let Some(session) = app.sessions.get_mut(&sid) {
|
||||||
|
if (user_id == session.p_dawn.user && session.game.turn & 1 == 0)
|
||||||
|
|| (user_id == session.p_dusk.user && session.game.turn & 1 == 1) {
|
||||||
|
|
||||||
|
// Check validation of play
|
||||||
|
if request.turn == session.game.turn {
|
||||||
|
|
||||||
|
// Update internal representation
|
||||||
|
if session.game.process(request.play).is_ok() {
|
||||||
|
request.status = STATUS_OK;
|
||||||
|
|
||||||
|
// Forward play to all clients
|
||||||
|
for cid in &session.get_connections() {
|
||||||
|
packets.push(QRPacket::new(
|
||||||
|
*cid,
|
||||||
|
QRPacketData::QGamePlay(request.clone())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.status != STATUS_ERROR {
|
||||||
|
for packet in packets {
|
||||||
|
app.send_response(packet).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates will have already been sent, so nothing is needed here.
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::None))
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Return error status.
|
||||||
|
Some(QRPacket::new(qr.id, QRPacketData::QGamePlay(request)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
_ => { Some(QRPacket::new(0, QRPacketData::None)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
} {
|
} {
|
||||||
if match response.data { QRPacketData::None => false, _ => true } {
|
app.send_response(response).await;
|
||||||
if let Some(conn) = app.connections.get_mut(response.id as usize) {
|
|
||||||
let mut socket = conn.stream.write().await;
|
|
||||||
|
|
||||||
match response.data {
|
|
||||||
QRPacketData::RRegister(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_REGISTER, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
QRPacketData::RAuth(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_AUTH, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
QRPacketData::RAuthResume(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_AUTH_RESUME, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
QRPacketData::RSessionList(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_SESSION_LIST, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
QRPacketData::RSessionCreate(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_SESSION_CREATE, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
QRPacketData::RSessionJoin(response) => {
|
|
||||||
socket.send(Message::Binary(
|
|
||||||
encode_response(CODE_SESSION_JOIN, response.encode())
|
|
||||||
)).await.ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,33 @@ pub async fn handle_ws(ws:WebSocketStream<TokioIo<Upgraded>>, args:HttpServiceAr
|
|||||||
Err(_) => { println!("error: packet decode failed."); }
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CODE_SESSION_LEAVE => {
|
||||||
|
args.bus.send(
|
||||||
|
bus_ds,
|
||||||
|
QRPacket::new(conn_id, QRPacketData::QSessionLeave)
|
||||||
|
).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_GAME_STATE => match PacketGameState::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
args.bus.send(
|
||||||
|
bus_ds,
|
||||||
|
QRPacket::new(conn_id, QRPacketData::QGameState(packet))
|
||||||
|
).ok();
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
|
}
|
||||||
|
|
||||||
|
CODE_GAME_PLAY => match PacketGamePlay::decode(&data, &mut index) {
|
||||||
|
Ok(packet) => {
|
||||||
|
args.bus.send(
|
||||||
|
bus_ds,
|
||||||
|
QRPacket::new(conn_id, QRPacketData::QGamePlay(packet))
|
||||||
|
).ok();
|
||||||
|
}
|
||||||
|
Err(_) => { println!("error: packet decode failed."); }
|
||||||
|
}
|
||||||
|
|
||||||
_ => { }
|
_ => { }
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -31,6 +31,15 @@ pub enum QRPacketData {
|
|||||||
|
|
||||||
QSessionJoin(PacketSessionJoin),
|
QSessionJoin(PacketSessionJoin),
|
||||||
RSessionJoin(PacketSessionJoinResponse),
|
RSessionJoin(PacketSessionJoinResponse),
|
||||||
|
|
||||||
|
QSessionRetire(PacketSessionRetire),
|
||||||
|
|
||||||
|
QSessionLeave,
|
||||||
|
|
||||||
|
QGameState(PacketGameState),
|
||||||
|
RGameState(PacketGameStateResponse),
|
||||||
|
|
||||||
|
QGamePlay(PacketGamePlay),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -1,18 +1,27 @@
|
|||||||
use crate::util::pack::{pack_u16, unpack_u16};
|
use crate::util::pack::{
|
||||||
|
pack_u16,
|
||||||
|
unpack_u16,
|
||||||
|
};
|
||||||
|
use game::{
|
||||||
|
history::Play,
|
||||||
|
util::mask,
|
||||||
|
};
|
||||||
|
|
||||||
use super::Packet;
|
use super::Packet;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PacketGamePlay {
|
pub struct PacketGamePlay {
|
||||||
pub handle:String,
|
pub status:u16,
|
||||||
pub secret:String,
|
pub turn:u16,
|
||||||
|
pub play:Play,
|
||||||
}
|
}
|
||||||
impl PacketGamePlay {
|
impl PacketGamePlay {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
handle:String::new(),
|
status:0,
|
||||||
secret:String::new(),
|
turn:0,
|
||||||
|
play:Play::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,55 +32,27 @@ impl Packet for PacketGamePlay {
|
|||||||
{
|
{
|
||||||
let mut result = Self::new();
|
let mut result = Self::new();
|
||||||
|
|
||||||
let mut length = unpack_u16(data, index) as usize;
|
result.status = unpack_u16(data, index);
|
||||||
if data.len() >= *index + length {
|
result.turn = unpack_u16(data, index);
|
||||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
|
||||||
Ok(text) => {
|
|
||||||
*index += length;
|
|
||||||
result.handle = text;
|
|
||||||
|
|
||||||
length = unpack_u16(data, index) as usize;
|
let play = unpack_u16(data, index) as u32;
|
||||||
if data.len() >= *index + length {
|
result.play.source = (play & mask(1, 0)) as u8;
|
||||||
|
result.play.from = ((play & mask(6, 1)) >> 1) as u8;
|
||||||
|
result.play.to = ((play & mask(6, 7)) >> 7) as u8;
|
||||||
|
|
||||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
Ok(result)
|
||||||
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>
|
fn encode(&self) -> Vec<u8>
|
||||||
{
|
{
|
||||||
|
let mut data = 0;
|
||||||
|
data |= self.play.source as u16;
|
||||||
|
data |= (self.play.from as u16) << 1;
|
||||||
|
data |= (self.play.to as u16) << 7;
|
||||||
[
|
[
|
||||||
pack_u16(self.status),
|
pack_u16(self.status),
|
||||||
|
pack_u16(self.turn),
|
||||||
|
pack_u16(data),
|
||||||
].concat()
|
].concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
app::session::SessionToken,
|
app::session::SessionToken,
|
||||||
util::pack::{pack_u16, unpack_u16},
|
util::pack::pack_u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Packet;
|
use super::Packet;
|
||||||
@ -24,21 +24,61 @@ impl Packet for PacketGameState {
|
|||||||
{
|
{
|
||||||
let mut result = Self::new();
|
let mut result = Self::new();
|
||||||
|
|
||||||
|
if data.len() - *index == 8 {
|
||||||
|
for i in 0..8 {
|
||||||
|
result.token[i] = data[*index];
|
||||||
|
*index += 1;
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct PacketGameStateResponsePiece {
|
||||||
|
pub valid:bool,
|
||||||
|
pub piece:u8,
|
||||||
|
pub promoted:bool,
|
||||||
|
pub player:u8,
|
||||||
|
pub tile:u8,
|
||||||
|
}
|
||||||
|
impl PacketGameStateResponsePiece {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
valid:false,
|
||||||
|
piece:0,
|
||||||
|
promoted:false,
|
||||||
|
player:0,
|
||||||
|
tile:0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct PacketGameStateResponse {
|
pub struct PacketGameStateResponse {
|
||||||
pub status:u16,
|
pub status:u16,
|
||||||
|
pub turn:u16,
|
||||||
|
pub dawn_handle:String,
|
||||||
|
pub dusk_handle:String,
|
||||||
|
pub dawn_pool:[u8; 6],
|
||||||
|
pub dusk_pool:[u8; 6],
|
||||||
|
pub pieces:[PacketGameStateResponsePiece; 38],
|
||||||
}
|
}
|
||||||
impl PacketGameStateResponse {
|
impl PacketGameStateResponse {
|
||||||
pub fn new() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
status:0,
|
status:0,
|
||||||
|
turn:0,
|
||||||
|
dawn_handle:String::new(),
|
||||||
|
dusk_handle:String::new(),
|
||||||
|
dawn_pool:[0; 6],
|
||||||
|
dusk_pool:[0; 6],
|
||||||
|
pieces:[PacketGameStateResponsePiece::new(); 38],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,9 +87,41 @@ impl Packet for PacketGameStateResponse {
|
|||||||
|
|
||||||
fn encode(&self) -> Vec<u8>
|
fn encode(&self) -> Vec<u8>
|
||||||
{
|
{
|
||||||
|
let mut piece_bytes = Vec::new();
|
||||||
|
for piece in &self.pieces {
|
||||||
|
let piece_data: u16 = piece.valid as u16
|
||||||
|
| ((piece.piece as u16) << 1)
|
||||||
|
| ((piece.promoted as u16) << 4)
|
||||||
|
| ((piece.player as u16) << 5)
|
||||||
|
| ((piece.tile as u16) << 6);
|
||||||
|
|
||||||
|
piece_bytes.append(&mut pack_u16(piece_data));
|
||||||
|
}
|
||||||
|
|
||||||
|
let dawn_pool_bytes :u16 = self.dawn_pool[0] as u16
|
||||||
|
| ((self.dawn_pool[1] as u16) << 5)
|
||||||
|
| ((self.dawn_pool[2] as u16) << 7)
|
||||||
|
| ((self.dawn_pool[3] as u16) << 9)
|
||||||
|
| ((self.dawn_pool[4] as u16) << 11)
|
||||||
|
| ((self.dawn_pool[5] as u16) << 13);
|
||||||
|
|
||||||
|
let dusk_pool_bytes :u16 = self.dusk_pool[0] as u16
|
||||||
|
| ((self.dusk_pool[1] as u16) << 5)
|
||||||
|
| ((self.dusk_pool[2] as u16) << 7)
|
||||||
|
| ((self.dusk_pool[3] as u16) << 9)
|
||||||
|
| ((self.dusk_pool[4] as u16) << 11)
|
||||||
|
| ((self.dusk_pool[5] as u16) << 13);
|
||||||
|
|
||||||
[
|
[
|
||||||
pack_u16(self.status),
|
pack_u16(self.status),
|
||||||
|
pack_u16(self.turn),
|
||||||
|
pack_u16(self.dawn_handle.len() as u16),
|
||||||
|
self.dawn_handle.as_bytes().to_vec(),
|
||||||
|
pack_u16(self.dusk_handle.len() as u16),
|
||||||
|
self.dusk_handle.as_bytes().to_vec(),
|
||||||
|
pack_u16(dawn_pool_bytes),
|
||||||
|
pack_u16(dusk_pool_bytes),
|
||||||
|
piece_bytes,
|
||||||
].concat()
|
].concat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ 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_retire; pub use session_retire::*;
|
||||||
mod session_leave; pub use session_leave::*;
|
|
||||||
|
|
||||||
mod game_state; pub use game_state::*;
|
mod game_state; pub use game_state::*;
|
||||||
mod game_play; pub use game_play::*;
|
mod game_play; pub use game_play::*;
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
use crate::app::session::SessionToken;
|
||||||
|
|
||||||
|
use super::Packet;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PacketSessionRetire {
|
||||||
|
pub token:SessionToken,
|
||||||
|
}
|
||||||
|
impl PacketSessionRetire {
|
||||||
|
pub fn new() -> Self
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
token:SessionToken::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Packet for PacketSessionRetire {
|
||||||
|
type Data = Self;
|
||||||
|
|
||||||
|
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||||
|
{
|
||||||
|
let mut result = Self::new();
|
||||||
|
|
||||||
|
if data.len() - *index == 8 {
|
||||||
|
for i in 0..8 { result.token[i] = data[*index]; *index += 1; }
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
320
server/src/system/cache/mod.rs
vendored
320
server/src/system/cache/mod.rs
vendored
@ -1,28 +1,22 @@
|
|||||||
use std::{io::Read, sync::{Arc, RwLock}};
|
use std::{
|
||||||
|
io::Read,
|
||||||
|
sync::{Arc, RwLock},
|
||||||
|
fs::File,
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use trie::Trie;
|
||||||
|
|
||||||
use crate::util::string::minimize_whitespace;
|
use crate::util::string::minimize_whitespace;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CacheData {
|
||||||
|
pub mime:String,
|
||||||
|
pub data:Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
struct WebCacheData {
|
struct WebCacheData {
|
||||||
html:String,
|
objects:Trie<CacheData>,
|
||||||
css:String,
|
|
||||||
js:String,
|
|
||||||
favicon:Vec<u8>,
|
|
||||||
|
|
||||||
dawn_omen:Vec<u8>,
|
|
||||||
dawn_dragon:Vec<u8>,
|
|
||||||
dawn_castle:Vec<u8>,
|
|
||||||
dawn_tower:Vec<u8>,
|
|
||||||
dawn_lance:Vec<u8>,
|
|
||||||
dawn_knight:Vec<u8>,
|
|
||||||
dawn_militia:Vec<u8>,
|
|
||||||
|
|
||||||
dusk_omen:Vec<u8>,
|
|
||||||
dusk_dragon:Vec<u8>,
|
|
||||||
dusk_castle:Vec<u8>,
|
|
||||||
dusk_tower:Vec<u8>,
|
|
||||||
dusk_lance:Vec<u8>,
|
|
||||||
dusk_knight:Vec<u8>,
|
|
||||||
dusk_militia:Vec<u8>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -30,230 +24,90 @@ pub struct WebCache {
|
|||||||
data:Arc<RwLock<WebCacheData>>,
|
data:Arc<RwLock<WebCacheData>>,
|
||||||
}
|
}
|
||||||
impl WebCache {
|
impl WebCache {
|
||||||
pub fn init() -> Self
|
pub fn new() -> Self
|
||||||
{
|
{
|
||||||
use std::fs::File;
|
|
||||||
|
|
||||||
// HTML
|
|
||||||
let mut html = String::new();
|
|
||||||
if let Ok(mut file) = File::open("www/.html") {
|
|
||||||
file.read_to_string(&mut html).ok();
|
|
||||||
html = minimize_whitespace(&html);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CSS
|
|
||||||
let css_path = std::path::Path::new("www/css/");
|
|
||||||
let css: String = [
|
|
||||||
"main.css",
|
|
||||||
"util.css",
|
|
||||||
"ui.css",
|
|
||||||
"form.css",
|
|
||||||
"game.css",
|
|
||||||
].map(|path| {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
if let Ok(mut file) = File::open(css_path.join(path)) {
|
|
||||||
file.read_to_string(&mut buffer).ok();
|
|
||||||
}
|
|
||||||
buffer
|
|
||||||
}).concat();
|
|
||||||
|
|
||||||
// JavaScript
|
|
||||||
let js_path = std::path::Path::new("www/js/");
|
|
||||||
let js = [
|
|
||||||
"const.js",
|
|
||||||
"util.js",
|
|
||||||
"game_asset.js",
|
|
||||||
"game.js",
|
|
||||||
"interface.js",
|
|
||||||
"ui.js",
|
|
||||||
"scene.js",
|
|
||||||
"system.js",
|
|
||||||
"main.js",
|
|
||||||
].map(|path| {
|
|
||||||
let mut buffer = String::new();
|
|
||||||
if let Ok(mut file) = File::open(js_path.join(path)) {
|
|
||||||
file.read_to_string(&mut buffer).ok();
|
|
||||||
}
|
|
||||||
buffer
|
|
||||||
}).concat();
|
|
||||||
|
|
||||||
// Favicon
|
|
||||||
let mut favicon = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/favicon.png") {
|
|
||||||
file.read_to_end(&mut favicon).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assets
|
|
||||||
let mut dawn_omen = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/omen_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_omen).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_dragon = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/dragon_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_dragon).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_castle = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/castle_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_castle).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_tower = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/tower_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_tower).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_lance = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/lance_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_lance).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_knight = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/knight_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_knight).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dawn_militia = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/militia_dawn.svg") {
|
|
||||||
file.read_to_end(&mut dawn_militia).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
let mut dusk_omen = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/omen_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_omen).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_dragon = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/dragon_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_dragon).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_castle = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/castle_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_castle).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_tower = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/tower_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_tower).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_lance = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/lance_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_lance).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_knight = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/knight_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_knight).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut dusk_militia = Vec::<u8>::new();
|
|
||||||
if let Ok(mut file) = File::open("www/asset/militia_dusk.svg") {
|
|
||||||
file.read_to_end(&mut dusk_militia).ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
data:Arc::new(RwLock::new(WebCacheData {
|
data:Arc::new(RwLock::new(WebCacheData {
|
||||||
html, css, js, favicon,
|
objects:Trie::new(),
|
||||||
|
|
||||||
dawn_omen,
|
|
||||||
dawn_dragon,
|
|
||||||
dawn_castle,
|
|
||||||
dawn_tower,
|
|
||||||
dawn_lance,
|
|
||||||
dawn_knight,
|
|
||||||
dawn_militia,
|
|
||||||
|
|
||||||
dusk_omen,
|
|
||||||
dusk_dragon,
|
|
||||||
dusk_castle,
|
|
||||||
dusk_tower,
|
|
||||||
dusk_lance,
|
|
||||||
dusk_knight,
|
|
||||||
dusk_militia,
|
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn html(&self) -> String
|
pub fn fetch(&self, object:&str) -> Option<CacheData>
|
||||||
{
|
|
||||||
match self.data.read() {
|
|
||||||
Ok(reader) => reader.html.to_string(),
|
|
||||||
Err(_) => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn css(&self) -> String
|
|
||||||
{
|
|
||||||
match self.data.read() {
|
|
||||||
Ok(reader) => reader.css.to_string(),
|
|
||||||
Err(_) => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn js(&self) -> String
|
|
||||||
{
|
|
||||||
match self.data.read() {
|
|
||||||
Ok(reader) => reader.js.to_string(),
|
|
||||||
Err(_) => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn favicon(&self) -> Vec<u8>
|
|
||||||
{
|
|
||||||
match self.data.read() {
|
|
||||||
Ok(reader) => reader.favicon.clone(),
|
|
||||||
Err(_) => Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn asset(&self, player:u8, id:u8) -> Vec<u8>
|
|
||||||
{
|
{
|
||||||
match self.data.read() {
|
match self.data.read() {
|
||||||
Ok(reader) => {
|
Ok(reader) => {
|
||||||
match id {
|
match reader.objects.get(object.as_bytes()) {
|
||||||
0 => match player {
|
Some(data) => Some(data.clone()),
|
||||||
0 => reader.dawn_militia.clone(),
|
None => None,
|
||||||
1 => reader.dusk_militia.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
1 => match player {
|
|
||||||
0 => reader.dawn_knight.clone(),
|
|
||||||
1 => reader.dusk_knight.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
2 => match player {
|
|
||||||
0 => reader.dawn_lance.clone(),
|
|
||||||
1 => reader.dusk_lance.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
3 => match player {
|
|
||||||
0 => reader.dawn_tower.clone(),
|
|
||||||
1 => reader.dusk_tower.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
4 => match player {
|
|
||||||
0 => reader.dawn_castle.clone(),
|
|
||||||
1 => reader.dusk_castle.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
5 => match player {
|
|
||||||
0 => reader.dawn_dragon.clone(),
|
|
||||||
1 => reader.dusk_dragon.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
6 => match player {
|
|
||||||
0 => reader.dawn_omen.clone(),
|
|
||||||
1 => reader.dusk_omen.clone(),
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
|
||||||
_ => Vec::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => Vec::new(),
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache(&self, mime:&str, object:&str, data:Vec<u8>)
|
||||||
|
{
|
||||||
|
match self.data.write() {
|
||||||
|
Ok(mut writer) => {
|
||||||
|
writer.objects.set(object.as_bytes(), CacheData {
|
||||||
|
mime:mime.to_string(),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Err(_) => { },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_file<P :AsRef<Path>>(&self, mime:&str, object:&str, path:P) -> Result<(),()>
|
||||||
|
{
|
||||||
|
match self.data.write() {
|
||||||
|
Ok(mut writer) => {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
if let Ok(mut file) = File::open(path) {
|
||||||
|
file.read_to_end(&mut data).ok();
|
||||||
|
writer.objects.set(object.as_bytes(), CacheData {
|
||||||
|
mime:mime.to_string(),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
} else { Err(()) }
|
||||||
|
}
|
||||||
|
Err(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_whitespace_minimize(&self, object:&str) -> Result<(),()>
|
||||||
|
{
|
||||||
|
match self.data.write() {
|
||||||
|
Ok(mut writer) => {
|
||||||
|
if let Some(data) = writer.objects.get_mut(object.as_bytes()) {
|
||||||
|
data.data = minimize_whitespace(&String::from_utf8(data.data.clone()).unwrap()).as_bytes().to_vec();
|
||||||
|
Ok(())
|
||||||
|
} else { Err(()) }
|
||||||
|
},
|
||||||
|
Err(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cache_file_group<P :AsRef<Path>>(&self, mime:&str, object:&str, paths:&[P]) -> Result<(),()>
|
||||||
|
{
|
||||||
|
match self.data.write() {
|
||||||
|
Ok(mut writer) => {
|
||||||
|
let data = paths.into_iter().map(|path| {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
if let Ok(mut file) = File::open(path) {
|
||||||
|
file.read_to_end(&mut buffer).ok();
|
||||||
|
}
|
||||||
|
buffer
|
||||||
|
}).collect::<Vec<Vec<u8>>>().concat();
|
||||||
|
writer.objects.set(object.as_bytes(), CacheData {
|
||||||
|
mime:mime.to_string(),
|
||||||
|
data:data,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(_) => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ use game::Game;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::{
|
app::{
|
||||||
session::{self, Session, SessionToken, SessionSecret},
|
session::{Session, SessionToken, SessionSecret},
|
||||||
user::User,
|
user::User,
|
||||||
},
|
},
|
||||||
util::pack::*,
|
util::pack::*,
|
||||||
@ -144,6 +144,8 @@ impl FileSystem {
|
|||||||
|
|
||||||
pub fn session_fetch(&mut self, id:u32) -> Result<Session,()>
|
pub fn session_fetch(&mut self, id:u32) -> Result<Session,()>
|
||||||
{
|
{
|
||||||
|
use crate::app::session::Player;
|
||||||
|
|
||||||
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
let bucket_index = id & !HANDLE_BUCKET_MASK;
|
||||||
let dir_index = id & HANDLE_BUCKET_MASK;
|
let dir_index = id & HANDLE_BUCKET_MASK;
|
||||||
|
|
||||||
@ -189,15 +191,15 @@ impl FileSystem {
|
|||||||
token,
|
token,
|
||||||
secret,
|
secret,
|
||||||
game:Game::new(),
|
game:Game::new(),
|
||||||
p_dawn:session::Viewer {
|
p_dawn:Player {
|
||||||
connection:None,
|
|
||||||
user:dawn,
|
user:dawn,
|
||||||
|
connections:Vec::new(),
|
||||||
},
|
},
|
||||||
p_dusk:session::Viewer {
|
p_dusk:Player {
|
||||||
connection:None,
|
|
||||||
user:dusk,
|
user:dusk,
|
||||||
|
connections:Vec::new(),
|
||||||
},
|
},
|
||||||
viewers:Vec::new(),
|
connections:Vec::new(),
|
||||||
time,
|
time,
|
||||||
chain_id:0,
|
chain_id:0,
|
||||||
})
|
})
|
||||||
|
@ -32,6 +32,24 @@ pub fn unpack_u16(data:&[u8], index:&mut usize) -> u16
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pack_u24(value:u32) -> Vec<u8>
|
||||||
|
{
|
||||||
|
vec![(value >> 16) as u8, (value >> 8) as u8, (value & 0xFF) as u8]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unpack_u24(data:&[u8], index:&mut usize) -> u32
|
||||||
|
{
|
||||||
|
let mut result = 0u32;
|
||||||
|
for _ in 0..3 {
|
||||||
|
result <<= 8;
|
||||||
|
if *index < data.len() {
|
||||||
|
result |= data[*index] as u32;
|
||||||
|
*index += 1;
|
||||||
|
} else { break; }
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pack_u32(value:u32) -> Vec<u8>
|
pub fn pack_u32(value:u32) -> Vec<u8>
|
||||||
{
|
{
|
||||||
vec![
|
vec![
|
||||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@ -4,6 +4,7 @@ let SCENE = null;
|
|||||||
|
|
||||||
let CONNECTED = false;
|
let CONNECTED = false;
|
||||||
let SOCKET = null;
|
let SOCKET = null;
|
||||||
|
|
||||||
let CONTEXT = {
|
let CONTEXT = {
|
||||||
Scene: null,
|
Scene: null,
|
||||||
Auth: null,
|
Auth: null,
|
||||||
|
375
www/js/game.js
375
www/js/game.js
@ -3,14 +3,15 @@ let GAME_DATA = null;
|
|||||||
|
|
||||||
GAME.Board = class {
|
GAME.Board = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME.Tile()); }
|
this.tiles = [ ]; for(let i = 0; i < 61; ++i) { this.tiles.push(new GAME.Tile(i)); }
|
||||||
this.pieces = [ ];
|
this.pieces = [ ]; for(let i = 0; i < 38; ++i) { this.pieces.push(null); }
|
||||||
|
this.columns = [ ]; for(let i = 0; i < 9; ++i) { this.columns.push(new GAME.Column()); }
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.pieces = [ ];
|
this.pieces = [ ]; for(let i = 0; i < 38; ++i) { this.pieces.push(null); }
|
||||||
|
|
||||||
// Describe Dawn layout
|
// Describe Dawn layout
|
||||||
let layout = [
|
let layout = [
|
||||||
@ -42,65 +43,74 @@ GAME.Board = class {
|
|||||||
|
|
||||||
// Add Dawn pieces
|
// Add Dawn pieces
|
||||||
for(let piece of layout) {
|
for(let piece of layout) {
|
||||||
this.place_piece(piece.piece, GAME.Const.Player.Dawn, piece.hex);
|
this.set_piece(piece.piece, GAME.Const.Player.Dawn, piece.hex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Dusk pieces
|
// Add Dusk pieces
|
||||||
for(let piece of layout) {
|
for(let piece of layout) {
|
||||||
let hex = new MATH.Vec2(8 - piece.hex.x, 8 - piece.hex.y);
|
let hex = new MATH.Vec2(8 - piece.hex.x, 8 - piece.hex.y);
|
||||||
this.place_piece(piece.piece, GAME.Const.Player.Dusk, hex);
|
this.set_piece(piece.piece, GAME.Const.Player.Dusk, hex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
place_piece(piece, player, hex) {
|
set_piece(piece, player, hex) {
|
||||||
|
let index = 0;
|
||||||
|
while(this.pieces[index] !== null) { index++; }
|
||||||
|
|
||||||
let game_piece = new GAME.Piece(piece, player);
|
let game_piece = new GAME.Piece(piece, player);
|
||||||
game_piece.tile = this.hex_to_tile(hex);
|
game_piece.tile = HEX.hex_to_tile(hex);
|
||||||
this.tiles[game_piece.tile].piece = this.pieces.length;
|
this.tiles[game_piece.tile].piece = index;
|
||||||
this.pieces.push(game_piece);
|
this.pieces[index] = game_piece;
|
||||||
}
|
}
|
||||||
|
|
||||||
hex_to_tile(hex) {
|
reset() {
|
||||||
let a = ((hex.x + 4) * (hex.x + 5) / 2) - 10;
|
for(let i = 0; i < this.tiles.length; ++i) { this.tiles[i].reset(); }
|
||||||
let b = (hex.x > 4) * ((hex.x - 4) + ((hex.x - 5) * (hex.x - 4)));
|
for(let i = 0; i < this.columns.length; ++i) { this.columns[i].reset(); }
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
GAME.Player = class {
|
|
||||||
constructor() {
|
|
||||||
this.handle = "";
|
|
||||||
this.pool = new GAME.Pool();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GAME.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(1); }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GAME.Column = class {
|
||||||
|
constructor() {
|
||||||
|
this.militia = [false, false];
|
||||||
|
this.extent = [0, 8];
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.militia = [false, false];
|
||||||
|
this.extent = [0, 8];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GAME.Tile = class {
|
GAME.Tile = class {
|
||||||
constructor() {
|
constructor(index) {
|
||||||
this.piece = null;
|
this.piece = null;
|
||||||
this.threaten = { dawn: 0, dusk: 0 };
|
|
||||||
this.checking = { dawn: false, dusk: false };
|
this.threaten = [false, false];
|
||||||
|
this.checking = [false, false];
|
||||||
|
|
||||||
|
this.hex = HEX.tile_to_hex(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.threaten = { dawn: 0, dusk: 0 };
|
this.threaten = [0, 0];
|
||||||
this.checking = { dawn: false, dusk: false };
|
this.checking = [false, false];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GAME.Move = class {
|
GAME.MovementTile = class {
|
||||||
|
constructor(tile, status) {
|
||||||
|
this.tile = tile;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GAME.Play = class {
|
||||||
constructor(source, from, to) {
|
constructor(source, from, to) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.from = from;
|
this.from = from;
|
||||||
@ -109,20 +119,10 @@ GAME.Move = class {
|
|||||||
};
|
};
|
||||||
|
|
||||||
GAME.GamePiece = class {
|
GAME.GamePiece = class {
|
||||||
constructor(name, assets, moves, promote_moves) {
|
constructor(name, moves, promote_moves) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.moves = moves;
|
this.moves = moves;
|
||||||
this.pmoves = promote_moves;
|
this.pmoves = promote_moves;
|
||||||
this.assets = null;
|
|
||||||
|
|
||||||
if(assets !== null) {
|
|
||||||
this.assets = [
|
|
||||||
new Image(),
|
|
||||||
new Image()
|
|
||||||
];
|
|
||||||
this.assets[0].src = assets[0];
|
|
||||||
this.assets[1].src = assets[1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -160,6 +160,18 @@ GAME.Piece = class {
|
|||||||
this.tile = 0;
|
this.tile = 0;
|
||||||
this.blocking = 0;
|
this.blocking = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
moves() {
|
||||||
|
let moves = null;
|
||||||
|
if(this.promoted) { moves = GAME.Const.Piece[this.piece].pmoves; }
|
||||||
|
else { moves = GAME.Const.Piece[this.piece].moves; }
|
||||||
|
if(this.player == GAME.Const.Player.Dusk) { moves = moves.rotate(); }
|
||||||
|
return moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.blocking = 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GAME.Game = class {
|
GAME.Game = class {
|
||||||
@ -167,33 +179,271 @@ GAME.Game = class {
|
|||||||
this.turn = 0;
|
this.turn = 0;
|
||||||
|
|
||||||
this.board = new GAME.Board();
|
this.board = new GAME.Board();
|
||||||
this.dawn = new GAME.Player();
|
this.pools = [
|
||||||
this.dusk = new GAME.Player();
|
new GAME.Pool(),
|
||||||
|
new GAME.Pool(),
|
||||||
|
];
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
check:false,
|
||||||
|
checkmate:false,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.update_board();
|
||||||
}
|
}
|
||||||
|
|
||||||
update_board() {
|
update_board() {
|
||||||
// Reset tiles
|
// Reset tiles
|
||||||
for(tile of this.board.tiles) { tile.reset(); }
|
this.board.reset();
|
||||||
|
|
||||||
|
this.state.check = false;
|
||||||
|
this.state.checkmate = false;
|
||||||
|
|
||||||
// Determine threaten, check, and blocking for each piece
|
// Determine threaten, check, and blocking for each piece
|
||||||
for(piece of this.board.pieces) {
|
for(let piece of this.board.pieces) {
|
||||||
|
if(piece !== null) {
|
||||||
|
let hex = this.board.tiles[piece.tile].hex;
|
||||||
|
|
||||||
|
// Check if column has militia.
|
||||||
|
if(piece.piece == GAME.Const.PieceId.Militia) {
|
||||||
|
this.board.columns[hex.x].militia[piece.player] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check furthest piece in column.
|
||||||
|
if(piece.player == 0) { this.board.columns[hex.x].extent[0] = Math.max(hex.y, this.board.columns[hex.x].extent[0]); }
|
||||||
|
else { this.board.columns[hex.x].extent[1] = Math.min(hex.y, this.board.columns[hex.x].extent[1]); }
|
||||||
|
|
||||||
|
for(let movement of this.movement_tiles_ext(piece, piece.tile)) {
|
||||||
|
if(movement.status != GAME.Const.MoveStatus.Invalid) {
|
||||||
|
if(movement.status == GAME.Const.MoveStatus.Check) {
|
||||||
|
this.state.check = true;
|
||||||
|
}
|
||||||
|
this.board.tiles[movement.tile].threaten[piece.player] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count moves available to next turn player to determine checkmate.
|
||||||
|
if(this.state.check) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
process(move) {
|
process(move) {
|
||||||
|
// Check if swapped piece.
|
||||||
|
// Check if piece should be promoted.
|
||||||
|
// Check if swapped piece should be promoted.
|
||||||
|
// Add piece to pool if taken.
|
||||||
|
// Move pieces.
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// - Validate move.
|
||||||
|
// - Improve data safety validation.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Move piece on board.
|
||||||
|
if(move.source == 0) {
|
||||||
|
let piece_id = this.board.tiles[move.from].piece;
|
||||||
|
let piece = this.board.pieces[piece_id];
|
||||||
|
piece.tile = move.to;
|
||||||
|
this.board.tiles[move.from].piece = null;
|
||||||
|
|
||||||
|
let target_id = this.board.tiles[move.to].piece;
|
||||||
|
if(target_id !== null) {
|
||||||
|
let target = this.board.pieces[target_id];
|
||||||
|
|
||||||
|
// Swap piece with moving piece.
|
||||||
|
if(target.player == piece.player) {
|
||||||
|
target.tile = move.from;
|
||||||
|
this.board.tiles[move.from].piece = target_id;
|
||||||
|
|
||||||
|
// Check if swap is promoted.
|
||||||
|
let hex = HEX.tile_to_hex(target.tile);
|
||||||
|
hex.y -= MATH.sign_branch(target.player);
|
||||||
|
if(!target.promoted && !HEX.is_valid(hex)) {
|
||||||
|
target.promoted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add captured piece to pool and destroy.
|
||||||
|
else {
|
||||||
|
console.log(piece.player);
|
||||||
|
this.pools[piece.player].pieces[target.piece] += 1;
|
||||||
|
this.board.pieces[target_id] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.board.tiles[move.to].piece = piece_id;
|
||||||
|
|
||||||
|
// Check if piece is promoted.
|
||||||
|
let hex = HEX.tile_to_hex(piece.tile);
|
||||||
|
hex.y -= MATH.sign_branch(piece.player);
|
||||||
|
if(!piece.promoted && !HEX.is_valid(hex)) {
|
||||||
|
piece.promoted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place piece from pool.
|
||||||
|
else {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
this.turn++;
|
||||||
|
|
||||||
// Recalculate new board state.
|
// Recalculate new board state.
|
||||||
GAME.update_board();
|
this.update_board();
|
||||||
|
|
||||||
|
// If check, detect checkmate.
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(move) {
|
movement_tiles(piece, tile) {
|
||||||
|
let tiles = movement_tiles_ext(piece, tile);
|
||||||
|
let result = [ ];
|
||||||
|
|
||||||
|
for(let movement in tiles) {
|
||||||
|
if(movement.status) { result.push(movement); }
|
||||||
}
|
}
|
||||||
|
|
||||||
get_move_tiles(piece_id) {
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
movement_tiles_ext(piece, tile) {
|
||||||
|
let tiles = [ ];
|
||||||
|
let moves = piece.moves();
|
||||||
|
let hex = this.board.tiles[tile].hex;
|
||||||
|
|
||||||
|
let directions = moves.direction;
|
||||||
|
let permitted_moves = directions;
|
||||||
|
if(piece.blocking != 0) {
|
||||||
|
if(piece.piece == GAME.Const.PieceId.Omen) {
|
||||||
|
permitted_moves &= ~piece.blocking;
|
||||||
|
} else {
|
||||||
|
permitted_moves &= piece.blocking;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check directions of movement.
|
||||||
|
for(let mask = BITWISE.lsb(directions); directions > 0; mask = BITWISE.lsb(directions)) {
|
||||||
|
let direction_id = BITWISE.ffs(mask);
|
||||||
|
let direction = GAME.Const.get_direction(direction_id);
|
||||||
|
let stride = (moves.stride & mask) != 0;
|
||||||
|
|
||||||
|
// Get initial status in direction.
|
||||||
|
let status = (permitted_moves & mask)? GAME.Const.MoveStatus.Valid : GAME.Const.MoveStatus.Invalid;
|
||||||
|
|
||||||
|
let move_hex = hex.copy();
|
||||||
|
|
||||||
|
// Check tiles in direction up to movement limit.
|
||||||
|
let max_dist = (stride)? 8 : 1;
|
||||||
|
for(let dist = 1; dist <= max_dist; ++dist) {
|
||||||
|
move_hex.add(direction);
|
||||||
|
|
||||||
|
if(HEX.is_valid(move_hex)) {
|
||||||
|
let tile_id = HEX.hex_to_tile(move_hex);
|
||||||
|
let tile_data = this.board.tiles[tile_id];
|
||||||
|
|
||||||
|
let result = status;
|
||||||
|
|
||||||
|
// King may not move onto threatened tile.
|
||||||
|
if(piece.piece == GAME.Const.PieceId.Omen && tile_data.threaten[+(!piece.player)]) {
|
||||||
|
status = GAME.Const.MoveStatus.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle occupied tile.
|
||||||
|
if(tile_data.piece !== null) {
|
||||||
|
let target = this.board.pieces[tile_data.piece];
|
||||||
|
|
||||||
|
// Target piece is ally.
|
||||||
|
if(target.player == piece.player) {
|
||||||
|
|
||||||
|
// Move is only valid if pieces are swappable.
|
||||||
|
if(!this.movement_swappable(target, mask, dist, tile)) {
|
||||||
|
result = GAME.Const.MoveStatus.Invalid;
|
||||||
|
}
|
||||||
|
status = GAME.Const.MoveStatus.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target piece is opposing.
|
||||||
|
else {
|
||||||
|
// Check if target piece is opposing king.
|
||||||
|
if(target.piece == GAME.Const.PieceId.Omen) {
|
||||||
|
if(status == GAME.Const.MoveStatus.Valid) {
|
||||||
|
result = GAME.Const.MoveStatus.Check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status = GAME.Const.MoveStatus.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.push(new GAME.MovementTile(tile_id, result));
|
||||||
|
} else { break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
directions &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
movement_swappable(target, mask, range, tile) {
|
||||||
|
mask = BITWISE.rotate_blocks(mask);
|
||||||
|
let moves = target.moves();
|
||||||
|
|
||||||
|
// King cannot swap onto tile that is threatened.
|
||||||
|
// This case should also cover blocking.
|
||||||
|
if(target.piece == GAME.Const.PieceId.Omen && this.board.tiles[tile].threaten[+(!target.player)]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ((moves.direction & mask) != 0 && (range == 1 || (moves.stride & mask) != 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
placement_tiles_ext(piece_id, player) {
|
||||||
|
let tiles = [ ];
|
||||||
|
let piece = new GAME.Piece(piece_id, player);
|
||||||
|
|
||||||
|
// Get tiles onto which piece may be placed.
|
||||||
|
for(let i = 0; i < this.board.tiles.length; ++i) {
|
||||||
|
let hex = HEX.tile_to_hex(i);
|
||||||
|
let tile = this.board.tiles[i];
|
||||||
|
let status = GAME.Const.MoveStatus.Invalid;
|
||||||
|
|
||||||
|
// Check if tile is occupied.
|
||||||
|
if(tile.piece === null) {
|
||||||
|
let position_valid = true;
|
||||||
|
|
||||||
|
// Check off-sides.
|
||||||
|
if(piece.player == 0) {
|
||||||
|
position_valid = hex.y <= this.board.columns[hex.x].extent[+(!player)];
|
||||||
|
} else {
|
||||||
|
position_valid = hex.y >= this.board.columns[hex.x].extent[+(!player)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check militia stacking.
|
||||||
|
if(piece_id == GAME.Const.PieceId.Militia && this.board.columns[hex.x].militia[player]) {
|
||||||
|
position_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if position puts king in check.
|
||||||
|
let checking = false;
|
||||||
|
let movements = this.movement_tiles_ext(piece, i);
|
||||||
|
for(let movement of movements) {
|
||||||
|
if(movement.status == GAME.Const.MoveStatus.Check) {
|
||||||
|
checking = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Piece must have movements and not put king in check.
|
||||||
|
if(position_valid && movements.length > 0 && !checking) {
|
||||||
|
status = GAME.Const.MoveStatus.Valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.push(new GAME.MovementTile(i, status));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tiles;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,6 +474,12 @@ GAME.Const = {
|
|||||||
new MATH.Vec2(-1, 1),
|
new MATH.Vec2(-1, 1),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
MoveStatus: {
|
||||||
|
Valid: 0,
|
||||||
|
Invalid: 1,
|
||||||
|
Check: 2,
|
||||||
|
},
|
||||||
|
|
||||||
PieceId: {
|
PieceId: {
|
||||||
Militia: 0,
|
Militia: 0,
|
||||||
Knight: 1,
|
Knight: 1,
|
||||||
@ -237,7 +493,6 @@ GAME.Const = {
|
|||||||
Piece: [
|
Piece: [
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Militia",
|
"Militia",
|
||||||
["asset/militia_dusk.svg", "asset/militia_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
@ -251,7 +506,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Knight",
|
"Knight",
|
||||||
["asset/knight_dusk.svg", "asset/knight_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add(3)
|
.add(3)
|
||||||
.add(6)
|
.add(6)
|
||||||
@ -271,7 +525,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Lance",
|
"Lance",
|
||||||
["asset/lance_dusk.svg", "asset/lance_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add_stride(0)
|
.add_stride(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
@ -286,7 +539,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Tower",
|
"Tower",
|
||||||
["asset/tower_dusk.svg", "asset/tower_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
@ -308,7 +560,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Castle",
|
"Castle",
|
||||||
["asset/castle_dusk.svg", "asset/castle_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
@ -329,7 +580,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Dragon",
|
"Dragon",
|
||||||
["asset/dragon_dusk.svg", "asset/dragon_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add_stride(6)
|
.add_stride(6)
|
||||||
.add_stride(7)
|
.add_stride(7)
|
||||||
@ -353,7 +603,6 @@ GAME.Const = {
|
|||||||
),
|
),
|
||||||
new GAME.GamePiece(
|
new GAME.GamePiece(
|
||||||
"Omen",
|
"Omen",
|
||||||
["asset/king_dusk.svg", "asset/king_dawn.svg"],
|
|
||||||
new GAME.PieceMovement()
|
new GAME.PieceMovement()
|
||||||
.add(0)
|
.add(0)
|
||||||
.add(1)
|
.add(1)
|
||||||
@ -365,6 +614,12 @@ GAME.Const = {
|
|||||||
.add(10),
|
.add(10),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
get_direction(direction_id) {
|
||||||
|
let direction = GAME.Const.Direction[direction_id % 12].copy();
|
||||||
|
direction.mul(Math.ceil((direction_id + 1) / 12));
|
||||||
|
return direction;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
GAME.init = () => {
|
GAME.init = () => {
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
const GAME_ASSET = { };
|
||||||
|
|
||||||
|
GAME_ASSET.load_image = (image) => {
|
||||||
|
let img = new Image();
|
||||||
|
img.src = image;
|
||||||
|
return img;
|
||||||
|
};
|
||||||
|
|
||||||
|
GAME_ASSET.Image = {
|
||||||
|
Promote: GAME_ASSET.load_image("asset/promote.svg"),
|
||||||
|
Piece: [
|
||||||
|
[ GAME_ASSET.load_image("asset/militia_dawn.svg"), GAME_ASSET.load_image("asset/militia_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/knight_dawn.svg"), GAME_ASSET.load_image("asset/knight_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/lance_dawn.svg"), GAME_ASSET.load_image("asset/lance_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/tower_dawn.svg"), GAME_ASSET.load_image("asset/tower_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/castle_dawn.svg"), GAME_ASSET.load_image("asset/castle_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/dragon_dawn.svg"), GAME_ASSET.load_image("asset/dragon_dusk.svg") ],
|
||||||
|
[ GAME_ASSET.load_image("asset/omen_dawn.svg"), GAME_ASSET.load_image("asset/omen_dusk.svg") ],
|
||||||
|
],
|
||||||
|
};
|
@ -18,53 +18,257 @@ const INTERFACE = {
|
|||||||
|
|
||||||
HintHover: "#71a1e8",
|
HintHover: "#71a1e8",
|
||||||
HintSelect: "#4a148c",
|
HintSelect: "#4a148c",
|
||||||
HintValid: "#1a237e",
|
HintValid: "#1a237e", HintValidBorder: "#5558fc",
|
||||||
HintAllowed: "#6a1b9a",
|
HintThreat: "#054719", HintThreatBorder: "#22b54e",
|
||||||
HintInvalid: "b71c1c",
|
HintOpponent: "#49136b", HintOpponentBorder: "#d74cef",
|
||||||
|
HintInvalid: "#b71c1c", HintInvalidBorder: "#ed3636",
|
||||||
HintPlay: "#083242",
|
HintPlay: "#083242",
|
||||||
HintWarn: "#054719",
|
|
||||||
HintCheck: "#C62828",
|
HintCheck: "#C62828",
|
||||||
|
},
|
||||||
|
|
||||||
|
TileStatus: {
|
||||||
|
Valid: 1,
|
||||||
|
Threat: 2,
|
||||||
|
Invalid: 3,
|
||||||
|
Opponent: 4,
|
||||||
|
Play: 5,
|
||||||
|
Check: 6,
|
||||||
|
},
|
||||||
|
|
||||||
|
Selection: class {
|
||||||
|
constructor(source, tile, hex) {
|
||||||
|
this.source = source;
|
||||||
|
this.tile = tile;
|
||||||
|
this.hex = hex;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve_board() {
|
||||||
|
for(let i = 0; i < INTERFACE_DATA.board_state.length; ++i) {
|
||||||
|
INTERFACE_DATA.board_state[i] = [0, 0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.select !== null) { INTERFACE.resolve_piece(INTERFACE_DATA.select, 1); }
|
||||||
|
if(INTERFACE_DATA.hover !== null) { INTERFACE.resolve_piece(INTERFACE_DATA.hover, 0); }
|
||||||
|
},
|
||||||
|
|
||||||
|
resolve_piece(selection, zone) {
|
||||||
|
// Determine piece movement hints.
|
||||||
|
|
||||||
|
let movements = null;
|
||||||
|
let target = null;
|
||||||
|
let player = 0;
|
||||||
|
if(selection.source == 0) {
|
||||||
|
let piece_id = GAME_DATA.board.tiles[selection.tile].piece;
|
||||||
|
if(piece_id !== null) {
|
||||||
|
let piece = GAME_DATA.board.pieces[piece_id];
|
||||||
|
player = piece.player;
|
||||||
|
movements = GAME_DATA.movement_tiles_ext(piece, selection.tile);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player = Math.floor(selection.tile / 6);
|
||||||
|
player ^= (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate;
|
||||||
|
movements = GAME_DATA.placement_tiles_ext(selection.tile % 6, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(movements !== null) {
|
||||||
|
// Generate hint for each potential movement.
|
||||||
|
for(let movement of movements) {
|
||||||
|
if(movement.status != GAME.Const.MoveStatus.Invalid) {
|
||||||
|
// Show valid/threat hints if piece belongs to player and is player turn.
|
||||||
|
if(INTERFACE_DATA.player == 2 || (player == INTERFACE_DATA.player && (GAME_DATA.turn & 1) == player)) {
|
||||||
|
if(GAME_DATA.board.tiles[movement.tile].threaten[+(!player)]) {
|
||||||
|
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Threat;
|
||||||
|
} else {
|
||||||
|
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Valid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Opponent;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
INTERFACE_DATA.board_state[movement.tile][zone] = INTERFACE.TileStatus.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
hover(event) {
|
hover(event) {
|
||||||
|
let initial_hover = INTERFACE_DATA.hover;
|
||||||
|
|
||||||
|
let apothem = INTERFACE_DATA.Ui.scale;
|
||||||
|
let radius = INTERFACE.Radius * apothem;
|
||||||
|
let halfradius = radius / 2;
|
||||||
|
let grid_offset_x = 1.5 * radius;
|
||||||
|
|
||||||
|
let hex_slope = 0.25 / 0.75;
|
||||||
|
|
||||||
|
INTERFACE_DATA.hover = null;
|
||||||
|
|
||||||
|
// Handle board area
|
||||||
|
if(event.offsetY >= INTERFACE_DATA.Ui.margin && event.offsetY < INTERFACE_DATA.Ui.margin + INTERFACE_DATA.Ui.area.y) {
|
||||||
|
if(event.offsetX >= INTERFACE_DATA.Ui.offset.x && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.board_width) {
|
||||||
|
|
||||||
|
let basis_x = INTERFACE_DATA.Ui.offset.x + halfradius;
|
||||||
|
let basis_y = INTERFACE_DATA.Ui.offset.y + (14 * apothem);
|
||||||
|
|
||||||
|
let x = (event.offsetX - basis_x) / grid_offset_x;
|
||||||
|
let y = -(event.offsetY - basis_y) / apothem;
|
||||||
|
|
||||||
|
let kx = Math.floor(x);
|
||||||
|
let ky = Math.floor(y);
|
||||||
|
|
||||||
|
let apo_offset = Math.abs(MATH.mod(y + (kx & 1), 2.0) - 1);
|
||||||
|
let rad_offset = MATH.mod(x, 1);
|
||||||
|
let rad_slope = 1 - (hex_slope * apo_offset);
|
||||||
|
|
||||||
|
let hx = kx + (rad_offset > rad_slope);
|
||||||
|
let hy = Math.floor((ky + hx) / 2.0);
|
||||||
|
|
||||||
|
if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate == 1) {
|
||||||
|
hx = 8 - hx;
|
||||||
|
hy = 8 - hy;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hex = new MATH.Vec2(hx, hy);
|
||||||
|
if(HEX.is_valid(hex)) {
|
||||||
|
let tile = HEX.hex_to_tile(hex);
|
||||||
|
INTERFACE_DATA.hover = new INTERFACE.Selection(0, tile, hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pool area
|
||||||
|
else if(event.offsetX >= INTERFACE_DATA.Ui.pool_offset && event.offsetX < INTERFACE_DATA.Ui.offset.x + INTERFACE_DATA.Ui.area.x) {
|
||||||
|
|
||||||
|
let basis_x = INTERFACE_DATA.Ui.pool_offset + halfradius;
|
||||||
|
let basis_y = INTERFACE_DATA.Ui.offset.y + (3 * apothem);
|
||||||
|
|
||||||
|
let x = (event.offsetX - basis_x) / grid_offset_x;
|
||||||
|
let y = (event.offsetY - basis_y) / apothem;
|
||||||
|
|
||||||
|
let kx = Math.floor(x);
|
||||||
|
let ky = Math.floor(y);
|
||||||
|
|
||||||
|
let apo_offset = Math.abs(MATH.mod(y + (kx & 1), 2.0) - 1);
|
||||||
|
let rad_offset = MATH.mod(x, 1);
|
||||||
|
let rad_slope = 1 - (hex_slope * apo_offset);
|
||||||
|
|
||||||
|
let hx = kx + (rad_offset > rad_slope);
|
||||||
|
let hy = Math.floor((ky - hx) / 2.0);
|
||||||
|
|
||||||
|
let hex = new MATH.Vec2(hx, hy);
|
||||||
|
if(INTERFACE.Ui.pool_hex_is_valid(hex)) {
|
||||||
|
let tile = (hx * 6) + hy;
|
||||||
|
INTERFACE_DATA.hover = new INTERFACE.Selection(1, tile, hex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(initial_hover != INTERFACE_DATA.hover) { INTERFACE.draw(); }
|
||||||
|
},
|
||||||
|
|
||||||
|
unhover() {
|
||||||
|
let redraw = (INTERFACE_DATA.hover !== null);
|
||||||
|
INTERFACE_DATA.hover = null;
|
||||||
|
if(redraw) { INTERFACE.draw(); }
|
||||||
},
|
},
|
||||||
|
|
||||||
click(event) {
|
click(event) {
|
||||||
|
let initial_select = INTERFACE_DATA.select;
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.hover !== null) {
|
||||||
|
if(INTERFACE.Ui.match_select(INTERFACE_DATA.hover, INTERFACE_DATA.select)) {
|
||||||
|
INTERFACE_DATA.select = null;
|
||||||
|
} else {
|
||||||
|
// Check if operation can be performed on new tile.
|
||||||
|
// Otherwise, switch selection.
|
||||||
|
let is_valid = false;
|
||||||
|
if(INTERFACE_DATA.select !== null && INTERFACE_DATA.hover.source == 0 && INTERFACE_DATA.player == (GAME_DATA.turn & 1)) {
|
||||||
|
let tile_state = INTERFACE_DATA.board_state[INTERFACE_DATA.hover.tile][1];
|
||||||
|
is_valid = (tile_state == INTERFACE.TileStatus.Valid || tile_state == INTERFACE.TileStatus.Threat);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_valid) {
|
||||||
|
let move_data = INTERFACE_DATA.select.source | (INTERFACE_DATA.select.tile << 1) | (INTERFACE_DATA.hover.tile << 7);
|
||||||
|
console.log("SEND Play");
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.GamePlay),
|
||||||
|
PACK.u16(0),
|
||||||
|
PACK.u16(GAME_DATA.turn),
|
||||||
|
PACK.u16(move_data),
|
||||||
|
]);
|
||||||
|
INTERFACE_DATA.select = null;
|
||||||
|
} else {
|
||||||
|
INTERFACE_DATA.select = null;
|
||||||
|
if(INTERFACE_DATA.hover.source == 0) {
|
||||||
|
if(GAME_DATA.board.tiles[INTERFACE_DATA.hover.tile].piece !== null) {
|
||||||
|
INTERFACE_DATA.select = INTERFACE_DATA.hover;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let pool_player = Math.floor(INTERFACE_DATA.hover.tile / 6);
|
||||||
|
pool_player ^= (INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate;
|
||||||
|
|
||||||
|
let pool_piece = INTERFACE_DATA.hover.tile % 6;
|
||||||
|
|
||||||
|
if(GAME_DATA.pools[pool_player].pieces[pool_piece] > 0) {
|
||||||
|
INTERFACE_DATA.select = INTERFACE_DATA.hover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear selection if no tile is hovered.
|
||||||
|
else {
|
||||||
|
INTERFACE_DATA.select = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(initial_select !== INTERFACE_DATA.select) {
|
||||||
|
INTERFACE.draw();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
draw() {
|
resize() {
|
||||||
let canvas = INTERFACE_DATA.canvas;
|
let width = INTERFACE_DATA.canvas.width = INTERFACE_DATA.canvas.clientWidth;
|
||||||
let ctx = INTERFACE_DATA.context;
|
let height = INTERFACE_DATA.canvas.height = INTERFACE_DATA.canvas.clientHeight;
|
||||||
|
|
||||||
// Determine interface configuration
|
let margin = INTERFACE_DATA.Ui.margin = Math.floor(Math.min(width, height) / 100);
|
||||||
canvas.width = canvas.clientWidth;
|
|
||||||
canvas.height = canvas.clientHeight;
|
|
||||||
|
|
||||||
let width = canvas.width;
|
let gui_width = width - (margin * 2);
|
||||||
let height = canvas.height;
|
let gui_height = height - (margin * 2);
|
||||||
|
|
||||||
// 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) {
|
if(gui_width < gui_height * INTERFACE.Ratio) {
|
||||||
gui_height = Math.floor(gui_width / INTERFACE.Ratio);
|
gui_height = Math.floor(gui_width / INTERFACE.Ratio);
|
||||||
} else {
|
} else {
|
||||||
gui_width = Math.floor(gui_height * INTERFACE.Ratio);
|
gui_width = Math.floor(gui_height * INTERFACE.Ratio);
|
||||||
}
|
}
|
||||||
|
|
||||||
let gui_scale = gui_height * INTERFACE.Scale;
|
let gui_scale = gui_height * INTERFACE.Scale;
|
||||||
|
|
||||||
// Boundries to center interface
|
INTERFACE_DATA.Ui.area.x = gui_width;
|
||||||
let gui_offset = new MATH.Vec2(
|
INTERFACE_DATA.Ui.area.y = gui_height;
|
||||||
(width - gui_width) / 2,
|
INTERFACE_DATA.Ui.scale = gui_scale;
|
||||||
(height - gui_height) / 2,
|
|
||||||
);
|
INTERFACE_DATA.Ui.offset.x = (width - gui_width) / 2;
|
||||||
|
INTERFACE_DATA.Ui.offset.y = (height - gui_height) / 2;
|
||||||
|
|
||||||
|
INTERFACE_DATA.Ui.board_width = Math.ceil(INTERFACE.BoardWidth * gui_scale);
|
||||||
|
INTERFACE_DATA.Ui.pool_offset = INTERFACE_DATA.Ui.offset.x + Math.floor(INTERFACE.PoolOffset * gui_scale);
|
||||||
|
},
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
let canvas = INTERFACE_DATA.canvas;
|
||||||
|
let ctx = INTERFACE_DATA.context;
|
||||||
|
|
||||||
|
INTERFACE.resize();
|
||||||
|
INTERFACE.resolve_board();
|
||||||
|
|
||||||
|
let width = canvas.width;
|
||||||
|
let height = canvas.height;
|
||||||
|
|
||||||
|
let gui_margin = INTERFACE_DATA.Ui.margin;
|
||||||
|
let gui_offset = INTERFACE_DATA.Ui.offset;
|
||||||
|
let gui_scale = INTERFACE_DATA.Ui.scale;
|
||||||
|
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
@ -77,17 +281,28 @@ const INTERFACE = {
|
|||||||
let radius = INTERFACE.Radius * gui_scale;
|
let radius = INTERFACE.Radius * gui_scale;
|
||||||
let basis_x = gui_offset.x + radius;
|
let basis_x = gui_offset.x + radius;
|
||||||
let basis_y = gui_offset.y + (13 * gui_scale);
|
let basis_y = gui_offset.y + (13 * gui_scale);
|
||||||
let icon_radius = 0.7 * radius;
|
let icon_radius = 0.69 * radius;
|
||||||
|
|
||||||
const TILE_SCALE = 0.9;
|
const TILE_SCALE = 0.9;
|
||||||
ctx.lineWidth = gui_scale * 0.06;
|
ctx.lineWidth = Math.min(gui_scale * 0.06, 3);
|
||||||
|
|
||||||
let draw = new INTERFACE.Draw(ctx, TILE_SCALE * gui_scale);
|
let draw = new INTERFACE.Draw(ctx, TILE_SCALE * gui_scale);
|
||||||
|
|
||||||
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
|
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
|
||||||
let tile = GAME_DATA.board.tiles[i];
|
let tile = GAME_DATA.board.tiles[i];
|
||||||
|
|
||||||
let coord = GAME_DATA.board.tile_to_hex(i);
|
let is_hover = INTERFACE.Ui.tile_is_hover(0, i);
|
||||||
|
let is_select = INTERFACE.Ui.tile_is_select(0, i);
|
||||||
|
|
||||||
|
let tile_state = INTERFACE_DATA.board_state[i][1];
|
||||||
|
let border_state = INTERFACE_DATA.board_state[i][0];
|
||||||
|
|
||||||
|
let coord = HEX.tile_to_hex(i);
|
||||||
|
if((INTERFACE_DATA.player & 1) ^ INTERFACE_DATA.rotate == 1) {
|
||||||
|
coord.x = 8 - coord.x;
|
||||||
|
coord.y = 8 - coord.y;
|
||||||
|
}
|
||||||
|
|
||||||
let gui_x = basis_x + (1.5 * radius * coord.x);
|
let gui_x = basis_x + (1.5 * radius * coord.x);
|
||||||
let gui_y = basis_y - (2 * gui_scale * coord.y) + (gui_scale * coord.x);
|
let gui_y = basis_y - (2 * gui_scale * coord.y) + (gui_scale * coord.x);
|
||||||
|
|
||||||
@ -96,44 +311,54 @@ const INTERFACE = {
|
|||||||
|
|
||||||
// Draw background.
|
// Draw background.
|
||||||
// Select indicator color or default to tile color.
|
// Select indicator color or default to tile color.
|
||||||
if(true) {
|
|
||||||
switch(MATH.mod(coord.x + coord.y, 3)) {
|
switch(MATH.mod(coord.x + coord.y, 3)) {
|
||||||
case 0: ctx.fillStyle = INTERFACE.Color.TileMedium; break;
|
case 0: ctx.fillStyle = INTERFACE.Color.TileMedium; break;
|
||||||
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
|
case 1: ctx.fillStyle = INTERFACE.Color.TileLight; break;
|
||||||
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
|
case 2: ctx.fillStyle = INTERFACE.Color.TileDark; break;
|
||||||
}
|
}
|
||||||
} else {
|
switch(tile_state) {
|
||||||
|
case INTERFACE.TileStatus.Valid: ctx.fillStyle = INTERFACE.Color.HintValid; break;
|
||||||
|
case INTERFACE.TileStatus.Threat: ctx.fillStyle = INTERFACE.Color.HintThreat; break;
|
||||||
|
case INTERFACE.TileStatus.Invalid: ctx.fillStyle = INTERFACE.Color.HintInvalid; break;
|
||||||
|
case INTERFACE.TileStatus.Opponent: ctx.fillStyle = INTERFACE.Color.HintOpponent; break;
|
||||||
|
}
|
||||||
|
if(is_select) {
|
||||||
|
ctx.fillStyle = INTERFACE.Color.HintSelect;
|
||||||
}
|
}
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
draw.hex();
|
draw.hex();
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
|
// Draw border
|
||||||
|
ctx.strokeStyle = INTERFACE.Color.TileBorder;
|
||||||
|
if(tile.piece !== null) {
|
||||||
|
if(GAME_DATA.board.pieces[tile.piece].player == GAME.Const.Player.Dawn) { ctx.strokeStyle = INTERFACE.Color.DawnDark; }
|
||||||
|
else { ctx.strokeStyle = INTERFACE.Color.DuskDark; }
|
||||||
|
}
|
||||||
|
switch(border_state) {
|
||||||
|
case INTERFACE.TileStatus.Valid: ctx.strokeStyle = INTERFACE.Color.HintValidBorder; break;
|
||||||
|
case INTERFACE.TileStatus.Threat: ctx.strokeStyle = INTERFACE.Color.HintThreatBorder; break;
|
||||||
|
case INTERFACE.TileStatus.Invalid: ctx.strokeStyle = INTERFACE.Color.HintInvalidBorder; break;
|
||||||
|
case INTERFACE.TileStatus.Opponent: ctx.strokeStyle = INTERFACE.Color.HintOpponentBorder; break;
|
||||||
|
}
|
||||||
|
if(is_hover) {
|
||||||
|
ctx.strokeStyle = INTERFACE.Color.HintHover;
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
draw.hex();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
// Draw tile content
|
// Draw tile content
|
||||||
if(tile.piece !== null) {
|
if(tile.piece !== null) {
|
||||||
let piece = GAME_DATA.board.pieces[tile.piece];
|
let piece = GAME_DATA.board.pieces[tile.piece];
|
||||||
let game_piece = GAME.Const.Piece[piece.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 border hints
|
||||||
draw.hints(piece);
|
if(!is_hover && border_state == 0) { draw.hints(piece); }
|
||||||
|
|
||||||
// Draw piece icon
|
// Draw piece icon
|
||||||
//if(piece.promoted) { ctx.drawImage(I_PROMOTE, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); }
|
if(piece.promoted) { ctx.drawImage(GAME_ASSET.Image.Promote, -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.); }
|
||||||
//ctx.drawImage(game_piece.assets[piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.);
|
ctx.drawImage(GAME_ASSET.Image.Piece[piece.piece][piece.player], -icon_radius, -icon_radius, icon_radius * 2., icon_radius * 2.);
|
||||||
|
|
||||||
} else {
|
|
||||||
// Draw standard border
|
|
||||||
ctx.strokeStyle = INTERFACE.Color.TileBorder;
|
|
||||||
ctx.beginPath();
|
|
||||||
draw.hex();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@ -142,8 +367,21 @@ const INTERFACE = {
|
|||||||
|
|
||||||
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
|
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
|
||||||
|
|
||||||
|
let player_identity = GAME.Const.Player.Dawn;
|
||||||
|
let player_color = INTERFACE.Color.Dawn;
|
||||||
|
let opponent_color = INTERFACE.Color.Dusk;
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.player == 1 || (INTERFACE_DATA.player == 2 && INTERFACE_DATA.rotate == 1)) {
|
||||||
|
player_identity = GAME.Const.Player.Dusk;
|
||||||
|
player_color = INTERFACE.Color.Dusk;
|
||||||
|
opponent_color = INTERFACE.Color.Dawn;
|
||||||
|
}
|
||||||
|
|
||||||
// Draw player pool
|
// Draw player pool
|
||||||
for(let i = 0; i < 6; ++i) {
|
for(let i = 0; i < 6; ++i) {
|
||||||
|
let is_hover = INTERFACE.Ui.tile_is_hover(1, i);
|
||||||
|
let is_select = INTERFACE.Ui.tile_is_select(1, i);
|
||||||
|
|
||||||
let gui_x = basis_x + (radius * 14);
|
let gui_x = basis_x + (radius * 14);
|
||||||
let gui_y = basis_y - (9 - (2 * i)) * gui_scale;
|
let gui_y = basis_y - (9 - (2 * i)) * gui_scale;
|
||||||
|
|
||||||
@ -151,79 +389,128 @@ const INTERFACE = {
|
|||||||
ctx.translate(gui_x, gui_y);
|
ctx.translate(gui_x, gui_y);
|
||||||
|
|
||||||
// Draw background if indicator is present.
|
// Draw background if indicator is present.
|
||||||
if(true) {
|
if(is_select) { ctx.fillStyle = INTERFACE.Color.HintSelect; }
|
||||||
ctx.fillStyle = INTERFACE.Color.TileDark;
|
else { ctx.fillStyle = INTERFACE.Color.TileDark; }
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
draw.hex();
|
draw.hex();
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
|
||||||
|
|
||||||
// Draw border
|
// Draw border
|
||||||
ctx.strokeStyle = INTERFACE.Color.Dawn;
|
if(is_select || is_hover || INTERFACE_DATA.player == (GAME_DATA.turn & 1) || (INTERFACE_DATA.player == 2 && player_identity == (GAME_DATA.turn & 1))) {
|
||||||
|
if(is_hover) { ctx.strokeStyle = INTERFACE.Color.HintHover; }
|
||||||
|
else { ctx.strokeStyle = player_color; }
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
draw.hex();
|
draw.hex();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillStyle = INTERFACE.Color.Dawn;
|
// Draw image
|
||||||
|
ctx.drawImage(GAME_ASSET.Image.Piece[i][+(player_identity == 1)], -icon_radius * 0.55, -icon_radius * 0.8, icon_radius * 1.6, icon_radius * 1.6);
|
||||||
|
|
||||||
|
// Draw count
|
||||||
|
ctx.fillStyle = player_color;
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "left";
|
||||||
ctx.fillText(GAME_DATA.dawn.pool.pieces[i], -0.5 * radius, 0);
|
ctx.fillText(GAME_DATA.pools[+(player_identity == 1)].pieces[i], -0.6 * radius, 0);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw opponent pool
|
// Draw opponent pool
|
||||||
for(let i = 0; i < 6; ++i) {
|
for(let i = 0; i < 6; ++i) {
|
||||||
|
let is_hover = INTERFACE.Ui.tile_is_hover(1, 6 + i);
|
||||||
|
let is_select = INTERFACE.Ui.tile_is_select(1, 6 + i);
|
||||||
|
|
||||||
let gui_x = basis_x + (radius * 15.5);
|
let gui_x = basis_x + (radius * 15.5);
|
||||||
let gui_y = basis_y - (8 - (2 * i)) * gui_scale;
|
let gui_y = basis_y - (8 - (2 * i)) * gui_scale;
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(gui_x, gui_y);
|
ctx.translate(gui_x, gui_y);
|
||||||
|
|
||||||
// Draw background.
|
// Draw background if indicator is present.
|
||||||
// Select indicator color or default to tile color.
|
if(is_select) { ctx.fillStyle = INTERFACE.Color.HintSelect; }
|
||||||
if(true) {
|
else { ctx.fillStyle = INTERFACE.Color.TileDark; }
|
||||||
ctx.fillStyle = INTERFACE.Color.TileDark;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
draw.hex();
|
draw.hex();
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
|
|
||||||
// Draw border
|
// Draw border
|
||||||
ctx.strokeStyle = INTERFACE.Color.Dusk;
|
if(is_select || is_hover || (INTERFACE_DATA.player == 2 && player_identity != (GAME_DATA.turn & 1))) {
|
||||||
|
if(is_hover) { ctx.strokeStyle = INTERFACE.Color.HintHover; }
|
||||||
|
else { ctx.strokeStyle = opponent_color; }
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
draw.hex();
|
draw.hex();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
ctx.fillStyle = INTERFACE.Color.Dusk;
|
// Draw image
|
||||||
|
ctx.drawImage(GAME_ASSET.Image.Piece[i][+(player_identity != 1)], -icon_radius * 0.55, -icon_radius * 0.8, icon_radius * 1.6, icon_radius * 1.6);
|
||||||
|
|
||||||
|
// Draw count
|
||||||
|
ctx.fillStyle = opponent_color;
|
||||||
ctx.textBaseline = "middle";
|
ctx.textBaseline = "middle";
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "center";
|
||||||
ctx.fillText(GAME_DATA.dusk.pool.pieces[i], -0.5 * radius, 0);
|
ctx.fillText(GAME_DATA.pools[+(player_identity != 1)].pieces[i], -0.5 * radius, 0);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Draw informational text
|
// Draw informational text
|
||||||
|
|
||||||
|
let handle_pos = [
|
||||||
|
new MATH.Vec2(
|
||||||
|
basis_x + (radius * 12),
|
||||||
|
basis_y - (11 * gui_scale)
|
||||||
|
),
|
||||||
|
new MATH.Vec2(
|
||||||
|
basis_x + (radius * 12),
|
||||||
|
basis_y + (3 * gui_scale)
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Player handles
|
||||||
|
ctx.font = Math.ceil(gui_scale / 1.3) + "px sans-serif";
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.handles[0] !== null) {
|
||||||
|
let pos = handle_pos[(1 ^ INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1];
|
||||||
|
|
||||||
|
ctx.fillStyle = INTERFACE.Color.Dawn;
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.fillText(INTERFACE_DATA.handles[0], pos.x, pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.handles[1] !== null) {
|
||||||
|
let pos = handle_pos[(INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1];
|
||||||
|
|
||||||
|
ctx.fillStyle = INTERFACE.Color.Dusk;
|
||||||
|
ctx.textBaseline = "middle";
|
||||||
|
ctx.textAlign = "center";
|
||||||
|
ctx.fillText(INTERFACE_DATA.handles[1], pos.x, pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tile information
|
||||||
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
|
ctx.font = Math.ceil(gui_scale / 2) + "px sans-serif";
|
||||||
|
|
||||||
|
if(INTERFACE_DATA.hover !== null) {
|
||||||
|
let text = "";
|
||||||
|
if(INTERFACE_DATA.hover.source == 0) {
|
||||||
|
text = INTERFACE.Ui.hex_to_alnum(INTERFACE_DATA.hover.hex);
|
||||||
|
let piece_id = GAME_DATA.board.tiles[INTERFACE_DATA.hover.tile].piece;
|
||||||
|
if(piece_id !== null) {
|
||||||
|
let piece_class = GAME_DATA.board.pieces[piece_id].piece;
|
||||||
|
text += " " + GAME.Const.Piece[piece_class].name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = " " + GAME.Const.Piece[INTERFACE_DATA.hover.tile % 6].name;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// Dawn handle
|
|
||||||
ctx.fillStyle = INTERFACE.Color.Text;
|
ctx.fillStyle = INTERFACE.Color.Text;
|
||||||
ctx.textBaseline = "top";
|
ctx.textBaseline = "top";
|
||||||
ctx.textAlign = "center";
|
ctx.textAlign = "left";
|
||||||
ctx.fillText(GAME_DATA.turn, width / 2, gui_margin);
|
ctx.fillText(text, gui_margin, 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
|
// Number of moves
|
||||||
ctx.fillStyle = INTERFACE.Color.Text;
|
ctx.fillStyle = INTERFACE.Color.Text;
|
||||||
@ -257,7 +544,7 @@ const INTERFACE = {
|
|||||||
let descriptor = GAME.Const.Piece[piece.piece];
|
let descriptor = GAME.Const.Piece[piece.piece];
|
||||||
let moves = descriptor.moves;
|
let moves = descriptor.moves;
|
||||||
if(piece.promoted) { moves = descriptor.pmoves; }
|
if(piece.promoted) { moves = descriptor.pmoves; }
|
||||||
if(piece.player == GAME.Const.Player.Dusk) { moves = moves.rotate(); }
|
if(((piece.player ^ INTERFACE_DATA.player ^ INTERFACE_DATA.rotate) & 1) != 0) { moves = moves.rotate(); }
|
||||||
moves = moves.direction;
|
moves = moves.direction;
|
||||||
|
|
||||||
for(let mask = BITWISE.lsb(moves); moves > 0; mask = BITWISE.lsb(moves)) {
|
for(let mask = BITWISE.lsb(moves); moves > 0; mask = BITWISE.lsb(moves)) {
|
||||||
@ -314,26 +601,85 @@ const INTERFACE = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
init(player_id) {
|
Ui: {
|
||||||
|
tile_is_hover(source, tile) {
|
||||||
|
return INTERFACE_DATA.hover !== null
|
||||||
|
&& INTERFACE_DATA.hover.source == source
|
||||||
|
&& INTERFACE_DATA.hover.tile == tile;
|
||||||
|
},
|
||||||
|
|
||||||
|
tile_is_select(source, tile) {
|
||||||
|
return INTERFACE_DATA.select !== null
|
||||||
|
&& INTERFACE_DATA.select.source == source
|
||||||
|
&& INTERFACE_DATA.select.tile == tile;
|
||||||
|
},
|
||||||
|
|
||||||
|
pool_hex_is_valid(hex) {
|
||||||
|
return (hex.x >= 0 && hex.x < 2 && hex.y >= 0 && hex.y < 6);
|
||||||
|
},
|
||||||
|
|
||||||
|
hex_to_alnum(hex) {
|
||||||
|
return String.fromCharCode(65 + hex.x) + (hex.y + 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
match_select(a, b) {
|
||||||
|
if(a !== null && b !== null) {
|
||||||
|
return (
|
||||||
|
a.source == b.source
|
||||||
|
&& a.tile == b.tile
|
||||||
|
&& a.hex.x == b.hex.x
|
||||||
|
&& a.hex.y == b.hex.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
init(data) {
|
||||||
GAME.init();
|
GAME.init();
|
||||||
|
|
||||||
INTERFACE_DATA = {
|
INTERFACE_DATA = {
|
||||||
|
token:data.token,
|
||||||
|
|
||||||
canvas: document.getElementById("game"),
|
canvas: document.getElementById("game"),
|
||||||
context: null,
|
context: null,
|
||||||
|
|
||||||
player_id: player_id,
|
player: data.mode,
|
||||||
|
rotate: 0,
|
||||||
|
|
||||||
|
hover: null,
|
||||||
|
select: null,
|
||||||
|
|
||||||
|
handles: [null, null],
|
||||||
|
board_state: [ ],
|
||||||
|
|
||||||
message: null,
|
message: null,
|
||||||
|
|
||||||
|
Ui: {
|
||||||
|
scale: 0,
|
||||||
|
margin: 0,
|
||||||
|
offset: new MATH.Vec2(),
|
||||||
|
area: new MATH.Vec2(),
|
||||||
|
|
||||||
|
board_width: 0,
|
||||||
|
pool_offset: 0,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
for(let i = 0; i < 61; ++i) { INTERFACE_DATA.board_state.push([0, 0]); }
|
||||||
|
|
||||||
let canvas = INTERFACE_DATA.canvas;
|
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("mouseout", INTERFACE.unhover);
|
||||||
canvas.addEventListener("mousedown", INTERFACE.click);
|
canvas.addEventListener("mousedown", INTERFACE.click);
|
||||||
window.addEventListener("resize", INTERFACE.draw);
|
window.addEventListener("resize", INTERFACE.draw);
|
||||||
this.draw();
|
|
||||||
|
MESSAGE_COMPOSE([
|
||||||
|
PACK.u16(OpCode.GameState),
|
||||||
|
INTERFACE_DATA.token,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -345,19 +691,45 @@ const INTERFACE = {
|
|||||||
message(code, data) {
|
message(code, data) {
|
||||||
switch(code) {
|
switch(code) {
|
||||||
case OpCode.GameState: {
|
case OpCode.GameState: {
|
||||||
// Build game state
|
GAME_DATA.turn = data.turn;
|
||||||
|
|
||||||
|
if(data.dawn.length > 0) { INTERFACE_DATA.handles[0] = data.dawn; }
|
||||||
|
if(data.dusk.length > 0) { INTERFACE_DATA.handles[1] = data.dusk; }
|
||||||
|
|
||||||
|
// Clear piece placement.
|
||||||
|
for(let i = 0; i < GAME_DATA.board.tiles.length; ++i) {
|
||||||
|
GAME_DATA.board.tiles[i].piece = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update pools.
|
||||||
|
GAME_DATA.pools[0].pieces = data.pool_dawn;
|
||||||
|
GAME_DATA.pools[1].pieces = data.pool_dusk;
|
||||||
|
|
||||||
|
// Replace pieces list.
|
||||||
|
for(let i = 0; i < GAME_DATA.board.pieces.length; ++i) {
|
||||||
|
GAME_DATA.board.pieces[i] = data.pieces[i];
|
||||||
|
if(data.pieces[i] !== null) {
|
||||||
|
GAME_DATA.board.tiles[data.pieces[i].tile].piece = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GAME_DATA.update_board();
|
||||||
INTERFACE.draw();
|
INTERFACE.draw();
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case OpCode.GamePlay: {
|
case OpCode.GamePlay: {
|
||||||
// Apply play to board
|
if(data.status == Status.Ok) {
|
||||||
GAME_DATA.turn += 1;
|
GAME_DATA.process(data.move);
|
||||||
|
|
||||||
INTERFACE.draw();
|
INTERFACE.draw();
|
||||||
|
}
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
rotate() {
|
||||||
|
INTERFACE_DATA.rotate = +(!INTERFACE_DATA.rotate);
|
||||||
|
INTERFACE.draw();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
INTERFACE.Radius = 2.0 / Math.sqrt(3.0);
|
INTERFACE.Radius = 2.0 / Math.sqrt(3.0);
|
||||||
@ -378,3 +750,6 @@ INTERFACE.HexVertex = [
|
|||||||
// top-left face
|
// top-left face
|
||||||
new MATH.Vec2(-INTERFACE.Radius, 0),
|
new MATH.Vec2(-INTERFACE.Radius, 0),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
INTERFACE.BoardWidth = INTERFACE.Radius * 14;
|
||||||
|
INTERFACE.PoolOffset = INTERFACE.BoardWidth;
|
||||||
|
140
www/js/scene.js
140
www/js/scene.js
@ -2,7 +2,7 @@ const SCENES = {
|
|||||||
Init:{
|
Init:{
|
||||||
load() {
|
load() {
|
||||||
LOAD_STACK(SCENES.Offline);
|
LOAD_STACK(SCENES.Offline);
|
||||||
CONTEXT.Scene = SCENES.Online;
|
CONTEXT.Scene = SCENES.Browse;
|
||||||
RECONNECT();
|
RECONNECT();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -79,7 +79,7 @@ const SCENES = {
|
|||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
case Status.Ok: {
|
case Status.Ok: {
|
||||||
CONTEXT.Auth = data;
|
CONTEXT.Auth = data;
|
||||||
LOAD(SCENES.Online);
|
LOAD(SCENES.Browse);
|
||||||
} break;
|
} break;
|
||||||
default: {
|
default: {
|
||||||
submit.removeAttribute("disabled");
|
submit.removeAttribute("disabled");
|
||||||
@ -158,7 +158,7 @@ const SCENES = {
|
|||||||
switch(data.status) {
|
switch(data.status) {
|
||||||
case Status.Ok: {
|
case Status.Ok: {
|
||||||
CONTEXT.Auth = data;
|
CONTEXT.Auth = data;
|
||||||
LOAD(SCENES.Online);
|
LOAD(SCENES.Browse);
|
||||||
} break;
|
} break;
|
||||||
case Status.Error: {
|
case Status.Error: {
|
||||||
submit.removeAttribute("disabled");
|
submit.removeAttribute("disabled");
|
||||||
@ -172,15 +172,15 @@ const SCENES = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
Online:{
|
Browse:{
|
||||||
load() {
|
load() {
|
||||||
UI.mainmenu();
|
CONTEXT.Data = {
|
||||||
|
|
||||||
CONTEXT.data = {
|
|
||||||
page:0,
|
page:0,
|
||||||
records:[],
|
records:[],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UI.mainmenu();
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
left_buttons.push(UI.button("Start", () => {
|
left_buttons.push(UI.button("Start", () => {
|
||||||
@ -210,25 +210,40 @@ const SCENES = {
|
|||||||
MESSAGE_SESSION_LIST(0, 0, false, false);
|
MESSAGE_SESSION_LIST(0, 0, false, false);
|
||||||
},
|
},
|
||||||
message(code, data) {
|
message(code, data) {
|
||||||
if(code == OpCode.SessionList) {
|
switch(code) {
|
||||||
|
case OpCode.SessionList: {
|
||||||
let table = document.getElementById("content");
|
let table = document.getElementById("content");
|
||||||
UI.clear(table);
|
UI.clear(table);
|
||||||
|
|
||||||
if(data !== null) {
|
if(data !== null) {
|
||||||
table.appendChild(UI.session_table(data.records));
|
table.appendChild(UI.session_table(data.records));
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
|
case OpCode.SessionJoin: {
|
||||||
|
if(data.status == Status.Ok) {
|
||||||
|
LOAD(SCENES.Game, data);
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Continue:{
|
Continue:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(CONTEXT.Auth === null) return false;
|
||||||
|
|
||||||
|
CONTEXT.Data = {
|
||||||
|
page:0,
|
||||||
|
records:[],
|
||||||
|
};
|
||||||
|
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
left_buttons.push(UI.button("Start", null));
|
left_buttons.push(UI.button("Start", () => {
|
||||||
|
MESSAGE_SESSION_START();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -237,41 +252,56 @@ const SCENES = {
|
|||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
|
UI.button("Refresh", null),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
table.setAttribute("id", "content");
|
table.setAttribute("id", "content");
|
||||||
|
table.setAttribute("class", "list");
|
||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
MAIN.setAttribute("class", "list");
|
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
|
MESSAGE_SESSION_LIST(0, 0, true, false);
|
||||||
},
|
},
|
||||||
message(code, data) {
|
message(code, data) {
|
||||||
if(code == OpCode.SessionList) {
|
switch(code) {
|
||||||
|
case OpCode.SessionList: {
|
||||||
let table = document.getElementById("content");
|
let table = document.getElementById("content");
|
||||||
UI.clear(table);
|
UI.clear(table);
|
||||||
|
|
||||||
if(data !== null) {
|
if(data !== null) {
|
||||||
table.appendChild(UI.session_table(data.records));
|
table.appendChild(UI.session_table(data.records));
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
|
case OpCode.SessionJoin: {
|
||||||
|
if(data.status == Status.Ok) {
|
||||||
|
LOAD(SCENES.Game, data);
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Join:{
|
Join:{
|
||||||
load() {
|
load() {
|
||||||
if(CONTEXT.Auth === null) return false;
|
if(CONTEXT.Auth === null) return false;
|
||||||
|
|
||||||
|
CONTEXT.Data = {
|
||||||
|
page:0,
|
||||||
|
records:[],
|
||||||
|
};
|
||||||
|
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
left_buttons.push(UI.button("Start", null));
|
left_buttons.push(UI.button("Start", () => {
|
||||||
|
MESSAGE_SESSION_START();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -280,40 +310,56 @@ const SCENES = {
|
|||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
|
UI.button("Refresh", null),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
table.setAttribute("id", "content");
|
table.setAttribute("id", "content");
|
||||||
|
table.setAttribute("class", "list");
|
||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
MAIN.setAttribute("class", "list");
|
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
|
MESSAGE_SESSION_LIST(0, 1, false, false);
|
||||||
},
|
},
|
||||||
message(code, data) {
|
message(code, data) {
|
||||||
if(code == OpCode.SessionList) {
|
switch(code) {
|
||||||
|
case OpCode.SessionList: {
|
||||||
let table = document.getElementById("content");
|
let table = document.getElementById("content");
|
||||||
UI.clear(table);
|
UI.clear(table);
|
||||||
|
|
||||||
if(data !== null) {
|
if(data !== null) {
|
||||||
table.appendChild(UI.session_table(data.records));
|
table.appendChild(UI.session_table(data.records));
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
|
case OpCode.SessionJoin: {
|
||||||
|
if(data.status == Status.Ok) {
|
||||||
|
LOAD(SCENES.Game, data);
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
Live:{
|
Live:{
|
||||||
load() {
|
load() {
|
||||||
|
if(CONTEXT.Auth === null) return false;
|
||||||
|
|
||||||
|
CONTEXT.Data = {
|
||||||
|
page:0,
|
||||||
|
records:[],
|
||||||
|
};
|
||||||
|
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
let left_buttons = [ ];
|
let left_buttons = [ ];
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
left_buttons.push(UI.button("Start", null));
|
left_buttons.push(UI.button("Start", () => {
|
||||||
|
MESSAGE_SESSION_START();
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -322,35 +368,47 @@ const SCENES = {
|
|||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
UI.button("▶", null),
|
UI.button("▶", null),
|
||||||
|
UI.button("Refresh", null),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
let table = document.createElement("table");
|
let table = document.createElement("table");
|
||||||
table.setAttribute("id", "content");
|
table.setAttribute("id", "content");
|
||||||
|
table.setAttribute("class", "list");
|
||||||
MAIN.appendChild(table);
|
MAIN.appendChild(table);
|
||||||
|
|
||||||
MAIN.setAttribute("class", "list");
|
|
||||||
|
|
||||||
SCENE.refresh();
|
SCENE.refresh();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh() {
|
||||||
|
MESSAGE_SESSION_LIST(0, 2, false, true);
|
||||||
},
|
},
|
||||||
message(code, data) {
|
message(code, data) {
|
||||||
if(code == OpCode.SessionList) {
|
switch(code) {
|
||||||
|
case OpCode.SessionList: {
|
||||||
let table = document.getElementById("content");
|
let table = document.getElementById("content");
|
||||||
UI.clear(table);
|
UI.clear(table);
|
||||||
|
|
||||||
if(data !== null) {
|
if(data !== null) {
|
||||||
table.appendChild(UI.session_table(data.records));
|
table.appendChild(UI.session_table(data.records));
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
|
case OpCode.SessionJoin: {
|
||||||
|
if(data.status == Status.Ok) {
|
||||||
|
LOAD(SCENES.Game, data);
|
||||||
}
|
}
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
|
||||||
History:{
|
History:{
|
||||||
load() {
|
load() {
|
||||||
|
CONTEXT.Data = {
|
||||||
|
page:0,
|
||||||
|
records:[],
|
||||||
|
};
|
||||||
|
|
||||||
UI.mainmenu();
|
UI.mainmenu();
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
@ -415,19 +473,20 @@ const SCENES = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
Game:{
|
Game:{
|
||||||
load() {
|
load(data) {
|
||||||
|
let buttons_bottom = [ ];
|
||||||
|
if(data.mode != 2) { buttons_bottom.push(UI.button("Retire", () => { })); }
|
||||||
|
buttons_bottom.push(UI.button("Back", () => { LOAD(SCENES.Browse) }));
|
||||||
|
|
||||||
UI.nav([
|
UI.nav([
|
||||||
UI.button("Rotate", () => { }),
|
UI.button("Rotate", () => { INTERFACE.rotate(); }),
|
||||||
], [
|
], buttons_bottom);
|
||||||
UI.button("Back", () => { LOAD(SCENES.Online) }),
|
|
||||||
UI.button("Retire", () => { }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
let canvas = document.createElement("canvas");
|
let canvas = document.createElement("canvas");
|
||||||
canvas.setAttribute("id", "game");
|
canvas.setAttribute("id", "game");
|
||||||
MAIN.appendChild(canvas);
|
MAIN.appendChild(canvas);
|
||||||
|
|
||||||
INTERFACE.init();
|
INTERFACE.init(data);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -438,28 +497,39 @@ const SCENES = {
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
message(code, data) {
|
message(code, data) {
|
||||||
if(code == OpCode.GameState || code == OpCode.GamePlay) {
|
switch(code) {
|
||||||
|
case OpCode.GameState: {
|
||||||
|
if(data.status == Status.Ok) {
|
||||||
INTERFACE.message(code, data);
|
INTERFACE.message(code, data);
|
||||||
|
} else {
|
||||||
|
LOAD(SCENES.Browse);
|
||||||
|
}
|
||||||
|
} break;
|
||||||
|
case OpCode.GamePlay: {
|
||||||
|
INTERFACE.message(code, data);
|
||||||
|
} break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function LOAD(scene) {
|
function LOAD(scene, data=null) {
|
||||||
UNLOAD();
|
UNLOAD();
|
||||||
UI.rebuild();
|
UI.rebuild();
|
||||||
SCENE = scene;
|
SCENE = scene;
|
||||||
CONTEXT.Scene = SCENE;
|
CONTEXT.Scene = SCENE;
|
||||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
CONTEXT.Data = null;
|
||||||
|
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function UNLOAD() {
|
function UNLOAD() {
|
||||||
if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
|
if(SCENE !== null && SCENE.unload !== undefined) { SCENE.unload(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
function LOAD_STACK(scene) {
|
function LOAD_STACK(scene, data=null) {
|
||||||
UNLOAD();
|
UNLOAD();
|
||||||
UI.rebuild();
|
UI.rebuild();
|
||||||
SCENE = scene;
|
SCENE = scene;
|
||||||
if(!SCENE.load()) { LOAD(SCENES.Online); }
|
CONTEXT.Data = null;
|
||||||
|
if(!SCENE.load(data)) { LOAD(SCENES.Browse); }
|
||||||
}
|
}
|
||||||
|
126
www/js/system.js
126
www/js/system.js
@ -39,8 +39,6 @@ function RESUME() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function MESSAGE(event) {
|
function MESSAGE(event) {
|
||||||
console.log("Message received.");
|
|
||||||
|
|
||||||
if(SCENE.message !== undefined) {
|
if(SCENE.message !== undefined) {
|
||||||
let bytes = new Uint8Array(event.data);
|
let bytes = new Uint8Array(event.data);
|
||||||
let code = 0;
|
let code = 0;
|
||||||
@ -157,9 +155,9 @@ function MESSAGE(event) {
|
|||||||
index = result.index;
|
index = result.index;
|
||||||
record.turn = result.data;
|
record.turn = result.data;
|
||||||
|
|
||||||
if(index <= bytes.length + 5) {
|
if(index <= bytes.length + 3) {
|
||||||
let move = new Uint8Array(5);
|
let move = new Uint8Array(3);
|
||||||
for(let i = 0; i < 5; ++i) {
|
for(let i = 0; i < 3; ++i) {
|
||||||
move[i] = bytes[index];
|
move[i] = bytes[index];
|
||||||
index += 1;
|
index += 1;
|
||||||
}
|
}
|
||||||
@ -179,29 +177,131 @@ function MESSAGE(event) {
|
|||||||
|
|
||||||
case OpCode.SessionCreate:
|
case OpCode.SessionCreate:
|
||||||
case OpCode.SessionJoin: {
|
case OpCode.SessionJoin: {
|
||||||
|
console.log("RECV SessionCreate/Join");
|
||||||
|
|
||||||
if(bytes.length - index == 11) {
|
if(bytes.length - index == 11) {
|
||||||
data = {
|
data = {
|
||||||
|
status:0,
|
||||||
token:new Uint8Array(8),
|
token:new Uint8Array(8),
|
||||||
mode:2,
|
mode:2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = UNPACK.u16(data, index);
|
result = UNPACK.u16(bytes, index);
|
||||||
index = result.index;
|
index = result.index;
|
||||||
let status = result.data;
|
data.status = result.data;
|
||||||
|
|
||||||
result = UNPACK.u8(data, index);
|
result = UNPACK.u8(bytes, index);
|
||||||
index = result.index;
|
index = result.index;
|
||||||
data.mode = result.data;
|
data.mode = result.data;
|
||||||
|
|
||||||
for(let i = 0; i < 8; ++i) { data.token[i] = data[index++]; }
|
for(let i = 0; i < 8; ++i) { data.token[i] = bytes[index++]; }
|
||||||
|
|
||||||
if(status == Status.Ok) {
|
|
||||||
LOAD(SCENES.Game);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case OpCode.GameState: {
|
||||||
|
console.log("RECV GameState");
|
||||||
|
|
||||||
|
//if(bytes.length - index >= 22) {
|
||||||
|
data = {
|
||||||
|
status:0,
|
||||||
|
turn:0,
|
||||||
|
dawn:"",
|
||||||
|
dusk:"",
|
||||||
|
pool_dawn:[ ],
|
||||||
|
pool_dusk:[ ],
|
||||||
|
pieces:[ ],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Status
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.status = result.data;
|
||||||
|
|
||||||
|
// Turn
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.turn = result.data;
|
||||||
|
|
||||||
|
// Handles
|
||||||
|
result = UNPACK.string(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.dawn = result.data;
|
||||||
|
|
||||||
|
result = UNPACK.string(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.dusk = result.data;
|
||||||
|
|
||||||
|
// Dawn pool
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
let dawn_pool_bits = result.data;
|
||||||
|
|
||||||
|
data.pool_dawn.push(dawn_pool_bits & 0x1F);
|
||||||
|
data.pool_dawn.push((dawn_pool_bits >> 5) & 0x3);
|
||||||
|
data.pool_dawn.push((dawn_pool_bits >> 7) & 0x3);
|
||||||
|
data.pool_dawn.push((dawn_pool_bits >> 9) & 0x3);
|
||||||
|
data.pool_dawn.push((dawn_pool_bits >> 11) & 0x3);
|
||||||
|
data.pool_dawn.push((dawn_pool_bits >> 13) & 0x1);
|
||||||
|
|
||||||
|
// Dusk pool
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
let dusk_pool_bits = result.data;
|
||||||
|
|
||||||
|
data.pool_dusk.push(dusk_pool_bits & 0x1f);
|
||||||
|
data.pool_dusk.push((dusk_pool_bits >> 5) & 0x3);
|
||||||
|
data.pool_dusk.push((dusk_pool_bits >> 7) & 0x3);
|
||||||
|
data.pool_dusk.push((dusk_pool_bits >> 9) & 0x3);
|
||||||
|
data.pool_dusk.push((dusk_pool_bits >> 11) & 0x3);
|
||||||
|
data.pool_dusk.push((dusk_pool_bits >> 13) & 0x1);
|
||||||
|
|
||||||
|
// Pieces
|
||||||
|
for(let i = 0; i < 38; ++i) {
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
let piece_data = result.data;
|
||||||
|
|
||||||
|
if((piece_data & 1) != 0) {
|
||||||
|
let piece = new GAME.Piece((piece_data >> 1) & 0x7, (piece_data >> 5) & 1);
|
||||||
|
piece.promoted = ((piece_data >> 4) & 1) != 0;
|
||||||
|
piece.tile = (piece_data >> 6) & 0x3F;
|
||||||
|
|
||||||
|
data.pieces.push(piece);
|
||||||
|
} else {
|
||||||
|
data.pieces.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case OpCode.GamePlay: {
|
||||||
|
console.log("RECV GamePlay");
|
||||||
|
|
||||||
|
data = {
|
||||||
|
status:0,
|
||||||
|
move:new GAME.Play(0, 0, 0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Status
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.status = result.data;
|
||||||
|
|
||||||
|
// Turn
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.turn = result.data;
|
||||||
|
|
||||||
|
// Play description
|
||||||
|
result = UNPACK.u16(bytes, index);
|
||||||
|
index = result.index;
|
||||||
|
data.move.source = result.data & 1;
|
||||||
|
data.move.from = (result.data >> 1) & 0x3F;
|
||||||
|
data.move.to = (result.data >> 7) & 0x3F;
|
||||||
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
console.log("RECV Undefined " + code);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ const UI = {
|
|||||||
let top = [ ];
|
let top = [ ];
|
||||||
let bottom = [ ];
|
let bottom = [ ];
|
||||||
|
|
||||||
top.push(UI.button("Online", () => { LOAD(SCENES.Online); }));
|
top.push(UI.button("Browse", () => { LOAD(SCENES.Browse); }));
|
||||||
if(CONTEXT.Auth !== null) {
|
if(CONTEXT.Auth !== null) {
|
||||||
top.push(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
top.push(UI.button("Continue", () => { LOAD(SCENES.Continue); }));
|
||||||
top.push(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
top.push(UI.button("Join", () => { LOAD(SCENES.Join); }));
|
||||||
|
@ -37,8 +37,8 @@ const UNPACK = {
|
|||||||
if(index + 4 <= data.length) {
|
if(index + 4 <= data.length) {
|
||||||
result = (data[index] << 24)
|
result = (data[index] << 24)
|
||||||
+ (data[index + 1] << 16)
|
+ (data[index + 1] << 16)
|
||||||
+ (data[index + 1] << 8)
|
+ (data[index + 2] << 8)
|
||||||
+ data[index + 1];
|
+ data[index + 3];
|
||||||
index += 4;
|
index += 4;
|
||||||
}
|
}
|
||||||
return { data: result, index: index };
|
return { data: result, index: index };
|
||||||
@ -59,7 +59,7 @@ const UNPACK = {
|
|||||||
index += length;
|
index += length;
|
||||||
|
|
||||||
result_str = dec.decode(bytes);
|
result_str = dec.decode(bytes);
|
||||||
}
|
} else { console.log("INV DATA LEN"); }
|
||||||
|
|
||||||
return { data: result_str, index: index };
|
return { data: result_str, index: index };
|
||||||
},
|
},
|
||||||
@ -112,10 +112,12 @@ const UNPACK = {
|
|||||||
|
|
||||||
const BITWISE = {
|
const BITWISE = {
|
||||||
lsb(x) {
|
lsb(x) {
|
||||||
|
// Least significant bit
|
||||||
return x & -x;
|
return x & -x;
|
||||||
},
|
},
|
||||||
|
|
||||||
ffs(x) {
|
ffs(x) {
|
||||||
|
// Find first set
|
||||||
return 31 - Math.clz32(x & -x);
|
return 31 - Math.clz32(x & -x);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -151,6 +153,20 @@ const MATH = {
|
|||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(v) {
|
||||||
|
this.x += v.x;
|
||||||
|
this.y += v.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
mul(v) {
|
||||||
|
this.x *= v;
|
||||||
|
this.y *= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
copy() {
|
||||||
|
return new MATH.Vec2(this.x, this.y);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
sign(a) {
|
sign(a) {
|
||||||
@ -160,6 +176,10 @@ const MATH = {
|
|||||||
mod(a, b) {
|
mod(a, b) {
|
||||||
return ((a % b) + b) % b
|
return ((a % b) + b) % b
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sign_branch(v) {
|
||||||
|
return (v * 2) - 1;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLOR = {
|
const COLOR = {
|
||||||
@ -171,3 +191,34 @@ const COLOR = {
|
|||||||
return "rgba(" + ur + "," + ug + "," + ub + "," + ua + ")";
|
return "rgba(" + ur + "," + ug + "," + ub + "," + ua + ")";
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HEX = {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
|
||||||
|
is_valid(hex) {
|
||||||
|
const COLUMNS = [
|
||||||
|
new MATH.Vec2(0, 4),
|
||||||
|
new MATH.Vec2(0, 5),
|
||||||
|
new MATH.Vec2(0, 6),
|
||||||
|
new MATH.Vec2(0, 7),
|
||||||
|
new MATH.Vec2(0, 8),
|
||||||
|
new MATH.Vec2(1, 8),
|
||||||
|
new MATH.Vec2(2, 8),
|
||||||
|
new MATH.Vec2(3, 8),
|
||||||
|
new MATH.Vec2(4, 8),
|
||||||
|
];
|
||||||
|
return (hex.x >= 0 && hex.x < 9 && hex.y >= COLUMNS[hex.x].x && hex.y <= COLUMNS[hex.x].y);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user