better performances + show team

This commit is contained in:
Arkitu 2025-04-11 20:28:25 +02:00
parent f3124bcf03
commit d949fd80c3
5 changed files with 93 additions and 43 deletions

View File

@ -12,17 +12,76 @@ static BOARD: AtomicU32 = AtomicU32::new(0);
pub struct GameClient { pub struct GameClient {
res_buf: Vec<u8, 4096>, res_buf: Vec<u8, 4096>,
/// State of the board last time it has been sent
last_board: u32,
team: Team team: Team
} }
impl GameClient { impl GameClient {
pub fn new(team: Team) -> Self { pub fn new(team: Team) -> Self {
Self { Self {
res_buf: Vec::new(), res_buf: Vec::new(),
last_board: 0,
team team
} }
} }
/// Generate board html
pub async fn generate_board_res<'a>(&'a mut self, board: u32, turn: Team, outer_html: bool) -> &'a [u8] {
self.res_buf.clear();
if outer_html {
unwrap(self.res_buf.extend_from_slice(
b"<div \
id=\"game\" \
hx-get=\"/ttt/game\" \
hx-swap=\"innerHTML\" \
hx-trigger=\"every 100ms\" \
hx-target=\"this\"\
>"
)).await;
}
unwrap(write!(
self.res_buf,
"<h3>Team : <span style=\"color:{}\">{}</span></h3>\
<div id=\"grid\">",
self.team.color(),
self.team.name()
)).await;
for c in 0..=8 {
let picked_by = if board & 2_u32.pow(c) != 0 {
Some(Team::Zero)
} else if board & 2_u32.pow(9 + c) != 0 {
Some(Team::One)
} else {
None
};
match picked_by {
Some(t) => {
unwrap(write!(
self.res_buf,
"<div class=\"cell\" style=\"background-color:{}\"></div>",
t.color()
)).await;
}
None => if self.team == turn.into() {
unwrap(write!(
self.res_buf,
"<button class=\"cell\" hx-post=\"/ttt/cell{}\" hx-trigger=\"click\" hx-target=\"#game\" hx-swap=\"innerHTML\"></button>",
c
)).await;
} else {
unwrap(self.res_buf.extend_from_slice(
b"<div class=\"cell\"></div>",
)).await;
}
};
}
unwrap(self.res_buf.extend_from_slice(b"</div>")).await;
if outer_html {
unwrap(self.res_buf.extend_from_slice(b"</div>")).await;
}
&self.res_buf
}
pub async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) { pub async fn handle_request<'a>(&'a mut self, path: &str) -> (HttpResCode, &'static str, &'a [u8]) {
if (path.starts_with("/ttt/cell") && path.len() == 10) || path == "/ttt/board" { if (path.starts_with("/ttt/cell") && path.len() == 10) || path == "/ttt/game" {
let mut board = BOARD.load(Ordering::Acquire); let mut board = BOARD.load(Ordering::Acquire);
let mut turn = TURN.load(Ordering::Acquire); let mut turn = TURN.load(Ordering::Acquire);
@ -46,42 +105,16 @@ impl GameClient {
BOARD.store(board, Ordering::Release); BOARD.store(board, Ordering::Release);
TURN.store(turn, Ordering::Release); TURN.store(turn, Ordering::Release);
} }
if self.last_board != board {
self.res_buf.clear(); self.last_board = board;
for c in 0..=8 { (HttpResCode::Ok, "html", self.generate_board_res(board, turn.into(), false).await)
let picked_by = if board & 2_u32.pow(c) != 0 { } else {
Some(Team::Zero) (HttpResCode::NoContent, "", &[])
} else if board & 2_u32.pow(9 + c) != 0 {
Some(Team::One)
} else {
None
};
match picked_by {
Some(Team::Zero) => {
unwrap(self.res_buf
.extend_from_slice(
b"<div class=\"cell\" style=\"background-color:blue\"></div>",
)).await;
}
Some(Team::One) => {
unwrap(self.res_buf.extend_from_slice(
b"<div class=\"cell\" style=\"background-color:red\"></div>",
)).await;
}
None => if self.team == turn.into() {
unwrap(write!(
self.res_buf,
"<button class=\"cell\" hx-post=\"/ttt/cell{}\" hx-trigger=\"click\" hx-target=\"#grid\" hx-swap=\"innerHTML\"></button>",
c
)).await;
} else {
unwrap(self.res_buf.extend_from_slice(
b"<div class=\"cell\"></div>",
)).await;
}
};
} }
(HttpResCode::Ok, "html", &self.res_buf) } else if path == "/ttt/initial_game" {
let board = BOARD.load(Ordering::Acquire);
let turn = TURN.load(Ordering::Acquire);
(HttpResCode::Ok, "html", self.generate_board_res(board, turn.into(), true).await)
} else { } else {
(HttpResCode::NotFound, "", &[]) (HttpResCode::NotFound, "", &[])
} }
@ -115,6 +148,20 @@ impl Not for Team {
} }
} }
} }
impl Team {
fn color(self) -> &'static str {
match self {
Team::Zero => "dodgerblue",
Team::One => "firebrick"
}
}
fn name(self) -> &'static str {
match self {
Team::Zero => "blue",
Team::One => "red"
}
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
enum Cell { enum Cell {

View File

@ -105,16 +105,16 @@ pub async fn listen_task(stack: embassy_net::Stack<'static>, team: Team, port: u
Timer::after_secs(0).await; Timer::after_secs(0).await;
let (code, res_type, res_content): (HttpResCode, &str, &[u8]) = match path { let (code, res_type, res_content): (HttpResCode, &str, &[u8]) = match path {
"/" => (HttpResCode::Ok, "html", include_bytes!("../web/index.html")), "/" => (HttpResCode::Ok, "html", include_bytes!("../static/index.html")),
"/htmx.min.js" => ( "/htmx.min.js" => (
HttpResCode::Ok, HttpResCode::Ok,
"javascript", "javascript",
include_bytes!("../web/htmx.min.js"), include_bytes!("../static/htmx.min.js"),
), ),
"/htmx.js" => ( "/htmx.js" => (
HttpResCode::Ok, HttpResCode::Ok,
"javascript", "javascript",
include_bytes!("../web/htmx.js"), include_bytes!("../static/htmx.js"),
), ),
p => game_client.handle_request(p).await p => game_client.handle_request(p).await
}; };
@ -168,6 +168,7 @@ impl Into<&str> for HttpRequestType {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum HttpResCode { pub enum HttpResCode {
Ok, Ok,
NoContent,
NotFound, NotFound,
Forbidden, Forbidden,
} }
@ -175,6 +176,7 @@ impl Into<&str> for HttpResCode {
fn into(self) -> &'static str { fn into(self) -> &'static str {
match self { match self {
HttpResCode::Ok => "HTTP/1.1 200 OK", HttpResCode::Ok => "HTTP/1.1 200 OK",
HttpResCode::NoContent => "HTTP/1.1 204 NO CONTENT",
HttpResCode::NotFound => "HTTP/1.1 404 NOT FOUND", HttpResCode::NotFound => "HTTP/1.1 404 NOT FOUND",
HttpResCode::Forbidden => "HTTP/1.1 403 FORBIDDEN", HttpResCode::Forbidden => "HTTP/1.1 403 FORBIDDEN",
} }

View File

@ -20,10 +20,11 @@
<body> <body>
<h1>TicTacToe</h1> <h1>TicTacToe</h1>
<div <div
id="grid" id="game"
hx-post="/ttt/board" hx-get="/ttt/initial_game"
hx-swap="innerHTML" hx-swap="outerHTML"
hx-trigger="every 1s" hx-trigger="load"
hx-target="this"
></div> ></div>
</body> </body>
</html> </html>