use async_trait::async_trait; use protocol::{ actions::Bid, bot::{BiddingBot, PlayingBot}, bridge_engine::{ BiddingStatePlayerView, GameState, PlayStatePlayerView, TableState, }, card::Card, simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot}, core::Deal, }; use rand::random; use sqlx::{PgPool, Postgres, Transaction}; use uuid::Uuid; use crate::error::BridgeError; #[async_trait] pub trait Table { fn board_number(&self) -> usize; fn state(&self) -> &TableState; async fn bid( self: Box, bid: Bid, ) -> Result, BridgeError>; async fn play( self: Box, card: Card, ) -> Result, BridgeError>; async fn set_board( self: Box, board_number: usize, deal: Deal, ) -> Result, BridgeError>; } pub struct InMemoryTable { pub state: TableState, board_number: usize, } impl InMemoryTable { pub fn new() -> Self { Self { state: TableState::Unknown, board_number: 0, } } } #[async_trait] impl Table for InMemoryTable { fn state(&self) -> &TableState { &self.state } fn board_number(&self) -> usize { self.board_number } async fn bid( self: Box, bid: Bid, ) -> Result, BridgeError> { let game = match self.state { TableState::Game(game) => game, _ => { return Err(BridgeError::InvalidRequest("no game".to_string())) } }; let game = game.bid(bid)?; Ok(Box::new(Self { state: game.into(), ..*self })) } async fn play( self: Box, card: Card, ) -> Result, BridgeError> { let game = match self.state { TableState::Game(game) => game, _ => { return Err(BridgeError::InvalidRequest("no game".to_string())) } }; let game = game.play(card)?; Ok(Box::new(Self { state: game.into(), ..*self })) } async fn set_board( self: Box, board_number: usize, deal: Deal, ) -> Result, BridgeError> { Ok(Box::new(Self { state: GameState::new(deal).into(), board_number, })) } } pub async fn advance_play( table: Box, ) -> Result, BridgeError> { let game = match table.state() { TableState::Game(game) => game, _ => return Err(BridgeError::InvalidRequest("no game".to_string())), }; let table = match game { GameState::Bidding(ref bidding) => { let player_view = BiddingStatePlayerView::from_bidding_state( &bidding, game.current_player(), ); let bot = AlwaysPassBiddingBot {}; let bid = bot.bid(&player_view).await; table.bid(bid).await } GameState::Play(game) => { let player_view = PlayStatePlayerView::from_play_state( &game, game.current_player(), ); let bot = RandomPlayingBot {}; let card = bot.play(&player_view).await; table.play(card).await } }; table } pub struct DbTable { db: PgPool, id: Uuid, pub inner: Box, } impl DbTable { pub async fn new(db: PgPool, id: Uuid, inner: Inner) -> Result { let mut txn = db.begin().await?; if let Some(_) = sqlx::query!("select id from bridge_table where id = $1", id) .fetch_optional(&mut txn) .await? { return Self::restore(db, txn, id, inner).await; } sqlx::query!("insert into bridge_table (id) values($1)", id) .execute(&mut txn) .await?; txn.commit().await?; Ok(Self { db, id, inner: Box::new(inner), }) } pub async fn restore( db: PgPool, txn: Transaction<'_, Postgres>, id: Uuid, inner: Inner, ) -> Result { txn.rollback().await?; Ok(Self { db, id, inner: Box::new(inner), }) } } #[async_trait] impl Table for DbTable { fn state(&self) -> &TableState { self.inner.state() } fn board_number(&self) -> usize { self.inner.board_number() } async fn bid( self: Box, bid: Bid, ) -> Result, BridgeError> { Ok(Box::new(Self { inner: self.inner.bid(bid).await?, ..*self })) } async fn play( self: Box, card: Card, ) -> Result, BridgeError> { Ok(Box::new(Self { inner: self.inner.play(card).await?, ..*self })) } async fn set_board( self: Box, board_number: usize, deal: Deal, ) -> Result, BridgeError> { let inner = self.inner.set_board(board_number, deal).await?; let deal: Deal = random(); sqlx::query!(r#" insert into table_boards (table_id, board_number, deal) values ($1, $2, $3) "#, self.id, board_number as i64, sqlx::types::Json(deal) as _) .execute(&self.db).await?; Ok(Box::new(Self { inner: inner, ..*self })) } }