Move JSON types to own module
This commit is contained in:
parent
2c3b3510c3
commit
674f677846
251
src/api_types.rs
Normal file
251
src/api_types.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
309
src/main.rs
309
src/main.rs
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue