Skip to content

Commit

Permalink
Makes game processor compatible with atomic transactions and adds tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
achur committed Apr 16, 2015
1 parent 4e96088 commit d90b2a9
Show file tree
Hide file tree
Showing 5 changed files with 531 additions and 103 deletions.
18 changes: 14 additions & 4 deletions src/main/java/com/achur/avalon/entity/Game.java
Expand Up @@ -64,14 +64,24 @@ public enum State {
Long currentLeader;

/**
* The current votes for.
* The current votes for the given team.
*/
List<Long> votesYay;
List<Long> teamVotesYay;

/**
* The current votes against.
* The current votes against the given team.
*/
List<Long> votesNay;
List<Long> teamVotesNay;

/**
* The current votes for the given quest.
*/
List<Long> questVotesYay;

/**
* The current votes against the given quest.
*/
List<Long> questVotesNay;

/**
* Outcome of the game (true = good wins).
Expand Down
228 changes: 132 additions & 96 deletions src/main/java/com/achur/avalon/processors/GameProcessorImpl.java
Expand Up @@ -5,6 +5,7 @@
import com.achur.avalon.storage.GameStore;
import com.achur.avalon.storage.PlayerStore;

import com.google.common.base.Function;
import com.google.inject.Inject;

import java.util.ArrayList;
Expand All @@ -16,18 +17,18 @@ public class GameProcessorImpl implements GameProcessor {
/**
* The Game Store service which handles persistance of Game objects.
*/
private GameStore gameStore;
private final GameStore gameStore;

/**
* The Player Store service which handles persistance of Player objects.
*/
private PlayerStore playerStore;
private final PlayerStore playerStore;

/**
* Guice-injected constructor.
*/
@Inject
GameProcessorImpl(GameStore gameStore, PlayerStore playerStore) {
GameProcessorImpl(final GameStore gameStore, final PlayerStore playerStore) {
this.gameStore = gameStore;
this.playerStore = playerStore;
}
Expand All @@ -37,11 +38,15 @@ public class GameProcessorImpl implements GameProcessor {
* {@inheritDoc}
*/
public Game startGame(Long id) {
Game game = gameStore.getGame(id);
assignPlayers(game);
game.setState(Game.State.START);
gameStore.saveGame(game);
return game;
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
assignPlayers(game);
game.setState(Game.State.START);
return game;
}
};
return gameStore.modifyGame(id, modifier);
}

/**
Expand All @@ -62,46 +67,65 @@ private void assignPlayers(Game game) {
* {@inheritDoc}
*/
public Game startSelection(Long id) {
Game game = gameStore.getGame(id);
game.setState(Game.State.TEAM_SELECTION);
gameStore.saveGame(game);
return game;
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
game.setState(Game.State.TEAM_SELECTION);
return game;
}
};
return gameStore.modifyGame(id, modifier);
}

/**
* {@inheritDoc}
*/
public Game proposeTeam(Long gameId, Long proposerId, List<Long> playerIdList) {
Game game = gameStore.getGame(gameId);

// TODO(achur): Here and elsewhere, use something better than assert.
assert game.getState() == Game.State.TEAM_SELECTION : "Wrong state";
assert game.getCurrentLeader() == proposerId : "Wrong proposer";
public Game proposeTeam(Long gameId, final Long proposerId, final List<Long> playerIdList) {
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
// TODO(achur): Here and elsewhere, use something better than assert.
assert game.getState() == Game.State.TEAM_SELECTION : "Wrong state";
assert game.getCurrentLeader() == proposerId : "Wrong proposer";

// TODO(achur): Logic to determine if the right number of players is questing.

game.setCurrentTeam(playerIdList);
clearTeamVotes(game);
game.setState(Game.State.TEAM_VOTING);
return game;
}
};
return gameStore.modifyGame(gameId, modifier);
}

// TODO(achur): Logic to determine if the right number of players is questing.
private void clearTeamVotes(Game game) {
game.setTeamVotesYay(new ArrayList<Long>());
game.setTeamVotesNay(new ArrayList<Long>());
}

game.setCurrentTeam(playerIdList);
game.setState(Game.State.TEAM_VOTING);
gameStore.saveGame(game);
return game;
private void clearQuestVotes(Game game) {
game.setQuestVotesYay(new ArrayList<Long>());
game.setQuestVotesNay(new ArrayList<Long>());
}

