From 9437a81bfb0de0f5e4dde02f181610cc92a3d171 Mon Sep 17 00:00:00 2001 From: yukirij Date: Thu, 22 Aug 2024 16:25:04 -0700 Subject: [PATCH] Add Guide and About pages. --- server/Cargo.toml | 1 + server/src/main.rs | 20 +++++++ server/src/system/cache/mod.rs | 22 +++++++ www/about/main.md | 13 ++++ www/css/main.css | 1 + www/css/ui.css | 61 +++++++++++++++++++ www/guide/game.md | 78 ++++++++++++++++++++++++ www/guide/interface.md | 16 +++++ www/guide/pieces.md | 69 +++++++++++++++++++++ www/js/scene.js | 106 +++++++++++++-------------------- www/js/ui.js | 21 ++++++- 11 files changed, 342 insertions(+), 66 deletions(-) create mode 100644 www/about/main.md create mode 100644 www/guide/game.md create mode 100644 www/guide/interface.md create mode 100644 www/guide/pieces.md diff --git a/server/Cargo.toml b/server/Cargo.toml index 9931c8d..ce37c1c 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -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" } diff --git a/server/src/main.rs b/server/src/main.rs index 08d96e5..01129e4 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -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(_) => { diff --git a/server/src/system/cache/mod.rs b/server/src/system/cache/mod.rs index 8d7d992..45036c1 100644 --- a/server/src/system/cache/mod.rs +++ b/server/src/system/cache/mod.rs @@ -77,6 +77,28 @@ impl WebCache { } } + pub fn cache_md

>(&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 = 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() { diff --git a/www/about/main.md b/www/about/main.md new file mode 100644 index 0000000..cdd51b9 --- /dev/null +++ b/www/about/main.md @@ -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. diff --git a/www/css/main.css b/www/css/main.css index eac6cff..bc6b389 100644 --- a/www/css/main.css +++ b/www/css/main.css @@ -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; diff --git a/www/css/ui.css b/www/css/ui.css index 0c1e5c6..e6e0a80 100644 --- a/www/css/ui.css +++ b/www/css/ui.css @@ -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; +} diff --git a/www/guide/game.md b/www/guide/game.md new file mode 100644 index 0000000..7dacb1f --- /dev/null +++ b/www/guide/game.md @@ -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. diff --git a/www/guide/interface.md b/www/guide/interface.md new file mode 100644 index 0000000..6e8995f --- /dev/null +++ b/www/guide/interface.md @@ -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. diff --git a/www/guide/pieces.md b/www/guide/pieces.md new file mode 100644 index 0000000..281ee51 --- /dev/null +++ b/www/guide/pieces.md @@ -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. diff --git a/www/js/scene.js b/www/js/scene.js index bb3c617..de8d40c 100644 --- a/www/js/scene.js +++ b/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(); }), diff --git a/www/js/ui.js b/www/js/ui.js index 625faf1..db8ec65 100644 --- a/www/js/ui.js +++ b/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); }