This commit is contained in:
Adam 2024-08-13 18:16:31 -04:00
parent dc7b3efd4f
commit 54d16b2c01
6 changed files with 133 additions and 50 deletions

View file

@ -9,11 +9,14 @@ use std::collections::BTreeSet;
#[component] #[component]
pub fn Browser() -> impl IntoView { pub fn Browser() -> impl IntoView {
let websocket = expect_context::<WebSocketContext>(); let websocket = expect_context::<WebSocketContext>();
// TODO: don't do this
let tx = websocket.clone();
let connected = move || websocket.ready_state.get() == ConnectionReadyState::Open; let connected = move || websocket.ready_state.get() == ConnectionReadyState::Open;
let game_update_context = expect_context::<ReadSignal<Option<GamesUpdate>>>(); let game_browser_context = expect_context::<ReadSignal<Vec<GameBrowserMeta>>>();
let card_packs = expect_context::<ReadSignal<CardPacksMeta>>(); let card_packs = expect_context::<ReadSignal<CardPacksMeta>>();
let (active_games, set_active_games) = create_signal::<Vec<String>>(vec![]);
let new_game_name_ref = create_node_ref::<Input>(); let new_game_name_ref = create_node_ref::<Input>();
let (selected_packs, set_selected_packs) = create_signal::<BTreeSet<u8>>(BTreeSet::new()); let (selected_packs, set_selected_packs) = create_signal::<BTreeSet<u8>>(BTreeSet::new());
@ -24,18 +27,15 @@ pub fn Browser() -> impl IntoView {
// Game stuff // Game stuff
let new_game = move |_| { let new_game = move |_| {
if let Some(input) = new_game_name_ref.get() { if let Some(input) = new_game_name_ref.get() {
if input.value() == String::from("") { if input.value() == *"" {
logging::log!("New game name is empty!"); logging::log!("New game name is empty!");
} else if selected_packs().is_empty() { } else if selected_packs().is_empty() {
logging::log!("New game selected packs is empty!"); logging::log!("New game selected packs is empty!");
} else { } else {
(websocket.send)( tx.send(
&to_string(&NewGameRequest { &to_string(&NewGameRequest {
name: input.value(), name: input.value(),
packs: selected_packs() packs: selected_packs().into_iter().collect::<Vec<_>>(),
.into_iter()
.map(|n| n.clone()) // hax
.collect::<Vec<_>>(),
}) })
.unwrap(), .unwrap(),
); );
@ -44,33 +44,65 @@ pub fn Browser() -> impl IntoView {
} }
}; };
create_effect(move |_| {
game_update_context.with(move |games| {
if let Some(games) = games {
set_active_games(games.games.clone());
}
})
});
// Clear games list on disconnect
create_effect(move |_| {
if !connected() {
set_active_games(vec![]);
}
});
let (show_packs, set_show_packs) = create_signal(false); let (show_packs, set_show_packs) = create_signal(false);
let show_packs_button = move |_| set_show_packs(!show_packs()); let show_packs_button = move |_| set_show_packs(!show_packs());
let (join_id, set_join_id) = create_signal("".to_string());
create_effect(move |_| {
websocket.send(&to_string(&GameJoinRequest { id: join_id() }).unwrap());
});
view! { view! {
<div class="p-1"> <div class="p-1">
<h2 class="text-2xl">Game Browser</h2> <h2 class="text-2xl">Game Browser</h2>
<ul> <table class="min-w-full border border-collapse table-auto">
{move || active_games().into_iter().map(|n| view! { <li>{n}</li> }).collect_view()} <thead>
</ul> <tr>
<th class="border-b">Name</th>
<th class="border-b">Host</th>
<th class="border-b">Players</th>
<th class="border-b">Card Packs</th>
<th class="border-b"></th>
</tr>
</thead>
{move || {
game_browser_context()
.iter()
.map(|game| {
view! {
<tr>
<td class="text-center border-b">{&game.name}</td>
<td class="text-center border-b">{&game.host}</td>
<td class="text-center border-b">
{&game.players.to_string()}
</td>
<td class="text-center border-b">
{&game.packs.len().to_string()}
</td>
<td class="text-center border-b">
<button
type="button"
value=&game.uuid
on:click=move |e| {
set_join_id(event_target_value(&e));
}
>
Join
</button>
<button type="button">Delete</button>
</td>
</tr>
}
})
.collect_view()
}}
</table>
</div> </div>
<hr/> <hr/>
<div class="flex p-1"> <div class="flex p-1">
<form onsubmit="return false" on:submit=new_game> <form onsubmit="return false" on:submit=new_game>
<h2 class="text-2xl">Create Game</h2> <h2 class="text-2xl">Create Game</h2>
@ -129,6 +161,8 @@ pub fn Browser() -> impl IntoView {
<label for=n.pack>{n.name}</label> <label for=n.pack>{n.name}</label>
<br/> <br/>
// hax
{set_selected_packs {set_selected_packs
.update(|packs| { .update(|packs| {
packs.insert(n.pack); packs.insert(n.pack);
@ -148,6 +182,7 @@ pub fn Browser() -> impl IntoView {
.map(|n| { .map(|n| {
view! { view! {
<input <input
checked
type="checkbox" type="checkbox"
value=n.pack value=n.pack
id=n.pack id=n.pack
@ -170,6 +205,12 @@ pub fn Browser() -> impl IntoView {
<label for=n.pack>{n.name}</label> <label for=n.pack>{n.name}</label>
<br/> <br/>
// hax
{set_selected_packs
.update(|packs| {
packs.insert(n.pack);
})}
} }
}) })
.collect_view() .collect_view()

View file

@ -1,10 +1,10 @@
use crate::components::websocket::WebSocketContext; // use crate::components::websocket::WebSocketContext;
use leptos::*; use leptos::*;
use lib::*; use lib::*;
#[component] #[component]
pub fn Game() -> impl IntoView { pub fn Game() -> impl IntoView {
let websocket = expect_context::<WebSocketContext>(); // let websocket = expect_context::<WebSocketContext>();
let game_meta = expect_context::<ReadSignal<Option<GameMeta>>>(); let game_meta = expect_context::<ReadSignal<Option<GameMeta>>>();
let (game_name, set_game_name) = create_signal("".to_string()); let (game_name, set_game_name) = create_signal("".to_string());
@ -27,18 +27,31 @@ pub fn Game() -> impl IntoView {
view! { view! {
<div class="p-1"> <div class="p-1">
<h2 class="text-2xl">Game</h2> <div class="relative">
<span class="flex"> <h2 class="text-2xl">Game</h2>
<p class="p-3">Name: {move || game_name()}</p> <span>
<p class="p-3">Host: {move || game_host()}</p> <p>Name: {move || game_name()}</p>
<p class="p-3">Czar: {move || game_czar()}</p> <p>Host: {move || game_host()}</p>
<p class="p-3">Players: {move || game_players()}</p> <p>Czar: {move || game_czar()}</p>
</span> </span>
<div class="relative w-40 h-60 text-white bg-black rounded-lg"> <span class="absolute top-0 right-0">
<p>Players:</p>
<ul>
{move || {
game_players()
.iter()
.map(|player| view! { <li>{player}</li> })
.collect_view()
}}
</ul>
</span>
</div>
<div class="relative m-auto w-40 h-60 text-white bg-black rounded-lg">
<p class="p-4">{move || game_black().0}</p> <p class="p-4">{move || game_black().0}</p>
<p class="absolute right-4 bottom-4">Pick: {move || game_black().1}</p> <p class="absolute right-4 bottom-4">Pick: {move || game_black().1}</p>
</div> </div>
<div class="inline-flex flex-wrap"> <div class="inline-flex flex-wrap justify-center">
{move || { {move || {
game_white() game_white()
.iter() .iter()

View file

@ -72,7 +72,7 @@ pub fn Websocket() -> impl IntoView {
let (user_update, set_user_update) = create_signal::<Option<UserUpdate>>(Option::None); let (user_update, set_user_update) = create_signal::<Option<UserUpdate>>(Option::None);
let (chat_update, set_chat_update) = create_signal::<Option<ChatUpdate>>(Option::None); let (chat_update, set_chat_update) = create_signal::<Option<ChatUpdate>>(Option::None);
let (chat_message, set_chat_message) = create_signal::<Option<ChatMessage>>(Option::None); let (chat_message, set_chat_message) = create_signal::<Option<ChatMessage>>(Option::None);
let (active_games, set_active_games) = create_signal::<Option<GamesUpdate>>(Option::None); let (active_games, set_active_games) = create_signal::<Vec<GameBrowserMeta>>(vec![]);
let (current_game, set_current_game) = create_signal::<Option<GameMeta>>(Option::None); let (current_game, set_current_game) = create_signal::<Option<GameMeta>>(Option::None);
let (card_packs_meta, set_card_packs_meta) = create_signal::<CardPacksMeta>(CardPacksMeta { let (card_packs_meta, set_card_packs_meta) = create_signal::<CardPacksMeta>(CardPacksMeta {
official_meta: vec![], official_meta: vec![],
@ -83,7 +83,7 @@ pub fn Websocket() -> impl IntoView {
provide_context::<ReadSignal<Option<UserUpdate>>>(user_update); provide_context::<ReadSignal<Option<UserUpdate>>>(user_update);
provide_context::<ReadSignal<Option<ChatUpdate>>>(chat_update); provide_context::<ReadSignal<Option<ChatUpdate>>>(chat_update);
provide_context::<ReadSignal<Option<ChatMessage>>>(chat_message); provide_context::<ReadSignal<Option<ChatMessage>>>(chat_message);
provide_context::<ReadSignal<Option<GamesUpdate>>>(active_games); provide_context::<ReadSignal<Vec<GameBrowserMeta>>>(active_games);
provide_context::<ReadSignal<Option<GameMeta>>>(current_game); provide_context::<ReadSignal<Option<GameMeta>>>(current_game);
provide_context::<ReadSignal<CardPacksMeta>>(card_packs_meta); provide_context::<ReadSignal<CardPacksMeta>>(card_packs_meta);
provide_context::<ReadSignal<Option<ServerStateSummary>>>(state_summary); provide_context::<ReadSignal<Option<ServerStateSummary>>>(state_summary);
@ -106,7 +106,7 @@ pub fn Websocket() -> impl IntoView {
} else if let Ok(chat_update) = from_str::<ChatUpdate>(message) { } else if let Ok(chat_update) = from_str::<ChatUpdate>(message) {
set_chat_update(Some(chat_update)); set_chat_update(Some(chat_update));
} else if let Ok(games_update) = from_str::<GamesUpdate>(message) { } else if let Ok(games_update) = from_str::<GamesUpdate>(message) {
set_active_games(Some(games_update)); set_active_games(games_update.games);
} else if let Ok(game_update) = from_str::<GameMeta>(message) { } else if let Ok(game_update) = from_str::<GameMeta>(message) {
set_current_game(Some(game_update)); set_current_game(Some(game_update));
} else if let Ok(packs_meta_update) = from_str::<CardPacksMeta>(message) { } else if let Ok(packs_meta_update) = from_str::<CardPacksMeta>(message) {

View file

@ -1,14 +1,32 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// Game join request
#[derive(Debug, Serialize, Deserialize)]
pub struct GameJoinRequest {
pub id: String,
}
/// Game meta /// Game meta
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GameMeta { pub struct GameMeta {
pub uuid: String,
pub name: String, pub name: String,
pub host: String, pub host: String,
pub players: Vec<String>, pub players: Vec<String>,
pub czar: String, pub czar: String,
pub black: (String, u8), pub black: (String, u8),
pub white: Vec<String>, pub white: Vec<String>,
pub packs: Vec<u8>,
}
/// Game browser meta
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GameBrowserMeta {
pub uuid: String,
pub name: String,
pub host: String,
pub players: usize,
pub packs: Vec<u8>,
} }
/// Card Pack Meta /// Card Pack Meta
@ -30,7 +48,7 @@ pub struct CardPacksMeta {
/// Games update /// Games update
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct GamesUpdate { pub struct GamesUpdate {
pub games: Vec<String>, pub games: Vec<GameBrowserMeta>,
} }
/// Chat update /// Chat update

View file

@ -161,7 +161,7 @@ struct NewGameManifest {
/// A struct that represents a player /// A struct that represents a player
#[derive(Debug)] #[derive(Debug)]
struct Player { pub struct Player {
/// The player's hand /// The player's hand
white: Vec<CardWhite>, white: Vec<CardWhite>,
/// The player's wins /// The player's wins
@ -171,6 +171,8 @@ struct Player {
/// The game master /// The game master
#[derive(Debug)] #[derive(Debug)]
pub struct Game { pub struct Game {
/// Game's UUID
pub uuid: Uuid,
/// The name of the game /// The name of the game
pub name: String, pub name: String,
/// The host user of the game /// The host user of the game
@ -179,20 +181,25 @@ pub struct Game {
white: Vec<CardWhite>, white: Vec<CardWhite>,
/// Black draw pile /// Black draw pile
black: Vec<CardBlack>, black: Vec<CardBlack>,
players: HashMap<Uuid, Player>, pub players: HashMap<Uuid, Player>,
/// Black card for the current round /// Black card for the current round
current_black: Option<CardBlack>, current_black: Option<CardBlack>,
pub packs: Vec<u8>,
} }
impl Game { impl Game {
fn new(state: Arc<AppState>, request: NewGameManifest) -> Result<Self> { fn new(state: Arc<AppState>, request: NewGameManifest) -> Result<Self> {
tracing::debug!("{:#?}", request.packs);
tracing::debug!("{:#?}", request.packs.len());
let mut game = Game { let mut game = Game {
uuid: Uuid::now_v7(),
name: request.host.read().unwrap().name.clone(), name: request.host.read().unwrap().name.clone(),
host: request.host.clone(), host: request.host.clone(),
white: vec![], white: vec![],
black: vec![], black: vec![],
players: HashMap::new(), players: HashMap::new(),
current_black: Option::None, current_black: Option::None,
packs: request.packs.clone(),
}; };
tracing::debug!( tracing::debug!(
"Creating game {} with {} as host", "Creating game {} with {} as host",
@ -367,6 +374,7 @@ impl GameHandler {
} }
let meta = GameMeta { let meta = GameMeta {
uuid: new_game_object.uuid.to_string(),
name: new_game_object.name.clone(), name: new_game_object.name.clone(),
host: new_game_object.host.read().unwrap().name.clone(), host: new_game_object.host.read().unwrap().name.clone(),
players: new_game_object players: new_game_object
@ -377,7 +385,7 @@ impl GameHandler {
.user_uuid .user_uuid
.read() .read()
.unwrap() .unwrap()
.get(&player.0) .get(player.0)
.unwrap() .unwrap()
.read() .read()
.unwrap() .unwrap()
@ -395,6 +403,7 @@ impl GameHandler {
.iter() .iter()
.map(|card| card.text.clone()) .map(|card| card.text.clone())
.collect(), .collect(),
packs: new_game_object.packs.clone(),
}; };
tx.send(serde_json::to_string(&meta).unwrap()) tx.send(serde_json::to_string(&meta).unwrap())

View file

@ -283,17 +283,19 @@ pub fn meta_server_summary_update(state: &Arc<AppState>) -> String {
/// Generate games list update /// Generate games list update
pub fn meta_games_browser_update(state: &Arc<AppState>) -> String { pub fn meta_games_browser_update(state: &Arc<AppState>) -> String {
// TODO: this may get expensive if there are many games // TODO: this may get expensive if there are many games
let mut names = vec![]; let mut games = vec![];
for game in state.games.read().unwrap().values() { for game in state.games.read().unwrap().values() {
names.push(format!( games.push(GameBrowserMeta {
"Name: {} Host: {}", uuid: game.read().unwrap().uuid.to_string(),
game.read().unwrap().name, name: game.read().unwrap().name.clone(),
game.read().unwrap().host.read().unwrap().name host: game.read().unwrap().host.read().unwrap().name.clone(),
)); players: game.read().unwrap().players.len(),
packs: game.read().unwrap().packs.clone(),
});
} }
to_string::<GamesUpdate>(&GamesUpdate { games: names }).unwrap() to_string::<GamesUpdate>(&GamesUpdate { games }).unwrap()
} }
/// Generate chatroom join announcement /// Generate chatroom join announcement