Compare commits
	
		
			2 Commits
		
	
	
		
			6189b82bd3
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 368d6585c4 | |||
| 92f7e5aa5f | 
							
								
								
									
										68
									
								
								public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								public/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					  <body>
 | 
				
			||||||
 | 
					    <script
 | 
				
			||||||
 | 
					      src="https://code.jquery.com/jquery-3.6.1.slim.min.js"
 | 
				
			||||||
 | 
					      integrity="sha256-w8CvhFs7iHNVUtnSP0YKEg00p9Ih13rlL9zGqvLdePA="
 | 
				
			||||||
 | 
					      crossorigin="anonymous"
 | 
				
			||||||
 | 
					    ></script>
 | 
				
			||||||
 | 
					    <h1 id="roomName">
 | 
				
			||||||
 | 
					      Not Connected
 | 
				
			||||||
 | 
					    </h1>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <button id="hostBtn" disabled>Host</button>
 | 
				
			||||||
 | 
					      <input id="roomTxt" type="text">
 | 
				
			||||||
 | 
					      <button id="joinBtn" disabled>Join</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <textarea readonly></textarea>
 | 
				
			||||||
 | 
					    <div>
 | 
				
			||||||
 | 
					      <input id="chatTxt" type="text" disabled>
 | 
				
			||||||
 | 
					      <button id="chatBtn" disabled>Chat</button>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <script>
 | 
				
			||||||
 | 
					     const ws = new WebSocket("ws://localhost:8080");
 | 
				
			||||||
 | 
					     
 | 
				
			||||||
 | 
					     ws.addEventListener('open', e => {
 | 
				
			||||||
 | 
					       $("#hostBtn").attr('disabled', false);
 | 
				
			||||||
 | 
					       $("#joinBtn").attr('disabled', false);
 | 
				
			||||||
 | 
					     });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     $("#hostBtn").on('click', e => ws.send("HOST:"));
 | 
				
			||||||
 | 
					     $("#joinBtn").on('click', e => {
 | 
				
			||||||
 | 
					       const roomCode = $('#roomTxt').val();
 | 
				
			||||||
 | 
					       const msg = `JOIN: ${roomCode}`;
 | 
				
			||||||
 | 
					       ws.send(msg);
 | 
				
			||||||
 | 
					       $("#roomName").text(roomCode);
 | 
				
			||||||
 | 
					     });
 | 
				
			||||||
 | 
					     $("#chatBtn").on('click', e => {
 | 
				
			||||||
 | 
					       let text = $('#chatTxt').val();
 | 
				
			||||||
 | 
					       $('#chatTxt').val('');
 | 
				
			||||||
 | 
					       msg = `CHAT: ${text}`;
 | 
				
			||||||
 | 
					       ws.send(msg);
 | 
				
			||||||
 | 
					     });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					     ws.addEventListener('message', e => {
 | 
				
			||||||
 | 
					       const msg = e.data;
 | 
				
			||||||
 | 
					       const pos = msg.indexOf(':');
 | 
				
			||||||
 | 
					       const command = msg.substring(0, pos);
 | 
				
			||||||
 | 
					       const args = msg.substring(pos+1).split(',');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					       switch(command) {
 | 
				
			||||||
 | 
						 case "ROOM_CODE":
 | 
				
			||||||
 | 
						   $("#roomName").text(args[0]);
 | 
				
			||||||
 | 
						   // Fallthrough
 | 
				
			||||||
 | 
						 case "JOIN_OK":
 | 
				
			||||||
 | 
						   $("#chatTxt").attr('disabled', false);
 | 
				
			||||||
 | 
						   $("#chatBtn").attr('disabled', false);
 | 
				
			||||||
 | 
						   break;
 | 
				
			||||||
 | 
						 case "CHAT":
 | 
				
			||||||
 | 
						   let text = $("textarea").val();
 | 
				
			||||||
 | 
						   text = text += `${args[0]}\n`;
 | 
				
			||||||
 | 
						   $("textarea").val(text);
 | 
				
			||||||
 | 
						 default:
 | 
				
			||||||
 | 
						   console.log("Unhandled message", msg);
 | 
				
			||||||
 | 
					       }
 | 
				
			||||||
 | 
					     });
 | 
				
			||||||
 | 
					    </script>
 | 
				
			||||||
 | 
					  </body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