private void issueVote(Game game, Long voterId, Boolean approve) {
if (game.getVotesYay().contains(voterId)) {
private void issueVote(
List<Long> votesYay, List<Long> votesNay, Long voterId, Boolean approve) {
if (votesYay.contains(voterId)) {
if (!approve) {
game.getVotesYay().remove(voterId);
game.getVotesNay().add(voterId);
votesYay.remove(voterId);
votesNay.add(voterId);
}
} else if (game.getVotesNay().contains(voterId)) {
} else if (votesNay.contains(voterId)) {
if (approve) {
game.getVotesNay().remove(voterId);
game.getVotesYay().add(voterId);
votesNay.remove(voterId);
votesYay.add(voterId);
}
} else {
if (approve) {
game.getVotesYay().add(voterId);
votesYay.add(voterId);
} else {
game.getVotesNay().add(voterId);
votesNay.add(voterId);
}
}
}
Expand Down Expand Up @@ -132,31 +156,35 @@ private Long getNextPlayer(Game game, Long playerId) {
/**
* {@inheritDoc}
*/
public Game issueTeamVote(Long gameId, Long voterId, Boolean approve) {
Game game = gameStore.getGame(gameId);

assert game.getState() == Game.State.TEAM_VOTING : "Wrong state";

issueVote(game, voterId, approve);

// If all votes are in, check if we're going questing.
if (game.getVotesYay().size() + game.getVotesNay().size() ==
game.getPlayers().size()) {
if (game.getVotesYay().size() > game.getVotesNay().size()) {
game.setState(Game.State.QUEST);
} else {
game.setVoteCount(game.getVoteCount() + 1);
if (game.getVoteCount() >= Constants.MAX_VOTES) {
game.setOutcome(false);
game.setState(Game.State.END);
} else {
game.setCurrentLeader(getNextLeader(game));
game.setState(Game.State.TEAM_SELECTION);
public Game issueTeamVote(Long gameId, final Long voterId, final Boolean approve) {
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
assert game.getState() == Game.State.TEAM_VOTING : "Wrong state";

issueVote(game.getTeamVotesYay(), game.getTeamVotesNay(), voterId, approve);

// If all votes are in, check if we're going questing.
if (game.getTeamVotesYay().size() + game.getTeamVotesNay().size() ==
game.getPlayers().size()) {
if (game.getTeamVotesYay().size() > game.getTeamVotesNay().size()) {
game.setState(Game.State.QUEST);
clearQuestVotes(game);
} else {
game.setVoteCount(game.getVoteCount() + 1);
if (game.getVoteCount() >= Constants.MAX_VOTES) {
game.setOutcome(false);
game.setState(Game.State.END);
} else {
game.setCurrentLeader(getNextLeader(game));
game.setState(Game.State.TEAM_SELECTION);
}
}
}
return game;
}
}
gameStore.saveGame(game);
return game;
};
return gameStore.modifyGame(gameId, modifier);
}

/**
Expand All @@ -177,55 +205,63 @@ private int countResults(Game game, Boolean success) {
/**
* {@inheritDoc}
*/
public Game issueQuestVote(Long gameId, Long voterId, Boolean succeed) {
Game game = gameStore.getGame(gameId);

assert game.getState() == Game.State.QUEST : "Wrong state";

if (!succeed) {
Player voter = playerStore.getPlayer(voterId);
assert Constants.BAD_ROLES.contains(voter.getRole()) :
"Only bad players can vote to fail a quest";
}
issueVote(game, voterId, succeed);

// If all votes are in, check the result of the quest.
if (game.getVotesYay().size() + game.getVotesNay().size() ==
Constants.getNumPlayers(game.getPlayers().size(), game.getQuestResults().size())) {
boolean result = game.getVotesNay().size() < Constants.getNumFailsRequired(
game.getPlayers().size(), game.getQuestResults().size());
game.getQuestResults().add(result);
if (countResults(game, true) > Constants.NUM_QUESTS / 2) {
game.setState(Game.State.GUESS_MERLIN);
} else {
game.setOutcome(false);
game.setState(Game.State.END);
public Game issueQuestVote(Long gameId, final Long voterId, final Boolean succeed) {
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
assert game.getState() == Game.State.QUEST : "Wrong state";

assert game.getCurrentTeam().contains(voterId) : "You must be on the team to vote";

if (!succeed) {
Player voter = playerStore.getPlayer(voterId);
assert Constants.BAD_ROLES.contains(voter.getRole()) :
"Only bad players can vote to fail a quest";
}
issueVote(game.getQuestVotesYay(), game.getQuestVotesNay(), voterId, succeed);

// If all votes are in, check the result of the quest.
if (game.getQuestVotesYay().size() + game.getQuestVotesNay().size() ==
Constants.getNumPlayers(game.getPlayers().size(), game.getQuestResults().size())) {
boolean result = game.getQuestVotesNay().size() < Constants.getNumFailsRequired(
game.getPlayers().size(), game.getQuestResults().size());
game.getQuestResults().add(result);
if (countResults(game, true) > Constants.NUM_QUESTS / 2) {
game.setState(Game.State.GUESS_MERLIN);
} else if (countResults(game, false) > Constants.NUM_QUESTS / 2) {
game.setOutcome(false);
game.setState(Game.State.END);
} else {
game.setCurrentLeader(getNextLeader(game));
game.setState(Game.State.TEAM_SELECTION);
}
}
return game;
}
} else {
game.setCurrentLeader(getNextLeader(game));
game.setState(Game.State.TEAM_SELECTION);
}

gameStore.saveGame(game);
return game;
};
return gameStore.modifyGame(gameId, modifier);
}

/**
* {@inheritDoc}
*/
public Game guessMerlin(Long gameId, Long voterId, Long guessId) {
Game game = gameStore.getGame(gameId);
public Game guessMerlin(Long gameId, final Long voterId, final Long guessId) {
Function<Game, Game> modifier = new Function<Game, Game>() {
@Override
public Game apply(Game game) {
assert game.getState() == Game.State.GUESS_MERLIN : "Wrong state";

assert game.getState() == Game.State.GUESS_MERLIN : "Wrong state";
Player voter = playerStore.getPlayer(voterId);

Player voter = playerStore.getPlayer(voterId);
assert Constants.BAD_ROLES.contains(voter.getRole()) :
"Only bad players can guess Merlin";

assert Constants.BAD_ROLES.contains(voter.getRole()) :
"Only bad players can guess Merlin";

Player guess = playerStore.getPlayer(guessId);
game.setOutcome(guess.getRole() != Player.Role.MERLIN);
gameStore.saveGame(game);
return game;
Player guess = playerStore.getPlayer(guessId);
game.setOutcome(guess.getRole() != Player.Role.MERLIN);
game.setState(Game.State.END);
return game;
}
};
return gameStore.modifyGame(gameId, modifier);
}
}
12 changes: 12 additions & 0 deletions src/main/java/com/achur/avalon/storage/GameStore.java
Expand Up @@ -2,6 +2,8 @@

import com.achur.avalon.entity.Game;

import com.google.common.base.Function;

/**
* Handles storing and updating {@link Game} objects.
*/
Expand All @@ -22,4 +24,14 @@ public interface GameStore {
* @return The game that was persisted.
*/
public Game saveGame(Game game);

/**
* Performs an atomic modification to a game.
*
* @param id The ID of the game to modify.
* @param modifier A function that modifies the Game which will be
* executed atomically for each game.
* @return The modified game.
*/
public Game modifyGame(Long id, Function<Game, Game> modifier);
}
10 changes: 10 additions & 0 deletions src/main/java/com/achur/avalon/storage/GameStoreImpl.java
Expand Up @@ -2,6 +2,8 @@

import com.achur.avalon.entity.Game;

import com.google.common.base.Function;

import java.util.HashMap;

/**
Expand All @@ -26,4 +28,12 @@ public Game saveGame(Game game) {
return game;
}

/**
* {@inheritDoc}
*/
public Game modifyGame(Long id, Function<Game, Game> modifier) {
Game game = getGame(id);
game = modifier.apply(game);
return saveGame(game);
}
}

0 comments on commit d90b2a9

Please sign in to comment.