Initialize repository.
This commit is contained in:
commit
329b97be9f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/target
|
||||
Cargo.lock
|
8
Cargo.toml
Normal file
8
Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
members = [
|
||||
"game",
|
||||
"client-web",
|
||||
"server",
|
||||
]
|
2
LICENSE.txt
Normal file
2
LICENSE.txt
Normal file
@ -0,0 +1,2 @@
|
||||
Project: Kirisame, Omen
|
||||
https://ykr.info/license
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Omen
|
||||
|
||||
Omen is an abstract strategy game—similar to chess and shogi—where players take turns moving pieces across a hexagon grid to capture their opponents' king.
|
||||
|
||||
## Installation
|
||||
|
||||
The project is composed of two core applications, a web client and a server.
|
||||
|
||||
### Building
|
24
client-web/Cargo.toml
Normal file
24
client-web/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "client-web"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.92"
|
||||
|
||||
game = { path = "../game" }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.69"
|
||||
features = [
|
||||
'Document',
|
||||
'Element',
|
||||
'HtmlElement',
|
||||
'Node',
|
||||
'Window',
|
||||
|
||||
'Event',
|
||||
]
|
67
client-web/src/app/mod.rs
Normal file
67
client-web/src/app/mod.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use crate::prelude::Scene;
|
||||
|
||||
pub mod session; use session::Session;
|
||||
pub mod scene;
|
||||
|
||||
pub struct App {
|
||||
pub session:Session,
|
||||
|
||||
pub document:web_sys::Document,
|
||||
pub menu:web_sys::Element,
|
||||
pub container:web_sys::Element,
|
||||
|
||||
unload:Option<fn()>,
|
||||
}
|
||||
impl App {
|
||||
pub fn init() -> Result<Self,()>
|
||||
{
|
||||
match web_sys::window() {
|
||||
Some(window) => match window.document() {
|
||||
Some(document) => {
|
||||
let menu = document.create_element("nav");
|
||||
let container = document.create_element("main");
|
||||
|
||||
if menu.is_ok() && container.is_ok() {
|
||||
Ok(Self {
|
||||
session:Session::new(),
|
||||
|
||||
document:document,
|
||||
menu:menu.unwrap(),
|
||||
container:container.unwrap(),
|
||||
|
||||
unload:None,
|
||||
})
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load<S:Scene>(&mut self)
|
||||
{
|
||||
match self.unload {
|
||||
Some(proc) => { proc(); }
|
||||
None => { }
|
||||
}
|
||||
|
||||
self.document_clear();
|
||||
S::load();
|
||||
self.unload = Some(S::unload);
|
||||
}
|
||||
|
||||
pub fn document_clear(&self)
|
||||
{
|
||||
match self.document.body() {
|
||||
Some(body) => {
|
||||
while let Some(child) = body.last_child() {
|
||||
body.remove_child(&child).ok();
|
||||
}
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
}
|
||||
}
|
24
client-web/src/app/scene/list_online.rs
Normal file
24
client-web/src/app/scene/list_online.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use crate::{
|
||||
scene::util,
|
||||
APP,
|
||||
};
|
||||
|
||||
/* List Online
|
||||
**
|
||||
** Displays a table of recent, ongoing contests.
|
||||
*/
|
||||
|
||||
pub struct SceneListOnline { }
|
||||
impl crate::prelude::Scene for SceneListOnline {
|
||||
fn load()
|
||||
{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(_app) => {
|
||||
util::load_mainmenu();
|
||||
|
||||
}
|
||||
None => { }
|
||||
}
|
||||
}
|
||||
}
|
3
client-web/src/app/scene/mod.rs
Normal file
3
client-web/src/app/scene/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod util;
|
||||
pub mod ui;
|
||||
mod list_online; pub use list_online::*;
|
16
client-web/src/app/scene/ui/mod.rs
Normal file
16
client-web/src/app/scene/ui/mod.rs
Normal file
@ -0,0 +1,16 @@
|
||||
use wasm_bindgen::{prelude::Closure, JsCast};
|
||||
|
||||
pub fn button(document:&web_sys::Document, text:&str, callback:fn(web_sys::Event)) -> Result<web_sys::Element,()>
|
||||
{
|
||||
match document.create_element("button") {
|
||||
Ok(element) => {
|
||||
element.set_text_content(Some(text));
|
||||
|
||||
let cl = Closure::wrap(Box::new(callback) as Box<dyn FnMut(_)>);
|
||||
element.add_event_listener_with_callback("click", cl.as_ref().unchecked_ref()).ok();
|
||||
cl.forget();
|
||||
Ok(element)
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
56
client-web/src/app/scene/util.rs
Normal file
56
client-web/src/app/scene/util.rs
Normal file
@ -0,0 +1,56 @@
|
||||
use crate::{
|
||||
scene::{self, ui},
|
||||
session::SessionState,
|
||||
APP
|
||||
};
|
||||
|
||||
pub fn load_mainmenu()
|
||||
{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => {
|
||||
|
||||
if app.session.state == SessionState::User {
|
||||
// Button: Online
|
||||
match ui::button(&app.document, "Online", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Button: Continue
|
||||
match ui::button(&app.document, "Continue", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
|
||||
// Button: Join
|
||||
match ui::button(&app.document, "Continue", |_e|{
|
||||
let mut app = unsafe {APP.borrow_mut()};
|
||||
match &mut *app {
|
||||
Some(app) => { app.load::<scene::SceneListOnline>(); }
|
||||
None => { }
|
||||
}
|
||||
}) {
|
||||
Ok(button) => { app.menu.append_child(&button).ok(); }
|
||||
Err(_) => { }
|
||||
}
|
||||
|
||||
|
||||
} None => { }
|
||||
}
|
||||
}
|
23
client-web/src/app/session/mod.rs
Normal file
23
client-web/src/app/session/mod.rs
Normal file
@ -0,0 +1,23 @@
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum SessionState {
|
||||
Anonymous,
|
||||
User,
|
||||
}
|
||||
|
||||
pub struct Session {
|
||||
pub state:SessionState,
|
||||
pub handle:String,
|
||||
pub token:[u8; 8],
|
||||
pub secret:[u8; 16],
|
||||
}
|
||||
impl Session {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
state:SessionState::Anonymous,
|
||||
handle:String::new(),
|
||||
token:[0; 8],
|
||||
secret:[0; 16],
|
||||
}
|
||||
}
|
||||
}
|
4
client-web/src/ext.rs
Normal file
4
client-web/src/ext.rs
Normal file
@ -0,0 +1,4 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" { }
|
32
client-web/src/lib.rs
Normal file
32
client-web/src/lib.rs
Normal file
@ -0,0 +1,32 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
mod ext;
|
||||
mod prelude;
|
||||
mod app; use app::*;
|
||||
|
||||
static mut APP :RefCell<Option<App>> = RefCell::new(None);
|
||||
|
||||
#[wasm_bindgen(start)]
|
||||
fn main() -> Result<(),JsValue>
|
||||
{
|
||||
match App::init() {
|
||||
Ok(app) => {
|
||||
unsafe {
|
||||
APP = RefCell::new(Some(app));
|
||||
|
||||
let mut app = APP.borrow_mut();
|
||||
match &mut *app {
|
||||
Some(app) => {
|
||||
app.load::<scene::SceneListOnline>();
|
||||
} None => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
Ok(())
|
||||
}
|
8
client-web/src/prelude.rs
Normal file
8
client-web/src/prelude.rs
Normal file
@ -0,0 +1,8 @@
|
||||
pub trait Scene {
|
||||
fn load();
|
||||
fn unload() { }
|
||||
|
||||
fn hover() { }
|
||||
fn click() { }
|
||||
fn keydown() { }
|
||||
}
|
9
game/Cargo.toml
Normal file
9
game/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "game"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.92"
|
||||
web-sys = "0.3.69"
|
||||
js-sys = "0.3.69"
|
31
game/src/board/mod.rs
Normal file
31
game/src/board/mod.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use crate::piece::{Piece, PIECE_NONE};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Tile {
|
||||
piece:u8,
|
||||
}
|
||||
impl Tile {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
piece:PIECE_NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Board {
|
||||
tiles:[Tile; 61],
|
||||
pieces:[Piece; 38],
|
||||
pool:[[usize; 6]; 2],
|
||||
}
|
||||
impl Board {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
tiles:[Tile::new(); 61],
|
||||
pieces:[Piece::new(); 38],
|
||||
pool:[[0; 6]; 2],
|
||||
}
|
||||
}
|
||||
}
|
9
game/src/game/mod.rs
Normal file
9
game/src/game/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
pub struct Game {
|
||||
|
||||
}
|
||||
impl Game {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self { }
|
||||
}
|
||||
}
|
22
game/src/history/mod.rs
Normal file
22
game/src/history/mod.rs
Normal file
@ -0,0 +1,22 @@
|
||||
#[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)]
|
||||
pub struct Play {
|
||||
form:PlayType,
|
||||
from:u8,
|
||||
to:u8,
|
||||
piece:u8,
|
||||
capture:u8,
|
||||
state:PlayState,
|
||||
}
|
8
game/src/lib.rs
Normal file
8
game/src/lib.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod util;
|
||||
pub mod piece;
|
||||
pub mod board;
|
||||
pub mod history;
|
||||
pub mod protocol;
|
||||
mod game; pub use game::Game;
|
119
game/src/piece/mod.rs
Normal file
119
game/src/piece/mod.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use crate::util::bit;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MoveSet {
|
||||
direction:u32,
|
||||
distance:u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct PieceClass {
|
||||
name:&'static str,
|
||||
moves:MoveSet,
|
||||
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] = [
|
||||
PieceClass {
|
||||
name: "Militia",
|
||||
moves: MoveSet {
|
||||
direction:bit(0) | bit(1) | bit(5),
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet{
|
||||
direction:bit(0) | bit(1) | bit(2) | bit(4) | bit(5),
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "Knight",
|
||||
moves: MoveSet {
|
||||
direction:bit(3) | bit(6) | bit(11) | bit(13) | bit(17),
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet{
|
||||
direction:bit(3) | bit(6) | bit(7) | bit(10) | bit(11) | bit(13) | bit(14) | bit(16) | bit(17),
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "Lance",
|
||||
moves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "Tower",
|
||||
moves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "Castle",
|
||||
moves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "Dragon",
|
||||
moves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
PieceClass {
|
||||
name: "King",
|
||||
moves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
pmoves: MoveSet {
|
||||
direction:0,
|
||||
distance:0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Piece {
|
||||
class:u8,
|
||||
promoted:bool,
|
||||
}
|
||||
impl Piece {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
class:PIECE_NONE,
|
||||
promoted:false,
|
||||
}
|
||||
}
|
||||
}
|
18
game/src/protocol/code.rs
Normal file
18
game/src/protocol/code.rs
Normal file
@ -0,0 +1,18 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub const STATUS_OK :u16 = 0x0000;
|
||||
pub const STATUS_BAD :u16 = 0x0001;
|
||||
pub const STATUS_NOT_IMPL :u16 = 0x0002;
|
||||
|
||||
pub const CODE_AUTH :u16 = 0x0000;
|
||||
pub const CODE_REGISTER :u16 = 0x0001;
|
||||
pub const CODE_RESUME :u16 = 0x0002;
|
||||
pub const CODE_EXIT :u16 = 0x0003;
|
||||
pub const CODE_LIST_GAME :u16 = 0x0010;
|
||||
pub const CODE_LIST_OPEN :u16 = 0x0011;
|
||||
pub const CODE_LIST_LIVE :u16 = 0x0012;
|
||||
pub const CODE_JOIN :u16 = 0x0020;
|
||||
pub const CODE_SPECTATE :u16 = 0x0021;
|
||||
pub const CODE_LEAVE :u16 = 0x0022;
|
||||
pub const CODE_RETIRE :u16 = 0x0023;
|
||||
pub const CODE_PLAY :u16 = 0x0030;
|
2
game/src/protocol/mod.rs
Normal file
2
game/src/protocol/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod code;
|
||||
pub mod packet;
|
92
game/src/protocol/packet/authenticate.rs
Normal file
92
game/src/protocol/packet/authenticate.rs
Normal file
@ -0,0 +1,92 @@
|
||||
use crate::util::{pack_u16, unpack_u16};
|
||||
|
||||
use super::Packet;
|
||||
|
||||
pub struct PacketAuth {
|
||||
pub handle:String,
|
||||
pub secret:String,
|
||||
}
|
||||
impl PacketAuth {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
handle:String::new(),
|
||||
secret:String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketAuth {
|
||||
type Data = Self;
|
||||
|
||||
fn encode(&self) -> Vec<u8>
|
||||
{
|
||||
[
|
||||
pack_u16(self.handle.len() as u16),
|
||||
self.handle.as_bytes().to_vec(),
|
||||
pack_u16(self.secret.len() as u16),
|
||||
self.secret.as_bytes().to_vec(),
|
||||
].concat()
|
||||
}
|
||||
|
||||
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>
|
||||
{
|
||||
let mut result = Self::new();
|
||||
|
||||
let mut length = unpack_u16(data, index) as usize;
|
||||
if data.len() >= *index + length {
|
||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
||||
Ok(text) => {
|
||||
*index += length;
|
||||
result.handle = text;
|
||||
|
||||
length = unpack_u16(data, index) as usize;
|
||||
if data.len() >= *index + length {
|
||||
|
||||
match String::from_utf8(data[*index..*index+length].to_vec()) {
|
||||
Ok(text) => {
|
||||
*index += length;
|
||||
result.secret = text;
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct PacketAuthResponse {
|
||||
pub status:bool,
|
||||
pub token:[u8; 8],
|
||||
pub secret:[u8; 16],
|
||||
}
|
||||
impl PacketAuthResponse {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
status:false,
|
||||
token:[0; 8],
|
||||
secret:[0; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Packet for PacketAuthResponse {
|
||||
type Data = Self;
|
||||
|
||||
fn encode(&self) -> Vec<u8>
|
||||
{
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn decode(_data:&Vec<u8>, _index:&mut usize) -> Result<Self::Data, ()>
|
||||
{
|
||||
Err(())
|
||||
}
|
||||
}
|
10
game/src/protocol/packet/mod.rs
Normal file
10
game/src/protocol/packet/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
mod authenticate; pub use authenticate::*;
|
||||
|
||||
mod prelude {
|
||||
pub trait Packet {
|
||||
type Data;
|
||||
|
||||
fn encode(&self) -> Vec<u8>;
|
||||
fn decode(data:&Vec<u8>, index:&mut usize) -> Result<Self::Data, ()>;
|
||||
}
|
||||
} use prelude::*;
|
1
game/src/util/binary.rs
Normal file
1
game/src/util/binary.rs
Normal file
@ -0,0 +1 @@
|
||||
pub const fn bit(v:u32) -> u32 { 1u32 << v }
|
2
game/src/util/mod.rs
Normal file
2
game/src/util/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
mod binary; pub use binary::*;
|
||||
mod pack; pub use pack::*;
|
50
game/src/util/pack.rs
Normal file
50
game/src/util/pack.rs
Normal file
@ -0,0 +1,50 @@
|
||||
pub fn pack_u16(value:u16) -> Vec<u8>
|
||||
{
|
||||
vec![(value >> 8) as u8, (value & 0xFF) as u8]
|
||||
}
|
||||
|
||||
pub fn unpack_u16(data:&Vec<u8>, index:&mut usize) -> u16
|
||||
{
|
||||
let mut result :u16 = 0;
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u16) << 8;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result |= data[*index] as u16;
|
||||
*index += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn pack_u32(value:u32) -> Vec<u8>
|
||||
{
|
||||
vec![
|
||||
(value >> 24) as u8,
|
||||
(value >> 16) as u8,
|
||||
(value >> 8) as u8,
|
||||
(value & 0xFF) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
pub fn unpack_u32(data:&Vec<u8>, index:&mut usize) -> u32
|
||||
{
|
||||
let mut result :u32 = 0;
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 24;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 16;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result = (data[*index] as u32) << 8;
|
||||
*index += 1;
|
||||
}
|
||||
if *index < data.len() {
|
||||
result |= data[*index] as u32;
|
||||
*index += 1;
|
||||
}
|
||||
result
|
||||
}
|
19
server/Cargo.toml
Normal file
19
server/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.39.2", features = ["full"] }
|
||||
tokio-stream = "0.1.15"
|
||||
tokio-tungstenite = "0.23.1"
|
||||
tokio-rustls = "0.26.0"
|
||||
rustls = "0.23.5"
|
||||
rustls-pemfile = "2.1.2"
|
||||
webpki-roots = "0.26"
|
||||
opaque-ke = "2.0.0"
|
||||
|
||||
game = { path = "../game" }
|
||||
|
||||
bus = { git = "https://git.tsukiyo.org/Utility/bus" }
|
||||
trie = { git = "https://git.tsukiyo.org/Utility/trie" }
|
15
server/src/app/contest.rs
Normal file
15
server/src/app/contest.rs
Normal file
@ -0,0 +1,15 @@
|
||||
use game::Game;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct User {
|
||||
id:usize,
|
||||
online:bool,
|
||||
}
|
||||
|
||||
pub struct Match {
|
||||
game:Game,
|
||||
|
||||
p_dawn:u64,
|
||||
p_dusk:u64,
|
||||
specators:Vec<u64>,
|
||||
}
|
3
server/src/app/context.rs
Normal file
3
server/src/app/context.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub enum Context {
|
||||
None,
|
||||
}
|
37
server/src/app/mod.rs
Normal file
37
server/src/app/mod.rs
Normal file
@ -0,0 +1,37 @@
|
||||
pub mod user; use user::User;
|
||||
pub mod session; use session::Session;
|
||||
pub mod contest; use contest::Contest;
|
||||
pub mod context;
|
||||
|
||||
pub struct App {
|
||||
users:Pool<User>,
|
||||
salts:Vec<[u8; 16]>,
|
||||
sessions:Pool<Session>,
|
||||
contests:Pool<Contest>,
|
||||
}
|
||||
impl App {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
users:Pool::new(),
|
||||
salts:Vec::new(),
|
||||
sessions:Pool::new(),
|
||||
contests:Pool::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(),std::io::Error>
|
||||
{ use std::{path::Path, fs};
|
||||
|
||||
let path_data = Path::new("data");
|
||||
if !path_data.exists() {
|
||||
fs::create_dir(path_data)?;
|
||||
fs::create_dir(path_data.join("session"))?;
|
||||
fs::create_dir(path_data.join("user"))?;
|
||||
|
||||
fs::write(path_data.join("user/index"), pack_u32(0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
81
server/src/app/net/mod.rs
Normal file
81
server/src/app/net/mod.rs
Normal file
@ -0,0 +1,81 @@
|
||||
fn handle_packet(data:&Vec<u8>) -> Result<Vec<u8>,()>
|
||||
{
|
||||
use game::protocol::{
|
||||
prelude::*,
|
||||
code::*,
|
||||
packet::*,
|
||||
};
|
||||
|
||||
let response = Vec::<u8>::new();
|
||||
let mut status = STATUS_OK;
|
||||
|
||||
let mut index = 2;
|
||||
if data.len() < 2 { return Err(()); }
|
||||
match ((data[0] as u16) << 8) + data[1] as u16 {
|
||||
CODE_AUTH => {
|
||||
status = STATUS_BAD;
|
||||
|
||||
match PacketAuth::decode(data, index) {
|
||||
Ok(data) => {
|
||||
// check if handle exists
|
||||
|
||||
status = STATUS_OK;
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
CODE_REGISTER => {
|
||||
|
||||
}
|
||||
|
||||
CODE_RESUME => {
|
||||
|
||||
}
|
||||
|
||||
CODE_EXIT => {
|
||||
|
||||
}
|
||||
|
||||
CODE_LIST_GAME => {
|
||||
|
||||
}
|
||||
|
||||
CODE_LIST_OPEN => {
|
||||
|
||||
}
|
||||
|
||||
CODE_LIST_LIVE => {
|
||||
|
||||
}
|
||||
|
||||
CODE_JOIN => {
|
||||
|
||||
}
|
||||
|
||||
CODE_SPECTATE => {
|
||||
|
||||
}
|
||||
|
||||
CODE_LEAVE => {
|
||||
|
||||
}
|
||||
|
||||
CODE_RETIRE => {
|
||||
|
||||
}
|
||||
|
||||
CODE_PLAY => {
|
||||
|
||||
}
|
||||
|
||||
_ => {
|
||||
status = STATUS_NOT_IMPL;
|
||||
}
|
||||
}
|
||||
|
||||
Ok([
|
||||
pack_u16(status),
|
||||
response,
|
||||
].concat())
|
||||
}
|
19
server/src/app/session.rs
Normal file
19
server/src/app/session.rs
Normal file
@ -0,0 +1,19 @@
|
||||
use crate::app::context::Context;
|
||||
|
||||
pub struct Session {
|
||||
key:u64,
|
||||
secret:[u8; 16],
|
||||
user:Option<usize>,
|
||||
context:Context,
|
||||
}
|
||||
impl Session {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
key:[0; 8],
|
||||
secret:[0; 16],
|
||||
user:None,
|
||||
context:Context::None,
|
||||
}
|
||||
}
|
||||
}
|
5
server/src/app/user.rs
Normal file
5
server/src/app/user.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub struct User {
|
||||
handle:String,
|
||||
secret:String,
|
||||
na_key:usize,
|
||||
}
|
124
server/src/main.rs
Normal file
124
server/src/main.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use bus::Bus;
|
||||
|
||||
mod util;
|
||||
mod app;
|
||||
mod system;
|
||||
mod protocol;
|
||||
|
||||
use app::App;
|
||||
use net::Server;
|
||||
use protocol::QRPacket;
|
||||
|
||||
struct ServiceArgs {
|
||||
bus:Bus<protocol::QRPacket>,
|
||||
}
|
||||
|
||||
async fn thread_datasystem(mut _app:App, bus:Bus<protocol::QRPacket>)
|
||||
{
|
||||
use protocol::QRPacket;
|
||||
use game::protocol::packet::*;
|
||||
|
||||
while match bus.receive_wait() {
|
||||
Some(packet) => {
|
||||
match packet.data {
|
||||
QRPacket::QAuthenticate(request) => {
|
||||
let mut response = PacketAuthResponse::new();
|
||||
|
||||
// get user id from handle
|
||||
|
||||
// get user salt
|
||||
|
||||
// hash request secret
|
||||
|
||||
// compare hash to user secret
|
||||
|
||||
// return response
|
||||
bus.send(packet.from, QRPacket::RAuth(response)).is_ok()
|
||||
}
|
||||
_ => { true }
|
||||
}
|
||||
}
|
||||
None => false,
|
||||
} { }
|
||||
}
|
||||
|
||||
fn handle_tcp(stream:net::tls::TlsStream, addr:SocketAddr, args:SericeArgs)
|
||||
{
|
||||
use game::util::unpack_u32;
|
||||
|
||||
let length = vec![0u8; 4];
|
||||
match stream.recv(&mut length) {
|
||||
Ok(4) => {
|
||||
let buffer = Vec::<u8>::with_capacity(unpack_u32(&length, &mut 0) as usize);
|
||||
match stream.recv(&mut buffer) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Ok(_) => {
|
||||
|
||||
}
|
||||
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main()
|
||||
{
|
||||
use system::net::{
|
||||
tls::*,
|
||||
certstore::CertStore,
|
||||
};
|
||||
|
||||
// Initialize application data.
|
||||
let mut app = App::new();
|
||||
if app.init().is_err() {
|
||||
println!("fatal: failed to initialize server.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Initialize service data.
|
||||
//let cs = CertStore::new();
|
||||
//cs.add("omen.kirisame.com", "cert/fullchain.pem", "cert/privkey.pem").ok();
|
||||
//let cs = cs.to_share();
|
||||
|
||||
|
||||
// Initialize central bus and data serivce.
|
||||
let bus = Bus::<protocol::QRPacket>::new_as(bus::Mode::Transmitter);
|
||||
match bus.connect() {
|
||||
Ok(bus) => {
|
||||
tokio::spawn(move || { thread_datasystem(app, bus); });
|
||||
}
|
||||
Err(_) => {
|
||||
println!("fatal: failed to initialize bus.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize HTTPS service.
|
||||
match bus.connect() {
|
||||
Ok(bus) => {
|
||||
// bind 38611
|
||||
}
|
||||
Err(_) => {
|
||||
println!("error: failed to initialize HTTPS service.");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize TLS service.
|
||||
match bus.connect() {
|
||||
Ok(bus) => {
|
||||
// bind 38612
|
||||
}
|
||||
Err(_) => {
|
||||
println!("error: failed to initialize TLS service.");
|
||||
}
|
||||
}
|
||||
|
||||
std::thread::park();
|
||||
}
|
6
server/src/protocol/mod.rs
Normal file
6
server/src/protocol/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use game::protocol::packet::*;
|
||||
|
||||
pub enum QRPacket {
|
||||
QAuth(PacketAuth),
|
||||
RAuth(PacketAuthResponse),
|
||||
}
|
1
server/src/system/mod.rs
Normal file
1
server/src/system/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod net;
|
94
server/src/system/net/certstore/mod.rs
Normal file
94
server/src/system/net/certstore/mod.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use std::{
|
||||
fs::File, io, sync::Arc
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use rustls::pki_types::{CertificateDer, PrivateKeyDer};
|
||||
|
||||
use trie::Trie;
|
||||
|
||||
pub struct Certificate {
|
||||
pub certs:Vec<CertificateDer<'static>>,
|
||||
pub key:PrivateKeyDer<'static>,
|
||||
}
|
||||
|
||||
pub struct CertificateStore {
|
||||
certs:Trie<Arc<Certificate>>,
|
||||
}
|
||||
impl CertificateStore {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
certs:Trie::<Arc<Certificate>>::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, domain:&str, cert_file:&str, key_file:&str) -> Result<(),()>
|
||||
{
|
||||
// load certificate file
|
||||
let certs = match File::open(cert_file) {
|
||||
Ok(fcert) => {
|
||||
let mut reader = io::BufReader::new(fcert);
|
||||
let mut certificates = Vec::<CertificateDer>::new();
|
||||
for result in rustls_pemfile::certs(&mut reader) {
|
||||
match result {
|
||||
Ok(certificate) => {
|
||||
certificates.push(certificate);
|
||||
}
|
||||
Err(_) => { }
|
||||
}
|
||||
}
|
||||
Ok(certificates)
|
||||
}
|
||||
Err(_) => Err(())
|
||||
};
|
||||
|
||||
// load private key file
|
||||
let key = match File::open(key_file) {
|
||||
Ok(fkey) => {
|
||||
let mut reader = io::BufReader::new(fkey);
|
||||
match rustls_pemfile::private_key(&mut reader) {
|
||||
Ok(result) => match result {
|
||||
Some(key) => Ok(key),
|
||||
None => Err(())
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
Err(_) => Err(())
|
||||
};
|
||||
|
||||
if certs.is_ok() && key.is_ok() {
|
||||
self.certs.set(domain, Arc::new(Certificate {
|
||||
certs:certs.unwrap(),
|
||||
key:key.unwrap(),
|
||||
}));
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, domain:&str) -> Result<(),()>
|
||||
{
|
||||
if self.certs.unset(domain) {
|
||||
Ok(())
|
||||
}
|
||||
else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, domain:&str) -> Result<Arc<Certificate>, ()>
|
||||
{
|
||||
match self.certs.get(domain) {
|
||||
Some(certs) => Ok(certs),
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_share(self) -> Arc<RwLock<Self>>
|
||||
{
|
||||
Arc::new(RwLock::new(self))
|
||||
}
|
||||
}
|
27
server/src/system/net/mod.rs
Normal file
27
server/src/system/net/mod.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use tokio::task::JoinHandle;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
pub trait Stream {
|
||||
type Socket;
|
||||
|
||||
fn recv(&mut self, buffer:&mut [u8]) -> Result<usize, ()>;
|
||||
fn send(&mut self, buffer:&mut [u8]) -> Result<(), ()>;
|
||||
fn stream(&mut self) -> &mut Self::Socket;
|
||||
}
|
||||
|
||||
pub trait Server {
|
||||
type Stream :Stream;
|
||||
|
||||
fn bind(&mut self, interface:&str) -> impl std::future::Future<Output = Result<(),()>>;
|
||||
//async fn accept(&self) -> Result<Self::Stream, ()>;
|
||||
fn accept<F, Fut, Args>(&self, callback:F, args:Args) -> impl std::future::Future<Output = Result<JoinHandle<Result<(),()>>,()>>
|
||||
where
|
||||
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
|
||||
Fut: std::future::Future<Output = Result<(),()>> + Send + 'static,
|
||||
Args: Send + 'static;
|
||||
fn close(&mut self);
|
||||
}
|
||||
|
||||
pub mod tcp;
|
||||
pub mod tls;
|
||||
pub mod certstore;
|
73
server/src/system/net/tcp.rs
Normal file
73
server/src/system/net/tcp.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use super::*;
|
||||
|
||||
use tokio::net::{self, TcpListener};
|
||||
|
||||
pub struct TcpStream {
|
||||
stream:net::TcpStream,
|
||||
}
|
||||
impl Stream for TcpStream {
|
||||
type Socket = net::TcpStream;
|
||||
|
||||
fn recv(&mut self, _buffer:&mut [u8]) -> Result<usize, ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn send(&mut self, _buffer:&mut [u8]) -> Result<(), ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn stream(&mut self) -> &mut Self::Socket {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TcpServer {
|
||||
listener:Option<net::TcpListener>,
|
||||
}
|
||||
impl TcpServer {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
listener:None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Server for TcpServer {
|
||||
type Stream = TcpStream;
|
||||
|
||||
async fn bind(&mut self, interface:&str) -> Result<(), ()>
|
||||
{
|
||||
match TcpListener::bind(interface).await {
|
||||
Ok(listener) => {
|
||||
self.listener = Some(listener);
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn accept<F, Fut, Args>(&self, callback:F, args:Args) -> Result<JoinHandle<Result<(),()>>,()>
|
||||
where
|
||||
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
|
||||
Fut: std::future::Future<Output = Result<(),()>> + Send + 'static,
|
||||
Args: Send + 'static
|
||||
{
|
||||
match &self.listener {
|
||||
Some(listener) => {
|
||||
match listener.accept().await {
|
||||
Ok((stream, addr)) => {
|
||||
Ok(tokio::spawn(async move {
|
||||
callback(Self::Stream { stream:stream }, addr, args).await
|
||||
}))
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.listener = None;
|
||||
}
|
||||
}
|
152
server/src/system/net/tls.rs
Normal file
152
server/src/system/net/tls.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use super::*;
|
||||
|
||||
use std::{
|
||||
net::SocketAddr, sync::Arc
|
||||
};
|
||||
use tokio::{net::{TcpListener, TcpStream}, sync::RwLock, task::JoinHandle};
|
||||
use rustls::{
|
||||
pki_types::{CertificateDer, PrivateKeyDer}, server::Acceptor, ServerConfig
|
||||
};
|
||||
use tokio_rustls::{server, LazyConfigAcceptor};
|
||||
|
||||
use certstore::CertificateStore;
|
||||
|
||||
struct PKP {
|
||||
pub certs:Vec<CertificateDer<'static>>,
|
||||
pub key:PrivateKeyDer<'static>,
|
||||
}
|
||||
|
||||
type CertStorePtr = Arc<RwLock<CertificateStore>>;
|
||||
|
||||
pub struct TlsStream {
|
||||
stream:server::TlsStream<TcpStream>,
|
||||
}
|
||||
impl TlsStream { }
|
||||
impl Stream for TlsStream {
|
||||
type Socket = server::TlsStream<TcpStream>;
|
||||
|
||||
fn recv(&mut self, _buffer:&mut [u8]) -> Result<usize, ()> {
|
||||
//match self.stream.read(&mut buffer) {
|
||||
// Ok(size) => Ok(size),
|
||||
// Err(_) => Err(())
|
||||
//}
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn send(&mut self, _buffer:&mut [u8]) -> Result<(), ()> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
fn stream(&mut self) -> &mut Self::Socket {
|
||||
&mut self.stream
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TlsServer {
|
||||
certificates:CertStorePtr,
|
||||
listener:Option<TcpListener>,
|
||||
}
|
||||
impl TlsServer {
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
certificates:Arc::new(RwLock::new(CertificateStore::new())),
|
||||
listener:None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn add_cert(&mut self, domain:&str, cert_file:&str, key_file:&str) -> Result<(),()>
|
||||
{
|
||||
let mut cert_handle = self.certificates.write().await;
|
||||
match cert_handle.add(domain, cert_file, key_file) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_cert(&mut self, domain:&str) -> Result<(),()>
|
||||
{
|
||||
let mut cert_handle = self.certificates.write().await;
|
||||
match cert_handle.remove(domain) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_server_config(certificates:CertStorePtr, domain :&str) -> Option<Arc<ServerConfig>>
|
||||
{
|
||||
match certificates.read().await.get(domain) {
|
||||
Ok(config) => {
|
||||
let server_config = ServerConfig::builder()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(config.certs.clone(), config.key.clone_key());
|
||||
match server_config {
|
||||
Ok(config) => Some(Arc::new(config)),
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Server for TlsServer {
|
||||
type Stream = TlsStream;
|
||||
|
||||
async fn bind(&mut self, interface:&str) -> Result<(), ()> {
|
||||
match tokio::net::TcpListener::bind(interface).await {
|
||||
Ok(listener) => {
|
||||
self.listener = Some(listener);
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn accept<F, Fut, Args>(&self, callback:F, args:Args) -> Result<JoinHandle<Result<(),()>>,()>
|
||||
where
|
||||
F: Fn(Self::Stream, SocketAddr, Args) -> Fut + Send + Sync + 'static,
|
||||
Fut: std::future::Future<Output = Result<(),()>> + Send + 'static,
|
||||
Args: Send + 'static
|
||||
{
|
||||
match &self.listener {
|
||||
Some(listener) => {
|
||||
if let Ok((socket, client_addr)) = listener.accept().await {
|
||||
let acceptor = LazyConfigAcceptor::new(Acceptor::default(), socket);
|
||||
let certificates = self.certificates.clone();
|
||||
|
||||
Ok(tokio::spawn(async move {
|
||||
match acceptor.await {
|
||||
Ok(hs) => {
|
||||
let hello = hs.client_hello();
|
||||
if let Some(server_name) = hello.server_name() {
|
||||
match Self::get_server_config(certificates, server_name).await {
|
||||
Some(server_config) => {
|
||||
match hs.into_stream(server_config).await {
|
||||
Ok(tlsstream) => {
|
||||
callback(Self::Stream { stream:tlsstream }, client_addr, args).await
|
||||
}
|
||||
Err(_) => Err(())
|
||||
}
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
else { Err(()) }
|
||||
}
|
||||
Err(e) => {
|
||||
println!("[conn failure] {:}", e.to_string());
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
else { Err(()) }
|
||||
}
|
||||
None => Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) {
|
||||
self.listener = None;
|
||||
}
|
||||
}
|
21
server/src/util/color.rs
Normal file
21
server/src/util/color.rs
Normal file
@ -0,0 +1,21 @@
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Color {
|
||||
r:f32,
|
||||
g:f32,
|
||||
b:f32,
|
||||
}
|
||||
impl Color {
|
||||
pub fn rgb(r:u8, g:u8, b:u8) -> Self
|
||||
{
|
||||
Self {
|
||||
r: r as f32 / 255.,
|
||||
g: g as f32 / 255.,
|
||||
b: b as f32 / 255.,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frgb(r:f32, g:f32, b:f32) -> Self
|
||||
{
|
||||
Self { r, g, b }
|
||||
}
|
||||
}
|
2
server/src/util/mod.rs
Normal file
2
server/src/util/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
#![allow(dead_code)]
|
||||
pub mod color;
|
254
server/www/.css
Normal file
254
server/www/.css
Normal file
@ -0,0 +1,254 @@
|
||||
* {
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:100%;
|
||||
padding:0;
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
justify-content:flex-start;
|
||||
width:100%;
|
||||
height:100%;
|
||||
padding:0;
|
||||
margin:0;
|
||||
overflow:hidden;
|
||||
|
||||
font-family:sans-serif;
|
||||
}
|
||||
|
||||
body>nav {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:9rem;
|
||||
height:100%;
|
||||
|
||||
background-color:#282828;
|
||||
}
|
||||
body>nav>button {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:2.5rem;
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
text-align:left;
|
||||
font-size:1.25rem;
|
||||
cursor:pointer;
|
||||
|
||||
background-color:#282828;
|
||||
border:0;
|
||||
color:#c0c0c0;
|
||||
} body>nav>button:hover {
|
||||
background-color:#383838;
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
body>nav>header {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:3rem;
|
||||
|
||||
line-height:3rem;
|
||||
text-align:center;
|
||||
font-size:1.8rem;
|
||||
font-weight:bold;
|
||||
font-variant:small-caps;
|
||||
|
||||
color:#d0d0d0;
|
||||
border-bottom:1px solid #404040;
|
||||
}
|
||||
|
||||
main {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:100%;
|
||||
|
||||
flex-grow:1;
|
||||
|
||||
background-color:#202020;
|
||||
}
|
||||
|
||||
main>nav {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
justify-content:flex-start;
|
||||
width:100%;
|
||||
height:3rem;
|
||||
|
||||
background-color:#282828;
|
||||
}
|
||||
|
||||
main>nav>section:first-child {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
justify-content:flex-start;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
main>nav>section:last-child {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
justify-content:flex-end;
|
||||
height:100%;
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
main>nav>section>button {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
height:100%;
|
||||
padding:0 1rem 0 1rem;
|
||||
text-align:left;
|
||||
font-size:1.25rem;
|
||||
cursor:pointer;
|
||||
|
||||
background-color:#282828;
|
||||
border:0;
|
||||
color:#c0c0c0;
|
||||
} main>nav>section>button:hover {
|
||||
background-color:#383838;
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
main>nav>section>div {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
height:100%;
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
line-height:3rem;
|
||||
text-align:left;
|
||||
font-size:1.25rem;
|
||||
|
||||
color:#e0e0e0;
|
||||
}
|
||||
|
||||
main table {
|
||||
width:100%;
|
||||
border-collapse:collapse;
|
||||
}
|
||||
main table tr {
|
||||
height:2.5rem;
|
||||
}
|
||||
main table th {
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
text-align:center;
|
||||
font-size:1.2rem;
|
||||
font-weight:bold;
|
||||
|
||||
background-color:#383838;
|
||||
color:#f0f0f0;
|
||||
border-bottom:1px solid #404040;
|
||||
}
|
||||
main table td {
|
||||
height:100%;
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
text-align:center;
|
||||
|
||||
background-color:#303030;
|
||||
color:#f0f0f0;
|
||||
}
|
||||
main table td:last-child {
|
||||
display:flex;
|
||||
flex-flow:row nowrap;
|
||||
align-items:flex-start;
|
||||
justify-content:flex-end;
|
||||
flex-grow:1;
|
||||
}
|
||||
|
||||
main table td>img {
|
||||
height:2rem;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
main table td:last-child>button {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:auto;
|
||||
height:100%;
|
||||
padding:0 1rem 0 1rem;
|
||||
|
||||
text-align:left;
|
||||
font-size:1.25rem;
|
||||
cursor:pointer;
|
||||
|
||||
background-color:#303030;
|
||||
border:0;
|
||||
color:#e0e0e0;
|
||||
} main table td:last-child>button:hover {
|
||||
background-color:#343434;
|
||||
}
|
||||
|
||||
main>canvas {
|
||||
display:block;
|
||||
position:relative;
|
||||
width:100%;
|
||||
height:100%;
|
||||
padding:0;
|
||||
margin:0;
|
||||
|
||||
background-color:#202020;
|
||||
}
|
||||
|
||||
/* Scene:game */
|
||||
main.game>div.sidemenu {
|
||||
display:flex;
|
||||
position:absolute;
|
||||
bottom:0px;
|
||||
right:1px;
|
||||
z-index:10;
|
||||
|
||||
flex-flow:column nowrap;
|
||||
|
||||
width:auto;
|
||||
height:auto;
|
||||
|
||||
border:0;
|
||||
border-top-left-radius:1rem;
|
||||
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
main.game>div.sidemenu>button {
|
||||
display:block;
|
||||
position:relative;
|
||||
padding:1rem;
|
||||
margin:0 0 1px 0;
|
||||
background-color:#202020;
|
||||
color:#F0F0F0;
|
||||
border:0;
|
||||
|
||||
font-family:sans-serif;
|
||||
font-size:1rem;
|
||||
|
||||
cursor:pointer;
|
||||
} main.game>div.sidemenu>button:hover {
|
||||
background-color:#404040;
|
||||
} main.game>div.sidemenu>button.warn:hover {
|
||||
background-color:#602020;
|
||||
}
|
||||
|
||||
span.text-system { color:#909090; }
|
15
server/www/.html
Normal file
15
server/www/.html
Normal file
@ -0,0 +1,15 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Omen</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="application-name" content="Omen">
|
||||
<meta name="description" content="Abstract strategy game mixing chess and shogi on a hexagon grid.">
|
||||
<link rel="stylesheet" href=".css">
|
||||
<script src=".js" async></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>This application requires JavaScript to function.</noscript>
|
||||
</body>
|
||||
</html>
|
1
server/www/.js
Normal file
1
server/www/.js
Normal file
@ -0,0 +1 @@
|
||||
import(".wasm");//.then(module=>{module.main();})
|
Loading…
x
Reference in New Issue
Block a user