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"
|
||||
ring = "0.17.8"
|
||||
const_format = "0.2.32"
|
||||
markdown = "0.3.0"
|
||||
|
||||
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();
|
||||
match tcp_server.bind("127.0.0.1:38611").await {
|
||||
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<(),()>
|
||||
{
|
||||
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;
|
||||
width:100%;
|
||||
height:3rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
background-color:#282828;
|
||||
border-bottom:1px solid #404040;
|
||||
|
@ -52,3 +52,64 @@ main>table.list td:last-child>button{
|
||||
main>table.list td:last-child>button:hover{
|
||||
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");
|
||||
|
||||
let left_buttons = [ ];
|
||||
if(CONTEXT.Auth !== null) {
|
||||
left_buttons.push(UI.button("Start", () => {
|
||||
MESSAGE_SESSION_START();
|
||||
}));
|
||||
}
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
UI.mainnav_std(
|
||||
[ ],
|
||||
[
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
@ -251,16 +243,8 @@ const SCENES = {
|
||||
};
|
||||
|
||||
UI.mainmenu("continue");
|
||||
|
||||
let left_buttons = [ ];
|
||||
if(CONTEXT.Auth !== null) {
|
||||
left_buttons.push(UI.button("Start", () => {
|
||||
MESSAGE_SESSION_START();
|
||||
}));
|
||||
}
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
UI.mainnav_std(
|
||||
[ ],
|
||||
[
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
@ -313,16 +297,8 @@ const SCENES = {
|
||||
};
|
||||
|
||||
UI.mainmenu("join");
|
||||
|
||||
let left_buttons = [ ];
|
||||
if(CONTEXT.Auth !== null) {
|
||||
left_buttons.push(UI.button("Start", () => {
|
||||
MESSAGE_SESSION_START();
|
||||
}));
|
||||
}
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
UI.mainnav_std(
|
||||
[ ],
|
||||
[
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
@ -373,16 +349,8 @@ const SCENES = {
|
||||
};
|
||||
|
||||
UI.mainmenu("live");
|
||||
|
||||
let left_buttons = [ ];
|
||||
if(CONTEXT.Auth !== null) {
|
||||
left_buttons.push(UI.button("Start", () => {
|
||||
MESSAGE_SESSION_START();
|
||||
}));
|
||||
}
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
UI.mainnav_std(
|
||||
[ ],
|
||||
[
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
@ -433,11 +401,8 @@ const SCENES = {
|
||||
};
|
||||
|
||||
UI.mainmenu("history");
|
||||
|
||||
let left_buttons = [ ];
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
UI.mainnav_std(
|
||||
[ ],
|
||||
[
|
||||
UI.div([UI.text("0 - 0 of 0")]),
|
||||
UI.button("◀", null),
|
||||
@ -483,18 +448,28 @@ const SCENES = {
|
||||
load() {
|
||||
UI.mainmenu("guide");
|
||||
UI.mainnav([
|
||||
UI.button("Game", () => { }),
|
||||
UI.button("Interface", () => { }),
|
||||
UI.button("Pieces", () => { }),
|
||||
UI.button("Dropping", () => { }),
|
||||
UI.button("Game", () => { SCENE.refresh("game.html"); }),
|
||||
UI.button("Pieces", () => { SCENE.refresh("pieces.html"); }),
|
||||
UI.button("Interface", () => { SCENE.refresh("interface.html"); }),
|
||||
], []);
|
||||
|
||||
let body = document.createElement("article");
|
||||
body.setAttribute("id", "article");
|
||||
MAIN.appendChild(body);
|
||||
|
||||
|
||||
this.refresh("game.html");
|
||||
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.mainnav([], []);
|
||||
|
||||
let body = document.createElement("article");
|
||||
body.setAttribute("id", "article");
|
||||
MAIN.appendChild(body);
|
||||
|
||||
|
||||
this.refresh("main.html");
|
||||
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(); }),
|
||||
], 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")]);
|
||||
ind_turn.setAttribute("id", "ind_turn");
|
||||
|
||||
UI.mainnav(
|
||||
left_buttons,
|
||||
[ ],
|
||||
[
|
||||
ind_turn,
|
||||
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) {
|
||||
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");
|
||||
|
||||
if(CONTEXT.Auth === null) {
|
||||
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register) }));
|
||||
left.appendChild(UI.button("Log In", () => { LOAD_STACK(SCENES.Authenticate) }));
|
||||
left.appendChild(UI.button("Register", () => { LOAD_STACK(SCENES.Register); }));
|
||||
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); }
|
||||
|
Loading…
x
Reference in New Issue
Block a user