pico-website/src/apps/ttt.rs

264 lines
7.5 KiB
Rust

use core::str::from_utf8_unchecked;
use core::{ops::Not, sync::atomic::Ordering};
use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
use embassy_sync::mutex::Mutex;
use embassy_time::{Duration, Instant, Timer};
use heapless::{String, Vec};
use log::{info, warn};
use pico_website::{unwrap, unwrap_opt};
use serde::Serialize;
use crate::apps::Content;
use crate::socket::ws::{Ws, WsMsg};
use crate::socket::{HttpRequestType, HttpResCode};
use super::App;
// bits [0; 8] : player zero board / bits [9; 17] : player one board / is_ended [18] / is_draw [19] / winner [20]: 0=blue 1=green / current_turn [21]: 0=blue 1=green
#[derive(Debug, Serialize, Clone, PartialEq, Eq, Default)]
struct Game {
board: [Option<Team>; 9],
turn: Option<Team>,
winner: Option<Team>,
}
static GAME: Mutex<ThreadModeRawMutex, Game> = Mutex::new(Game {
board: [None; 9],
turn: Some(Team::Zero),
winner: None,
});
// {"board"=[null,null,null,null,null,null,null,null,null],"turn"=null,"winner":null}
pub struct TttApp {
team: Team,
last_game: Game,
end: Option<(Instant, Option<Team>)>,
json_buf: [u8; 128],
}
impl TttApp {
pub fn new(team: Team) -> Self {
Self {
team,
last_game: Game::default(),
end: None,
json_buf: [0; 128],
}
}
// pub fn is_ended(&self, board: u32) -> (bool, Option<Team>) {
// if let Some((_, t)) = self.end {
// return (true, t);
// }
// for (t, m) in [(Team::Zero, 0), (Team::One, 9)] {
// for w in [
// 0b111000000,
// 0b000111000,
// 0b000000111,
// 0b100100100,
// 0b010010010,
// 0b001001001,
// 0b100010001,
// 0b001010100,
// ] {
// if board & (w << m) == (w << m) {
// return (true, Some(t));
// }
// }
// }
// if ((board | (board >> 9)) & 0b111111111) == 0b111111111 {
// return (true, None);
// }
// (false, None)
// }
// pub fn update_end_state(&mut self, board: &mut u32) {
// if let Some((i, _)) = self.end {
// if i + Duration::from_secs(7) < Instant::now() {
// self.end = None;
// // BOARD.store(0, Ordering::Release);
// *board = 0;
// }
// } else {
// if let (true, t) = self.is_ended(*board) {
// self.end = Some((Instant::now(), t));
// }
// }
// }
}
impl App for TttApp {
fn socket_name(&self) -> &'static str {
self.team.name()
}
async fn handle_request<'a>(
&'a mut self,
path: &str,
_req_type: HttpRequestType,
_content: &str,
) -> (HttpResCode, &'static str, Option<Content<'a>>) {
match path {
"/" | "/index" | "/index.html" | "/ttt" | "/ttt.html" => (
HttpResCode::Ok,
"html",
Some(include_str!("ttt.html").into()),
),
_ => (HttpResCode::NotFound, "", None),
}
}
fn accept_ws(&self, path: &str) -> bool {
matches!(path, "/blue" | "/red")
}
async fn handle_ws<'a, const BUF_SIZE: usize, const RES_HEAD_BUF_SIZE: usize>(
&'a mut self,
_path: &str,
mut ws: Ws<'a, BUF_SIZE, RES_HEAD_BUF_SIZE>,
) {
Timer::after_millis(500).await;
let r: Result<(), ()> = try {
loop {
Timer::after_millis(1).await;
let Ok(mut game) = GAME.try_lock() else {
info!("locked");
continue;
};
// match GAME.try_lock() ;
if self.last_game != *game {
// let json = unwrap(serde_json_core::to_string::<Game, 128>(&game)).await;
let n = unwrap(serde_json_core::to_slice(&(*game), &mut self.json_buf)).await;
let json =
unsafe { from_utf8_unchecked(&unwrap_opt(self.json_buf.get(..n)).await) };
info!("{:?}", json);
ws.send(WsMsg::Text(json)).await?;
self.last_game = game.clone();
}
if ws.last_msg.elapsed() >= Duration::from_secs(5) {
ws.send(WsMsg::Ping(&[])).await?;
info!("ping");
}
while let Some(r) = ws.rcv().await? {
info!("{:?}", r);
if let WsMsg::Bytes([c]) = r {
let c = *c;
info!("c={}", c);
if c > 8 {}
match game.board.get_mut(c as usize) {
None => {
warn!("Cell played is too big!");
return;
}
Some(Some(_)) => {
warn!("Cell is already taken!");
return;
}
Some(cell) => {
*cell = Some(self.team);
}
}
info!("{:#?}", game);
}
}
// Timer::after_secs(1).await;
}
};
warn!("error: {:?}", r);
Timer::after_micros(100).await;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Team {
Zero = 0,
One = 1,
}
impl From<bool> for Team {
fn from(value: bool) -> Self {
if value { Team::One } else { Team::Zero }
}
}
impl Into<bool> for Team {
fn into(self) -> bool {
match self {
Team::Zero => false,
Team::One => true,
}
}
}
impl Not for Team {
type Output = Team;
fn not(self) -> Self::Output {
match self {
Team::Zero => Team::One,
Team::One => Team::Zero,
}
}
}
impl Serialize for Team {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
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)]
enum Cell {
C0 = 0,
C1 = 1,
C2 = 2,
C3 = 3,
C4 = 4,
C5 = 5,
C6 = 6,
C7 = 7,
C8 = 8,
}
impl TryFrom<char> for Cell {
type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> {
Ok(match value {
'0' => Cell::C0,
'1' => Cell::C1,
'2' => Cell::C2,
'3' => Cell::C3,
'4' => Cell::C4,
'5' => Cell::C5,
'6' => Cell::C6,
'7' => Cell::C7,
'8' => Cell::C8,
_ => return Err(()),
})
}
}
impl TryFrom<u8> for Cell {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Cell::C0,
1 => Cell::C1,
2 => Cell::C2,
3 => Cell::C3,
4 => Cell::C4,
5 => Cell::C5,
6 => Cell::C6,
7 => Cell::C7,
8 => Cell::C8,
_ => return Err(()),
})
}
}