Add Guide and About pages.
This commit is contained in:
parent
cc83ec117d
commit
9437a81bfb
@ -20,6 +20,7 @@ futures = "0.3.30"
|
|||||||
rust-argon2 = "2.1.0"
|
rust-argon2 = "2.1.0"
|
||||||
ring = "0.17.8"
|
ring = "0.17.8"
|
||||||
const_format = "0.2.32"
|
const_format = "0.2.32"
|
||||||
|
markdown = "0.3.0"
|
||||||
|
|
||||||
game = { path = "../game" }
|
game = { path = "../game" }
|
||||||
|
|
||||||
|
@ -186,6 +186,26 @@ async fn main()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let about_path = std::path::Path::new("www/about");
|
||||||
|
for doc in [
|
||||||
|
"main",
|
||||||
|
] {
|
||||||
|
if cache.cache_md(&format!("/about/{}.html", doc), about_path.join(format!("{}.md", doc))).is_err() {
|
||||||
|
println!("error: failed to load: {}", doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let guide_path = std::path::Path::new("www/guide");
|
||||||
|
for doc in [
|
||||||
|
"game",
|
||||||
|
"pieces",
|
||||||
|
"interface",
|
||||||
|
] {
|
||||||
|
if cache.cache_md(&format!("/guide/{}.html", doc), guide_path.join(format!("{}.md", doc))).is_err() {
|
||||||
|
println!("error: failed to load: {}", doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut tcp_server = TcpServer::new();
|
let mut tcp_server = TcpServer::new();
|
||||||
match tcp_server.bind("127.0.0.1:38611").await {
|
match tcp_server.bind("127.0.0.1:38611").await {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
22
server/src/system/cache/mod.rs
vendored
22
server/src/system/cache/mod.rs
vendored
@ -77,6 +77,28 @@ impl WebCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cache_md<P :AsRef<Path>>(&self, object:&str, path:P) -> Result<(),()>
|
||||||
|
{
|
||||||
|
match self.data.write() {
|
||||||
|
Ok(mut writer) => {
|
||||||
|
match markdown::file_to_html(&path.as_ref()) {
|
||||||
|
Ok(text) => {
|
||||||
|
let text :Vec<String> = text.trim().lines().map(|line| line.trim().to_string()).collect();
|
||||||
|
let data = text.concat().as_bytes().to_vec();
|
||||||
|
|
||||||
|
writer.objects.set(object.as_bytes(), CacheData {
|
||||||
|
mime:String::from("text/html"),
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(_) => Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cache_whitespace_minimize(&self, object:&str) -> Result<(),()>
|
pub fn cache_whitespace_minimize(&self, object:&str) -> Result<(),()>
|
||||||
{
|
{
|
||||||
match self.data.write() {
|
match self.data.write() {
|
||||||
|
13
www/about/main.md
Normal file
13
www/about/main.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# About
|
||||||
|
|
||||||
|
Omen is a work of [Project Kirisame](https://kirisame.com).
|
||||||
|
|
||||||
|
- [Project License](https://ykr.info/license)
|
||||||
|
|
||||||
|
© 2024 Yukiri Corporation
|
||||||
|
|
||||||
|
|
||||||
|
## User Privacy
|
||||||
|
|
||||||
|
This website does not collect any information beyond that used to implement user accounts and gameplay.
|
||||||
|
Information is not provided to third parties except in serving the application to clients.
|
@ -107,6 +107,7 @@ main>nav{
|
|||||||
justify-content:flex-start;
|
justify-content:flex-start;
|
||||||
width:100%;
|
width:100%;
|
||||||
height:3rem;
|
height:3rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
background-color:#282828;
|
background-color:#282828;
|
||||||
border-bottom:1px solid #404040;
|
border-bottom:1px solid #404040;
|
||||||
|
@ -52,3 +52,64 @@ main>table.list td:last-child>button{
|
|||||||
main>table.list td:last-child>button:hover{
|
main>table.list td:last-child>button:hover{
|
||||||
background-color:#343434;
|
background-color:#343434;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main>article{
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
line-height: 1.4em;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: #c0c0c0;
|
||||||
|
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
main>article>h1{
|
||||||
|
padding: 0.5rem;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #a0a0a0;
|
||||||
|
color: #202020;
|
||||||
|
}
|
||||||
|
main>article>h2{
|
||||||
|
padding: 0 0 0.5rem 0.25rem;
|
||||||
|
margin: 1.5rem 0 0.5rem 0;
|
||||||
|
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 2px solid #c0c0c0;
|
||||||
|
color: #c0c0c0;
|
||||||
|
}
|
||||||
|
main>article>h3{
|
||||||
|
padding: 0 0 0 0.25rem;
|
||||||
|
margin: 1.5rem 0 0.5rem 0;
|
||||||
|
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #b0b0b0;
|
||||||
|
}
|
||||||
|
main>article>h4{
|
||||||
|
padding: 0 0 0 0.25rem;
|
||||||
|
margin: 1.5rem 0 0.5rem 0;
|
||||||
|
|
||||||
|
font-size: 1.3rem;
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #a0a0a0;
|
||||||
|
}
|
||||||
|
main>article>p{
|
||||||
|
padding: 0 0.5rem 0 0.5rem;
|
||||||
|
}
|
||||||
|
main>article a{
|
||||||
|
color: #db758e;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
main>article>a:hover{
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
78
www/guide/game.md
Normal file
78
www/guide/game.md
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Game
|
||||||
|
|
||||||
|
Omen is an abstract strategy game, similar to chess and shogi, in which players take turns moving and placing pieces on a hexagon grid to capture the opponent's king (omen).
|
||||||
|
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### Board
|
||||||
|
|
||||||
|
Omen is played on a hexagon grid that is 9 tiles across and 5 tiles on each side, with opposite corners facing the players, such that the hexagon tiles form columns between them.
|
||||||
|
|
||||||
|
- Each player has 9 Militia placed between the 2nd ranks of the edges, leaving one or two tiles between opposing Militia.
|
||||||
|
- The back line has the Omen at the center, flanked on either side by first Towers, then Castles, Knights, and Lances, such that the Lances are in the adjacent corners.
|
||||||
|
- The Behemoth is placed in front of the Omen and the Dragon in front of the Behemoth.
|
||||||
|
|
||||||
|
|
||||||
|
### Turns
|
||||||
|
|
||||||
|
At the start of the game, each player is assigned either the Dawn or Dusk seat—either randomly or alternating, if more than one game is played.
|
||||||
|
|
||||||
|
Players take turns making actions on the board until the game is over, with the Dawn player making the first move.
|
||||||
|
|
||||||
|
|
||||||
|
## Actions
|
||||||
|
|
||||||
|
Each turn, the acting player must make one of three actions on the board: Move, Swap, or Drop.
|
||||||
|
|
||||||
|
|
||||||
|
### Move
|
||||||
|
|
||||||
|
Each piece has a specific set of moves it may make, with each move falling into one of two categories: Direct and Stride.
|
||||||
|
|
||||||
|
Direct moves are limited to a single tile in the given direction but are not blocked by other pieces.
|
||||||
|
Stride moves are not limited in distance but may not jump over other pieces.
|
||||||
|
|
||||||
|
Pieces may move onto any tile that is empty, contains an opposing piece, or contains a swappable piece.
|
||||||
|
|
||||||
|
|
||||||
|
#### Capturing
|
||||||
|
|
||||||
|
If a piece is moved onto the tile of an opposing piece, the opposing piece is captured and added to the capturing player's pool.
|
||||||
|
|
||||||
|
|
||||||
|
#### Promotion
|
||||||
|
|
||||||
|
A piece that lands on the opponent's back line is promoted, gaining a new move set.
|
||||||
|
|
||||||
|
|
||||||
|
### Swap
|
||||||
|
|
||||||
|
Two pieces that can each jump to each others tiles may swap places as a single action.
|
||||||
|
|
||||||
|
|
||||||
|
### Drop
|
||||||
|
|
||||||
|
A player may place a piece from their pool back onto the board as their own.
|
||||||
|
|
||||||
|
|
||||||
|
#### Militia Stacking
|
||||||
|
|
||||||
|
Militia may not be placed onto a column in which the player already has a Militia, with the exception of promoted Militia.
|
||||||
|
|
||||||
|
|
||||||
|
#### Off-sides
|
||||||
|
|
||||||
|
A piece may not be placed behind the first opposing piece in a column.
|
||||||
|
|
||||||
|
|
||||||
|
## Check
|
||||||
|
|
||||||
|
If an opposing piece can make a move that captures your omen, then you are in check.
|
||||||
|
|
||||||
|
While in check, a player may only make actions that remove check.
|
||||||
|
|
||||||
|
|
||||||
|
### Checkmate
|
||||||
|
|
||||||
|
If a player is in check and can make no valid action, then the game is over and the checking player wins.
|
16
www/guide/interface.md
Normal file
16
www/guide/interface.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Interface
|
||||||
|
|
||||||
|
## Game
|
||||||
|
|
||||||
|
### Board
|
||||||
|
|
||||||
|
|
||||||
|
### Pools
|
||||||
|
|
||||||
|
|
||||||
|
### Indicators
|
||||||
|
|
||||||
|
- The player's turn is indicated by the presence of borders around the player's pool tiles.
|
||||||
|
- The turn counter is displayed in the top-right corner.
|
||||||
|
- The status of the game may be displayed in the bottom-left corner.
|
||||||
|
- If the user hovers over a tile, the position and piece are displayed in the top-left corner.
|
69
www/guide/pieces.md
Normal file
69
www/guide/pieces.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Pieces
|
||||||
|
|
||||||
|
## Militia
|
||||||
|
|
||||||
|
Militia are the primary offensive pieces, capable of moving in the three forward directions.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Militia may move onto any adjacent tile.
|
||||||
|
|
||||||
|
|
||||||
|
## Lance
|
||||||
|
|
||||||
|
The Lance is similar to the Militia but may move any number of tiles in the forward direction.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Lance may move onto any adjacent tile, similar to the Militia.
|
||||||
|
|
||||||
|
|
||||||
|
## Knight
|
||||||
|
|
||||||
|
The Knight may move to the forward and back diagonal tiles, as well as the second adjacent side tiles.
|
||||||
|
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The Knight has no promotion.
|
||||||
|
|
||||||
|
|
||||||
|
## Tower
|
||||||
|
|
||||||
|
The Tower may move to the three forward adjacent tiles, two forward diagonal tiles, and back tile.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Tower may additionally move backwards with its forward move set.
|
||||||
|
|
||||||
|
|
||||||
|
## Castle
|
||||||
|
|
||||||
|
The Castle may move onto any of the adjacent tiles, except back, as well as to the diagonal sides.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Castle gains movement onto the back tile and may move any number of tiles to the diagonal sides.
|
||||||
|
|
||||||
|
|
||||||
|
## Dragon
|
||||||
|
|
||||||
|
The Dragon may move any number of tiles in the diagonal directions.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Dragon may additionally move to any of the adjacent tiles.
|
||||||
|
|
||||||
|
|
||||||
|
## Behemoth
|
||||||
|
|
||||||
|
The Behemoth may move any number of tiles in the adjacent directions.
|
||||||
|
|
||||||
|
### Promotion
|
||||||
|
|
||||||
|
The promoted Behemoth may additionally move to the second tiles in the adjacent directions.
|
||||||
|
|
||||||
|
|
||||||
|
## Omen
|
||||||
|
|
||||||
|
The Omen may move onto any of the adjacent tiles, as wella s to the diagonal sides.
|
106
www/js/scene.js
106
www/js/scene.js
@ -189,16 +189,8 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("browse");
|
UI.mainmenu("browse");
|
||||||
|
UI.mainnav_std(
|
||||||
let left_buttons = [ ];
|
[ ],
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
left_buttons.push(UI.button("Start", () => {
|
|
||||||
MESSAGE_SESSION_START();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.mainnav(
|
|
||||||
left_buttons,
|
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
@ -251,16 +243,8 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("continue");
|
UI.mainmenu("continue");
|
||||||
|
UI.mainnav_std(
|
||||||
let left_buttons = [ ];
|
[ ],
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
left_buttons.push(UI.button("Start", () => {
|
|
||||||
MESSAGE_SESSION_START();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.mainnav(
|
|
||||||
left_buttons,
|
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
@ -313,16 +297,8 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("join");
|
UI.mainmenu("join");
|
||||||
|
UI.mainnav_std(
|
||||||
let left_buttons = [ ];
|
[ ],
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
left_buttons.push(UI.button("Start", () => {
|
|
||||||
MESSAGE_SESSION_START();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.mainnav(
|
|
||||||
left_buttons,
|
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
@ -373,16 +349,8 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("live");
|
UI.mainmenu("live");
|
||||||
|
UI.mainnav_std(
|
||||||
let left_buttons = [ ];
|
[ ],
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
left_buttons.push(UI.button("Start", () => {
|
|
||||||
MESSAGE_SESSION_START();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
UI.mainnav(
|
|
||||||
left_buttons,
|
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
@ -433,11 +401,8 @@ const SCENES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
UI.mainmenu("history");
|
UI.mainmenu("history");
|
||||||
|
UI.mainnav_std(
|
||||||
let left_buttons = [ ];
|
[ ],
|
||||||
|
|
||||||
UI.mainnav(
|
|
||||||
left_buttons,
|
|
||||||
[
|
[
|
||||||
UI.div([UI.text("0 - 0 of 0")]),
|
UI.div([UI.text("0 - 0 of 0")]),
|
||||||
UI.button("◀", null),
|
UI.button("◀", null),
|
||||||
@ -483,18 +448,28 @@ const SCENES = {
|
|||||||
load() {
|
load() {
|
||||||
UI.mainmenu("guide");
|
UI.mainmenu("guide");
|
||||||
UI.mainnav([
|
UI.mainnav([
|
||||||
UI.button("Game", () => { }),
|
UI.button("Game", () => { SCENE.refresh("game.html"); }),
|
||||||
UI.button("Interface", () => { }),
|
UI.button("Pieces", () => { SCENE.refresh("pieces.html"); }),
|
||||||
UI.button("Pieces", () => { }),
|
UI.button("Interface", () => { SCENE.refresh("interface.html"); }),
|
||||||
UI.button("Dropping", () => { }),
|
|
||||||
], []);
|
], []);
|
||||||
|
|
||||||
|
let body = document.createElement("article");
|
||||||
|
body.setAttribute("id", "article");
|
||||||
|
MAIN.appendChild(body);
|
||||||
|
|
||||||
|
this.refresh("game.html");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh(page) {
|
||||||
|
fetch("/guide/" + page)
|
||||||
|
.then((response) => {
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((text) => {
|
||||||
|
let parser = new DOMParser();
|
||||||
|
let body = parser.parseFromString(text, "text/html");
|
||||||
|
document.getElementById("article").innerHTML = body.body.innerHTML;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -503,12 +478,23 @@ const SCENES = {
|
|||||||
UI.mainmenu("about");
|
UI.mainmenu("about");
|
||||||
UI.mainnav([], []);
|
UI.mainnav([], []);
|
||||||
|
|
||||||
|
let body = document.createElement("article");
|
||||||
|
body.setAttribute("id", "article");
|
||||||
|
MAIN.appendChild(body);
|
||||||
|
|
||||||
|
this.refresh("main.html");
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
refresh() {
|
refresh(page) {
|
||||||
|
fetch("/about/" + page)
|
||||||
|
.then((response) => {
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then((text) => {
|
||||||
|
let parser = new DOMParser();
|
||||||
|
let body = parser.parseFromString(text, "text/html");
|
||||||
|
document.getElementById("article").innerHTML = body.body.innerHTML;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -590,19 +576,11 @@ const SCENES = {
|
|||||||
UI.button("Mirror", () => { INTERFACE.mirror(); }),
|
UI.button("Mirror", () => { INTERFACE.mirror(); }),
|
||||||
], buttons_bottom);
|
], buttons_bottom);
|
||||||
|
|
||||||
|
|
||||||
let left_buttons = [ ];
|
|
||||||
if(CONTEXT.Auth !== null) {
|
|
||||||
left_buttons.push(UI.button("Start", () => {
|
|
||||||
MESSAGE_SESSION_START();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
let ind_turn = UI.div([UI.text("0 of 0")]);
|
let ind_turn = UI.div([UI.text("0 of 0")]);
|
||||||
ind_turn.setAttribute("id", "ind_turn");
|
ind_turn.setAttribute("id", "ind_turn");
|
||||||
|
|
||||||
UI.mainnav(
|
UI.mainnav(
|
||||||
left_buttons,
|
[ ],
|
||||||
[
|
[
|
||||||
ind_turn,
|
ind_turn,
|
||||||
UI.button("◀", () => { INTERFACE.replay_prev(); }),
|
UI.button("◀", () => { INTERFACE.replay_prev(); }),
|
||||||
|
21
www/js/ui.js
21
www/js/ui.js
@ -82,11 +82,28 @@ const UI = {
|
|||||||
|
|
||||||
mainnav(left_children, right_children) {
|
mainnav(left_children, right_children) {
|
||||||
let header = document.createElement("nav");
|
let header = document.createElement("nav");
|
||||||
|
|
||||||
|
let left = document.createElement("section");
|
||||||
|
for(child of left_children) { left.appendChild(child); }
|
||||||
|
|
||||||
|
let right = document.createElement("section");
|
||||||
|
for(child of right_children) { right.appendChild(child); }
|
||||||
|
|
||||||
|
header.appendChild(left);
|
||||||
|
header.appendChild(right);
|
||||||
|
|
||||||
|
MAIN.appendChild(header);
|
||||||
|
},
|
||||||
|
|
||||||
|
mainnav_std(left_children, right_children) {
|
||||||
|
let header = document.createElement("nav");
|
||||||
let left = document.createElement("section");
|
let left = document.createElement("section");
|
||||||
|
|
||||||
if(CONTEXT.Auth === null) {
|
if(CONTEXT.Auth === null) {
|
||||||
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register) }));
|
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register); }));
|
||||||
left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate) }));
|
left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate); }));
|
||||||
|
} else {
|
||||||
|
left.push(UI.button("Start", () => { MESSAGE_SESSION_START(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
for(child of left_children) { left.appendChild(child); }
|
for(child of left_children) { left.appendChild(child); }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user