Skip to content

Commit c311939

Browse files
committed
refactor(Game): handle player action errors solely in GameRoom
1 parent 686583d commit c311939

File tree

4 files changed

+60
-74
lines changed

4 files changed

+60
-74
lines changed

plugin/src/server/sc/plugin2021/Game.kt

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,19 +137,15 @@ class Game: AbstractGame<Player>(GamePlugin.PLUGIN_UUID) {
137137

138138
@Throws(InvalidMoveException::class)
139139
override fun onRoundBasedAction(fromPlayer: Player, data: ProtocolMessage) {
140-
try {
141-
if (data !is Move)
142-
throw InvalidMoveException(MoveMistake.INVALID_FORMAT)
143-
144-
logger.debug("Current State: $currentState")
145-
logger.debug("Performing Move $data")
146-
GameRuleLogic.performMove(currentState, data)
147-
GameRuleLogic.removeInvalidColors(currentState)
148-
next(if (isGameOver) null else currentState.currentPlayer)
149-
logger.debug("Current Board:\n${currentState.board}")
150-
} catch(e: InvalidMoveException) {
151-
handleInvalidMove(e, fromPlayer)
152-
}
140+
if (data !is Move)
141+
throw InvalidMoveException(MoveMistake.INVALID_FORMAT)
142+
143+
logger.debug("Current State: $currentState")
144+
logger.debug("Performing Move $data")
145+
GameRuleLogic.performMove(currentState, data)
146+
GameRuleLogic.removeInvalidColors(currentState)
147+
next(if (isGameOver) null else currentState.currentPlayer)
148+
logger.debug("Current Board:\n${currentState.board}")
153149
}
154150

155151
override fun toString(): String =
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package sc.api.plugins.exceptions
2+
3+
import sc.framework.plugins.Player
4+
import sc.protocol.responses.ProtocolMessage
5+
6+
data class NotYourTurnException(
7+
val expected: Player?,
8+
val actual: Player,
9+
val data: ProtocolMessage):
10+
GameLogicException("It's not your turn yet; expected: $expected, got $actual (msg was $data).")

sdk/src/server-api/sc/framework/plugins/AbstractGame.kt

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import org.slf4j.LoggerFactory
44
import sc.api.plugins.IGameInstance
55
import sc.api.plugins.IGameState
66
import sc.api.plugins.exceptions.GameLogicException
7+
import sc.api.plugins.exceptions.NotYourTurnException
78
import sc.api.plugins.host.IGameListener
8-
import sc.protocol.responses.ProtocolErrorMessage
99
import sc.protocol.responses.ProtocolMessage
1010
import sc.shared.*
1111

@@ -39,34 +39,25 @@ abstract class AbstractGame<P : Player>(override val pluginUUID: String) : IGame
3939
*
4040
* @throws GameLogicException if any invalid action is done, i.e. game rule violation
4141
*/
42-
@Throws(GameLogicException::class)
42+
@Throws(GameLogicException::class, InvalidMoveException::class)
4343
override fun onAction(fromPlayer: Player, data: ProtocolMessage) {
44-
var error: String? = null
45-
if (fromPlayer == activePlayer) {
46-
moveRequestTimeout?.let { timer ->
47-
timer.stop()
48-
logger.info("Time needed for move:" + timer.timeDiff)
49-
if (timer.didTimeout()) {
50-
logger.warn("Client hit soft-timeout.")
51-
fromPlayer.softTimeout = true
52-
onPlayerLeft(fromPlayer, ScoreCause.SOFT_TIMEOUT)
53-
} else {
54-
onRoundBasedAction(fromPlayer, data)
55-
}
56-
} ?: run {
57-
error = "We didn't request a move from you yet."
44+
if (fromPlayer != activePlayer)
45+
throw NotYourTurnException(activePlayer, fromPlayer, data)
46+
moveRequestTimeout?.let { timer ->
47+
timer.stop()
48+
logger.info("Time needed for move: " + timer.timeDiff)
49+
if (timer.didTimeout()) {
50+
logger.warn("Client hit soft-timeout.")
51+
fromPlayer.softTimeout = true
52+
onPlayerLeft(fromPlayer, ScoreCause.SOFT_TIMEOUT)
53+
} else {
54+
onRoundBasedAction(fromPlayer, data)
5855
}
59-
} else {
60-
error = "It's not your turn yet; expected: $activePlayer, got $fromPlayer (msg was $data)."
61-
}
62-
error?.let {
63-
fromPlayer.notifyListeners(ProtocolErrorMessage(data, it))
64-
throw GameLogicException(it)
65-
}
56+
} ?: throw GameLogicException("We didn't request a move from you yet.")
6657
}
6758

68-
/** Called by [onAction] to execute a move from a Player. */
69-
@Throws(GameLogicException::class, InvalidMoveException::class)
59+
/** Called by [onAction] to execute a move of a Player. */
60+
@Throws(InvalidMoveException::class)
7061
abstract fun onRoundBasedAction(fromPlayer: Player, data: ProtocolMessage)
7162

7263
/**
@@ -214,22 +205,4 @@ abstract class AbstractGame<P : Player>(override val pluginUUID: String) : IGame
214205
}
215206
}
216207
}
217-
218-
/**
219-
* For use in a catch block when an invalid move was performed.
220-
* Logs and notifies listeners about the violation.
221-
*
222-
* @param e catched Exception, rethrown at the end
223-
* @param author player who caused the exception
224-
*
225-
* @throws InvalidMoveException Always thrown
226-
*/
227-
@Throws(InvalidMoveException::class)
228-
fun handleInvalidMove(e: InvalidMoveException, author: Player): Nothing {
229-
val error = "Ungueltiger Zug von '${author.displayName}'.\n$e"
230-
logger.error(error)
231-
author.violationReason = e.message
232-
author.notifyListeners(ProtocolErrorMessage(e.move, error))
233-
throw e
234-
}
235208
}

server/src/sc/server/gaming/GameRoom.java

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@
55
import org.slf4j.LoggerFactory;
66
import sc.api.plugins.IGameInstance;
77
import sc.api.plugins.IGameState;
8-
import sc.api.plugins.exceptions.GameLogicException;
9-
import sc.api.plugins.exceptions.RescuableClientException;
10-
import sc.api.plugins.exceptions.TooManyPlayersException;
8+
import sc.api.plugins.exceptions.*;
119
import sc.api.plugins.host.IGameListener;
1210
import sc.framework.plugins.AbstractGame;
1311
import sc.framework.plugins.Player;
@@ -263,7 +261,7 @@ public String getId() {
263261
*
264262
* @return true if successfully joined
265263
*/
266-
public synchronized boolean join(Client client) throws RescuableClientException {
264+
public synchronized boolean join(Client client) throws GameRoomException {
267265
PlayerSlot openSlot = null;
268266

269267
for (PlayerSlot slot : this.playerSlots) {
@@ -295,7 +293,7 @@ public synchronized boolean join(Client client) throws RescuableClientException
295293
* @param openSlot PlayerSlot to fill
296294
* @param client Client to fill PlayerSlot
297295
*/
298-
synchronized void fillSlot(PlayerSlot openSlot, Client client) throws RescuableClientException {
296+
synchronized void fillSlot(PlayerSlot openSlot, Client client) throws GameRoomException {
299297
openSlot.setClient(client); // set role of Slot as PlayerRole
300298

301299
if (!this.prepared) // is set when game is game is created or prepared
@@ -313,7 +311,7 @@ synchronized void fillSlot(PlayerSlot openSlot, Client client) throws RescuableC
313311
* Registers player to role in given slot.
314312
* Sends JoinGameProtocolMessage when successful.
315313
*/
316-
private void syncSlot(PlayerSlot slot) throws RescuableClientException {
314+
private void syncSlot(PlayerSlot slot) throws GameRoomException {
317315
// create new player in gameState of game
318316
Player player = game.onPlayerJoined();
319317
// set attributes for player
@@ -347,7 +345,7 @@ private boolean isReady() {
347345
}
348346

349347
/** Starts game if ready and not over. */
350-
private void startIfReady() throws RescuableClientException {
348+
private void startIfReady() throws GameRoomException {
351349
logger.debug("startIfReady called");
352350
if (isOver()) {
353351
logger.warn("Game already over: {}", game);
@@ -364,7 +362,7 @@ private void startIfReady() throws RescuableClientException {
364362
}
365363

366364
/** If the Game is prepared, sync all slots. */
367-
private synchronized void start() throws RescuableClientException {
365+
private synchronized void start() throws GameRoomException {
368366
if (this.prepared) // sync slots for prepared game. This was already called for PlayerSlots in a game created by a join
369367
{
370368
for (PlayerSlot slot : this.playerSlots) {
@@ -427,34 +425,43 @@ public synchronized List<String> reserveAllSlots() {
427425
* @param source Client which caused the event
428426
* @param data ProtocolMessage containing the action
429427
*/
430-
public synchronized void onEvent(Client source, ProtocolMessage data) throws RescuableClientException, InvalidGameStateException {
428+
public synchronized void onEvent(Client source, ProtocolMessage data) throws GameRoomException {
431429
if (isOver())
432-
throw new RescuableClientException("Game is already over, but got message: " + data.getClass());
430+
throw new GameException("Game is already over, but got " + data);
433431

432+
Player player = resolvePlayer(source);
434433
try {
435-
this.game.onAction(resolvePlayer(source), data);
434+
game.onAction(player, data);
436435
} catch (InvalidMoveException e) {
437-
this.observerBroadcast(new RoomPacket(this.id, new ProtocolErrorMessage(e.move, e.getMessage())));
438-
this.game.onPlayerLeft(resolvePlayer(source), ScoreCause.RULE_VIOLATION);
439-
throw new GameLogicException(e.toString());
436+
final String error = String.format("Ungueltiger Zug von '%s'.\n%s", player.getDisplayName(), e);
437+
logger.error(error);
438+
player.setViolationReason(e.getMessage());
439+
ProtocolErrorMessage errorMessage = new ProtocolErrorMessage(e.move, error);
440+
player.notifyListeners(errorMessage);
441+
observerBroadcast(new RoomPacket(id, errorMessage));
442+
game.onPlayerLeft(player, ScoreCause.RULE_VIOLATION);
443+
throw new GameLogicException(e.toString(), e);
444+
} catch (GameLogicException e) {
445+
player.notifyListeners(new ProtocolErrorMessage(data, e.getMessage()));
446+
throw e;
440447
}
441448
}
442449

443450
/** Finds player matching the given client. */
444-
private Player resolvePlayer(Client client) throws RescuableClientException {
451+
private Player resolvePlayer(Client client) throws GameRoomException {
445452
for (PlayerRole role : getPlayers()) {
446453
if (role.getClient().equals(client)) {
447454
Player resolvedPlayer = role.getPlayer();
448455

449456
if (resolvedPlayer == null) {
450-
throw new RescuableClientException("Game isn't ready. Please wait before sending messages.");
457+
throw new GameException("Game isn't ready. Please wait before sending messages.");
451458
}
452459

453460
return resolvedPlayer;
454461
}
455462
}
456463

457-
throw new RescuableClientException("Client is not a member of game " + this.id);
464+
throw new GameRoomException("Client is not a member of game " + this.id);
458465
}
459466

460467
/** Get {@link PlayerRole Players} that occupy a slot. */
@@ -519,7 +526,7 @@ public synchronized void pause(boolean pause) {
519526
* @param forced If true, the game will be forcibly started if starting
520527
* conditions are not met. This should result in a GameOver.
521528
*/
522-
public synchronized void step(boolean forced) throws RescuableClientException {
529+
public synchronized void step(boolean forced) throws GameRoomException {
523530
if (this.status == GameStatus.CREATED) {
524531
if (forced) {
525532
logger.warn("Forcing game start for {}", game);

0 commit comments

Comments
 (0)