Multiplayer #1

Merged
joel merged 19 commits from multiplayer into main 2024-12-26 18:38:24 +00:00
5 changed files with 58 additions and 68 deletions
Showing only changes of commit 12ce134047 - Show all commits

View file

@ -66,7 +66,7 @@ enum RoomEvent {
PlayerJoined(Player),
PlayerLeft(Player),
AIJoined(Difficulty),
//AILeft(Difficulty),
AILeft { index: usize },
}
#[derive(Clone, Serialize, Debug)]
@ -74,6 +74,7 @@ enum RoomEvent {
enum ServerToClientMessage {
RoomChange { event: RoomEvent, info: PartyInfo },
GameEvent { state: ApiState },
Invalid { reason: String },
}
#[derive(Deserialize, Debug)]
@ -84,6 +85,7 @@ enum ClientToServerMessage {
StartGame,
GameMove,
AddAI { difficulty: Difficulty },
RemoveAI { index: usize },
}
#[derive(Clone, Debug)]
@ -99,6 +101,7 @@ async fn incoming_message_handler<E: std::fmt::Display>(
sender: &Sender<InnerRoomMessage>,
player: &Player,
room: &Arc<RwLock<Room>>,
stream: &mut DuplexStream,
) -> bool {
match message {
None => {
@ -184,6 +187,23 @@ async fn incoming_message_handler<E: std::fmt::Display>(
};
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
}
ClientToServerMessage::RemoveAI { index } => {
let mut room = room.write().await;
if index < room.party_info.ais.len() {
room.party_info.ais.remove(index);
let event = ServerToClientMessage::RoomChange {
event: RoomEvent::AILeft { index },
info: room.party_info.clone(),
};
sender.send(InnerRoomMessage::PassThrough(event)).unwrap();
} else {
let event = ServerToClientMessage::Invalid {
reason: format!("{index} is out of bounds"),
};
let event = serde_json::to_string(&event).unwrap();
let _ = stream.send(event.into()).await;
}
}
}
}
Err(e) => {
@ -204,6 +224,7 @@ async fn outgoing_message_handler<E: std::fmt::Debug>(
stream: &mut DuplexStream,
) -> bool {
let message = message.unwrap();
println!("Inner room message - {:#?}", message);
let message = match message {
InnerRoomMessage::PassThrough(event) => serde_json::to_string(&event).unwrap(),
InnerRoomMessage::GameEvent => {
@ -290,7 +311,7 @@ async fn chat(
// Rust formatter can't reach into this macro, hence we broke out the logic
// into sub-functions
message = incoming_message => {
if incoming_message_handler(message, &sender, &player, &room).await {
if incoming_message_handler(message, &sender, &player, &room, &mut stream).await {
return Ok(())
}
},
@ -304,56 +325,6 @@ async fn chat(
}
})
})
//
// ws.channel(move |mut stream| Box::pin(async move {
// let mut interval = interval(Duration::from_secs(10));
// while !stream.is_terminated(){ // always seems to return true?
// let ws_incoming = stream.next();
// let other_incoming = receiver.recv();
// let ping_tick = interval.tick();
//
// // pin_mut!(ws_incoming, other_incoming); // no clue what this does
//
// select! {
// message = ws_incoming => {
// if message.is_none() {
// println!("Websocket closed");
// return Ok(())
// }
//
// println!("websocket received a websocket message");
// let message = message.unwrap()?;
//
// if let ws::Message::Close(close_frame) = &message {
// println!("Received close message");
// println!("{close_frame:?}")
// } else if let ws::Message::Text(text) = &message {
// println!("Received text {text:?}");
// sender.send(text.to_string()).unwrap();
// } else {
// println!("Received non-text message: {message:?}")
// }
// },
// message = other_incoming => {
// let message = message.unwrap();
// println!("Sending message \"{message}\" via websocket");
//
//
//
// let _ = stream.send(message.into()).await; // always seems to return Ok(()), even after a disconnection
// //println!("Message sent: {blat:?}");
// }
// _ = ping_tick => {
// println!("ping_tick");
// let message = Message::Ping(Vec::new());
// let _ = stream.send(message.into()).await;
// }
// }
// }
// Ok(())
//
// }))
}
#[launch]

View file

@ -18,6 +18,16 @@ interface PartyInfo {
players: Player[]
}
const LOGBASE = 10000;
function unprocessAIRandomness(processedAIRandomness: number): string {
const x = 100*(LOGBASE**processedAIRandomness -1) / (LOGBASE-1)
return x.toFixed(0)
}
function unprocessedAIProportion(processedProportion: number): string {
return (100 * (1-processedProportion)).toFixed(0);
}
export function Menu(): React.JSX.Element {
const [roomName, setRoomName] = useState<string>("");
@ -31,8 +41,7 @@ export function Menu(): React.JSX.Element {
// Can change log scale to control shape of curve using following equation:
// aiRandomness = log(1 + x*(n-1))/log(n) when x, the user input, ranges between 0 and 1
const logBase: number = 10000;
const processedAIRandomness = Math.log(1 + (logBase - 1)*aiRandomness/100) / Math.log(logBase);
const processedAIRandomness = Math.log(1 + (LOGBASE - 1)*aiRandomness/100) / Math.log(LOGBASE);
const processedProportionDictionary = 1.0 - proportionDictionary / 100;
if(socket != null && partyInfo == null) {
@ -42,8 +51,19 @@ export function Menu(): React.JSX.Element {
return <li key={x.id}>{x.name}</li>;
});
const ais = partyInfo.ais.map((x, i) => {
return <li key={i}>Proportion: {x.proportion} / Randomness: {x.randomness}</li>
})
return <li key={i}>
<span>Proportion: {unprocessedAIProportion(x.proportion)} / Randomness: {unprocessAIRandomness(x.randomness)}</span>
<button onClick={() => {
const event = {
type: "RemoveAI",
index: i
};
socket.send(JSON.stringify(event));
}
}>X
</button>
</li>
});
return <div>
<p>Connected to {roomName}</p>

View file

@ -26,19 +26,19 @@ impl WasmAPI {
pub fn exchange(&mut self, selection: JsValue) -> JsValue {
let selection: Vec<bool> = serde_wasm_bindgen::from_value(selection).unwrap();
let result = self.0.exchange("Player".to_string(), selection);
let result = self.0.exchange("Player", selection);
serde_wasm_bindgen::to_value(&result).unwrap()
}
pub fn pass(&mut self) -> JsValue {
let result = self.0.pass("Player".to_string());
let result = self.0.pass("Player");
serde_wasm_bindgen::to_value(&result).unwrap()
}
pub fn load(&mut self) -> JsValue {
let result = self.0.load("Player".to_string());
let result = self.0.load("Player");
serde_wasm_bindgen::to_value(&result).unwrap()
}

View file

@ -128,13 +128,13 @@ impl APIGame {
}
}
pub fn exchange(&mut self, player: String, tray_tiles: Vec<bool>) -> Result<ApiState, Error> {
self.player_exists_and_turn(&player)?;
pub fn exchange(&mut self, player: &str, tray_tiles: Vec<bool>) -> Result<ApiState, Error> {
self.player_exists_and_turn(player)?;
let (tray, turn_action, game_state) = self.0.exchange_tiles(tray_tiles)?;
let update = Update {
r#type: turn_action,
player,
player: player.to_string(),
};
self.1.push(update.clone());
@ -142,14 +142,14 @@ impl APIGame {
Ok(self.build_result(tray, Some(game_state), Some(update)))
}
pub fn pass(&mut self, player: String) -> Result<ApiState, Error> {
self.player_exists_and_turn(&player)?;
pub fn pass(&mut self, player: &str) -> Result<ApiState, Error> {
self.player_exists_and_turn(player)?;
let game_state = self.0.pass()?;
let tray = self.0.player_states.get_tray(&player).unwrap().clone();
let tray = self.0.player_states.get_tray(player).unwrap().clone();
let update = Update {
r#type: TurnAction::Pass,
player,
player: player.to_string(),
};
self.1.push(update.clone());

View file

@ -8,7 +8,7 @@ use crate::player_interaction::ai::{Difficulty, AI};
use crate::player_interaction::Tray;
use rand::prelude::SliceRandom;
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use rand::SeedableRng;
use serde::{Deserialize, Serialize, Serializer};
pub enum Player {
@ -204,7 +204,6 @@ pub struct Game {
}
impl Game {
pub fn new_specific(
mut rng: SmallRng,
dictionary: DictionaryImpl,