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)] struct Game { board: [Option; 9], turn: Option, winner: Option, } impl Game { const fn default() -> Self { Game { board: [None; 9], turn: Some(Team::Zero), winner: None, } } fn check_end(&mut self) -> bool { for [a, b, c] in [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ] { if let Some(t) = self.board[a] { if self.board[b] == Some(t) && self.board[c] == Some(t) { self.winner = Some(t); self.turn = None; return true; } } } false } } static GAME: Mutex = Mutex::new(Game::default()); // {"board"=[null,null,null,null,null,null,null,null,null],"turn"=null,"winner":null} pub struct TttApp { team: Team, last_game: Game, /// Only one socket manages the end, this can be None even when it's the end end: Option, json_buf: [u8; 128], } impl TttApp { pub fn new(team: Team) -> Self { Self { team, last_game: Game { board: [None; 9], turn: None, winner: None, }, end: None, json_buf: [0; 128], } } // pub fn is_ended(&self, board: u32) -> (bool, Option) { // 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>) { 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)).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"); } if self.end.map(|e| e.elapsed()).unwrap_or_default() > Duration::from_secs(5) { self.end = None; *game = Game { turn: Some(!unwrap_opt(game.winner).await), ..Game::default() }; } while let Some(r) = ws.rcv().await? { info!("{:?}", r); if let WsMsg::Bytes([c]) = r { let c = *c as usize; info!("c={}", c); if c >= game.board.len() { warn!("Cell played is too big!"); return; } if game.board[c].is_some() { warn!("Cell is already taken!"); return; } if game.turn == Some(self.team) { game.board[c] = Some(self.team); game.turn = Some(!self.team); if game.check_end() { self.end = Some(Instant::now()); } } else { warn!("It's not your turn!"); return; } 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 for Team { fn from(value: bool) -> Self { if value { Team::One } else { Team::Zero } } } impl Into 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(&self, serializer: S) -> Result 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 for Cell { type Error = (); fn try_from(value: char) -> Result { 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 for Cell { type Error = (); fn try_from(value: u8) -> Result { 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(()), }) } }