Add Guide and About pages.

This commit is contained in:
yukirij 2024-08-22 16:25:04 -07:00
parent cc83ec117d
commit 9437a81bfb
11 changed files with 342 additions and 66 deletions

View File

@ -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" }

View File

@ -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(_) => {

View File

@ -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
View 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.

View File

@ -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;

View File

@ -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
View 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
View 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
View 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.

View File

@ -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(); }),

View File

@ -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); }