cards/src/main.rs

240 lines
6.9 KiB
Rust
Raw Normal View History

2024-04-27 02:34:28 -04:00
use futures_util::{SinkExt, StreamExt, TryFutureExt};
use std::collections::HashMap;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
2024-04-13 21:04:42 -04:00
};
2024-04-27 02:34:28 -04:00
use std::{error::Error, fs, result::Result};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::wrappers::UnboundedReceiverStream;
use warp::ws::{Message, WebSocket};
use warp::Filter;
2024-04-13 21:04:42 -04:00
#[allow(non_snake_case)]
pub mod CAHd_game;
use crate::CAHd_game::*;
2024-04-05 22:38:41 -04:00
2024-04-06 22:38:00 -04:00
/// Parse json for card data
2024-04-27 01:03:20 -04:00
fn load_json(path: &str) -> Result<Vec<CAHCardSet>, Box<dyn Error>> {
2024-04-05 22:38:41 -04:00
let data: String = fs::read_to_string(path).expect("Error reading file");
let jayson: Vec<CAHCardSet> = serde_json::from_str(&data)?;
Ok(jayson)
}
2024-04-27 01:03:20 -04:00
fn test() -> Result<(), Box<dyn Error>> {
2024-04-06 22:38:00 -04:00
// choose decks
2024-04-05 22:38:41 -04:00
let cards_input_path: &str = "data/cah-cards-full.json";
2024-04-12 18:23:33 -04:00
// TODO: this should be a master card database and pointers
// to the cards should be passed to the game instead of actual cards
2024-04-12 02:04:58 -04:00
let chosen_packs: Vec<CAHCardSet> = load_json(cards_input_path)?;
2024-04-12 18:23:33 -04:00
println!("{}", &chosen_packs.len());
2024-04-05 22:38:41 -04:00
2024-04-12 02:04:58 -04:00
let test_player0 = CAHPlayer {
2024-04-12 18:35:13 -04:00
player_name: "Adam".to_string(),
role: PlayerRole::Host,
white: vec![],
black: vec![],
};
2024-04-12 02:04:58 -04:00
let test_player1 = CAHPlayer {
2024-04-12 18:35:13 -04:00
player_name: "Ferris".to_string(),
role: PlayerRole::Player,
white: vec![],
black: vec![],
};
2024-04-12 02:04:58 -04:00
// make some games
2024-04-12 18:23:33 -04:00
// use hashmap?
2024-04-12 02:04:58 -04:00
let mut games: Vec<CAHGame> = vec![];
// create game with/for player 0
2024-04-12 18:35:13 -04:00
let test_game0 = NewGameRequest {
name: "Test0".to_string(),
host: test_player0,
packs: chosen_packs,
};
2024-04-13 21:04:42 -04:00
games.push(CAHGame::new(test_game0)?);
2024-04-12 02:04:58 -04:00
2024-04-12 18:23:33 -04:00
// a new game request struct but this player is a player
games[0].create_player(test_player1)?;
2024-04-10 04:18:31 -04:00
2024-04-12 02:35:35 -04:00
// start round
2024-04-12 18:23:33 -04:00
games[0].game_start()?;
2024-04-13 21:04:42 -04:00
2024-04-24 02:24:10 -04:00
println!("----------------------");
for card in &games[0].players[0].white {
println!("{}", card.text);
2024-04-27 01:03:20 -04:00
}
Ok(())
}
2024-04-27 02:34:28 -04:00
/// Our global unique user id counter.
static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
/// Our state of currently connected users.
///
/// - Key is their id
/// - Value is a sender of `warp::ws::Message`
type Users = Arc<RwLock<HashMap<usize, mpsc::UnboundedSender<Message>>>>;
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
pretty_env_logger::init();
test()?;
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// Keep track of all connected users, key is usize, value
// is a websocket sender.
let users = Users::default();
// Turn our "state" into a new Filter...
let users = warp::any().map(move || users.clone());
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// GET /chat -> websocket upgrade
let chat = warp::path("chat")
// The `ws()` filter will prepare Websocket handshake...
.and(warp::ws())
.and(users)
.map(|ws: warp::ws::Ws, users| {
// This will call our function if the handshake succeeds.
ws.on_upgrade(move |socket| user_connected(socket, users))
});
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// GET / -> index html
let index = warp::path::end().map(|| warp::reply::html(INDEX_HTML));
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
let routes = index.or(chat);
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
Ok(())
}
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
async fn user_connected(ws: WebSocket, users: Users) {
// Use a counter to assign a new unique ID for this user.
let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
eprintln!("User {} connected!", my_id);
// Split the socket into a sender and receive of messages.
let (mut user_ws_tx, mut user_ws_rx) = ws.split();
// Use an unbounded channel to handle buffering and flushing of messages
// to the websocket...
let (tx, rx) = mpsc::unbounded_channel();
let mut rx = UnboundedReceiverStream::new(rx);
let _ = user_ws_tx.send(Message::text(format!("Server Message: Welcome User {}",my_id))).await;
tokio::task::spawn(async move {
while let Some(message) = rx.next().await {
user_ws_tx
.send(message)
.unwrap_or_else(|e| {
eprintln!("websocket send error: {}", e);
})
.await;
}
2024-04-27 01:03:20 -04:00
});
2024-04-27 02:34:28 -04:00
// Save the sender in our list of connected users.
users.write().await.insert(my_id, tx);
// Return a `Future` that is basically a state machine managing
// this specific user's connection.
// Every time the user sends a message, broadcast it to
// all other users...
while let Some(result) = user_ws_rx.next().await {
let msg = match result {
Ok(msg) => msg,
Err(e) => {
eprintln!("websocket error(uid={}): {}", my_id, e);
break;
}
};
user_message(my_id, msg, &users).await;
}
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// user_ws_rx stream will keep processing as long as the user stays
// connected. Once they disconnect, then...
user_disconnected(my_id, &users).await;
2024-04-27 01:03:20 -04:00
}
2024-04-27 02:34:28 -04:00
async fn user_message(my_id: usize, msg: Message, users: &Users) {
// Skip any non-Text messages...
let msg = if let Ok(s) = msg.to_str() {
s
} else {
return;
};
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
let new_msg = format!("<User#{}>: {}", my_id, msg);
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// New message from this user, send it to everyone else (except same uid)...
for (&uid, tx) in users.read().await.iter() {
if my_id != uid {
if let Err(_disconnected) = tx.send(Message::text(new_msg.clone())) {
// The tx is disconnected, our `user_disconnected` code
// should be happening in another task, nothing more to
// do here.
}
}
}
}
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
async fn user_disconnected(my_id: usize, users: &Users) {
eprintln!("User {} left.", my_id);
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
// Stream closed up, so remove from the user list
users.write().await.remove(&my_id);
}
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
static INDEX_HTML: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
<title>Warp Chat</title>
</head>
<body>
<h1>Cards</h1>
<div id="chat">
<p><em>Connecting...</em></p>
</div>
<input type="text" id="text" />
<button type="button" id="send">Send</button>
<script type="text/javascript">
const chat = document.getElementById('chat');
const text = document.getElementById('text');
const uri = 'ws://' + location.host + '/chat';
const ws = new WebSocket(uri);
function message(data) {
const line = document.createElement('p');
line.innerText = data;
chat.appendChild(line);
}
2024-04-27 01:03:20 -04:00
2024-04-27 02:34:28 -04:00
ws.onopen = function() {
chat.innerHTML = '<p><em>Connected!</em></p>';
};
ws.onmessage = function(msg) {
message(msg.data);
};
ws.onclose = function() {
chat.getElementsByTagName('em')[0].innerText = 'Disconnected!';
};
send.onclick = function() {
const msg = text.value;
ws.send(msg);
text.value = '';
message('<You>: ' + msg);
};
</script>
</body>
</html>
"#;