use std::sync::Arc;
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use tokio::sync::Mutex;
 | 
				
			||||||
use futures::{select, FutureExt};
 | 
					use futures::{select, FutureExt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{msg, GlobalState};
 | 
					use crate::{msg, GlobalState};
 | 
				
			||||||
@@ -44,18 +45,23 @@ impl Client {
 | 
				
			|||||||
        match msg.command.as_str() {
 | 
					        match msg.command.as_str() {
 | 
				
			||||||
            "HOST" => {
 | 
					            "HOST" => {
 | 
				
			||||||
                let room_code = self.global_state.code_generator.lock().await.generate();
 | 
					                let room_code = self.global_state.code_generator.lock().await.generate();
 | 
				
			||||||
                let game_controller = GameController::default();
 | 
					                let game_controller = Arc::new(Mutex::new(GameController::default()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let (client_channel, server_channel) = channel_pair();
 | 
					                let (client_channel, server_channel) = channel_pair();
 | 
				
			||||||
                self.channel = Some(client_channel);
 | 
					                self.channel = Some(client_channel);
 | 
				
			||||||
                game_controller.channels.lock().await.push(server_channel);
 | 
					                game_controller.lock().await.push(server_channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.global_state
 | 
					                self.global_state
 | 
				
			||||||
                    .rooms
 | 
					                    .rooms
 | 
				
			||||||
                    .lock()
 | 
					                    .lock()
 | 
				
			||||||
                    .await
 | 
					                    .await
 | 
				
			||||||
                    .insert(room_code.clone(), game_controller.clone());
 | 
					                    .insert(room_code.clone(), Arc::clone(&game_controller));
 | 
				
			||||||
                tokio::spawn(async move { game_controller.run_loop().await });
 | 
					                tokio::spawn(async move {
 | 
				
			||||||
 | 
					                    loop {
 | 
				
			||||||
 | 
					                        game_controller.lock().await.poll().await;
 | 
				
			||||||
 | 
					                        tokio::task::yield_now().await;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
                self.ws.send(msg!(ROOM_CODE, room_code)).await.unwrap();
 | 
					                self.ws.send(msg!(ROOM_CODE, room_code)).await.unwrap();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            "JOIN" => {
 | 
					            "JOIN" => {
 | 
				
			||||||
@@ -67,8 +73,9 @@ impl Client {
 | 
				
			|||||||
                    Some(room) => {
 | 
					                    Some(room) => {
 | 
				
			||||||
                        let (client_channel, server_channel) = channel_pair();
 | 
					                        let (client_channel, server_channel) = channel_pair();
 | 
				
			||||||
                        self.channel = Some(client_channel);
 | 
					                        self.channel = Some(client_channel);
 | 
				
			||||||
                        room.channels.lock().await.push(server_channel);
 | 
					                        room.lock().await.push(server_channel);
 | 
				
			||||||
                        self.ws.send(msg!(JOIN_OK)).await.unwrap();
 | 
					                        self.ws.send(msg!(JOIN_OK)).await.unwrap();
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    None => {
 | 
					                    None => {
 | 
				
			||||||
                        self.ws.send(msg!(JOIN_INVALID)).await.unwrap();
 | 
					                        self.ws.send(msg!(JOIN_INVALID)).await.unwrap();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										55
									
								
								src/game.rs
									
									
									
									
									
								
							
							
						
						
									
										55
									
								
								src/game.rs
									
									
									
									
									
								
							@@ -1,20 +1,17 @@
 | 
				
			|||||||
use std::sync::Arc;
 | 
					use tokio::sync::mpsc;
 | 
				
			||||||
 | 
					 | 
				
			||||||
use tokio::sync::{ mpsc, Mutex };
 | 
					 | 
				
			||||||
use tokio::sync::mpsc::error::TryRecvError;
 | 
					use tokio::sync::mpsc::error::TryRecvError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::message::Message;
 | 
					use crate::message::Message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone)]
 | 
					struct Player {
 | 
				
			||||||
pub struct GameController {
 | 
					    name: String,
 | 
				
			||||||
    pub channels: Arc<Mutex<Channels>>,
 | 
					    channel: Option<Channel>,
 | 
				
			||||||
 | 
					    seat: Option<usize>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl std::default::Default for GameController {
 | 
					#[derive(Default)]
 | 
				
			||||||
    fn default() -> Self {
 | 
					pub struct GameController {
 | 
				
			||||||
        let channels = Arc::new(Mutex::new(Channels::default()));
 | 
					    players: Vec<Player>,
 | 
				
			||||||
        GameController { channels }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Channel {
 | 
					pub struct Channel {
 | 
				
			||||||
@@ -22,26 +19,32 @@ pub struct Channel {
 | 
				
			|||||||
    pub rx: mpsc::Receiver<Message>,
 | 
					    pub rx: mpsc::Receiver<Message>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Default)]
 | 
					impl GameController {
 | 
				
			||||||
pub struct Channels(Vec<Channel>);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Channels {
 | 
					 | 
				
			||||||
    pub fn try_recv(&mut self) -> Result<Message, TryRecvError> {
 | 
					    pub fn try_recv(&mut self) -> Result<Message, TryRecvError> {
 | 
				
			||||||
        for channel in self.0.iter_mut() {
 | 
					        for player in self.players.iter_mut() {
 | 
				
			||||||
 | 
					            if let Some(channel) = &mut player.channel {
 | 
				
			||||||
                let res = channel.rx.try_recv();
 | 
					                let res = channel.rx.try_recv();
 | 
				
			||||||
                if !(res == Err(TryRecvError::Empty)) {
 | 
					                if !(res == Err(TryRecvError::Empty)) {
 | 
				
			||||||
                    return res
 | 
					                    return res
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        Err(TryRecvError::Empty)
 | 
					        Err(TryRecvError::Empty)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    pub async fn broadcast(&mut self, msg: Message) {
 | 
					    pub async fn broadcast(&mut self, msg: Message) {
 | 
				
			||||||
        for channel in self.0.iter_mut() {
 | 
					        for player in self.players.iter() {
 | 
				
			||||||
 | 
					            if let Some(channel) = &player.channel {
 | 
				
			||||||
                channel.tx.send(msg.clone()).await.unwrap();
 | 
					                channel.tx.send(msg.clone()).await.unwrap();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    pub fn push(&mut self, channel: Channel) {
 | 
					    pub fn push(&mut self, channel: Channel) {
 | 
				
			||||||
        self.0.push(channel);
 | 
					        let player = Player {
 | 
				
			||||||
 | 
					            name: "Guest".to_string(),
 | 
				
			||||||
 | 
					            channel: Some(channel),
 | 
				
			||||||
 | 
					            seat: None, // TODO
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        self.players.push(player);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,19 +55,15 @@ pub fn channel_pair() -> (Channel, Channel) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl GameController {
 | 
					impl GameController {
 | 
				
			||||||
    pub async fn run_loop(&self) {
 | 
					    pub async fn poll(&mut self) {
 | 
				
			||||||
        loop {
 | 
					        if let Ok(msg) = self.try_recv() {
 | 
				
			||||||
            let mut channels = self.channels.lock().await;
 | 
					            self.dispatch(msg).await;
 | 
				
			||||||
            if let Ok(msg) = channels.try_recv() {
 | 
					 | 
				
			||||||
                dispatch(&mut channels, msg).await;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    }
 | 
					    async fn dispatch(&mut self, msg: Message) {
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async fn dispatch(channels: &mut Channels, msg: Message) {
 | 
					 | 
				
			||||||
        match msg.command.as_str() {
 | 
					        match msg.command.as_str() {
 | 
				
			||||||
        "CHAT" => channels.broadcast(msg).await,
 | 
					            "CHAT" => self.broadcast(msg).await,
 | 
				
			||||||
            _ => ()
 | 
					            _ => ()
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,5 +14,5 @@ use code_generator::CodeGenerator;
 | 
				
			|||||||
#[derive(Default)]
 | 
					#[derive(Default)]
 | 
				
			||||||
pub struct GlobalState {
 | 
					pub struct GlobalState {
 | 
				
			||||||
    pub code_generator: Arc<Mutex<CodeGenerator>>,
 | 
					    pub code_generator: Arc<Mutex<CodeGenerator>>,
 | 
				
			||||||
    pub rooms: Arc<Mutex<HashMap<String, GameController>>>,
 | 
					    pub rooms: Arc<Mutex<HashMap<String, Arc<Mutex<GameController>>>>>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user