feat: connections & ready up
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,11 +18,9 @@ target
|
|||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
.idea/
|
||||||
|
|
||||||
|
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
|
||||||
/.idea
|
|
||||||
|
|||||||
114
src/main.rs
114
src/main.rs
@@ -1,15 +1,15 @@
|
|||||||
mod types;
|
mod types;
|
||||||
|
|
||||||
|
use crate::types::{Color, MatchMaker, Role, AI};
|
||||||
|
use futures_util::{SinkExt, StreamExt};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tokio_tungstenite::{accept_async, tungstenite::Message};
|
use tokio_tungstenite::{accept_async, tungstenite::Message};
|
||||||
use futures_util::{SinkExt, StreamExt};
|
use tracing::{error, info, warn};
|
||||||
use std::collections::HashMap;
|
|
||||||
use tracing::{info, error, warn};
|
|
||||||
use types::Client;
|
use types::Client;
|
||||||
use crate::types::{Color, Role, AI};
|
|
||||||
|
|
||||||
type Clients = Arc<RwLock<HashMap<SocketAddr, Client>>>;
|
type Clients = Arc<RwLock<HashMap<SocketAddr, Client>>>;
|
||||||
|
|
||||||
@@ -18,14 +18,20 @@ async fn main() -> Result<(), anyhow::Error> {
|
|||||||
// Initialize logging
|
// Initialize logging
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
let addr = "127.0.0.1:8080";
|
let addr = "0.0.0.0:8080";
|
||||||
let listener = TcpListener::bind(&addr).await?;
|
let listener = TcpListener::bind(&addr).await?;
|
||||||
info!("WebSocket server listening on: {}", addr);
|
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 {
|
while let Ok((stream, addr)) = listener.accept().await {
|
||||||
tokio::spawn(handle_connection(stream, addr, clients.clone()));
|
tokio::spawn(handle_connection(
|
||||||
|
stream,
|
||||||
|
addr,
|
||||||
|
clients.clone(),
|
||||||
|
match_maker.clone(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -35,6 +41,7 @@ async fn handle_connection(
|
|||||||
stream: TcpStream,
|
stream: TcpStream,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
clients: Clients,
|
clients: Clients,
|
||||||
|
match_maker: Arc<RwLock<MatchMaker>>,
|
||||||
) -> Result<(), anyhow::Error> {
|
) -> Result<(), anyhow::Error> {
|
||||||
info!("New WebSocket connection from: {}", addr);
|
info!("New WebSocket connection from: {}", addr);
|
||||||
|
|
||||||
@@ -44,7 +51,10 @@ async fn handle_connection(
|
|||||||
|
|
||||||
// Store the client
|
// Store the client
|
||||||
{
|
{
|
||||||
clients.write().await.insert(addr, Client::new(String::new(), Role::Observer, tx));
|
clients
|
||||||
|
.write()
|
||||||
|
.await
|
||||||
|
.insert(addr, Client::new(String::new(), Role::Observer, tx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spawn task to handle outgoing messages
|
// Spawn task to handle outgoing messages
|
||||||
@@ -68,36 +78,108 @@ async fn handle_connection(
|
|||||||
for client in clients.read().await.values() {
|
for client in clients.read().await.values() {
|
||||||
if let Some(username) = &client.username {
|
if let Some(username) = &client.username {
|
||||||
if *username == requested_username {
|
if *username == requested_username {
|
||||||
|
let _ = clients
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&addr)
|
||||||
|
.unwrap()
|
||||||
|
.send(&format!("ERROR:INVALID:ID:{}", requested_username));
|
||||||
is_taken = true;
|
is_taken = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_taken {
|
if is_taken { continue; }
|
||||||
let _ = clients.read().await.get(&addr).unwrap().send("ERROR:INVALID:ID:".to_string() + &*requested_username);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not taken
|
// not taken
|
||||||
clients.write().await.get_mut(&addr).unwrap().role = Role::Player;
|
clients.write().await.get_mut(&addr).unwrap().role = Role::Player;
|
||||||
clients.write().await.get_mut(&addr).unwrap().username = Some(requested_username);
|
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") {
|
else if text.starts_with("READY") {
|
||||||
// TODO!
|
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 already_ready { continue; }
|
||||||
|
|
||||||
|
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") {
|
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 {
|
else {
|
||||||
let _ = clients.read().await.get(&addr).unwrap().send("ERROR:UNKNOWN".to_string());
|
let _ = clients
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&addr)
|
||||||
|
.unwrap()
|
||||||
|
.send("ERROR:UNKNOWN");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Message::Close(_)) => {
|
Ok(Message::Close(_)) => {
|
||||||
info!("Client {} disconnected", addr);
|
info!("Client {} disconnected", addr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Ok(_) => { let _ = clients.read().await.get(&addr).unwrap().send("ERROR:UNKNOWN".to_string()); }
|
Ok(_) => {
|
||||||
|
let _ = clients
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get(&addr)
|
||||||
|
.unwrap()
|
||||||
|
.send("ERROR:UNKNOWN");
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("WebSocket error for {}: {}", addr, e);
|
error!("WebSocket error for {}: {}", addr, e);
|
||||||
break;
|
break;
|
||||||
@@ -107,7 +189,11 @@ async fn handle_connection(
|
|||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
send_task.abort();
|
send_task.abort();
|
||||||
|
|
||||||
|
// TODO: Remove and terminate any matches
|
||||||
|
|
||||||
clients.write().await.remove(&addr);
|
clients.write().await.remove(&addr);
|
||||||
|
|
||||||
info!("Client {} removed", addr);
|
info!("Client {} removed", addr);
|
||||||
|
|
||||||
Ok(())
|
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::error::SendError;
|
||||||
use tokio::sync::mpsc::UnboundedSender;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
use tokio_tungstenite::tungstenite::Message;
|
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))
|
self.connection.send(Message::text(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,14 +53,42 @@ impl AI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct Match {
|
pub struct Match {
|
||||||
pub id: u32,
|
|
||||||
pub board: Vec<Vec<Color>>,
|
pub board: Vec<Vec<Color>>,
|
||||||
|
pub viewers: Vec<SocketAddr>,
|
||||||
pub player1: AI,
|
pub player1: AI,
|
||||||
pub player2: AI,
|
pub player2: AI,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Match {
|
impl Match {
|
||||||
pub fn new(id: u32, player1: AI, player2: AI) -> Match {
|
pub fn new(player1: AI, player2: AI) -> Match {
|
||||||
Match { id, board: Vec::new(), player1, player2 }
|
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