ye
This commit is contained in:
parent
c04f9dd6df
commit
9f405d9580
4 changed files with 87 additions and 43 deletions
|
@ -53,7 +53,7 @@ pub enum PlayerRole {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CAHPlayer {
|
pub struct CAHPlayer {
|
||||||
/// Player's username
|
/// Player's username
|
||||||
pub player_name: String,
|
pub name: String,
|
||||||
/// This player's role
|
/// This player's role
|
||||||
pub role: PlayerRole,
|
pub role: PlayerRole,
|
||||||
/// The player's hand
|
/// The player's hand
|
||||||
|
@ -72,7 +72,7 @@ pub struct CAHGame {
|
||||||
/// Black draw pile
|
/// Black draw pile
|
||||||
pub black: Vec<CAHCardBlack>,
|
pub black: Vec<CAHCardBlack>,
|
||||||
/// White discard pile
|
/// White discard pile
|
||||||
pub jwhite_discard: Vec<CAHCardWhite>,
|
pub white_discard: Vec<CAHCardWhite>,
|
||||||
/// Black discard pile
|
/// Black discard pile
|
||||||
pub black_discard: Vec<CAHCardBlack>,
|
pub black_discard: Vec<CAHCardBlack>,
|
||||||
/// Indicates game active/game over
|
/// Indicates game active/game over
|
||||||
|
@ -189,14 +189,14 @@ impl CAHGame {
|
||||||
pub fn create_player(&mut self, mut player: CAHPlayer) -> Result<()> {
|
pub fn create_player(&mut self, mut player: CAHPlayer) -> Result<()> {
|
||||||
println!(
|
println!(
|
||||||
"Creating player {} as {:?}",
|
"Creating player {} as {:?}",
|
||||||
&player.player_name, &player.role
|
&player.name, &player.role
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut hand_buf = vec![];
|
let mut hand_buf = vec![];
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
hand_buf.push(self.draw_one_white()?);
|
hand_buf.push(self.draw_one_white()?);
|
||||||
}
|
}
|
||||||
println!("Dealing hand for {}", &player.player_name);
|
println!("Dealing hand for {}", &player.name);
|
||||||
player.white.extend(hand_buf);
|
player.white.extend(hand_buf);
|
||||||
|
|
||||||
self.players.push(player);
|
self.players.push(player);
|
||||||
|
|
37
src/api.rs
37
src/api.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::AppState;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{
|
extract::{
|
||||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
ws::{Message, WebSocket, WebSocketUpgrade},
|
||||||
|
@ -6,19 +7,7 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
};
|
};
|
||||||
use futures::{sink::SinkExt, stream::StreamExt};
|
use futures::{sink::SinkExt, stream::StreamExt};
|
||||||
use std::{
|
use std::sync::Arc;
|
||||||
collections::HashSet,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
use tokio::sync::broadcast;
|
|
||||||
|
|
||||||
// Our shared state
|
|
||||||
pub struct AppState {
|
|
||||||
// We require unique usernames. This tracks which usernames have been taken.
|
|
||||||
pub user_set: Mutex<HashSet<String>>,
|
|
||||||
// Channel used to send messages to all connected clients.
|
|
||||||
pub tx: broadcast::Sender<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn websocket_handler(
|
pub async fn websocket_handler(
|
||||||
ws: WebSocketUpgrade,
|
ws: WebSocketUpgrade,
|
||||||
|
@ -35,18 +24,18 @@ pub async fn websocket(stream: WebSocket, state: Arc<AppState>) {
|
||||||
let (mut sender, mut receiver) = stream.split();
|
let (mut sender, mut receiver) = stream.split();
|
||||||
|
|
||||||
// Username gets set in the receive loop, if it's valid.
|
// Username gets set in the receive loop, if it's valid.
|
||||||
let mut username = String::new();
|
let mut newplayer = String::new();
|
||||||
// Loop until a text message is found.
|
// Loop until a text message is found.
|
||||||
while let Some(Ok(message)) = receiver.next().await {
|
while let Some(Ok(message)) = receiver.next().await {
|
||||||
if let Message::Text(name) = message {
|
if let Message::Text(name) = message {
|
||||||
// If username that is sent by client is not taken, fill username string.
|
// If newplayer that is sent by client is not taken, fill newplayer string.
|
||||||
check_username(&state, &mut username, &name);
|
check_username(&state, &mut newplayer, &name);
|
||||||
|
|
||||||
// If not empty we want to quit the loop else we want to quit function.
|
// If not empty we want to quit the loop else we want to quit function.
|
||||||
if !username.is_empty() {
|
if !newplayer.is_empty() {
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// Only send our client that username is taken.
|
// Only send our client that newplayer is taken.
|
||||||
let _ = sender
|
let _ = sender
|
||||||
.send(Message::Text(String::from("Username already taken.")))
|
.send(Message::Text(String::from("Username already taken.")))
|
||||||
.await;
|
.await;
|
||||||
|
@ -61,7 +50,7 @@ pub async fn websocket(stream: WebSocket, state: Arc<AppState>) {
|
||||||
let mut rx = state.tx.subscribe();
|
let mut rx = state.tx.subscribe();
|
||||||
|
|
||||||
// Now send the "joined" message to all subscribers.
|
// Now send the "joined" message to all subscribers.
|
||||||
let msg = format!("{username} joined.");
|
let msg = format!("{newplayer} joined.");
|
||||||
tracing::debug!("{msg}");
|
tracing::debug!("{msg}");
|
||||||
let _ = state.tx.send(msg);
|
let _ = state.tx.send(msg);
|
||||||
|
|
||||||
|
@ -78,13 +67,13 @@ pub async fn websocket(stream: WebSocket, state: Arc<AppState>) {
|
||||||
|
|
||||||
// Clone things we want to pass (move) to the receiving task.
|
// Clone things we want to pass (move) to the receiving task.
|
||||||
let tx = state.tx.clone();
|
let tx = state.tx.clone();
|
||||||
let name = username.clone();
|
let name = newplayer.clone();
|
||||||
|
|
||||||
// Spawn a task that takes messages from the websocket, prepends the user
|
// Spawn a task that takes messages from the websocket, prepends the user
|
||||||
// name, and sends them to all broadcast subscribers.
|
// name, and sends them to all broadcast subscribers.
|
||||||
let mut recv_task = tokio::spawn(async move {
|
let mut recv_task = tokio::spawn(async move {
|
||||||
while let Some(Ok(Message::Text(text))) = receiver.next().await {
|
while let Some(Ok(Message::Text(text))) = receiver.next().await {
|
||||||
// Add username before message.
|
// Add newplayer before message.
|
||||||
let _ = tx.send(format!("{name}: {text}"));
|
let _ = tx.send(format!("{name}: {text}"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -96,12 +85,12 @@ pub async fn websocket(stream: WebSocket, state: Arc<AppState>) {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send "user left" message (similar to "joined" above).
|
// Send "user left" message (similar to "joined" above).
|
||||||
let msg = format!("{username} left.");
|
let msg = format!("{newplayer} left.");
|
||||||
tracing::debug!("{msg}");
|
tracing::debug!("{msg}");
|
||||||
let _ = state.tx.send(msg);
|
let _ = state.tx.send(msg);
|
||||||
|
|
||||||
// Remove username from map so new clients can take it again.
|
// Remove newplayer from map so new clients can take it again.
|
||||||
state.user_set.lock().unwrap().remove(&username);
|
state.user_set.lock().unwrap().remove(&newplayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_username(state: &AppState, string: &mut String, name: &str) {
|
pub fn check_username(state: &AppState, string: &mut String, name: &str) {
|
||||||
|
|
42
src/main.rs
42
src/main.rs
|
@ -22,6 +22,7 @@ fn load_json(path: &str) -> Result<Vec<CAHCardSet>, Box<dyn Error>> {
|
||||||
Ok(jayson)
|
Ok(jayson)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
fn test() -> Result<(), Box<dyn Error>> {
|
fn test() -> Result<(), Box<dyn Error>> {
|
||||||
// choose decks
|
// choose decks
|
||||||
let cards_input_path: &str = "data/cah-cards-full.json";
|
let cards_input_path: &str = "data/cah-cards-full.json";
|
||||||
|
@ -32,14 +33,14 @@ fn test() -> Result<(), Box<dyn Error>> {
|
||||||
println!("{}", &chosen_packs.len());
|
println!("{}", &chosen_packs.len());
|
||||||
|
|
||||||
let test_player0 = CAHPlayer {
|
let test_player0 = CAHPlayer {
|
||||||
player_name: "Adam".to_string(),
|
name: "Adam".to_string(),
|
||||||
role: PlayerRole::Host,
|
role: PlayerRole::Host,
|
||||||
white: vec![],
|
white: vec![],
|
||||||
black: vec![],
|
black: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
let test_player1 = CAHPlayer {
|
let test_player1 = CAHPlayer {
|
||||||
player_name: "Ferris".to_string(),
|
name: "Ferris".to_string(),
|
||||||
role: PlayerRole::Player,
|
role: PlayerRole::Player,
|
||||||
white: vec![],
|
white: vec![],
|
||||||
black: vec![],
|
black: vec![],
|
||||||
|
@ -71,8 +72,20 @@ fn test() -> Result<(), Box<dyn Error>> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Our shared state
|
||||||
|
pub struct AppState {
|
||||||
|
// We require unique usernames. This tracks which usernames have been taken.
|
||||||
|
user_set: Mutex<HashSet<String>>,
|
||||||
|
// Channel used to send messages to all connected clients.
|
||||||
|
tx: broadcast::Sender<String>,
|
||||||
|
// Master card decks
|
||||||
|
all_cards: Mutex<Vec<CAHCardSet>>,
|
||||||
|
// Games list
|
||||||
|
games: Mutex<Vec<CAHGame>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(
|
.with(
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||||
|
@ -81,13 +94,28 @@ async fn main() {
|
||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let _ = test();
|
|
||||||
|
|
||||||
// Set up application state for use with with_state().
|
// Set up application state for use with with_state().
|
||||||
let user_set = Mutex::new(HashSet::new());
|
let user_set = Mutex::new(HashSet::new());
|
||||||
let (tx, _rx) = broadcast::channel(100);
|
let (tx, _rx) = broadcast::channel(100);
|
||||||
|
|
||||||
let app_state = Arc::new(AppState { user_set, tx });
|
// choose decks
|
||||||
|
let cards_input_path: &str = "data/cah-cards-full.json";
|
||||||
|
|
||||||
|
// TODO: this should be a master card database and pointers
|
||||||
|
// to the cards should be passed to the game instead of actual cards
|
||||||
|
let all_cards = Mutex::new(load_json(cards_input_path)?);
|
||||||
|
let games = Mutex::new(vec![]);
|
||||||
|
let app_state = Arc::new(AppState {
|
||||||
|
user_set,
|
||||||
|
tx,
|
||||||
|
all_cards,
|
||||||
|
games,
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::debug!(
|
||||||
|
"Loaded {} Card packs!",
|
||||||
|
&app_state.all_cards.lock().unwrap().len()
|
||||||
|
);
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
|
@ -97,6 +125,8 @@ async fn main() {
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3030").await.unwrap();
|
let listener = tokio::net::TcpListener::bind("0.0.0.0:3030").await.unwrap();
|
||||||
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
tracing::debug!("listening on {}", listener.local_addr().unwrap());
|
||||||
axum::serve(listener, app).await.unwrap();
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Include utf-8 file at **compile** time.
|
// Include utf-8 file at **compile** time.
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
div {
|
div {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -19,11 +22,19 @@
|
||||||
<div id="status">
|
<div id="status">
|
||||||
<p><em>Disconnected...</em></p>
|
<p><em>Disconnected...</em></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div style="display: flex;">
|
||||||
<form id="login" onsubmit="loginSubmit();return false">
|
<span>
|
||||||
<input id="username" style="display:block; width:100px; box-sizing: border-box" type="text" placeholder="username">
|
<p>Username</p>
|
||||||
<button id="join-chat" type="submit">Join Chat</button>
|
<p>Game Name</p>
|
||||||
</form>
|
</span>
|
||||||
|
<span>
|
||||||
|
<form id="new-game" onsubmit="createGame();return false">
|
||||||
|
<input id="username" style="display:block; width:100px; box-sizing: border-box" type="text" placeholder="username">
|
||||||
|
<input id="gamename" style="display:block; width:100px; box-sizing: border-box" type="text" placeholder="game name">
|
||||||
|
<br />
|
||||||
|
<button id="create-game" type="submit">Create Game</button>
|
||||||
|
</form>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<form id="chat" onsubmit="chatSubmit();return false">
|
<form id="chat" onsubmit="chatSubmit();return false">
|
||||||
|
@ -34,9 +45,23 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
websocket = new WebSocket("ws://localhost:3030/websocket");
|
websocket = new WebSocket("ws://localhost:3030/websocket");
|
||||||
|
|
||||||
function loginSubmit() {
|
function createGame() {
|
||||||
document.getElementById("join-chat").disabled = true;
|
document.getElementById("create-game").disabled = true;
|
||||||
websocket.send(username.value);
|
|
||||||
|
let CAHPlayer = {
|
||||||
|
name: username.value,
|
||||||
|
role: 'h',
|
||||||
|
white: [],
|
||||||
|
black: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
let NewGameRequest = {
|
||||||
|
name: gamename.value,
|
||||||
|
host: CAHPlayer,
|
||||||
|
packs: [0],
|
||||||
|
};
|
||||||
|
|
||||||
|
websocket.send(JSON.stringify(NewGameRequest));
|
||||||
};
|
};
|
||||||
|
|
||||||
websocket.onopen = function() {
|
websocket.onopen = function() {
|
||||||
|
|
Loading…
Add table
Reference in a new issue