team04_server/lobby/game/
placement.rs1use std::{sync::Arc, time::Duration};
2
3use tokio::sync::Semaphore;
4
5use crate::{
6 lobby::state::{LobbyPhase, LockedLobbyState, SharedLobbyState},
7 log,
8};
9
10impl SharedLobbyState {
11 pub async fn placements(&self) -> LockedLobbyState {
22 let mut pfx = log::pfx();
23 pfx.lobby(self.id());
24
25 let mut lock = self.lock().await;
26 assert!(lock.game_started());
27
28 let timeout = Duration::from_millis(lock.configs.game_config.timeout_placement_phase);
29 lock.phase = LobbyPhase::PlacementPhase;
30 lock.broadcast_gamestate().await;
31
32 log::debug!("Unit placement phase starting"; &pfx);
33
34 let player_count = lock.clients.players.len();
36 let semaphore = Arc::new(Semaphore::new(player_count));
37 for player in lock.clients.players.players_alive_mut() {
38 let permit = Arc::clone(&semaphore)
39 .try_acquire_owned()
40 .expect("there are enough permits available for all players");
41 player.new_placement = Err(Some(permit));
42 player.new_placement_allowed = true;
43 }
44 drop(lock);
45
46 let hit_timeout =
48 tokio::time::timeout(timeout, semaphore.acquire_many(player_count as u32))
49 .await
50 .is_err();
51 log::debug!("Unit placement phase ended {}", if hit_timeout { "due to timeout" } else { "early (all players have confirmed their placement)" }; &pfx);
52
53 let mut lock = self.lock().await;
55 for player in lock.clients.players.players_alive_mut() {
56 player.new_placement_allowed = false;
57 if player.new_placement.is_err() {
58 player.new_placement =
59 Ok((player.unit_placement.clone(), player.unit_bank.clone()));
60 }
61 }
62 lock
63 }
64}
65
66#[cfg(test)]
67mod test {
68 use std::sync::Arc;
69
70 use serde::Deserialize;
71 use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
72
73 use crate::{
74 lobby::{
75 state::{SharedLobbyState, clients::PlayerId, players::PlayerInMatchup},
76 test::{FakeCon, config_set_modified, get_server_and_lobby_with_config, player_join},
77 },
78 log,
79 messages::{
80 RxMessage,
81 error::error_code,
82 placement_complete::{PlacedUnit, PlacementComplete},
83 },
84 server::state::SyncedServerState,
85 unit::UnitType,
86 };
87
88 async fn placement_phase(
89 unit_bank: Vec<UnitType>,
90 player_in_matchup: PlayerInMatchup,
91 placement: PlacementComplete,
92 ) -> (
93 JoinHandle<()>,
94 Arc<SyncedServerState>,
95 SharedLobbyState,
96 PlayerId,
97 UnboundedReceiver<String>,
98 ) {
99 let (server, lobby) = get_server_and_lobby_with_config(config_set_modified(
100 |cfg| cfg.timeout_placement_phase = 10,
101 |_| {},
102 |_| {},
103 ));
104 let (pid, _, mut con) = player_join(&server, &lobby).await;
105 lobby.lock().await.start_game_now().await;
106 let mut lock = lobby.lock().await;
107 let player = lock.clients.players.players_all_mut().next().unwrap();
108 player.unit_bank = unit_bank;
109 player.player_in_matchup = player_in_matchup;
110 drop(lock);
111 con.clear();
112
113 let phase = tokio::spawn({
115 let lobby = lobby.clone();
116 async move {
117 let _ = crate::lobby::game::placements(&lobby, &log::pfx()).await;
118 }
119 });
120
121 con.recv().await;
123
124 lobby
126 .lock()
127 .await
128 .message_from(&pid, RxMessage::PlacementComplete(placement), String::new())
129 .await;
130
131 (phase, server, lobby, pid, con)
132 }
133
134 #[tokio::test]
135 async fn valid_placement() {
136 let (phase, _, lobby, _, _) = placement_phase(
137 vec![UnitType::Wookie, UnitType::Stormtrooper],
138 PlayerInMatchup::Player1,
139 PlacementComplete {
140 unit_placement: vec![
141 PlacedUnit {
142 unit: UnitType::Stormtrooper,
143 position: [0, 0],
144 },
145 PlacedUnit {
146 unit: UnitType::Wookie,
147 position: [1, 0],
148 },
149 ],
150 unit_bank: vec![],
151 },
152 )
153 .await;
154
155 phase.await.unwrap();
156
157 let lock = lobby.lock().await;
158 let player = lock.clients.players.players_all().next().unwrap();
159 assert_eq!(player.unit_placement[0].unit_type, UnitType::Stormtrooper);
160 assert_eq!(player.unit_placement[1].unit_type, UnitType::Wookie);
161 assert!(player.unit_bank.is_empty());
162 }
163
164 #[tokio::test]
165 async fn wrong_units_placement() {
166 let (phase, _, lobby, _, mut con) = placement_phase(
167 vec![UnitType::Wookie, UnitType::Stormtrooper],
168 PlayerInMatchup::Player1,
169 PlacementComplete {
170 unit_placement: vec![
171 PlacedUnit {
172 unit: UnitType::Droid,
173 position: [0, 0],
174 },
175 PlacedUnit {
176 unit: UnitType::Wookie,
177 position: [0, 1],
178 },
179 ],
180 unit_bank: vec![],
181 },
182 )
183 .await;
184
185 #[derive(Deserialize)]
186 struct ErrorMessage {
187 pub code: String,
188 }
189 let ErrorMessage { code, .. } = serde_json::from_str(&con.recv().await.unwrap())
190 .expect("should receive an error message");
191 assert_eq!(code.as_str(), error_code::INVALID_PLACEMENT_UNITS);
192
193 phase.await.unwrap();
194
195 let lock = lobby.lock().await;
196 let player = lock.clients.players.players_all().next().unwrap();
197 assert!(player.unit_placement.is_empty());
199 assert!(player.unit_bank.len() == 2);
200 }
201
202 #[tokio::test]
203 async fn double_placement() {
204 let (phase, _, lobby, _, mut con) = placement_phase(
205 vec![UnitType::Wookie, UnitType::Stormtrooper],
206 PlayerInMatchup::Player1,
207 PlacementComplete {
208 unit_placement: vec![
209 PlacedUnit {
210 unit: UnitType::Stormtrooper,
211 position: [0, 0],
212 },
213 PlacedUnit {
214 unit: UnitType::Wookie,
215 position: [0, 0],
216 },
217 ],
218 unit_bank: vec![],
219 },
220 )
221 .await;
222
223 #[derive(Deserialize)]
224 struct ErrorMessage {
225 pub code: String,
226 }
227 let ErrorMessage { code, .. } = serde_json::from_str(&con.recv().await.unwrap())
228 .expect("should receive an error message");
229 assert_eq!(
230 code.as_str(),
231 error_code::INVALID_PLACEMENT_TWO_UNITS_ON_ONE_FIELD
232 );
233
234 phase.await.unwrap();
235
236 let lock = lobby.lock().await;
237 let player = lock.clients.players.players_all().next().unwrap();
238 assert!(player.unit_placement.is_empty());
240 assert!(player.unit_bank.len() == 2);
241 }
242}