9
9
import java .util .concurrent .Executor ;
10
10
import java .util .concurrent .TimeUnit ;
11
11
import java .util .concurrent .atomic .AtomicReference ;
12
+ import java .util .function .Consumer ;
12
13
import java .util .random .RandomGenerator ;
13
14
import java .util .stream .Collectors ;
14
15
@@ -48,20 +49,22 @@ public class BoardLifecycleManager {
48
49
*
49
50
* @param contestId
50
51
* @param runnable
52
+ * @return
51
53
*/
52
- protected void executeBoardChange (UUID contestId , Runnable runnable ) {
54
+ protected BoardSnapshotPostEvent executeBoardChange (UUID contestId , Consumer < IKumiteBoard > runnable ) {
53
55
if (isDirect (boardEvolutionExecutor )) {
54
- boardEvolutionExecutor . execute ( runnable );
56
+ return executeBoardMutation ( contestId , runnable );
55
57
} else {
56
58
CountDownLatch cdl = new CountDownLatch (1 );
57
59
AtomicReference <Throwable > refT = new AtomicReference <>();
60
+ AtomicReference <BoardSnapshotPostEvent > refBoard = new AtomicReference <>();
58
61
59
62
log .trace ("Submitting task for contestId={}" , contestId );
60
63
getExecutor (contestId ).execute (() -> {
61
64
try {
62
- log . trace ( "Runnning task for contestId={}" , contestId );
63
- runnable . run ();
64
- log . trace ( "Runnned task for contestId={}" , contestId );
65
+ BoardSnapshotPostEvent snapshot = executeBoardMutation ( contestId , runnable );
66
+
67
+ refBoard . set ( snapshot );
65
68
} catch (Throwable t ) {
66
69
refT .compareAndSet (null , t );
67
70
} finally {
@@ -90,9 +93,33 @@ protected void executeBoardChange(UUID contestId, Runnable runnable) {
90
93
// BEWARE WHAT DOES IT MEAN? THE BOARD IS CORRUPTED?
91
94
throw new IllegalStateException ("One move has been too slow to be processed" );
92
95
}
96
+
97
+ return refBoard .get ();
93
98
}
94
99
}
95
100
101
+ private BoardSnapshotPostEvent executeBoardMutation (UUID contestId , Consumer <IKumiteBoard > runnable ) {
102
+ IHasBoard hasBoard = boardRegistry .makeDynamicBoardHolder (contestId );
103
+ IKumiteBoard board = hasBoard .get ();
104
+ Set <UUID > playerCanMoveBefore = playersCanMove (contestId , board );
105
+
106
+ log .trace ("Runnning task for contestId={}" , contestId );
107
+ runnable .accept (board );
108
+
109
+ Set <UUID > enabledPlayersIds = new HashSet <>();
110
+ {
111
+ Set <UUID > playerCanMoveAfter = playersCanMove (contestId , board );
112
+
113
+ // This does an intersection: players turned movable can now move, through they could not move
114
+ // before
115
+ enabledPlayersIds .addAll (playerCanMoveAfter );
116
+ enabledPlayersIds .removeAll (playerCanMoveBefore );
117
+ }
118
+
119
+ log .trace ("Runnned task for contestId={}" , contestId );
120
+ return BoardSnapshotPostEvent .builder ().board (board ).enabledPlayerIds (enabledPlayersIds ).build ();
121
+ }
122
+
96
123
/**
97
124
*
98
125
* @param contestId
@@ -118,25 +145,11 @@ public IKumiteBoardView registerPlayer(Contest contest, PlayerJoinRaw playerRegi
118
145
119
146
AtomicReference <IKumiteBoardView > refBoardView = new AtomicReference <>();
120
147
121
- Set <UUID > enabledPlayersIds = new HashSet <>();
122
-
123
- executeBoardChange (contestId , () -> {
124
- IKumiteBoard boardBefore = boardRegistry .makeDynamicBoardHolder (contestId ).get ();
125
-
126
- Set <UUID > playerCanMoveBefore = playersCanMove (contestId , boardBefore );
127
-
128
- // The registry takes in charge the registration in the board
148
+ BoardSnapshotPostEvent boardSnapshot = executeBoardChange (contestId , board -> {
149
+ // contestPlayersRegistry takes in charge the registration in the board
129
150
contestPlayersRegistry .registerPlayer (contest , playerRegistrationRaw );
130
151
131
- IKumiteBoard boardAfter = boardRegistry .makeDynamicBoardHolder (contestId ).get ();
132
-
133
- Set <UUID > playerCanMoveAfter = playersCanMove (contestId , boardBefore );
134
-
135
- // This does an intersection: players turned movable can now move, through they could not move before
136
- enabledPlayersIds .addAll (playerCanMoveAfter );
137
- enabledPlayersIds .removeAll (playerCanMoveBefore );
138
-
139
- refBoardView .set (boardAfter .asView (playerId ));
152
+ refBoardView .set (board .asView (playerId ));
140
153
});
141
154
142
155
IKumiteBoardView boardViewPostMove = refBoardView .get ();
@@ -151,10 +164,14 @@ public IKumiteBoardView registerPlayer(Contest contest, PlayerJoinRaw playerRegi
151
164
// Hence we do not guarantee other events interleaved when the event is processed
152
165
eventBus .post (PlayerJoinedBoard .builder ().contestId (contestId ).playerId (playerId ).build ());
153
166
154
- enabledPlayersIds .forEach (enabledPlayerId -> {
167
+ boardSnapshot . getEnabledPlayerIds () .forEach (enabledPlayerId -> {
155
168
eventBus .post (PlayerCanMove .builder ().contestId (contestId ).playerId (playerId ).build ());
156
169
});
157
170
171
+ if (boardRegistry .hasGameover (contest .getGame (), contestId ).isGameOver ()) {
172
+ eventBus .post (ContestIsGameover .builder ().contestId (contestId ).build ());
173
+ }
174
+
158
175
return boardViewPostMove ;
159
176
}
160
177
@@ -178,11 +195,7 @@ public IKumiteBoardView onPlayerMove(Contest contest, PlayerMoveRaw playerMove)
178
195
UUID contestId = contest .getContestId ();
179
196
UUID playerId = playerMove .getPlayerId ();
180
197
181
- AtomicReference <IKumiteBoard > refBoard = new AtomicReference <>();
182
-
183
- Set <UUID > enabledPlayersIds = new HashSet <>();
184
-
185
- executeBoardChange (contestId , () -> {
198
+ BoardSnapshotPostEvent boardSnapshot = executeBoardChange (contestId , currentBoard -> {
186
199
if (!contestPlayersRegistry .isRegisteredPlayer (contestId , playerId )) {
187
200
List <UUID > contestPlayers = contestPlayersRegistry .makeDynamicHasPlayers (contestId )
188
201
.getPlayers ()
@@ -196,36 +209,28 @@ public IKumiteBoardView onPlayerMove(Contest contest, PlayerMoveRaw playerMove)
196
209
+ contestPlayers );
197
210
}
198
211
199
- IKumiteBoard currentBoard = boardRegistry .makeDynamicBoardHolder (contestId ).get ();
200
-
201
- Set <UUID > playerCanMoveBefore = playersCanMove (contestId , currentBoard );
202
-
203
212
// First `.checkMove`: these are generic checks (e.g. is the gamerOver?)
204
213
try {
205
214
contest .checkValidMove (playerMove );
206
215
} catch (IllegalArgumentException e ) {
207
- throw new IllegalArgumentException ("Issue on contest=" + contest , e );
216
+ throw new IllegalArgumentException ("Issue on contest=" + contest + " for move=" + playerMove , e );
208
217
}
209
218
210
219
log .info ("Registering move for contestId={} by playerId={}" , contestId , playerId );
211
220
212
221
// This may still fail (e.g. the move is illegal given game rules)
213
222
currentBoard .registerMove (playerMove );
214
223
215
- Set <UUID > playerCanMoveAfter = playersCanMove (contestId , currentBoard );
216
-
217
- // This does an intersection: players turned movable can now move, through they could not move before
218
- enabledPlayersIds .addAll (playerCanMoveAfter );
219
- enabledPlayersIds .removeAll (playerCanMoveBefore );
220
-
221
224
// Persist the board (e.g. for concurrent changes)
222
225
boardRegistry .updateBoard (contestId , currentBoard );
223
226
224
- refBoard .set (currentBoard );
225
-
227
+ if (boardRegistry .hasGameover (contest .getGame (), contestId ).isGameOver ()) {
228
+ log .info ("playerMove led to gameOver for contestId={}" , contestId );
229
+ doGameOver (contestId );
230
+ }
226
231
});
227
232
228
- IKumiteBoard boardAfter = refBoard . get ();
233
+ IKumiteBoard boardAfter = boardSnapshot . getBoard ();
229
234
IKumiteBoardView boardViewPostMove = boardAfter .asView (playerId );
230
235
231
236
if (boardViewPostMove == null ) {
@@ -234,14 +239,38 @@ public IKumiteBoardView onPlayerMove(Contest contest, PlayerMoveRaw playerMove)
234
239
235
240
eventBus .post (PlayerMoved .builder ().contestId (contestId ).playerId (playerId ).build ());
236
241
237
- enabledPlayersIds .forEach (enabledPlayerId -> {
242
+ boardSnapshot . getEnabledPlayerIds () .forEach (enabledPlayerId -> {
238
243
eventBus .post (PlayerCanMove .builder ().contestId (contestId ).playerId (playerId ).build ());
239
244
});
240
245
241
- if (contest .getGame (). makeDynamicGameover (() -> boardAfter ).isGameOver ()) {
246
+ if (boardRegistry . hasGameover ( contest .getGame (), contestId ).isGameOver ()) {
242
247
eventBus .post (ContestIsGameover .builder ().contestId (contestId ).build ());
243
248
}
244
249
245
250
return boardViewPostMove ;
246
251
}
252
+
253
+ public void forceGameOver (Contest contest ) {
254
+ UUID contestId = contest .getContestId ();
255
+
256
+ if (boardRegistry .hasGameover (contest .getGame (), contestId ).isGameOver ()) {
257
+ log .info ("contestId={} is already gameOver" , contestId );
258
+ }
259
+
260
+ executeBoardChange (contestId , board -> {
261
+ log .info ("Registering forcedGameOver for contestId={}" , contestId );
262
+
263
+ doGameOver (contestId );
264
+ });
265
+
266
+ if (boardRegistry .hasGameover (contest .getGame (), contestId ).isGameOver ()) {
267
+ eventBus .post (ContestIsGameover .builder ().contestId (contestId ).build ());
268
+ }
269
+ }
270
+
271
+ private void doGameOver (UUID contestId ) {
272
+ boardRegistry .forceGameover (contestId );
273
+ contestsRegistry .deleteContest (contestId );
274
+ contestPlayersRegistry .forceGameover (contestId );
275
+ }
247
276
}
0 commit comments