Move JSON types to own module

This commit is contained in:
Aode (lion) 2021-12-31 14:15:52 -06:00
parent 2c3b3510c3
commit 674f677846
2 changed files with 287 additions and 273 deletions

251
src/api_types.rs Normal file
View file

@ -0,0 +1,251 @@
//! Everything in this file is part of the public JSON api
//!
//! Adding methods on types is fine, but modifying structure breaks compatibility
use uuid::Uuid;
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(transparent)]
pub(crate) struct GameId {
inner: Uuid,
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub(crate) struct Piece {
pub(crate) kind: PieceKind,
pub(crate) color: Color,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
pub(crate) struct Coordinates {
pub(crate) file: File,
pub(crate) rank: Rank,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Move {
pub(crate) piece: Piece,
pub(crate) from: Coordinates,
pub(crate) to: Coordinates,
pub(crate) game_id: GameId,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct Start {
pub(crate) player_color: Color,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct GameStart {
pub(crate) board: Vec<(File, Rank, PieceKind, Color)>,
pub(crate) game_id: GameId,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
pub(crate) enum Color {
Black,
White,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
pub(crate) enum PieceKind {
Pawn,
Rook,
Knight,
Bishop,
Queen,
King,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde_repr::Deserialize_repr,
serde_repr::Serialize_repr,
)]
#[repr(u8)]
pub(crate) enum Rank {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
pub(crate) enum File {
A,
B,
C,
D,
E,
F,
G,
H,
}
impl GameId {
pub(crate) fn new() -> Self {
GameId {
inner: Uuid::new_v4(),
}
}
}
impl Rank {
fn next(&self) -> Option<Self> {
match self {
Self::One => Some(Self::Two),
Self::Two => Some(Self::Three),
Self::Three => Some(Self::Four),
Self::Four => Some(Self::Five),
Self::Five => Some(Self::Six),
Self::Six => Some(Self::Seven),
Self::Seven => Some(Self::Eight),
Self::Eight => None,
}
}
fn prev(&self) -> Option<Self> {
match self {
Self::One => None,
Self::Two => Some(Self::One),
Self::Three => Some(Self::Two),
Self::Four => Some(Self::Three),
Self::Five => Some(Self::Four),
Self::Six => Some(Self::Five),
Self::Seven => Some(Self::Six),
Self::Eight => Some(Self::Seven),
}
}
fn diff(&self, other: &Self) -> Option<u8> {
if self > other {
Some(self.to_number() - other.to_number())
} else {
None
}
}
fn absolute_diff(&self, other: &Self) -> u8 {
self.diff(other).or_else(|| other.diff(self)).unwrap_or(0)
}
fn to_number(&self) -> u8 {
match self {
Self::One => 0,
Self::Two => 1,
Self::Three => 2,
Self::Four => 3,
Self::Five => 4,
Self::Six => 5,
Self::Seven => 6,
Self::Eight => 7,
}
}
fn from_number(value: u8) -> Option<Self> {
match value {
0 => Some(Self::One),
1 => Some(Self::Two),
2 => Some(Self::Three),
3 => Some(Self::Four),
4 => Some(Self::Five),
5 => Some(Self::Six),
6 => Some(Self::Seven),
7 => Some(Self::Eight),
_ => None,
}
}
}
impl File {
fn next(&self) -> Option<Self> {
match self {
Self::A => Some(Self::B),
Self::B => Some(Self::C),
Self::C => Some(Self::D),
Self::D => Some(Self::E),
Self::E => Some(Self::F),
Self::F => Some(Self::G),
Self::G => Some(Self::H),
Self::H => None,
}
}
fn prev(&self) -> Option<Self> {
match self {
Self::A => None,
Self::B => Some(Self::A),
Self::C => Some(Self::B),
Self::D => Some(Self::C),
Self::E => Some(Self::D),
Self::F => Some(Self::E),
Self::G => Some(Self::F),
Self::H => Some(Self::G),
}
}
fn diff(&self, other: &Self) -> Option<u8> {
if self > other {
Some(self.to_number() - other.to_number())
} else {
None
}
}
fn absolute_diff(&self, other: &Self) -> u8 {
self.diff(other).or_else(|| other.diff(self)).unwrap_or(0)
}
fn to_number(&self) -> u8 {
match self {
Self::A => 0,
Self::B => 1,
Self::C => 2,
Self::D => 3,
Self::E => 4,
Self::F => 5,
Self::G => 6,
Self::H => 7,
}
}
fn from_number(value: u8) -> Option<Self> {
match value {
0 => Some(Self::A),
1 => Some(Self::B),
2 => Some(Self::C),
3 => Some(Self::D),
4 => Some(Self::E),
5 => Some(Self::F),
6 => Some(Self::G),
7 => Some(Self::H),
_ => None,
}
}
}

View file

@ -5,11 +5,14 @@ use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::mpsc::Sender;
use tracing::Instrument;
use tracing_actix_web::TracingLogger;
use uuid::Uuid;
mod api_types;
mod init_tracing;
use api_types::{Color, Coordinates, File, GameId, GameStart, Move, Piece, PieceKind, Rank, Start};
const INITIAL_PIECES: [(File, Rank, PieceKind, Color); 32] = [
(File::A, Rank::One, PieceKind::Rook, Color::White),
(File::B, Rank::One, PieceKind::Knight, Color::White),
@ -45,245 +48,6 @@ const INITIAL_PIECES: [(File, Rank, PieceKind, Color); 32] = [
(File::H, Rank::Eight, PieceKind::Rook, Color::Black),
];
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(transparent)]
struct GameId {
inner: Uuid,
}
impl GameId {
fn new() -> Self {
GameId {
inner: Uuid::new_v4(),
}
}
}
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
struct Piece {
kind: PieceKind,
color: Color,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
struct Coordinates {
file: File,
rank: Rank,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct Move {
piece: Piece,
from: Coordinates,
to: Coordinates,
game_id: GameId,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct Start {
player_color: Color,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
enum Color {
Black,
White,
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
enum PieceKind {
Pawn,
Rook,
Knight,
Bishop,
Queen,
King,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
serde_repr::Deserialize_repr,
serde_repr::Serialize_repr,
)]
#[repr(u8)]
enum Rank {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
Eight = 8,
}
impl Rank {
fn next(&self) -> Option<Self> {
match self {
Self::One => Some(Self::Two),
Self::Two => Some(Self::Three),
Self::Three => Some(Self::Four),
Self::Four => Some(Self::Five),
Self::Five => Some(Self::Six),
Self::Six => Some(Self::Seven),
Self::Seven => Some(Self::Eight),
Self::Eight => None,
}
}
fn prev(&self) -> Option<Self> {
match self {
Self::One => None,
Self::Two => Some(Self::One),
Self::Three => Some(Self::Two),
Self::Four => Some(Self::Three),
Self::Five => Some(Self::Four),
Self::Six => Some(Self::Five),
Self::Seven => Some(Self::Six),
Self::Eight => Some(Self::Seven),
}
}
fn diff(&self, other: &Self) -> Option<u8> {
if self > other {
Some(self.to_number() - other.to_number())
} else {
None
}
}
fn absolute_diff(&self, other: &Self) -> u8 {
self.diff(other).or_else(|| other.diff(self)).unwrap_or(0)
}
fn to_number(&self) -> u8 {
match self {
Self::One => 0,
Self::Two => 1,
Self::Three => 2,
Self::Four => 3,
Self::Five => 4,
Self::Six => 5,
Self::Seven => 6,
Self::Eight => 7,
}
}
fn from_number(value: u8) -> Option<Self> {
match value {
0 => Some(Self::One),
1 => Some(Self::Two),
2 => Some(Self::Three),
3 => Some(Self::Four),
4 => Some(Self::Five),
5 => Some(Self::Six),
6 => Some(Self::Seven),
7 => Some(Self::Eight),
_ => None,
}
}
}
#[derive(
Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "camelCase")]
enum File {
A,
B,
C,
D,
E,
F,
G,
H,
}
impl File {
fn next(&self) -> Option<Self> {
match self {
Self::A => Some(Self::B),
Self::B => Some(Self::C),
Self::C => Some(Self::D),
Self::D => Some(Self::E),
Self::E => Some(Self::F),
Self::F => Some(Self::G),
Self::G => Some(Self::H),
Self::H => None,
}
}
fn prev(&self) -> Option<Self> {
match self {
Self::A => None,
Self::B => Some(Self::A),
Self::C => Some(Self::B),
Self::D => Some(Self::C),
Self::E => Some(Self::D),
Self::F => Some(Self::E),
Self::G => Some(Self::F),
Self::H => Some(Self::G),
}
}
fn diff(&self, other: &Self) -> Option<u8> {
if self > other {
Some(self.to_number() - other.to_number())
} else {
None
}
}
fn absolute_diff(&self, other: &Self) -> u8 {
self.diff(other).or_else(|| other.diff(self)).unwrap_or(0)
}
fn to_number(&self) -> u8 {
match self {
Self::A => 0,
Self::B => 1,
Self::C => 2,
Self::D => 3,
Self::E => 4,
Self::F => 5,
Self::G => 6,
Self::H => 7,
}
}
fn from_number(value: u8) -> Option<Self> {
match value {
0 => Some(Self::A),
1 => Some(Self::B),
2 => Some(Self::C),
3 => Some(Self::D),
4 => Some(Self::E),
5 => Some(Self::F),
6 => Some(Self::G),
7 => Some(Self::H),
_ => None,
}
}
}
#[derive(Clone, Debug, Default)]
struct BoardState {
inner: HashMap<Coordinates, Piece>,
@ -325,13 +89,6 @@ struct MoveSession {
reply: tokio::sync::oneshot::Sender<Reply>,
}
#[derive(serde::Serialize)]
#[serde(rename_all = "camelCase")]
struct GameStart {
board: Vec<(File, Rank, PieceKind, Color)>,
game_id: GameId,
}
#[derive(Clone, Debug)]
struct GameData {
sender: Sender<MoveSession>,
@ -352,11 +109,12 @@ struct GameDropper(GameState, GameId);
impl Drop for GameDropper {
fn drop(&mut self) {
tracing::info!("Dropping");
self.0.inner.lock().unwrap().remove(&self.1);
}
}
async fn start(start: Json<Start>, game_state: Data<GameState>) -> HttpResponse {
async fn start(Json(start): Json<Start>, game_state: Data<GameState>) -> HttpResponse {
let game_id = GameId::new();
let (tx, mut rx) = tokio::sync::mpsc::channel(1);
@ -375,22 +133,31 @@ async fn start(start: Json<Start>, game_state: Data<GameState>) -> HttpResponse
.unwrap()
.insert(game_id.clone(), game_data);
actix_web::rt::spawn(async move {
while let Ok(Some(msg)) =
actix_web::rt::time::timeout(Duration::from_secs(60 * 5), rx.recv()).await
{
/* do stuff */
if let Some(piece) = board_state.inner.remove(&msg.action.from) {
board_state.inner.insert(msg.action.to, piece);
let game_span = tracing::info_span!(
parent: None,
"Game Session",
game.id = tracing::field::debug(&game_id)
);
actix_web::rt::spawn(
async move {
while let Ok(Some(msg)) =
actix_web::rt::time::timeout(Duration::from_secs(60 * 5), rx.recv()).await
{
/* do stuff */
if let Some(piece) = board_state.inner.remove(&msg.action.from) {
board_state.inner.insert(msg.action.to, piece);
}
let _ = msg.reply.send(Reply {
board_state: board_state.clone(),
});
}
let _ = msg.reply.send(Reply {
board_state: board_state.clone(),
});
drop(game_dropper);
}
drop(game_dropper);
});
.instrument(game_span),
);
HttpResponse::Ok().json(GameStart {
board: serializable,
@ -398,17 +165,11 @@ async fn start(start: Json<Start>, game_state: Data<GameState>) -> HttpResponse
})
}
async fn make_move(action: Json<Move>, game_state: Data<GameState>) -> HttpResponse {
if let Some(data) = game_state.data_for_id(&action.0.game_id) {
let (tx, rx) = tokio::sync::oneshot::channel();
async fn make_move(Json(action): Json<Move>, game_state: Data<GameState>) -> HttpResponse {
if let Some(data) = game_state.data_for_id(&action.game_id) {
let (reply, rx) = tokio::sync::oneshot::channel();
let res = data
.sender
.send(MoveSession {
action: action.into_inner(),
reply: tx,
})
.await;
let res = data.sender.send(MoveSession { action, reply }).await;
if res.is_err() {
return HttpResponse::InternalServerError().finish();
@ -428,9 +189,11 @@ async fn make_move(action: Json<Move>, game_state: Data<GameState>) -> HttpRespo
async fn main() -> Result<(), Box<dyn std::error::Error>> {
init_tracing::init_tracing()?;
HttpServer::new(|| {
let game_state = GameState::default();
HttpServer::new(move || {
App::new()
.app_data(Data::new(GameState::default()))
.app_data(Data::new(game_state.clone()))
.wrap(TracingLogger::default())
.wrap(
Cors::default()