summaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorKjetil Orbekk <kj@orbekk.com>2023-01-01 11:52:28 -0500
committerKjetil Orbekk <kj@orbekk.com>2023-01-01 11:52:28 -0500
commitbb2ed3a2926384df063e476d10613fa310cd7ffa (patch)
treecc9c6ea4979eef3850d78cd0b1390dfbccb5921b /server
parent1e3014a777805d3dcb691ee6ebe59c62f58f8222 (diff)
Add Table to be used with db schema
Diffstat (limited to 'server')
-rw-r--r--server/migrations/20221008120534_init.down.sql4
-rw-r--r--server/migrations/20221008120534_init.up.sql22
-rw-r--r--server/src/lib.rs21
-rw-r--r--server/src/main.rs48
-rw-r--r--server/src/table.rs115
-rw-r--r--server/tests/table_test.rs26
6 files changed, 197 insertions, 39 deletions
diff --git a/server/migrations/20221008120534_init.down.sql b/server/migrations/20221008120534_init.down.sql
index 3bff171..c57e653 100644
--- a/server/migrations/20221008120534_init.down.sql
+++ b/server/migrations/20221008120534_init.down.sql
@@ -2,8 +2,10 @@
begin;
drop table if exists sessions;
drop table if exists table_players;
+drop table if exists table_moves;
+drop table if exists table_boards;
drop table if exists object_journal;
-drop table if exists active_tables;
+drop table if exists bridge_table;
drop table if exists players;
drop type if exists player_position;
drop type if exists suit;
diff --git a/server/migrations/20221008120534_init.up.sql b/server/migrations/20221008120534_init.up.sql
index 05b7697..b8e8470 100644
--- a/server/migrations/20221008120534_init.up.sql
+++ b/server/migrations/20221008120534_init.up.sql
@@ -12,7 +12,7 @@ create table sessions (
last_refresh timestamp with time zone not null default now()
);
-create table active_tables (
+create table bridge_table (
id uuid primary key not null
);
@@ -29,9 +29,25 @@ create table object_journal (
create unique index journal_entry on object_journal (id, seq);
create table table_players (
- active_tables_id uuid not null references active_tables (id),
+ table_id uuid not null references bridge_table (id),
player_id varchar(64) not null references players (id),
position player_position,
- primary key(active_tables_id, player_id, position)
+ primary key(table_id, player_id, position)
);
create unique index player_table on table_players (player_id);
+
+create table table_boards (
+ table_id uuid not null references bridge_table (id),
+ board_number integer not null,
+ deal jsonb not null,
+ primary key(table_id, board_number)
+);
+
+create table table_moves (
+ table_id uuid not null,
+ board_number integer not null,
+ move_number integer not null,
+ move jsonb not null,
+ foreign key (table_id, board_number) references table_boards (table_id, board_number),
+ primary key(table_id, board_number, move_number)
+);
diff --git a/server/src/lib.rs b/server/src/lib.rs
new file mode 100644
index 0000000..6ca9e49
--- /dev/null
+++ b/server/src/lib.rs
@@ -0,0 +1,21 @@
+pub mod auth;
+pub mod error;
+#[cfg(debug_assertions)]
+pub mod fake_auth;
+pub mod play;
+pub mod server;
+pub mod table;
+
+#[cfg(test)]
+mod tests {
+ use env_logger::Env;
+
+ pub fn test_setup() {
+ dotenv::dotenv().ok();
+ let _ = env_logger::Builder::from_env(
+ Env::default().default_filter_or("info"),
+ )
+ .is_test(true)
+ .try_init();
+ }
+}
diff --git a/server/src/main.rs b/server/src/main.rs
index 10b1361..3e3985f 100644
--- a/server/src/main.rs
+++ b/server/src/main.rs
@@ -2,7 +2,7 @@ use serde_json::json;
use std::{collections::HashMap, env, str::FromStr, sync::Arc};
use uuid::Uuid;
-use auth::AuthenticatedSession;
+use server::auth::AuthenticatedSession;
use axum::{
extract::{Path, Query, State},
response::{Html, Redirect},
@@ -14,27 +14,18 @@ use protocol::{
card::Card,
};
use protocol::{Table, UserInfo};
-use server::ServerState;
+use server::server::ServerState;
use tower_cookies::{Cookie, CookieManagerLayer, Cookies};
use tower_http::trace::TraceLayer;
use tracing::{info, log::warn};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
-mod auth;
-mod error;
-#[cfg(debug_assertions)]
-mod fake_auth;
-mod play;
-mod server;
-use crate::{
- auth::{OauthAuthenticator, SessionId},
+use server::{
+ auth::{OauthAuthenticator, SessionId, Authenticator},
play::advance_play,
server::ServerContext,
-};
-use crate::{
error::BridgeError,
play::{DbJournal, Journal},
};
-use auth::Authenticator;
use sqlx::{postgres::PgPoolOptions, PgPool};
async fn create_default_authenticator(
@@ -51,7 +42,7 @@ async fn create_authenticator(
if std::env::var("AUTHENTICATOR").unwrap_or("".to_string())
== FAKE_AUTHENTICATOR
{
- Box::new(fake_auth::FakeAuthenticator::new())
+ Box::new(server::fake_auth::FakeAuthenticator::new())
} else {
create_default_authenticator(db_pool).await
}
@@ -119,7 +110,7 @@ async fn main() {
.route("/api/table/:id/play", post(post_play))
.route("/api/table/:id/admin/deal", post(table_new_deal))
.route("/api/login", get(login))
- .route(auth::LOGIN_CALLBACK, get(login_callback))
+ .route(server::auth::LOGIN_CALLBACK, get(login_callback))
.layer(CookieManagerLayer::new())
.layer(TraceLayer::new_for_http())
.with_state(state);
@@ -155,7 +146,7 @@ async fn get_table_view(
info!("Getting table state for {id:}");
let player_position = Player::South;
let jnl = DbJournal::new(state.db.clone(), id);
- let mut table = play::Table::new_or_replay(jnl).await?;
+ let mut table = server::play::Table::new_or_replay(jnl).await?;
info!("Advancing play");
while table.game_in_progress()
&& table.game()?.current_player() != player_position
@@ -179,7 +170,7 @@ async fn table_new_deal(
) -> Result<Json<()>, BridgeError> {
info!("Getting table state for {id:}");
let jnl = DbJournal::new(state.db.clone(), id);
- let mut table = play::Table::replay(jnl).await?;
+ let mut table = server::play::Table::replay(jnl).await?;
table.new_deal().await?;
Ok(Json(()))
}
@@ -192,7 +183,7 @@ async fn post_bid(
) -> Result<Json<()>, BridgeError> {
info!("Getting table state for {id:}");
let jnl = DbJournal::new(state.db.clone(), id);
- let mut table = play::Table::replay(jnl).await?;
+ let mut table = server::play::Table::replay(jnl).await?;
table.bid(bid).await?;
Ok(Json(()))
}
@@ -205,7 +196,7 @@ async fn post_play(
) -> Result<Json<()>, BridgeError> {
info!("Getting table state for {id:}");
let jnl = DbJournal::new(state.db.clone(), id);
- let mut table = play::Table::replay(jnl).await?;
+ let mut table = server::play::Table::replay(jnl).await?;
table.play(card).await?;
Ok(Json(()))
}
@@ -232,7 +223,7 @@ async fn create_table(
let txn = state.db.begin().await?;
let table_id = sqlx::query!(
r#"
- insert into active_tables (id)
+ insert into bridge_table (id)
values ($1)
returning id
"#,
@@ -244,7 +235,7 @@ async fn create_table(
sqlx::query!(
r#"
- insert into table_players (active_tables_id,
+ insert into table_players (table_id,
player_id,
position)
values ($1, $2, 'south')
@@ -282,7 +273,7 @@ async fn user_table(
r#"
select tables.id
from table_players players
- natural join active_tables tables
+ natural join bridge_table tables
where player_id = $1
"#,
session.player_id
@@ -318,16 +309,3 @@ async fn login(cookies: Cookies, State(state): ServerState) -> Redirect {
Redirect::temporary(auth_url.as_str())
}
-#[cfg(test)]
-mod tests {
- use env_logger::Env;
-
- pub fn test_setup() {
- dotenv::dotenv().ok();
- let _ = env_logger::Builder::from_env(
- Env::default().default_filter_or("info"),
- )
- .is_test(true)
- .try_init();
- }
-}
diff --git a/server/src/table.rs b/server/src/table.rs
new file mode 100644
index 0000000..068fb24
--- /dev/null
+++ b/server/src/table.rs
@@ -0,0 +1,115 @@
+use async_trait::async_trait;
+use protocol::{
+ bot::{BiddingBot, PlayingBot},
+ bridge_engine::{
+ Bid, BiddingStatePlayerView, GameState, PlayStatePlayerView, TableState,
+ },
+ card::Card,
+ simple_bots::{AlwaysPassBiddingBot, RandomPlayingBot},
+};
+use rand::random;
+
+use crate::error::BridgeError;
+
+#[async_trait]
+pub trait Table {
+ fn state(&self) -> &TableState;
+ async fn bid(
+ self: Box<Self>,
+ bid: Bid,
+ ) -> Result<Box<dyn Table>, BridgeError>;
+ async fn play(
+ self: Box<Self>,
+ card: Card,
+ ) -> Result<Box<dyn Table>, BridgeError>;
+ async fn new_deal(self: Box<Self>) -> Result<Box<dyn Table>, BridgeError>;
+}
+
+pub struct InMemoryTable {
+ pub state: TableState,
+}
+
+impl InMemoryTable {
+ pub fn new() -> Self {
+ Self {
+ state: TableState::Unknown,
+ }
+ }
+}
+
+#[async_trait]
+impl Table for InMemoryTable {
+ fn state(&self) -> &TableState {
+ &self.state
+ }
+
+ async fn bid(
+ self: Box<Self>,
+ bid: Bid,
+ ) -> Result<Box<dyn Table>, 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() }))
+ }
+
+ async fn play(
+ self: Box<Self>,
+ card: Card,
+ ) -> Result<Box<dyn Table>, 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() }))
+ }
+
+ async fn new_deal(self: Box<Self>) -> Result<Box<dyn Table>, BridgeError> {
+ Ok(Box::new(Self {
+ state: GameState::new(random(), random()).into(),
+ }))
+ }
+}
+
+pub async fn advance_play(
+ table: Box<dyn Table>,
+) -> Result<Box<dyn Table>, 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,
+// pub state: TableState,
+// }
diff --git a/server/tests/table_test.rs b/server/tests/table_test.rs
new file mode 100644
index 0000000..4d80f32
--- /dev/null
+++ b/server/tests/table_test.rs
@@ -0,0 +1,26 @@
+use protocol::{card::{Rank, Suit}, bridge_engine::TableState};
+use server::table::{Table, InMemoryTable};
+
+mod common;
+
+async fn table_basic_test(table: Box<dyn Table>) -> Result<(), anyhow::Error> {
+ assert!(matches!(table.state(), TableState::Unknown));
+ let mut table = table.new_deal().await?;
+ assert!(matches!(table.state(), TableState::Game(_)));
+ while matches!(table.state(), TableState::Game(_)) {
+ table = server::table::advance_play(table).await?;
+ }
+ assert!(matches!(table.state(), TableState::Result(_)));
+ table = table.new_deal().await?;
+ assert!(matches!(table.state(), TableState::Game(_)));
+
+ Ok(())
+}
+
+#[tokio::test]
+#[ignore]
+async fn in_memory_table() -> Result<(), anyhow::Error> {
+ common::test_setup();
+ table_basic_test(Box::new(InMemoryTable::new())).await?;
+ Ok(())
+}