feat: connections & ready up
This commit is contained in:
258
src/main.rs
258
src/main.rs
@@ -1,123 +1,209 @@
|
||||
mod types;
|
||||
|
||||
use crate::types::{Color, MatchMaker, Role, AI};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio_tungstenite::{accept_async, tungstenite::Message};
|
||||
use futures_util::{SinkExt, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use tracing::{info, error, warn};
|
||||
use tracing::{error, info, warn};
|
||||
use types::Client;
|
||||
use crate::types::{Color, Role, AI};
|
||||
|
||||
type Clients = Arc<RwLock<HashMap<SocketAddr, Client>>>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
// Initialize logging
|
||||
tracing_subscriber::fmt::init();
|
||||
// Initialize logging
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let addr = "127.0.0.1:8080";
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
info!("WebSocket server listening on: {}", addr);
|
||||
let addr = "0.0.0.0:8080";
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
info!("WebSocket server listening on: {}", addr);
|
||||
|
||||
let mut clients: Clients = Arc::new(RwLock::new(HashMap::new()));
|
||||
let mut clients: Clients = Arc::new(RwLock::new(HashMap::new()));
|
||||
let mut match_maker: Arc<RwLock<MatchMaker>> = Arc::new(RwLock::new(MatchMaker::new()));
|
||||
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
tokio::spawn(handle_connection(stream, addr, clients.clone()));
|
||||
}
|
||||
while let Ok((stream, addr)) = listener.accept().await {
|
||||
tokio::spawn(handle_connection(
|
||||
stream,
|
||||
addr,
|
||||
clients.clone(),
|
||||
match_maker.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_connection(
|
||||
stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
clients: Clients,
|
||||
stream: TcpStream,
|
||||
addr: SocketAddr,
|
||||
clients: Clients,
|
||||
match_maker: Arc<RwLock<MatchMaker>>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
info!("New WebSocket connection from: {}", addr);
|
||||
info!("New WebSocket connection from: {}", addr);
|
||||
|
||||
let ws_stream = accept_async(stream).await?;
|
||||
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
let ws_stream = accept_async(stream).await?;
|
||||
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
|
||||
let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
// Store the client
|
||||
{
|
||||
clients.write().await.insert(addr, Client::new(String::new(), Role::Observer, tx));
|
||||
}
|
||||
// Store the client
|
||||
{
|
||||
clients
|
||||
.write()
|
||||
.await
|
||||
.insert(addr, Client::new(String::new(), Role::Observer, tx));
|
||||
}
|
||||
|
||||
// Spawn task to handle outgoing messages
|
||||
let mut send_task = tokio::spawn(async move {
|
||||
while let Some(msg) = rx.recv().await {
|
||||
if ws_sender.send(msg).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
// Spawn task to handle outgoing messages
|
||||
let mut send_task = tokio::spawn(async move {
|
||||
while let Some(msg) = rx.recv().await {
|
||||
if ws_sender.send(msg).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle incoming messages
|
||||
while let Some(msg) = ws_receiver.next().await {
|
||||
match msg {
|
||||
Ok(Message::Text(text)) => {
|
||||
info!("Received text from {}: {}", addr, text);
|
||||
// Handle incoming messages
|
||||
while let Some(msg) = ws_receiver.next().await {
|
||||
match msg {
|
||||
Ok(Message::Text(text)) => {
|
||||
info!("Received text from {}: {}", addr, text);
|
||||
|
||||
if text.starts_with("CONNECT") {
|
||||
let requested_username = text.split(":").collect::<Vec<&str>>()[1].to_string();
|
||||
let mut is_taken = false;
|
||||
for client in clients.read().await.values() {
|
||||
if let Some(username) = &client.username {
|
||||
if *username == requested_username {
|
||||
is_taken = true;
|
||||
break;
|
||||
}
|
||||
if text.starts_with("CONNECT") {
|
||||
let requested_username = text.split(":").collect::<Vec<&str>>()[1].to_string();
|
||||
let mut is_taken = false;
|
||||
for client in clients.read().await.values() {
|
||||
if let Some(username) = &client.username {
|
||||
if *username == requested_username {
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send(&format!("ERROR:INVALID:ID:{}", requested_username));
|
||||
is_taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if is_taken { continue; }
|
||||
|
||||
// not taken
|
||||
clients.write().await.get_mut(&addr).unwrap().role = Role::Player;
|
||||
clients.write().await.get_mut(&addr).unwrap().username = Some(requested_username);
|
||||
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("CONNECT:ACK");
|
||||
}
|
||||
else if text.starts_with("READY") {
|
||||
if clients.read().await.get(&addr).unwrap().role != Role::Player {
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("ERROR:INVALID");
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut already_ready = false;
|
||||
for ready_player in &match_maker.read().await.ready_players {
|
||||
if ready_player.username.eq(clients.read().await.get(&addr).unwrap().username.as_ref().unwrap()) {
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("ERROR:INVALID");
|
||||
already_ready = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if is_taken {
|
||||
let _ = clients.read().await.get(&addr).unwrap().send("ERROR:INVALID:ID:".to_string() + &*requested_username);
|
||||
continue;
|
||||
}
|
||||
if already_ready { continue; }
|
||||
|
||||
// not taken
|
||||
clients.write().await.get_mut(&addr).unwrap().role = Role::Player;
|
||||
clients.write().await.get_mut(&addr).unwrap().username = Some(requested_username);
|
||||
}
|
||||
else if text.starts_with("READY") {
|
||||
// TODO!
|
||||
}
|
||||
match_maker.write().await.ready_players.push(AI::new(
|
||||
clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.username
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
Color::None,
|
||||
));
|
||||
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("READY:ACK");
|
||||
}
|
||||
else if text.starts_with("PLAY") {
|
||||
// TODO!
|
||||
}
|
||||
// TODO!
|
||||
// Check if client is valid
|
||||
// Check if it's their move
|
||||
// Check if valid move
|
||||
|
||||
// Place it
|
||||
// Check game end conditions
|
||||
// Broadcast move
|
||||
}
|
||||
else {
|
||||
let _ = clients.read().await.get(&addr).unwrap().send("ERROR:UNKNOWN".to_string());
|
||||
}
|
||||
}
|
||||
Ok(Message::Close(_)) => {
|
||||
info!("Client {} disconnected", addr);
|
||||
break;
|
||||
}
|
||||
Ok(_) => { let _ = clients.read().await.get(&addr).unwrap().send("ERROR:UNKNOWN".to_string()); }
|
||||
Err(e) => {
|
||||
error!("WebSocket error for {}: {}", addr, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("ERROR:UNKNOWN");
|
||||
}
|
||||
}
|
||||
Ok(Message::Close(_)) => {
|
||||
info!("Client {} disconnected", addr);
|
||||
break;
|
||||
}
|
||||
Ok(_) => {
|
||||
let _ = clients
|
||||
.read()
|
||||
.await
|
||||
.get(&addr)
|
||||
.unwrap()
|
||||
.send("ERROR:UNKNOWN");
|
||||
}
|
||||
Err(e) => {
|
||||
error!("WebSocket error for {}: {}", addr, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
send_task.abort();
|
||||
clients.write().await.remove(&addr);
|
||||
info!("Client {} removed", addr);
|
||||
// Clean up
|
||||
send_task.abort();
|
||||
|
||||
Ok(())
|
||||
// TODO: Remove and terminate any matches
|
||||
|
||||
clients.write().await.remove(&addr);
|
||||
|
||||
info!("Client {} removed", addr);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn broadcast_message(clients: &Clients, msg: Message) {
|
||||
let clients = clients.read().await;
|
||||
for (_, client) in clients.iter() {
|
||||
if client.role == Role::Admin || client.role == Role::Observer {
|
||||
client.connection.send(msg.clone()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
let clients = clients.read().await;
|
||||
for (_, client) in clients.iter() {
|
||||
if client.role == Role::Admin || client.role == Role::Observer {
|
||||
client.connection.send(msg.clone()).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
38
src/types.rs
38
src/types.rs
@@ -1,3 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio_tungstenite::tungstenite::Message;
|
||||
@@ -33,7 +35,7 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self, text: String) -> Result<(), SendError<Message>> {
|
||||
pub fn send(&self, text: &str) -> Result<(), SendError<Message>> {
|
||||
self.connection.send(Message::text(text))
|
||||
}
|
||||
}
|
||||
@@ -51,14 +53,42 @@ impl AI {
|
||||
}
|
||||
|
||||
pub struct Match {
|
||||
pub id: u32,
|
||||
pub board: Vec<Vec<Color>>,
|
||||
pub viewers: Vec<SocketAddr>,
|
||||
pub player1: AI,
|
||||
pub player2: AI,
|
||||
}
|
||||
|
||||
impl Match {
|
||||
pub fn new(id: u32, player1: AI, player2: AI) -> Match {
|
||||
Match { id, board: Vec::new(), player1, player2 }
|
||||
pub fn new(player1: AI, player2: AI) -> Match {
|
||||
Match { board: Vec::new(), viewers: Vec::new(), player1, player2 }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MatchMaker {
|
||||
pub matches: HashMap<u32, Match>,
|
||||
pub ready_players: Vec<AI>,
|
||||
}
|
||||
|
||||
impl MatchMaker {
|
||||
pub fn new() -> MatchMaker {
|
||||
MatchMaker { matches: HashMap::new(), ready_players: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn watch(&mut self, viewer: SocketAddr, match_id: u32) {
|
||||
for aMatch in &mut self.matches.values_mut() {
|
||||
let mut found = false;
|
||||
for i in 0..aMatch.viewers.len() {
|
||||
if aMatch.viewers[i] == viewer {
|
||||
aMatch.viewers.remove(i);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if found { break; }
|
||||
}
|
||||
|
||||
self.matches.get_mut(&match_id).unwrap().viewers.push(viewer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user