264 lines
7.5 KiB
Rust
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(()),
|
|
})
|
|
}
|
|
}
|