diff --git a/src/ship/location.rs b/src/ship/location.rs index 666b797..a8c23c7 100644 --- a/src/ship/location.rs +++ b/src/ship/location.rs @@ -1,3 +1,4 @@ +use std::collections::HashMap; use std::sync::{Arc, RwLock}; use std::time::SystemTime; @@ -75,7 +76,10 @@ impl InnerClientArea<{N}> { } } + +#[derive(Debug, Copy, Clone, PartialEq)] pub struct LobbyId(pub usize); +#[derive(Debug, Copy, Clone, PartialEq)] pub struct RoomId(pub usize); pub type Lobby = InnerClientArea<12>; @@ -174,20 +178,21 @@ impl Area { } } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum CreateRoomError { NoOpenSlots, ClientInAreaAlready, + JoinError, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum JoinRoomError { RoomDoesNotExist, RoomFull, ClientInAreaAlready, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum JoinLobbyError { LobbyDoesNotExist, LobbyFull, @@ -303,3 +308,412 @@ impl ClientLocation { .count() as u8 } } + + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct LocalClientId(usize); + + +#[derive(Debug, Copy, Clone, PartialEq)] +struct RoomClient { + client: ClientId, + local_client: LocalClientId, + time_join: SystemTime, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct AreaClient2 { + client: ClientId, + local_client: LocalClientId, + time_join: SystemTime, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct Lobby2([Option; 12]); +#[derive(Debug, Copy, Clone, PartialEq)] +struct Room2([Option; 4]); + +#[derive(Debug, Copy, Clone, PartialEq)] +enum RoomLobby { + Room(RoomId), + Lobby(LobbyId), +} + +#[derive(Debug, PartialEq)] +pub enum ClientRemovalError { + ClientNotInArea, + InvalidArea, +} + +#[derive(Debug, PartialEq)] +pub enum GetClientsError { + InvalidArea, +} + +#[derive(Debug, PartialEq)] +pub enum GetNeighborError { + InvalidClient, + InvalidArea, +} + +#[derive(Debug, PartialEq)] +pub enum GetLeaderError { + InvalidClient, + InvalidArea, + NoClientInArea, +} + +pub struct ClientLocation2 { + lobbies: [Lobby2; 15], + rooms: [Option; MAX_ROOMS], + client_location: HashMap, +} + +impl ClientLocation2 { + pub fn new() -> ClientLocation2 { + ClientLocation2 { + lobbies: [Lobby2([None; 12]); 15], + rooms: [None; MAX_ROOMS], + client_location: HashMap::new(), + } + } + + pub fn add_client_to_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result<(), JoinLobbyError> { + let l = self.lobbies.get_mut(lobby.0).ok_or(JoinLobbyError::LobbyDoesNotExist)?; + let (index, empty_slot) = l.0.iter_mut() + .enumerate() + .filter(|(_, k)| k.is_none()) + .nth(0) + .ok_or(JoinLobbyError::LobbyFull)?; + *empty_slot = Some(AreaClient2 { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.remove_client_from_area(id); + self.client_location.insert(id, RoomLobby::Lobby(lobby)); + Ok(()) + } + + + pub fn add_client_to_next_available_lobby(&mut self, id: ClientId, lobby: LobbyId) -> Result { + let l = (0..15) + .map(|lobby_index| { + let new_lobby = LobbyId((lobby.0 + lobby_index) % 15); + (new_lobby, self.add_client_to_lobby(id, new_lobby)) + }) + .filter(|(_, lobby_option)| { + lobby_option.is_ok() + }) + .nth(0) + .ok_or(JoinLobbyError::LobbyFull)?; + + Ok(l.0) + } + + fn create_new_room(&mut self, id: ClientId) -> Result { + let (index, empty_slot) = self.rooms.iter_mut() + .enumerate() + .filter(|(_, r)| r.is_none()) + .nth(0) + .ok_or(CreateRoomError::NoOpenSlots)?; + *empty_slot = Some(Room2([None; 4])); + self.add_client_to_room(id, RoomId(index)).map_err(|err| CreateRoomError::JoinError)?; + + Ok(RoomId(index)) + } + + fn add_client_to_room(&mut self, id: ClientId, room: RoomId) -> Result<(), JoinRoomError> { + let r = self.rooms.get_mut(room.0) + .ok_or(JoinRoomError::RoomDoesNotExist)? + .as_mut() + .ok_or(JoinRoomError::RoomDoesNotExist)?; + let (index, empty_slot) = r.0.iter_mut() + .enumerate() + .filter(|(_, k)| k.is_none()) + .nth(0) + .ok_or(JoinRoomError::RoomFull)?; + *empty_slot = Some(AreaClient2 { + client: id, + local_client: LocalClientId(index), + time_join: SystemTime::now(), + }); + self.remove_client_from_area(id); + self.client_location.insert(id, RoomLobby::Room(room)); + Ok(()) + } + + + fn get_client_neighbors(&self, id: ClientId) -> Result, GetNeighborError> { + let area = self.client_location.get(&id).ok_or(GetNeighborError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + Ok(self.get_clients_in_room(*room).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.0 != id) + .collect()) + }, + RoomLobby::Lobby(lobby) => { + Ok(self.get_clients_in_lobby(*lobby).map_err(|_| GetNeighborError::InvalidArea)? + .into_iter() + .filter(|c| c.0 != id) + .collect()) + } + } + } + + fn get_room_leader_by_client(&self, id: ClientId) -> Result<(ClientId, LocalClientId), GetLeaderError> { + let area = self.client_location.get(&id).ok_or(GetLeaderError::InvalidClient)?; + match area { + RoomLobby::Room(room) => { + let mut r = self.rooms[room.0] + .as_ref() + .ok_or(GetLeaderError::InvalidArea)? + .0.iter().flat_map(|k| k) + .collect::>(); + //r.sort_by(|a, b| a.time_join.cmp(&b.time_join)); + r.sort_by_key(|k| k.time_join); + let c = r.get(0).ok_or(GetLeaderError::NoClientInArea)?; + Ok((c.client, c.local_client)) + }, + RoomLobby::Lobby(lobby) => { + let mut l = self.lobbies[lobby.0] + .0.iter().flat_map(|k| k) + .collect::>(); + l.sort_by_key(|k| k.time_join); + let c = l.get(0).ok_or(GetLeaderError::NoClientInArea)?; + Ok((c.client, c.local_client)) + } + } + } + + pub fn get_clients_in_lobby(&self, lobby: LobbyId) -> Result, GetClientsError> { + Ok(self.lobbies.get(lobby.0).ok_or(GetClientsError::InvalidArea)?.0 + .iter() + .filter_map(|client| { + client.map(|c| { + (c.client, c.local_client) + }) + }).collect()) + } + + pub fn get_clients_in_room(&self, room: RoomId) -> Result, GetClientsError> { + Ok(self.rooms.get(room.0) + .ok_or(GetClientsError::InvalidArea)? + .ok_or(GetClientsError::InvalidArea)?.0 + .iter() + .filter_map(|client| { + client.map(|c| { + (c.client, c.local_client) + }) + }).collect()) + } + + pub fn remove_client_from_area(&mut self, id: ClientId) -> Result<(), ClientRemovalError> { + let area = self.client_location.get_mut(&id).ok_or(ClientRemovalError::ClientNotInArea)?; + let client_list = match area { + RoomLobby::Room(room) => { + self.rooms[room.0].as_mut().map_or(None, |r| { + Some(r.0.iter_mut()) + }) + }, + RoomLobby::Lobby(lobby) => { + Some(self.lobbies[lobby.0].0.iter_mut()) + } + }; + + client_list + .ok_or(ClientRemovalError::InvalidArea)? + .filter(|client| { + client.map_or(false, |c| { + c.client == id + }) + }) + .for_each(|client| { + *client = None + }); + Ok(()) + } +} + + + + + + + + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_add_client_to_lobby() { + let mut cl = ClientLocation2::new(); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + cl.add_client_to_lobby(ClientId(13), LobbyId(1)); + cl.add_client_to_lobby(ClientId(14), LobbyId(0)); + + assert!(cl.get_clients_in_lobby(LobbyId(0)) == Ok(vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(14), LocalClientId(1)), + ])); + } + + #[test] + fn test_add_client_to_full_lobby() { + let mut cl = ClientLocation2::new(); + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)); + }); + assert!(cl.add_client_to_lobby(ClientId(99), LobbyId(0)) == Err(JoinLobbyError::LobbyFull)); + } + + #[test] + fn test_add_client_to_next_available_lobby() { + let mut cl = ClientLocation2::new(); + (1..4).for_each(|lobby| { + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); + }); + }); + assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Ok(LobbyId(4))); + } + + #[test] + fn test_add_to_lobby_when_all_are_full() { + let mut cl = ClientLocation2::new(); + (0..15).for_each(|lobby| { + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(lobby*12+i), LobbyId(lobby)); + }); + }); + assert!(cl.add_client_to_next_available_lobby(ClientId(99), LobbyId(1)) == Err(JoinLobbyError::LobbyFull)); + } + + #[test] + fn test_new_room() { + let mut cl = ClientLocation2::new(); + assert!(cl.create_new_room(ClientId(12)) == Ok(RoomId(0))); + } + + #[test] + fn test_add_client_to_room() { + let mut cl = ClientLocation2::new(); + let room = cl.create_new_room(ClientId(12)).unwrap(); + assert!(cl.add_client_to_room(ClientId(234), room) == Ok(())); + assert!(cl.get_clients_in_room(room) == Ok(vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(234), LocalClientId(1)), + ])); + } + + #[test] + fn test_no_new_room_slots() { + let mut cl = ClientLocation2::new(); + for i in 0..128 { + cl.create_new_room(ClientId(i)); + } + assert!(cl.create_new_room(ClientId(234)) == Err(CreateRoomError::NoOpenSlots)); + } + + #[test] + fn test_joining_full_room() { + let mut cl = ClientLocation2::new(); + let room = cl.create_new_room(ClientId(0)).unwrap(); + assert!(cl.add_client_to_room(ClientId(1), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(2), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(3), room) == Ok(())); + assert!(cl.add_client_to_room(ClientId(234), room) == Err(JoinRoomError::RoomFull)); + } + + #[test] + fn test_adding_client_to_room_removes_from_lobby() { + let mut cl = ClientLocation2::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + let room = cl.create_new_room(ClientId(51)).unwrap(); + assert!(cl.add_client_to_room(ClientId(93), room) == Ok(())); + assert!(cl.get_clients_in_lobby(LobbyId(0)) == Ok(vec![ + (ClientId(23), LocalClientId(1)), + (ClientId(12), LocalClientId(3)), + ])); + assert!(cl.get_clients_in_room(room) == Ok(vec![ + (ClientId(51), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + ])); + } + + #[test] + fn test_getting_neighbors() { + let mut cl = ClientLocation2::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + assert!(cl.get_client_neighbors(ClientId(23)) == Ok(vec![ + (ClientId(93), LocalClientId(0)), + (ClientId(51), LocalClientId(2)), + (ClientId(12), LocalClientId(3)), + ])); + } + + #[test] + fn test_failing_to_join_lobby_does_not_remove_from_current_area() { + let mut cl = ClientLocation2::new(); + (0..12).for_each(|i| { + cl.add_client_to_lobby(ClientId(i), LobbyId(0)); + }); + cl.add_client_to_lobby(ClientId(99), LobbyId(1)); + cl.add_client_to_lobby(ClientId(99), LobbyId(0)); + assert!(cl.get_clients_in_lobby(LobbyId(0)).unwrap().len() == 12); + assert!(cl.get_clients_in_lobby(LobbyId(1)) == Ok(vec![ + (ClientId(99), LocalClientId(0)), + ])); + } + + #[test] + fn test_room_leader() { + let mut cl = ClientLocation2::new(); + cl.add_client_to_lobby(ClientId(93), LobbyId(0)); + cl.add_client_to_lobby(ClientId(23), LobbyId(0)); + cl.add_client_to_lobby(ClientId(51), LobbyId(0)); + cl.add_client_to_lobby(ClientId(12), LobbyId(0)); + + assert!(cl.get_room_leader_by_client(ClientId(51)) == Ok((ClientId(93), LocalClientId(0)))); + } + + #[test] + fn test_remove_client_from_room() { + let mut cl = ClientLocation2::new(); + let room = cl.create_new_room(ClientId(51)).unwrap(); + cl.add_client_to_room(ClientId(93), room); + cl.add_client_to_room(ClientId(23), room); + cl.remove_client_from_area(ClientId(51)); + cl.add_client_to_room(ClientId(12), room); + + assert!(cl.get_clients_in_room(room) == Ok(vec![ + (ClientId(12), LocalClientId(0)), + (ClientId(93), LocalClientId(1)), + (ClientId(23), LocalClientId(2)), + ])); + } + + #[test] + fn test_room_leader_changes_on_leader_leaving() { + let mut cl = ClientLocation2::new(); + let room = cl.create_new_room(ClientId(51)).unwrap(); + cl.add_client_to_room(ClientId(93), room); + cl.add_client_to_room(ClientId(23), room); + cl.remove_client_from_area(ClientId(51)); + cl.add_client_to_room(ClientId(12), room); + cl.remove_client_from_area(ClientId(23)); + cl.add_client_to_room(ClientId(99), room); + + assert!(cl.get_room_leader_by_client(ClientId(12)) == Ok((ClientId(93), LocalClientId(1)))); + } +}