actions = wrappedFM.computeAvailableActions(gameState);
+ for (IPlayerDecorator decorator : decorators) {
+ if (decorator.decisionPlayerOnly() && gameState.getCurrentPlayer() != decisionPlayerID)
+ continue;
+ actions = decorator.actionFilter(gameState, actions);
+ }
+ return actions;
+ }
+
+ @Override
+ protected AbstractForwardModel _copy() {
+ return wrappedFM._copy();
+ }
+
+ @Override
+ protected void endPlayerTurn(AbstractGameState state) {
+ wrappedFM.endPlayerTurn(state);
+ }
+
+}
diff --git a/src/main/java/core/LLMTest.java b/src/main/java/core/LLMTest.java
new file mode 100644
index 000000000..f1f420d9c
--- /dev/null
+++ b/src/main/java/core/LLMTest.java
@@ -0,0 +1,94 @@
+package core;
+
+import games.GameType;
+import players.basicMCTS.BasicMCTSParams;
+import players.basicMCTS.BasicMCTSPlayer;
+import players.heuristics.StringHeuristic;
+import players.human.ActionController;
+import players.simple.OSLAPlayer;
+import utilities.Utils;
+
+import java.util.ArrayList;
+
+
+public class LLMTest {
+
+ public enum Agent
+ {
+ OSLA,
+ MCTS;
+
+ Agent(){}
+
+ public AbstractPlayer createPlayer(String evaluatorFileName, String className) {
+ if (this == OSLA){
+ return new OSLAPlayer(new StringHeuristic(evaluatorFileName, className));
+ }
+ else if (this == MCTS){
+ BasicMCTSParams params = new BasicMCTSParams();
+ params.heuristic = new StringHeuristic(evaluatorFileName, className);
+ return new BasicMCTSPlayer(params);
+ }
+ return null;
+ }
+ }
+
+ public static void main(String[] args) {
+// evaluate(args, "LoveLetter", "llm/LoveLetterEvaluator.java", "LoveLetterEvaluator", Agent.OSLA);
+ evaluate(args, "TicTacToe", "llm/TicTacToeEvaluator.java", "TicTacToeEvaluator", Agent.OSLA);
+ }
+
+
+ /**
+ * The recommended way to run a game is via evaluations.Frontend, however that may not work on
+ * some games for some screen sizes due to the vagaries of Java Swing...
+ *
+ * Test class used to run a specific game. The user must specify:
+ * 1. Action controller for GUI interactions / null for no visuals
+ * 2. Random seed for the game
+ * 3. Players for the game
+ * 4. Game parameter configuration
+ * 5. Mode of running
+ * and then run this class.
+ */
+ private static void evaluate (String args[], String gameStr, String evaluatorFileName, String className, Agent agent)
+ {
+ GameType gameType = GameType.valueOf(Utils.getArg(args, "game", gameStr));
+ boolean useGUI = Utils.getArg(args, "gui", true);
+ int turnPause = Utils.getArg(args, "turnPause", 0);
+ long seed = Utils.getArg(args, "seed", System.currentTimeMillis());
+ ActionController ac = new ActionController();
+
+ /* Set up players for the game */
+ ArrayList players = new ArrayList<>();
+// players.add(new MCTSPlayer());
+ players.add(new OSLAPlayer());
+ players.add(agent.createPlayer(evaluatorFileName, className));
+
+// MCTSParams params = new MCTSParams();
+// params.heuristic = new StringHeuristic();
+// players.add(new MCTSPlayer(params));
+
+ int nGames = 20;
+ int wins = 0;
+ int ties = 0;
+ int playerToMonitor = 1;
+ for (int i = 0; i < nGames; i++) {
+ Game game = gameType.createGameInstance(players.size());
+ game.reset(players);
+ game.run();
+ CoreConstants.GameResult[] results = game.getGameState().getPlayerResults();
+ if (results[playerToMonitor] == CoreConstants.GameResult.WIN_GAME) {
+ wins++;
+ } else if (results[playerToMonitor] == CoreConstants.GameResult.DRAW_GAME) {
+ ties++;
+ }
+ }
+ System.out.println(wins*1.0/nGames + "," + ties*1.0/nGames);
+
+ // TODO human in the loop with GUI
+ }
+
+
+
+}
diff --git a/src/main/java/core/PyTAG.java b/src/main/java/core/PyTAG.java
index 2148140f9..7d28ebea0 100644
--- a/src/main/java/core/PyTAG.java
+++ b/src/main/java/core/PyTAG.java
@@ -18,7 +18,7 @@
import players.simple.RandomPlayer;
import utilities.ActionTreeNode;
-import games.explodingkittens.*;
+import games.explodingkittensOLD.*;
import java.util.ArrayList;
diff --git a/src/main/java/core/actions/AbstractAction.java b/src/main/java/core/actions/AbstractAction.java
index fce3b9164..11cad70f4 100644
--- a/src/main/java/core/actions/AbstractAction.java
+++ b/src/main/java/core/actions/AbstractAction.java
@@ -51,7 +51,7 @@ public String getString(AbstractGameState gs, int perspectivePlayer) {
* The GUI formally supports multiple players viewing a game. This in practice is only going to be used
* for games with (near) perfect information. For games that actually implement hidden information in
* a move (Resistance, Hanabi, Sushi Go, etc), we will only need the game actions to implement
- * getString(AbstractGameState, int). This is a helper method to make this downstream imnplementation
+ * getString(AbstractGameState, int). This is a helper method to make this downstream implementation
* easier without trying to puzzle out what it means to have multiple players viewing a game with hidden information.
*
* @param gs
diff --git a/src/main/java/core/components/Deck.java b/src/main/java/core/components/Deck.java
index 51572beeb..9763df3b3 100644
--- a/src/main/java/core/components/Deck.java
+++ b/src/main/java/core/components/Deck.java
@@ -19,8 +19,8 @@
* A deck is defined as a "group of components". Examples of decks are:
* * A hand of components
* * A deck to draw from
- * * components played on the player's area
- * * Discomponent pile
+ * * Components played on the player's area
+ * * Discard pile
*/
public class Deck extends Component implements IComponentContainer, Iterable {
diff --git a/src/main/java/core/components/GridBoard.java b/src/main/java/core/components/GridBoard.java
index 171588fcd..c7ed74804 100644
--- a/src/main/java/core/components/GridBoard.java
+++ b/src/main/java/core/components/GridBoard.java
@@ -19,6 +19,11 @@
import static core.CoreConstants.imgHash;
import static utilities.Utils.getNeighbourhood;
+/**
+ * GridBoard is a 2D grid of Components. It can be used to represent a board in a game, a map, or any other 2D grid.
+ * Each cell on the grid can contain a Component of any type.
+ * @param
+ */
public class GridBoard extends Component implements IComponentContainer {
private int width; // Width of the board
diff --git a/src/main/java/evaluation/RunArg.java b/src/main/java/evaluation/RunArg.java
index f89a33c2b..e52244bce 100644
--- a/src/main/java/evaluation/RunArg.java
+++ b/src/main/java/evaluation/RunArg.java
@@ -2,8 +2,13 @@
import org.apache.hadoop.yarn.webapp.hamlet.HamletSpec;
import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
import java.util.*;
import static java.util.stream.Collectors.toList;
@@ -232,9 +237,35 @@ public static Map parseConfig(String[] args, List usages)
public static Map parseConfig(String[] args, List usages, boolean checkUnknownArgs) {
if (checkUnknownArgs)
checkUnknownArgs(args, usages);
- return Arrays.stream(RunArg.values())
+ Map commandLineArgs = Arrays.stream(RunArg.values())
.filter(arg -> usages.stream().anyMatch(arg::isUsedIn))
.collect(toMap(arg -> arg, arg -> arg.parse(args)));
+ // then as a second stage we unpack any arguments specified in the config file
+ String setupFile = commandLineArgs.getOrDefault(RunArg.config, "").toString();
+ if (!setupFile.isEmpty()) {
+ // Read from file in addition (any values on the command line override the file contents)
+ try {
+ FileReader reader = new FileReader(setupFile);
+ JSONParser parser = new JSONParser();
+ JSONObject json = (JSONObject) parser.parse(reader);
+ Map configFileArgs = parseConfig(json, usages);
+ for (RunArg key : configFileArgs.keySet()) {
+ // we check if the key is already in the command line arguments (we check args directly as
+ // parseConfig populates all the Map with the default value if not present in the array)
+ if (Arrays.stream(args).noneMatch(s -> s.startsWith(key.name()))) {
+ commandLineArgs.put(key, configFileArgs.get(key));
+ } else {
+ System.out.println("Using command line value for " + key + " of " + commandLineArgs.get(key));
+ }
+ }
+ } catch (FileNotFoundException ignored) {
+ throw new AssertionError("Config file not found : " + setupFile);
+ // parseConfig(runGames, args);
+ } catch (IOException | ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ return commandLineArgs;
}
public static void checkUnknownArgs(String[] args, List usages) {
diff --git a/src/main/java/evaluation/RunGames.java b/src/main/java/evaluation/RunGames.java
index 6c39bd464..5bb1ce524 100644
--- a/src/main/java/evaluation/RunGames.java
+++ b/src/main/java/evaluation/RunGames.java
@@ -57,27 +57,11 @@ public static void main(String[] args) {
RunGames runGames = new RunGames();
runGames.config = parseConfig(args, Collections.singletonList(Usage.RunGames));
- String setupFile = runGames.config.getOrDefault(RunArg.config, "").toString();
- if (!setupFile.isEmpty()) {
- // Read from file instead
- try {
- FileReader reader = new FileReader(setupFile);
- JSONParser parser = new JSONParser();
- JSONObject json = (JSONObject) parser.parse(reader);
- runGames.config = parseConfig(json, Usage.RunGames);
- } catch (FileNotFoundException ignored) {
- throw new AssertionError("Config file not found : " + setupFile);
- // parseConfig(runGames, args);
- } catch (IOException | ParseException e) {
- throw new RuntimeException(e);
- }
- }
runGames.initialiseGamesAndPlayerCount();
if (!runGames.config.get(RunArg.gameParams).equals("") && runGames.gamesAndPlayerCounts.keySet().size() > 1)
throw new IllegalArgumentException("Cannot yet provide a gameParams argument if running multiple games");
// 2. Setup
-
LinkedList agents = new LinkedList<>();
if (!runGames.config.get(playerDirectory).equals("")) {
agents.addAll(PlayerFactory.createPlayers((String) runGames.config.get(playerDirectory)));
diff --git a/src/main/java/evaluation/tournaments/RoundRobinTournament.java b/src/main/java/evaluation/tournaments/RoundRobinTournament.java
index 856c11c68..6aacb571c 100644
--- a/src/main/java/evaluation/tournaments/RoundRobinTournament.java
+++ b/src/main/java/evaluation/tournaments/RoundRobinTournament.java
@@ -559,6 +559,11 @@ protected void reportResults() {
// To file
if (toFile) {
try {
+ File resultsFile = new File(this.resultsFile);
+ if (!resultsFile.exists()) {
+ File dir = resultsFile.getParentFile();
+ dir.mkdirs();
+ }
FileWriter writer = new FileWriter(resultsFile, true);
for (String line : dataDump)
writer.write(line);
@@ -737,11 +742,11 @@ protected double[] reportAlphaRank(List dataDump, int[][] values) {
}
public double getWinRate(int agentID) {
- return finalWinRanking.get(agentID).a;
+ return finalWinRanking.get(agentID) == null ? 0.0 : finalWinRanking.get(agentID).a;
}
public double getWinStdErr(int agentID) {
- return finalWinRanking.get(agentID).b;
+ return finalWinRanking.get(agentID) == null ? 0.0 : finalWinRanking.get(agentID).b;
}
public double getOrdinalRank(int agentID) {
diff --git a/src/main/java/games/GameType.java b/src/main/java/games/GameType.java
index c62fab68e..a5e2bb786 100644
--- a/src/main/java/games/GameType.java
+++ b/src/main/java/games/GameType.java
@@ -70,10 +70,12 @@
import gametemplate.GTGameState;
import gametemplate.GTParameters;
import gui.*;
+import llm.DocumentSummariser;
import org.apache.commons.lang3.reflect.ConstructorUtils;
import players.human.ActionController;
import players.human.HumanGUIPlayer;
+import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
@@ -254,6 +256,40 @@ public enum GameType {
this(minPlayers, maxPlayers, categories, mechanics, gameStateClass, forwardModelClass, parameterClass, guiManagerClass, null);
}
+ public String loadRulebook() {
+ String pdfFilePath = "data/" + this.name().toLowerCase() + "/rulebook.pdf";
+ String ruleSummaryPath = "data/" + this.name().toLowerCase() + "/ruleSummary.txt";
+ // The first time we process the rulebook we create rule and strategy summaries for use
+ // with LLM-created heuristics (etc.)
+
+ File ruleSummaryFile = new File(ruleSummaryPath);
+ if (ruleSummaryFile.exists()) {
+ try {
+ Scanner scanner = new Scanner(ruleSummaryFile);
+ StringBuilder sb = new StringBuilder();
+ while (scanner.hasNextLine()) {
+ sb.append(scanner.nextLine()).append("\n");
+ }
+ return sb.toString();
+ } catch (FileNotFoundException e) {
+ throw new AssertionError("File exists but could not be read: " + ruleSummaryPath);
+ }
+ }
+
+ DocumentSummariser summariser = new DocumentSummariser(pdfFilePath);
+ String rulesText = summariser.processText("game rules and strategy", 500);
+ // Then write this to file
+ try {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(ruleSummaryPath));
+ writer.write(rulesText);
+ writer.close();
+ } catch (IOException e) {
+ throw new AssertionError("Error writing rule summary file: " + ruleSummaryPath);
+ }
+
+ return rulesText;
+ }
+
// Getters
public int getMinPlayers() {
return minPlayers;
@@ -275,6 +311,22 @@ public String getDataPath() {
return dataPath;
}
+ public Class extends AbstractGameState> getGameStateClass() {
+ return gameStateClass;
+ }
+
+ public Class extends AbstractForwardModel> getForwardModelClass() {
+ return forwardModelClass;
+ }
+
+ public Class extends AbstractGUIManager> getGuiManagerClass() {
+ return guiManagerClass;
+ }
+
+ public Class extends AbstractParameters> getParameterClass() {
+ return parameterClass;
+ }
+
public AbstractGameState createGameState(AbstractParameters params, int nPlayers) {
if (gameStateClass == null) throw new AssertionError("No game state class declared for the game: " + this);
try {
diff --git a/src/main/java/games/cantstop/CantStopGameState.java b/src/main/java/games/cantstop/CantStopGameState.java
index e02d3d3aa..91d4ff4a9 100644
--- a/src/main/java/games/cantstop/CantStopGameState.java
+++ b/src/main/java/games/cantstop/CantStopGameState.java
@@ -48,17 +48,23 @@ public void rollDice() {
dice.forEach(d -> d.roll(rnd));
}
- public boolean trackComplete(int n) {
+ /**
+ * True if the specified number track is complete and has been scored
+ **/
+ public boolean isTrackComplete(int n) {
return completedColumns[n];
}
public void moveMarker(int n) {
- if (!trackComplete(n)) {
+ if (!isTrackComplete(n)) {
int currentPosition = temporaryMarkerPositions.getOrDefault(n, playerMarkerPositions[getCurrentPlayer()][n]);
temporaryMarkerPositions.put(n, currentPosition + 1);
}
}
+ /**
+ * Returns the current dice roll as an int[2]
+ **/
public int[] getDice() {
return dice.stream().mapToInt(Dice::getValue).toArray();
}
@@ -71,14 +77,23 @@ public void setDice(int[] numbers) {
}
}
+ /**
+ * The current marker position for the specified player and number track
+ **/
public int getMarkerPosition(int number, int player) {
return playerMarkerPositions[player][number];
}
+ /**
+ * The temporary marker position for the current player on the specified number track
+ **/
public int getTemporaryMarkerPosition(int number) {
return temporaryMarkerPositions.getOrDefault(number, 0);
}
+ /**
+ * The numbers of the tracks that have had markers moved in the current turn
+ **/
public List getMarkersMoved() {
return new ArrayList<>(temporaryMarkerPositions.keySet());
}
@@ -184,7 +199,7 @@ public void printToConsole() {
sb.append("Max\n");
for (int n = 2; n <= 12; n++) {
sb.append(String.format("%2d :\t", n));
- if (trackComplete(n))
+ if (isTrackComplete(n))
sb.append(" COMPLETED");
else {
for (int p = 0; p < getNPlayers(); p++) {
diff --git a/src/main/java/games/cantstop/actions/AllocateDice.java b/src/main/java/games/cantstop/actions/AllocateDice.java
index 05e3ebfd3..e766bb849 100644
--- a/src/main/java/games/cantstop/actions/AllocateDice.java
+++ b/src/main/java/games/cantstop/actions/AllocateDice.java
@@ -27,7 +27,7 @@ public boolean isLegal(CantStopGameState state) {
Map numberCounts = Arrays.stream(numberSplit).boxed().collect(groupingBy(n -> n, counting()));
for (int n : numberSplit) {
int markerPosition = Math.max(state.getTemporaryMarkerPosition(n), state.getMarkerPosition(n, state.getCurrentPlayer()));
- boolean canMoveOnTrack = !state.trackComplete(n) && (markerPosition + numberCounts.get(n) - 1) < params.maxValue(n);
+ boolean canMoveOnTrack = !state.isTrackComplete(n) && (markerPosition + numberCounts.get(n) - 1) < params.maxValue(n);
retValue = canMoveOnTrack && retValue;
}
// then each number must either have a marker already, or a spare marker is available
diff --git a/src/main/java/games/catan/CatanGameState.java b/src/main/java/games/catan/CatanGameState.java
index 1566c7661..097c10020 100644
--- a/src/main/java/games/catan/CatanGameState.java
+++ b/src/main/java/games/catan/CatanGameState.java
@@ -41,6 +41,7 @@ public class CatanGameState extends AbstractGameState {
public int negotiationStepsCount;
public int nTradesThisTurn;
+ // Details of the current trade offer (if any, may be null)
public AbstractAction getTradeOffer() {
return tradeOffer;
}
@@ -142,6 +143,7 @@ public void setRollValue(int rollValue) {
this.rollValue = rollValue;
}
+ // value of the currently rolled dice
public int getRollValue() {
return rollValue;
}
@@ -179,6 +181,7 @@ public void addKnight(int playerID) {
}
}
+ // The number of knights that each player has
public int[] getKnights() {
return Arrays.copyOf(knights, knights.length);
}
@@ -237,6 +240,7 @@ public Map getExchangeRates(int playerID) {
return exchangeRates.get(playerID);
}
+ // The road distance between two adjacent settlements, one at x, y, the other along the specified edgeIdx
public int getRoadDistance(int x, int y, int edgeIdx) {
// As the settlements are the nodes, we expand them to find roads
// calculates the distance length of the road
@@ -305,6 +309,7 @@ private Set expandRoad(Set roadSet, List unexpanded, List<
return expandRoad(roadSet, unexpanded, expanded);
}
+ // The number of resource cards in a player's hand
public int getNResourcesInHand(int player) {
int deckSize = 0;
for (Map.Entry e: playerResources.get(player).entrySet()) {
diff --git a/src/main/java/games/coltexpress/ColtExpressForwardModel.java b/src/main/java/games/coltexpress/ColtExpressForwardModel.java
index 24b3dbada..b1cab898d 100644
--- a/src/main/java/games/coltexpress/ColtExpressForwardModel.java
+++ b/src/main/java/games/coltexpress/ColtExpressForwardModel.java
@@ -112,7 +112,7 @@ private void setupRounds(ColtExpressGameState cegs, ColtExpressParameters cep) {
}
cegs.rounds.shuffle(rndForRoundCards);
- RoundCard endCard = cegs.getRandomEndRoundCard(cep, rndForRoundCards);
+ RoundCard endCard = cegs.randomEndRoundCard(cep, rndForRoundCards);
cegs.rounds.addToBottom(endCard);
}
diff --git a/src/main/java/games/coltexpress/ColtExpressGameState.java b/src/main/java/games/coltexpress/ColtExpressGameState.java
index b23de27ec..2cd5febc5 100644
--- a/src/main/java/games/coltexpress/ColtExpressGameState.java
+++ b/src/main/java/games/coltexpress/ColtExpressGameState.java
@@ -208,7 +208,7 @@ protected AbstractGameStateWithTurnOrder __copy(int playerId) {
for (int i = 0; i < rounds.getSize(); i++) {
if (!rounds.isComponentVisible(i, playerId)) {
if (i == rounds.getSize() - 1) { // last card, so use an End Round Card
- copy.rounds.setComponent(i, getRandomEndRoundCard((ColtExpressParameters) getGameParameters(), null));
+ copy.rounds.setComponent(i, randomEndRoundCard((ColtExpressParameters) getGameParameters(), null));
} else {
copy.rounds.setComponent(i, getRandomRoundCard((ColtExpressParameters) getGameParameters(), i, exclusionList));
exclusionList.add(copy.rounds.get(i));
@@ -225,6 +225,10 @@ protected double _getHeuristicScore(int playerId) {
return new ColtExpressHeuristic().evaluateState(this, playerId);
}
+ /**
+ * The score that playerID would have if the game ended immediately.
+ * This includes the value of all loot, and the possible bonus shooter reward.
+ */
@Override
public double getGameScore(int playerId) {
double retValue = getLoot(playerId).sumInt(Loot::getValue);
@@ -357,7 +361,6 @@ void distributeCards(){
}
}
- // Getters, setters
public LinkedList getTrainCompartments() {
return trainCompartments;
}
@@ -366,10 +369,17 @@ public Deck getLoot(int playerID) {
return playerLoot.get(playerID);
}
+ /**
+ * The Deck of played cards that will be executed in order in the execution phase
+ * Only some of these are visible.
+ */
public PartialObservableDeck getPlannedActions() {
return plannedActions;
}
+ /**
+ * The draw decks of each player (a List in playerID order)
+ */
public List> getPlayerDecks() {
return playerDecks;
}
@@ -378,14 +388,24 @@ public PartialObservableDeck getRounds() {
return rounds;
}
+ /**
+ * A mapping of playerID to the character they are playing
+ */
public HashMap getPlayerCharacters() {
return playerCharacters;
}
+ /**
+ * The hands of each player (a List in playerID order)
+ * Only a player's own hand is visible
+ */
public List> getPlayerHandCards() {
return playerHandCards;
}
+ /**
+ * An array of the number of bullets each player has remaining
+ */
public int[] getBulletsLeft() {
return bulletsLeft;
}
@@ -451,13 +471,13 @@ public String printTrain() {
* Helper getter methods for round card composition.
*/
- RoundCard getRandomEndRoundCard(ColtExpressParameters cep, Random overrideRnd) {
+ RoundCard randomEndRoundCard(ColtExpressParameters cep, Random overrideRnd) {
int nEndCards = cep.endRoundCards.length;
int choice = overrideRnd == null ? rnd.nextInt(nEndCards) : overrideRnd.nextInt(nEndCards);
return getEndRoundCard(cep, choice);
}
- RoundCard getEndRoundCard(ColtExpressParameters cep, int idx) {
+ private RoundCard getEndRoundCard(ColtExpressParameters cep, int idx) {
if (idx >= 0 && idx < cep.endRoundCards.length) {
RoundCard.TurnType[] turnTypes = cep.endRoundCards[idx].getTurnTypeSequence();
RoundEvent event = cep.endRoundCards[idx].getEndCardEvent();
@@ -466,7 +486,7 @@ RoundCard getEndRoundCard(ColtExpressParameters cep, int idx) {
return null;
}
- RoundCard getRandomRoundCard(ColtExpressParameters cep, int i, List exclusionList) {
+ private RoundCard getRandomRoundCard(ColtExpressParameters cep, int i, List exclusionList) {
List namesToExclude = exclusionList.stream().map(RoundCard::getComponentName).collect(toList());
List availableTypes = Arrays.stream(cep.roundCards)
.filter(rc -> !namesToExclude.contains(rc.name())).collect(toList());
diff --git a/src/main/java/games/coltexpress/components/Loot.java b/src/main/java/games/coltexpress/components/Loot.java
index d8df4c292..4639bf491 100644
--- a/src/main/java/games/coltexpress/components/Loot.java
+++ b/src/main/java/games/coltexpress/components/Loot.java
@@ -29,6 +29,7 @@ public static Loot createStrongbox(){
return new Loot(ColtExpressTypes.LootType.Strongbox, 1000);
}
+ // point value of the loot
public int getValue(){
return value;
}
diff --git a/src/main/java/games/coltexpress/gui/ColtExpressGUIManager.java b/src/main/java/games/coltexpress/gui/ColtExpressGUIManager.java
index d5036c7ee..3032191aa 100644
--- a/src/main/java/games/coltexpress/gui/ColtExpressGUIManager.java
+++ b/src/main/java/games/coltexpress/gui/ColtExpressGUIManager.java
@@ -237,7 +237,7 @@ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
// Update decks and visibility
ColtExpressGameState cegs = (ColtExpressGameState)gameState;
for (int i = 0; i < gameState.getNPlayers(); i++) {
- playerHands[i].update((ColtExpressGameState) gameState, humanPlayerId);
+ playerHands[i].update((ColtExpressGameState) gameState, humanPlayerIds);
// Highlight active player
if (i == gameState.getCurrentPlayer()) {
@@ -251,7 +251,7 @@ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
plannedActions.updateComponent(cegs.getPlannedActions());
int activePlayer = player != null? (gameState.getCoreGameParameters().alwaysDisplayCurrentPlayer ||
gameState.getCoreGameParameters().alwaysDisplayFullObservable? player.getPlayerID():
- humanPlayerId.contains(player.getPlayerID())? player.getPlayerID():-1) : -1;
+ humanPlayerIds.contains(player.getPlayerID())? player.getPlayerID():-1) : -1;
plannedActions.informActivePlayer(activePlayer);
// Show planned actions from the first played
diff --git a/src/main/java/games/connect4/Connect4GameState.java b/src/main/java/games/connect4/Connect4GameState.java
index 6ad289a8d..43f0b36d6 100644
--- a/src/main/java/games/connect4/Connect4GameState.java
+++ b/src/main/java/games/connect4/Connect4GameState.java
@@ -29,6 +29,14 @@ public Connect4GameState(AbstractParameters gameParameters, int nPlayers) {
gridBoard = null;
}
+ /**
+ * This returns the player id of the token at the given position. Or -1 if this is empty.
+ */
+ public int getPlayerAt(int x, int y) {
+ Token token = gridBoard.getElement(x, y);
+ return token == null ? -1 : token.getOwnerId();
+ }
+
@Override
protected GameType _getGameType() {
return GameType.Connect4;
@@ -59,12 +67,7 @@ protected double _getHeuristicScore(int playerId) {
}
/**
- * This provides the current score in game turns. This will only be relevant for games that have the concept
- * of victory points, etc.
- * If a game does not support this directly, then just return 0.0
- *
- * @param playerId
- * @return - double, score of current state
+ * Score is not relevant for Connect4. This will be 0.0 if a game is nto finished.
*/
@Override
public double getGameScore(int playerId) {
diff --git a/src/main/java/games/diamant/DiamantGameState.java b/src/main/java/games/diamant/DiamantGameState.java
index 5e5d6d9a3..e8947efde 100644
--- a/src/main/java/games/diamant/DiamantGameState.java
+++ b/src/main/java/games/diamant/DiamantGameState.java
@@ -148,12 +148,7 @@ protected double _getHeuristicScore(int playerId)
return new DiamantHeuristic().evaluateState(this, playerId);
}
/**
- * This provides the current score in game turns. This will only be relevant for games that have the concept
- * of victory points, etc.
- * If a game does not support this directly, then just return 0.0
- *
- * @param playerId
- * @return - double, score of current state
+ * Current score
*/
@Override
public double getGameScore(int playerId) {
@@ -216,7 +211,6 @@ protected boolean _equals(Object o)
/**
* Returns the number of player already in the cave
*/
-
public int getNPlayersInCave()
{
int n = 0;
@@ -264,7 +258,7 @@ public void printToConsole() {
}
}
- private int getNHazardCardsInMainDeck(DiamantCard.HazardType ht)
+ public int getNHazardCardsInMainDeck(DiamantCard.HazardType ht)
{
int n = 0;
for (int i=0; i _computeAvailableActions(AbstractGameState gameSt
switch (state.getGamePhase().toString()) {
case "Play":
- if (state.actionsLeft() > 0) {
+ if (state.getActionsLeft() > 0) {
List actionCards = state.getDeck(DeckType.HAND, playerID).stream()
.filter(DominionCard::isActionCard).collect(toList());
List availableActions = actionCards.stream()
@@ -166,8 +166,8 @@ protected List _computeAvailableActions(AbstractGameState gameSt
return Collections.singletonList(new EndPhase(DominionGameState.DominionGamePhase.Play));
case "Buy":
// we return every available card for purchase within our price range
- int budget = state.availableSpend(playerID);
- List options = state.cardsToBuy().stream()
+ int budget = state.getAvailableSpend(playerID);
+ List options = state.getCardsToBuy().stream()
.filter(ct -> ct.cost <= budget)
.sorted(Comparator.comparingInt(c -> -c.cost))
.map(ct -> new BuyCard(ct, playerID))
diff --git a/src/main/java/games/dominion/DominionGameState.java b/src/main/java/games/dominion/DominionGameState.java
index 338fa76d3..483136db4 100644
--- a/src/main/java/games/dominion/DominionGameState.java
+++ b/src/main/java/games/dominion/DominionGameState.java
@@ -122,7 +122,10 @@ public boolean moveCard(DominionCard cardToMove, int fromPlayer, DeckType fromDe
return false;
}
- public int actionsLeft() {
+ /**
+ * The number of actions the current player has left
+ */
+ public int getActionsLeft() {
return actionsLeftForCurrentPlayer;
}
@@ -130,7 +133,10 @@ public void changeActions(int delta) {
actionsLeftForCurrentPlayer += delta;
}
- public int buysLeft() {
+ /**
+ * The number of buys the current player has left
+ */
+ public int getBuysLeft() {
return buysLeftForCurrentPlayer;
}
@@ -146,7 +152,10 @@ public void changeAdditionalSpend(int delta) {
additionalSpendAvailable += delta;
}
- public int availableSpend(int playerID) {
+ /**
+ * How much money does the player have available to spend?
+ */
+ public int getAvailableSpend(int playerID) {
if (playerID != turnOwner) {
return 0;
}
@@ -175,6 +184,10 @@ protected List _getAllComponents() {
return components;
}
+ /**
+ * Get the Deck of the specified type for the specified player.
+ * DeckType.ALL is not valid
+ */
public Deck getDeck(DeckType deck, int playerId) {
switch (deck) {
case HAND:
@@ -191,6 +204,10 @@ public Deck getDeck(DeckType deck, int playerId) {
throw new AssertionError("Unknown deck type " + deck);
}
+ /**
+ * How many cards of the specified type are in the player's deck?
+ * Use DeckType.ALL to count all cards in all decks.
+ */
public int cardsOfType(CardType type, int playerId, DeckType deck) {
Deck allCards;
switch (deck) {
@@ -217,7 +234,7 @@ public int cardsOfType(CardType type, int playerId, DeckType deck) {
return (int) allCards.stream().filter(c -> c.cardType() == type).count();
}
- public List cardsToBuy() {
+ public List getCardsToBuy() {
return cardsIncludedInGame.keySet().stream()
.filter(c -> cardsIncludedInGame.get(c) > 0)
.sorted(comparingInt(c -> -c.cost))
@@ -325,22 +342,23 @@ protected double _getHeuristicScore(int playerId) {
}
/**
- * This provides the current score in game turns. This will only be relevant for games that have the concept
- * of victory points, etc.
- * If a game does not support this directly, then just return 0.0
- *
- * @param playerId Player number
- * @return - double, score of current state
+ * This provides the current score for the specified player.
*/
@Override
public double getGameScore(int playerId) {
return getTotal(playerId, c -> c.victoryPoints(playerId, this));
}
+ /**
+ * This provides the total value of the specified deck for the player using the provided cardValuer function
+ */
public int getTotal(int playerId, DeckType deck, Function cardValuer) {
return getDeck(deck, playerId).sumInt(cardValuer);
}
+ /**
+ * This provides the total value across all decks for the player using the provided cardValuer function
+ */
public int getTotal(int playerId, Function cardValuer) {
int score = playerHands[playerId].sumInt(cardValuer);
score += playerDiscards[playerId].sumInt(cardValuer);
@@ -349,6 +367,7 @@ public int getTotal(int playerId, Function cardValuer) {
return score;
}
+ // How many cards does the player have in total?
public int getTotalCards(int playerId) {
return playerDrawPiles[playerId].getSize() + playerDiscards[playerId].getSize()
+ playerHands[playerId].getSize() + playerTableaux[playerId].getSize();
diff --git a/src/main/java/games/dominion/DominionHeuristic.java b/src/main/java/games/dominion/DominionHeuristic.java
index ca88be736..bbf96e9ff 100644
--- a/src/main/java/games/dominion/DominionHeuristic.java
+++ b/src/main/java/games/dominion/DominionHeuristic.java
@@ -99,12 +99,12 @@ public double evaluateState(AbstractGameState gs, int playerId) {
// actionsLeft / 5.
if (actionsLeft != 0.0)
if (state.getCurrentPlayer() == playerId)
- retValue += actionsLeft * Math.min(state.actionsLeft() / 5.0, 1.0);
+ retValue += actionsLeft * Math.min(state.getActionsLeft() / 5.0, 1.0);
// buysLeft / 5
if (buysLeft != 0.0)
if (state.getCurrentPlayer() == playerId)
- retValue += buysLeft * Math.min(state.buysLeft() / 5.0, 1.0);
+ retValue += buysLeft * Math.min(state.getBuysLeft() / 5.0, 1.0);
if (provinceCount != 0.0)
retValue += provinceCount * state.getTotal(playerId, c -> c.cardType() == CardType.PROVINCE ? 1 : 0) / 12.0;
diff --git a/src/main/java/games/dominion/actions/Artisan.java b/src/main/java/games/dominion/actions/Artisan.java
index 31002f286..76cf24259 100644
--- a/src/main/java/games/dominion/actions/Artisan.java
+++ b/src/main/java/games/dominion/actions/Artisan.java
@@ -34,7 +34,7 @@ boolean _execute(DominionGameState state) {
public List _computeAvailableActions(AbstractGameState gs) {
DominionGameState state = (DominionGameState) gs;
if (!gainedCard) {
- return state.cardsToBuy().stream()
+ return state.getCardsToBuy().stream()
.filter(c -> c.cost <= MAX_COST_OF_GAINED_CARD)
.map(c -> new GainCard(c, player, DeckType.HAND))
.collect(toList());
diff --git a/src/main/java/games/dominion/actions/BuyCard.java b/src/main/java/games/dominion/actions/BuyCard.java
index 699daee8e..58a4e18d3 100644
--- a/src/main/java/games/dominion/actions/BuyCard.java
+++ b/src/main/java/games/dominion/actions/BuyCard.java
@@ -27,7 +27,7 @@ public boolean execute(AbstractGameState gs) {
// ii) Removing the card from the table and adding it to the player's discard pile
// iii) Updating the available money and buy actions
DominionGameState state = (DominionGameState) gs;
- if (state.buysLeft() > 0 && state.availableSpend(buyingPlayer) >= cardType.cost) {
+ if (state.getBuysLeft() > 0 && state.getAvailableSpend(buyingPlayer) >= cardType.cost) {
boolean success = super.execute(state);
if (success) {
state.changeBuys(-1);
diff --git a/src/main/java/games/dominion/actions/DominionAction.java b/src/main/java/games/dominion/actions/DominionAction.java
index a6dcca52a..2231652db 100644
--- a/src/main/java/games/dominion/actions/DominionAction.java
+++ b/src/main/java/games/dominion/actions/DominionAction.java
@@ -34,7 +34,7 @@ public boolean execute(AbstractGameState gs) {
System.out.println(gs);
throw new AssertionError("Attempting to play an action out of turn : " + this);
}
- if (!dummyAction && state.actionsLeft() < 1) {
+ if (!dummyAction && state.getActionsLeft() < 1) {
System.out.println(gs);
throw new AssertionError("Insufficient actions to play action card " + this);
}
diff --git a/src/main/java/games/dominion/actions/Mine.java b/src/main/java/games/dominion/actions/Mine.java
index 376e909f1..d57fec83f 100644
--- a/src/main/java/games/dominion/actions/Mine.java
+++ b/src/main/java/games/dominion/actions/Mine.java
@@ -47,7 +47,7 @@ public List _computeAvailableActions(AbstractGameState gs) {
.map(c -> new TrashCard(c.cardType(), player))
.distinct().collect(toList());
} else if (!gainedCard) {
- retValue = state.cardsToBuy().stream()
+ retValue = state.getCardsToBuy().stream()
.filter(c -> c.isTreasure && c.cost <= trashValue + BONUS_OVER_TRASHED_VALUE)
.map(c -> new GainCard(c, player, DominionConstants.DeckType.HAND))
.collect(toList());
@@ -71,7 +71,7 @@ public void _afterAction(AbstractGameState gs, AbstractAction action) {
if (!trashedCard && action instanceof TrashCard && ((TrashCard) action).player == player) {
trashedCard = true;
trashValue = ((TrashCard) action).trashedCard.cost;
- if (state.cardsToBuy().stream().noneMatch(c -> c.isTreasure && c.cost <= trashValue + BONUS_OVER_TRASHED_VALUE))
+ if (state.getCardsToBuy().stream().noneMatch(c -> c.isTreasure && c.cost <= trashValue + BONUS_OVER_TRASHED_VALUE))
gainedCard = true; // there are no valid cards to gain, so we skip the next decision
// this is rare, but can happen if SILVER is exhausted with random players, say
}
diff --git a/src/main/java/games/dominion/actions/Remodel.java b/src/main/java/games/dominion/actions/Remodel.java
index a000e486b..46b66e237 100644
--- a/src/main/java/games/dominion/actions/Remodel.java
+++ b/src/main/java/games/dominion/actions/Remodel.java
@@ -53,7 +53,7 @@ public List _computeAvailableActions(AbstractGameState gs) {
} else {
//Phase 2 - gain a card costing up to 2 more
int budget = cardTrashed.cost + BONUS_OVER_TRASHED_VALUE;
- retValue = state.cardsToBuy().stream()
+ retValue = state.getCardsToBuy().stream()
.filter(ct -> ct.cost <= budget)
.map(ct -> new GainCard(ct, player))
.collect(toList());
diff --git a/src/main/java/games/dominion/actions/Workshop.java b/src/main/java/games/dominion/actions/Workshop.java
index 472ca8cba..51962724b 100644
--- a/src/main/java/games/dominion/actions/Workshop.java
+++ b/src/main/java/games/dominion/actions/Workshop.java
@@ -32,7 +32,7 @@ boolean _execute(DominionGameState state) {
@Override
public List _computeAvailableActions(AbstractGameState gs) {
DominionGameState state = (DominionGameState) gs;
- List retValue = state.cardsToBuy().stream()
+ List retValue = state.getCardsToBuy().stream()
.filter(c -> c.cost <= COST_OF_GAINED_CARD)
.map(c -> new GainCard(c, state.getCurrentPlayer()))
.collect(toList());
diff --git a/src/main/java/games/dominion/gui/DominionPlayerView.java b/src/main/java/games/dominion/gui/DominionPlayerView.java
index 0bd4a9b58..c0556ede1 100644
--- a/src/main/java/games/dominion/gui/DominionPlayerView.java
+++ b/src/main/java/games/dominion/gui/DominionPlayerView.java
@@ -78,9 +78,9 @@ public void update(DominionGameState state) {
// There is no need to updateComponent on the DeckViews, as once initialised, the same Deck is retained throughout a Game
// (if we ignore the copies of GameState used in MCTS etc.)
if (state.getCurrentPlayer() == playerId) {
- actions = state.actionsLeft();
- spendAvailable = state.availableSpend(playerId);
- buys = state.buysLeft();
+ actions = state.getActionsLeft();
+ spendAvailable = state.getAvailableSpend(playerId);
+ buys = state.getBuysLeft();
} else {
actions = 0;
spendAvailable = 0;
diff --git a/src/main/java/games/dominion/metrics/DomStateFeatures.java b/src/main/java/games/dominion/metrics/DomStateFeatures.java
index 9fa74c927..4615588aa 100644
--- a/src/main/java/games/dominion/metrics/DomStateFeatures.java
+++ b/src/main/java/games/dominion/metrics/DomStateFeatures.java
@@ -58,11 +58,11 @@ public double[] localFeatureVector(AbstractGameState gs, int playerId) {
// actionsLeft / 5.
if (state.getCurrentPlayer() == playerId)
- retValue[4] = state.actionsLeft() / 5.0;
+ retValue[4] = state.getActionsLeft() / 5.0;
// buysLeft / 5
if (state.getCurrentPlayer() == playerId)
- retValue[5] = state.buysLeft() / 5.0;
+ retValue[5] = state.getBuysLeft() / 5.0;
// Total cards / 40
retValue[6] = state.getTotalCards(playerId) / 40.0;
diff --git a/src/main/java/games/dominion/metrics/DomStateFeaturesReduced.java b/src/main/java/games/dominion/metrics/DomStateFeaturesReduced.java
index 07b74dcec..3f5e73b52 100644
--- a/src/main/java/games/dominion/metrics/DomStateFeaturesReduced.java
+++ b/src/main/java/games/dominion/metrics/DomStateFeaturesReduced.java
@@ -1,7 +1,6 @@
package games.dominion.metrics;
import core.AbstractGameState;
-import core.CoreConstants;
import core.interfaces.IStateFeatureVector;
import games.dominion.DominionConstants;
import games.dominion.DominionGameState;
@@ -38,11 +37,11 @@ public double[] featureVector(AbstractGameState gs, int playerId) {
// actionsLeft
if (state.getCurrentPlayer() == playerId)
- retValue[5] = state.actionsLeft();
+ retValue[5] = state.getActionsLeft();
// buysLeft
if (state.getCurrentPlayer() == playerId)
- retValue[6] = state.buysLeft() / 5.0;
+ retValue[6] = state.getBuysLeft() / 5.0;
retValue[7] = state.getTotal(playerId, c -> c.cardType() == CardType.PROVINCE ? 1 : 0);
diff --git a/src/main/java/games/dominion/metrics/DominionMetrics.java b/src/main/java/games/dominion/metrics/DominionMetrics.java
index a0725c2e4..4783e5f47 100644
--- a/src/main/java/games/dominion/metrics/DominionMetrics.java
+++ b/src/main/java/games/dominion/metrics/DominionMetrics.java
@@ -113,9 +113,9 @@ public Map> getColumns(int nPlayersPerGame, Set playerN
@Override
protected boolean _run(MetricsGameListener listener, Event e, Map records) {
DominionGameState state = (DominionGameState)e.state;
- records.put("Money", state.availableSpend(e.playerID));
- records.put("Actions", state.actionsLeft());
- records.put("Buys", state.buysLeft());
+ records.put("Money", state.getAvailableSpend(e.playerID));
+ records.put("Actions", state.getActionsLeft());
+ records.put("Buys", state.getBuysLeft());
return true;
}
diff --git a/src/main/java/games/dominion/players/BigMoney.java b/src/main/java/games/dominion/players/BigMoney.java
index 0d40c5d39..45c77066a 100644
--- a/src/main/java/games/dominion/players/BigMoney.java
+++ b/src/main/java/games/dominion/players/BigMoney.java
@@ -29,7 +29,7 @@ public AbstractAction _getAction(AbstractGameState gameState, List cardTypes = new ArrayList<>(Arrays.asList("EXPLODING_KITTEN", "DEFUSE", "NOPE", "ATTACK", "SKIP", "FAVOR",
- "SHUFFLE", "SEETHEFUTURE", "TACOCAT", "MELONCAT", "FURRYCAT", "BEARDCAT", "RAINBOWCAT"));
- private final int N_CARDS_TO_CHECK = 10;
+public class ExplodingKittensForwardModel extends StandardForwardModel {
- /**
- * Performs initial game setup according to game rules.
- * @param firstState - the state to be modified to the initial game state.
- */
+
+ @Override
protected void _setup(AbstractGameState firstState) {
- ExplodingKittensGameState ekgs = (ExplodingKittensGameState)firstState;
- ExplodingKittensParameters ekp = (ExplodingKittensParameters)firstState.getGameParameters();
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) firstState;
+ ExplodingKittensParameters ekp = (ExplodingKittensParameters) firstState.getGameParameters();
ekgs.playerHandCards = new ArrayList<>();
- ekgs.playerGettingAFavor = -1;
- ekgs.actionStack = null;
// Set up draw pile deck
- PartialObservableDeck drawPile = new PartialObservableDeck<>("Draw Pile", -1, firstState.getNPlayers(), VisibilityMode.HIDDEN_TO_ALL);
- ekgs.setDrawPile(drawPile);
+ PartialObservableDeck drawPile = new PartialObservableDeck<>("Draw Pile", -1, firstState.getNPlayers(), CoreConstants.VisibilityMode.HIDDEN_TO_ALL);
+ ekgs.drawPile = drawPile;
+ ekgs.inPlay = new Deck<>("In Play", CoreConstants.VisibilityMode.VISIBLE_TO_ALL);
// Add all cards but defuse and exploding kittens
for (HashMap.Entry entry : ekp.cardCounts.entrySet()) {
- if (entry.getKey() == ExplodingKittensCard.CardType.DEFUSE || entry.getKey() == ExplodingKittensCard.CardType.EXPLODING_KITTEN)
+ if (entry.getKey() == DEFUSE || entry.getKey() == EXPLODING_KITTEN)
continue;
for (int i = 0; i < entry.getValue(); i++) {
ExplodingKittensCard card = new ExplodingKittensCard(entry.getKey());
drawPile.add(card);
}
}
- ekgs.getDrawPile().shuffle(ekgs.getRnd());
+ ekgs.drawPile.shuffle(ekgs.getRnd());
// Set up player hands
List> playerHandCards = new ArrayList<>(firstState.getNPlayers());
@@ -62,435 +47,169 @@ protected void _setup(AbstractGameState firstState) {
playerHandCards.add(playerCards);
// Add defuse card
- ExplodingKittensCard defuse = new ExplodingKittensCard(ExplodingKittensCard.CardType.DEFUSE);
+ ExplodingKittensCard defuse = new ExplodingKittensCard(DEFUSE);
defuse.setOwnerId(i);
playerCards.add(defuse);
// Add N random cards from the deck
for (int j = 0; j < ekp.nCardsPerPlayer; j++) {
- ExplodingKittensCard c = ekgs.getDrawPile().draw();
+ ExplodingKittensCard c = ekgs.drawPile.draw();
c.setOwnerId(i);
playerCards.add(c);
}
}
- ekgs.setPlayerHandCards(playerHandCards);
- ekgs.setDiscardPile(new Deck<>("Discard Pile", VisibilityMode.VISIBLE_TO_ALL));
+ ekgs.playerHandCards = playerHandCards;
+ ekgs.discardPile = new Deck<>("Discard Pile", CoreConstants.VisibilityMode.VISIBLE_TO_ALL);
// Add remaining defuse cards and exploding kitten cards to the deck and shuffle again
- for (int i = ekgs.getNPlayers(); i < ekp.nDefuseCards; i++){
- ExplodingKittensCard defuse = new ExplodingKittensCard(ExplodingKittensCard.CardType.DEFUSE);
+ for (int i = ekgs.getNPlayers(); i < ekp.nDefuseCards; i++) {
+ ExplodingKittensCard defuse = new ExplodingKittensCard(DEFUSE);
drawPile.add(defuse);
}
- for (int i = 0; i < ekgs.getNPlayers() + ekp.cardCounts.get(ExplodingKittensCard.CardType.EXPLODING_KITTEN); i++){
- ExplodingKittensCard explodingKitten = new ExplodingKittensCard(ExplodingKittensCard.CardType.EXPLODING_KITTEN);
+ for (int i = 0; i < ekgs.getNPlayers() + ekp.cardCounts.get(EXPLODING_KITTEN); i++) {
+ ExplodingKittensCard explodingKitten = new ExplodingKittensCard(EXPLODING_KITTEN);
drawPile.add(explodingKitten);
}
drawPile.shuffle(ekgs.getRnd());
- ekgs.setActionStack(new Stack<>());
ekgs.orderOfPlayerDeath = new int[ekgs.getNPlayers()];
+ ekgs.currentPlayerTurnsLeft = 1;
ekgs.setGamePhase(CoreConstants.DefaultGamePhase.Main);
}
- /**
- * Applies the given action to the game state and executes any other game rules.
- * @param gameState - current game state, to be modified by the action.
- * @param action - action requested to be played by a player.
- */
@Override
- protected void _next(AbstractGameState gameState, AbstractAction action) {
- ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
- ExplodingKittensTurnOrder ekTurnOrder = (ExplodingKittensTurnOrder) ekgs.getTurnOrder();
- Stack actionStack = ekgs.getActionStack();
+ protected void _afterAction(AbstractGameState state, AbstractAction action) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) state;
+ if (ekgs.isActionInProgress())
+ return;
- if (action instanceof IsNopeable) {
- actionStack.add(action);
- ((IsNopeable) action).actionPlayed(ekgs);
+ // We may have some special statuses to be aware of:
+ // - If a player has skipped, they skip the draw card
- ekTurnOrder.registerNopeableActionByPlayer(ekgs, ekTurnOrder.getCurrentPlayer(ekgs));
- if (action instanceof NopeAction) {
- // Nope cards added immediately to avoid infinite nopeage
- action.execute(ekgs);
+ // - If a player has been attacked, they will have to take two turns
+ // - This is stored in the currentAttackLevel, which has to decrement down to zero before play passes on
+ // - the nextAttackLevel indicates the attack level that will then be applied to the next player
+
+ if (ekgs.isNotTerminal()) {
+ if (ekgs.skip) {
+ ekgs.setSkip(false);
} else {
- if (ekTurnOrder.reactionsFinished()){
- action.execute(ekgs);
- actionStack.clear();
+ // Draw a card for the current player
+ int currentPlayer = ekgs.getCurrentPlayer();
+ ExplodingKittensCard card = ekgs.drawPile.draw();
+ if (card == null) {
+ throw new AssertionError("No cards left in the draw pile");
}
- }
- } else if (action instanceof PassAction) {
-
- ekTurnOrder.endPlayerTurnStep(gameState);
-
- if (ekTurnOrder.reactionsFinished()) {
- // apply stack
- if (actionStack.size()%2 == 0){
- while (actionStack.size() > 1) {
- actionStack.pop();
+ if (card.cardType == EXPLODING_KITTEN) {
+ if (ekgs.playerHandCards.get(currentPlayer).stream().anyMatch(c -> c.cardType == DEFUSE)) {
+ // Exploding kitten drawn, player has defuse
+ ExplodingKittensCard defuseCard = ekgs.playerHandCards.get(currentPlayer).stream().filter(c -> c.cardType == DEFUSE).findFirst().get();
+ ekgs.playerHandCards.get(currentPlayer).remove(defuseCard);
+ ekgs.playerHandCards.get(currentPlayer).add(card); // add Exploding Kitten (to be removed)
+ ekgs.discardPile.add(defuseCard);
+ ekgs.setActionInProgress(new DefuseKitten(currentPlayer));
+ return;
+ } else {
+ // Exploding kitten drawn, player is dead
+ if (ekgs.getPlayerResults()[currentPlayer] == CoreConstants.GameResult.LOSE_GAME) {
+ throw new AssertionError("Player " + currentPlayer + " is already dead");
+ }
+ ekgs.discardPile.add(card);
+ killPlayer(ekgs, currentPlayer);
+ ekgs.currentPlayerTurnsLeft = 1; // force end of player turn later
}
- //Action was successfully noped
- ((IsNopeable) actionStack.pop()).nopedExecute(gameState);
-// if (gameState.getCoreGameParameters().verbose) {
-// System.out.println("Action was successfully noped");
-// }
} else {
-// if (actionStack.size() > 2 && gameState.getCoreGameParameters().verbose) {
-// System.out.println("All nopes were noped");
-// }
-
- while (actionStack.size() > 1) {
- actionStack.pop();
- }
-
- //Action can be played
- AbstractAction stackedAction = actionStack.get(0);
- stackedAction.execute(gameState);
- }
- actionStack.clear();
- if (ekgs.getGamePhase() == Nope) {
- ekgs.setGamePhase(CoreConstants.DefaultGamePhase.Main);
+ card.setOwnerId(currentPlayer);
+ ekgs.playerHandCards.get(currentPlayer).add(card);
}
}
- } else {
- action.execute(gameState);
+ ekgs.currentPlayerTurnsLeft--;
+ if (ekgs.currentPlayerTurnsLeft == 0 && ekgs.isNotTerminal()) {
+ endPlayerTurn(ekgs);
+ ekgs.currentPlayerTurnsLeft = Math.max(1, ekgs.nextAttackLevel);
+ ekgs.nextAttackLevel = 0;
+ }
}
}
- @Override
- protected List _computeAvailableActions(AbstractGameState gameState) {
- return _computeAvailableActions(gameState, ActionSpace.Default);
- }
-
- /**
- * Calculates the list of currently available actions, possibly depending on the game phase.
- * @return - List of AbstractAction objects.
- */
- @Override
- protected List _computeAvailableActions(AbstractGameState gameState, ActionSpace actionSpace) {
- ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
- ArrayList actions;
- // The actions per player do not change a lot in between two turns
- // Could update an existing list instead of generating a new list every time we query this function
+ public void killPlayer(ExplodingKittensGameState ekgs, int playerID) {
+ ekgs.setPlayerResult(CoreConstants.GameResult.LOSE_GAME, playerID);
+ ekgs.logEvent(new IGameEvent() {
+ @Override
+ public String name() {
+ return "Player " + playerID + " has been killed";
+ }
- // Find actions for the player depending on current game phase
- int player = ekgs.getCurrentPlayer();
- if (CoreConstants.DefaultGamePhase.Main.equals(ekgs.getGamePhase())) {
- actions = playerActions(ekgs, player);
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse.equals(ekgs.getGamePhase())) {
- actions = placeKittenActions(ekgs, player);
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Nope.equals(ekgs.getGamePhase())) {
- actions = nopeActions(ekgs, player);
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Favor.equals(ekgs.getGamePhase())) {
- actions = favorActions(ekgs, player);
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture.equals(ekgs.getGamePhase())) {
- actions = seeTheFutureActions(ekgs, player);
- } else {
- actions = new ArrayList<>();
+ @Override
+ public Set getValues() {
+ return Set.of();
+ }
+ });
+ int players = ekgs.getNPlayers();
+ int nPlayersActive = 0;
+ for (int i = 0; i < players; i++) {
+ if (ekgs.getPlayerResults()[i] == CoreConstants.GameResult.GAME_ONGOING) nPlayersActive++;
+ }
+ ekgs.orderOfPlayerDeath[playerID] = players - nPlayersActive;
+ if (nPlayersActive == 1) {
+ endGame(ekgs);
}
-
- return actions;
}
- @Override
- protected AbstractForwardModel _copy() {
- return new ExplodingKittensForwardModel();
- }
+ /**
+ * Calculates the list of currently available actions, possibly depending on the game phase.
+ *
+ * @return - List of AbstractAction objects.
+ */
@Override
- protected void endPlayerTurn(AbstractGameState state) {
- ExplodingKittensGameState ekgs = (ExplodingKittensGameState) state;
- ekgs.getTurnOrder().endPlayerTurn(ekgs);
- }
+ protected List _computeAvailableActions(AbstractGameState gameState) {
- private ArrayList playerActions(ExplodingKittensGameState ekgs, int playerID){
- ArrayList actions = new ArrayList<>();
+ // This is called when it is a player's main turn; not during the Nope interrupts
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+ int playerID = ekgs.getCurrentPlayer();
+ List actions = new ArrayList<>();
Deck playerDeck = ekgs.playerHandCards.get(playerID);
-// ActionTreeNode playNode = root.findChildrenByName("PLAY");
- HashSet types = new HashSet<>();
- for (int c = 0; c < playerDeck.getSize(); c++) {
- ExplodingKittensCard card = playerDeck.get(c);
- if (types.contains(card.cardType)) continue;
- types.add(card.cardType);
+ // We can pass, or play any card in our hand
+ actions.add(new Pass());
+ // get all unique playable types in hand
+ Set playableTypes = new HashSet<>();
+ for (int i = 0; i < playerDeck.getSize(); i++) {
+ playableTypes.add(playerDeck.get(i).cardType);
+ }
+ // remove defuse and exploding kittens from playable types
+ playableTypes.remove(DEFUSE);
+ playableTypes.remove(EXPLODING_KITTEN);
+ playableTypes.remove(NOPE);
- switch (card.cardType) {
- case DEFUSE:
- case MELONCAT:
- case RAINBOWCAT:
- case FURRYCAT:
- case BEARDCAT:
- case TACOCAT:
- case NOPE:
- case EXPLODING_KITTEN:
- break;
- case SKIP:
-// playNode.setValue(1);
- actions.add(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
-// playNode.findChildrenByName("SKIP").setAction(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
+ for (ExplodingKittensCard.CardType type : playableTypes) {
+ switch (type) {
case FAVOR:
- for (int player = 0; player < ekgs.getNPlayers(); player++) {
- if (player == playerID)
- continue;
- if (ekgs.playerHandCards.get(player).getSize() > 0) {
- actions.add(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
-// playNode.setValue(1);
-// playNode.findChildrenByName("FAVOR").setAction(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
+ for (int i = 0; i < ekgs.getNPlayers(); i++) {
+ if (i != playerID) {
+ if (ekgs.isNotTerminalForPlayer(i) && ekgs.getPlayerHand(i).getSize() > 0)
+ actions.add(new PlayEKCard(FAVOR, i));
}
}
break;
- case ATTACK:
- for (int targetPlayer = 0; targetPlayer < ekgs.getNPlayers(); targetPlayer++) {
-
- if (targetPlayer == playerID || ekgs.getPlayerResults()[targetPlayer] != CoreConstants.GameResult.GAME_ONGOING)
- continue;
-
- actions.add(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
-// playNode.setValue(1);
-// playNode.findChildrenByName("ATTACK").setAction(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
- }
- break;
- case SHUFFLE:
- actions.add(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
-// playNode.setValue(1);
-// playNode.findChildrenByName("SHUFFLE").setAction(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
- case SEETHEFUTURE:
-// actions.add(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
-// playNode.setValue(1);
-// playNode.findChildrenByName("SEETHEFUTURE").setAction(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
+ case SHUFFLE, SKIP, ATTACK, SEETHEFUTURE:
+ actions.add(new PlayEKCard(type));
break;
default:
- System.out.println("No actions known for cardtype: " + card.cardType);
- }
- }
- /* todo add special combos
- // can take any card from anyone
- for (int i = 0; i < nPlayers; i++){
- if (i != activePlayer){
- Deck otherDeck = (Deck)this.areas.get(activePlayer).getComponent(playerHandHash);
- for (Card card: otherDeck.getCards()){
- core.actions.add(new TakeCard(card, i));
- }
- }
- }*/
-
- // add end turn by drawing a card
- actions.add(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
-// root.findChildrenByName("DRAW").setAction(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
- return actions;
- }
-
- private ArrayList placeKittenActions(ExplodingKittensGameState ekgs, int playerID){
- ArrayList actions = new ArrayList<>();
- Deck playerDeck = ekgs.playerHandCards.get(playerID);
- int explodingKittenCard = -1;
- for (int i = 0; i < playerDeck.getSize(); i++) {
- if (playerDeck.getComponents().get(i).cardType == ExplodingKittensCard.CardType.EXPLODING_KITTEN) {
- explodingKittenCard = i;
- break;
- }
- }
- if (explodingKittenCard != -1) {
- for (int i = 0; i <= ekgs.drawPile.getSize(); i++) {
- actions.add(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
-// if (i < N_CARDS_TO_CHECK){
-// root.findChildrenByName("DEFUSE").findChildrenByName("PUT" + i, true).setAction(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
-// }
- }
- }
- return actions;
- }
-
- private ArrayList nopeActions(ExplodingKittensGameState ekgs, int playerID){
- ArrayList actions = new ArrayList<>();
- Deck playerDeck = ekgs.playerHandCards.get(playerID);
- for (int c = 0; c < playerDeck.getSize(); c++) {
- if (playerDeck.getComponents().get(c).cardType == ExplodingKittensCard.CardType.NOPE) {
- actions.add(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
-// root.findChildrenByName("NOPE").findChildrenByName("NOPE").setAction(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
- }
- }
-// root.findChildrenByName("NOPE").findChildrenByName("PASS").setAction(new PassAction());
- actions.add(new PassAction());
- return actions;
- }
-
- private ArrayList favorActions(ExplodingKittensGameState ekgs, int playerID){
- ArrayList actions = new ArrayList<>();
- Deck playerDeck = ekgs.playerHandCards.get(playerID);
- Deck receiverDeck = ekgs.playerHandCards.get(ekgs.playerGettingAFavor);
- // todo this requires mapping the card type to indices
- for (int card = 0; card < playerDeck.getSize(); card++) {
- actions.add(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
-// root.findChildrenByName("FAVOR").findChildrenByName(playerDeck.get(card).toString()).setAction(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
- }
- if (actions.isEmpty()) // the target has no cards.
- actions.add(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), -1));
- return actions;
- }
-
- private ArrayList seeTheFutureActions(ExplodingKittensGameState ekgs, int playerID){
- ArrayList actions = new ArrayList<>();
- Deck playerDeck = ekgs.playerHandCards.get(playerID);
-
- int cardIdx = -1;
- for (int c = 0; c < playerDeck.getSize(); c++) {
- if (playerDeck.get(c).cardType == ExplodingKittensCard.CardType.SEETHEFUTURE) {
- cardIdx = c;
- break;
- }
- }
-
- if (cardIdx != -1) {
- int numberOfCards = ekgs.drawPile.getSize();
- int n = Math.min(((ExplodingKittensParameters) ekgs.getGameParameters()).nSeeFutureCards, numberOfCards);
- if (n > 0) {
-
- ArrayList permutations = new ArrayList<>();
- int[] order = new int[n];
- for (int i = 0; i < n; i++) {
- order[i] = i;
- }
- generatePermutations(n, order, permutations);
- for (int[] perm : permutations) {
- actions.add(new ChooseSeeTheFutureOrder(playerDeck.getComponentID(),
- ekgs.discardPile.getComponentID(), cardIdx, ekgs.drawPile.getComponentID(), perm));
- }
- }
- } else {
- throw new AssertionError("Player " + playerID + " does not have a SeeTheFuture card in hand.");
- }
-
- return actions;
- }
-
- @Override
- public ActionTreeNode updateActionTree(ActionTreeNode root, AbstractGameState gameState) {
- root.resetTree();
- ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
-
- int playerID = ekgs.getCurrentPlayer();
- Deck playerDeck = ekgs.playerHandCards.get(playerID);
- if (CoreConstants.DefaultGamePhase.Main.equals(ekgs.getGamePhase())) {
-
- ActionTreeNode playNode = root.findChildrenByName("PLAY");
-
- HashSet types = new HashSet<>();
- for (int c = 0; c < playerDeck.getSize(); c++) {
- ExplodingKittensCard card = playerDeck.get(c);
- if (types.contains(card.cardType)) continue;
- types.add(card.cardType);
-
- switch (card.cardType) {
- case SKIP:
- playNode.setValue(1);
- playNode.findChildrenByName("SKIP").setAction(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
- case FAVOR:
- for (int player = 0; player < ekgs.getNPlayers(); player++) {
- if (player == playerID)
- continue;
- if (ekgs.playerHandCards.get(player).getSize() > 0) {
- playNode.setValue(1);
- playNode.findChildrenByName("FAVOR").setAction(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
- }
+ // for Cat Cards we need a pair
+ for (int i = 0; i < ekgs.getNPlayers(); i++) {
+ if (i != playerID) {
+ if (ekgs.isNotTerminalForPlayer(i) && ekgs.getPlayerHand(i).getSize() > 0)
+ if (ekgs.playerHandCards.get(playerID).stream().filter(c -> c.cardType == type).count() > 1)
+ actions.add(new PlayEKCard(type, i));
}
- break;
- case ATTACK:
- for (int targetPlayer = 0; targetPlayer < ekgs.getNPlayers(); targetPlayer++) {
-
- if (targetPlayer == playerID || ekgs.getPlayerResults()[targetPlayer] != CoreConstants.GameResult.GAME_ONGOING)
- continue;
-
- playNode.setValue(1);
- playNode.findChildrenByName("ATTACK").setAction(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
- }
- break;
- case SHUFFLE:
- playNode.setValue(1);
- playNode.findChildrenByName("SHUFFLE").setAction(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
- case SEETHEFUTURE:
- // todo this action not implemented correctly
- // actions.add(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
-// playNode.setValue(1);
- // playNode.findChildrenByName("SEETHEFUTURE").setAction(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
- break;
-// default:
-// System.out.println("No actions known for cardtype: " + card.cardType);
- }
- }
-
- // add end turn by drawing a card
- root.findChildrenByName("DRAW").setAction(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
-
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse.equals(ekgs.getGamePhase())) {
- int explodingKittenCard = -1;
- for (int i = 0; i < playerDeck.getSize(); i++) {
- if (playerDeck.getComponents().get(i).cardType == ExplodingKittensCard.CardType.EXPLODING_KITTEN) {
- explodingKittenCard = i;
- break;
- }
- }
- if (explodingKittenCard != -1) {
- for (int i = 0; i <= ekgs.drawPile.getSize(); i++) {
- if (i < N_CARDS_TO_CHECK){
- root.findChildrenByName("DEFUSE").findChildrenByName("PUT" + i, true).setAction(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
}
- }
- }
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Nope.equals(ekgs.getGamePhase())) {
- for (int c = 0; c < playerDeck.getSize(); c++) {
- if (playerDeck.getComponents().get(c).cardType == ExplodingKittensCard.CardType.NOPE) {
- root.findChildrenByName("NOPE").findChildrenByName("NOPE").setAction(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
- break;
- }
}
- root.findChildrenByName("NOPE").findChildrenByName("PASS").setAction(new PassAction());
- System.out.println("got boht nope actions");
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Favor.equals(ekgs.getGamePhase())) {
- Deck receiverDeck = ekgs.playerHandCards.get(ekgs.playerGettingAFavor);
- // this requires mapping the card type to indices
- for (int card = 0; card < playerDeck.getSize(); card++) {
- root.findChildrenByName("FAVOR").findChildrenByName(playerDeck.get(card).toString()).setAction(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
- }
- } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture.equals(ekgs.getGamePhase())) {
- // todo this is not correctly implemented
}
-
- return root;
+ return actions;
}
- public ActionTreeNode initActionTree(AbstractGameState gs){
- // set up the action tree
- ActionTreeNode tree = new ActionTreeNode(0, "root");
- tree.addChild(0, "DRAW");
- ActionTreeNode play = tree.addChild(0, "PLAY");
- ActionTreeNode favor = tree.addChild(0, "FAVOR");
- ActionTreeNode nope = tree.addChild(0, "NOPE");
- ActionTreeNode defuse = tree.addChild(0, "DEFUSE");
- for (String cardType : cardTypes) {
- ActionTreeNode cardTypeNode = play.addChild(0, cardType);
- // can ask a favor from any other player
- if (cardType.equals("FAVOR")) {
- for (int j = 0; j < GameType.ExplodingKittens.getMaxPlayers(); j++) {
- cardTypeNode.addChild(0, "PLAYER" + j);
- }
- }
- favor.addChild(0, cardType);
- }
- // if player has a nope card it can decide if it wants to play it or not
- nope.addChild(0, "NOPE");
- nope.addChild(0, "PASS");
-
- // allow to put the back the defuse card to these positions
- for (int i =0; i < N_CARDS_TO_CHECK; i++){
- defuse.addChild(0, "PUT"+i);
-
- }
- return tree;
- }
}
diff --git a/src/main/java/games/explodingkittens/ExplodingKittensGameState.java b/src/main/java/games/explodingkittens/ExplodingKittensGameState.java
index 5faf9857e..d010f28c4 100644
--- a/src/main/java/games/explodingkittens/ExplodingKittensGameState.java
+++ b/src/main/java/games/explodingkittens/ExplodingKittensGameState.java
@@ -1,23 +1,20 @@
package games.explodingkittens;
-import core.AbstractGameStateWithTurnOrder;
+import core.AbstractGameState;
import core.AbstractParameters;
import core.CoreConstants;
-import core.actions.AbstractAction;
import core.components.Component;
import core.components.Deck;
import core.components.PartialObservableDeck;
-import core.interfaces.IGamePhase;
-import core.interfaces.IPrintable;
-import core.turnorders.TurnOrder;
import games.GameType;
import games.explodingkittens.cards.ExplodingKittensCard;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
-import static core.CoreConstants.VisibilityMode;
-
-public class ExplodingKittensGameState extends AbstractGameStateWithTurnOrder implements IPrintable {
+public class ExplodingKittensGameState extends AbstractGameState {
// Cards in each player's hand, index corresponds to player ID
List> playerHandCards;
@@ -25,19 +22,15 @@ public class ExplodingKittensGameState extends AbstractGameStateWithTurnOrder im
PartialObservableDeck drawPile;
// Cards in the discard pile
Deck discardPile;
- // Player ID of the player currently getting a favor
- int playerGettingAFavor;
- // Current stack of actions
- Stack actionStack;
+ Deck inPlay;
+ int currentPlayerTurnsLeft = 0;
+ int nextAttackLevel = 0;
+ boolean skip = false;
+
int[] orderOfPlayerDeath;
+
public ExplodingKittensGameState(AbstractParameters gameParameters, int nPlayers) {
super(gameParameters, nPlayers);
- playerGettingAFavor = -1;
- playerHandCards = new ArrayList<>();
- }
- @Override
- protected TurnOrder _createTurnOrder(int nPlayers) {
- return new ExplodingKittensTurnOrder(nPlayers);
}
@Override
@@ -47,22 +40,30 @@ protected GameType _getGameType() {
@Override
protected List _getAllComponents() {
- return new ArrayList() {{
- add(drawPile);
- add(discardPile);
- addAll(playerHandCards);
- }};
+ List ret = new ArrayList<>();
+ ret.add(drawPile);
+ ret.add(discardPile);
+ ret.addAll(playerHandCards);
+ return ret;
+ }
+
+ // when a card is played to the table, but before
+ public void setInPlay(ExplodingKittensCard.CardType cardType, int playerID) {
+ ExplodingKittensCard card = playerHandCards.get(playerID).stream()
+ .filter(c -> c.cardType == cardType)
+ .findFirst()
+ .orElseThrow(() -> new AssertionError("Player " + playerID + " does not have card " + cardType + " to play"));
+ inPlay.add(card);
+ playerHandCards.get(playerID).remove(card);
}
@Override
- protected AbstractGameStateWithTurnOrder __copy(int playerId) {
+ protected ExplodingKittensGameState _copy(int playerId) {
ExplodingKittensGameState ekgs = new ExplodingKittensGameState(gameParameters.copy(), getNPlayers());
ekgs.discardPile = discardPile.copy();
- ekgs.playerGettingAFavor = playerGettingAFavor;
- ekgs.actionStack = new Stack<>();
- for (AbstractAction a : actionStack) {
- ekgs.actionStack.add(a.copy());
- }
+ ekgs.currentPlayerTurnsLeft = currentPlayerTurnsLeft;
+ ekgs.nextAttackLevel = nextAttackLevel;
+ ekgs.inPlay = inPlay.copy();
ekgs.orderOfPlayerDeath = orderOfPlayerDeath.clone();
ekgs.playerHandCards = new ArrayList<>();
for (PartialObservableDeck d : playerHandCards) {
@@ -92,7 +93,7 @@ protected AbstractGameStateWithTurnOrder __copy(int playerId) {
// Shuffles only hidden cards in draw pile, if player knows what's on top those will stay in place
ekgs.drawPile.redeterminiseUnknown(redeterminisationRnd, playerId);
- Deck explosive = new Deck<>("tmp", VisibilityMode.HIDDEN_TO_ALL);
+ Deck explosive = new Deck<>("tmp", CoreConstants.VisibilityMode.HIDDEN_TO_ALL);
for (int i = 0; i < getNPlayers(); i++) {
if (i != playerId) {
for (int j = 0; j < playerHandCards.get(i).getSize(); j++) {
@@ -122,19 +123,28 @@ protected AbstractGameStateWithTurnOrder __copy(int playerId) {
return ekgs;
}
+ public void setNextAttackLevel(int value) {
+ nextAttackLevel = value;
+ }
+
+ public int getCurrentPlayerTurnsLeft() {
+ return currentPlayerTurnsLeft;
+ }
+ public void setCurrentPlayerTurnsLeft(int value) {
+ currentPlayerTurnsLeft = value;
+ }
+
+ public boolean skipNext() {
+ return skip;
+ }
+ public void setSkip(boolean skip) {
+ this.skip = skip;
+ }
@Override
protected double _getHeuristicScore(int playerId) {
return new ExplodingKittensHeuristic().evaluateState(this, playerId);
}
- /**
- * This provides the current score in game turns. This will only be relevant for games that have the concept
- * of victory points, etc.
- * If a game does not support this directly, then just return 0.0
- *
- * @param playerId
- * @return - double, score of current state
- */
@Override
public double getGameScore(int playerId) {
if (playerResults[playerId] == CoreConstants.GameResult.LOSE_GAME)
@@ -144,147 +154,37 @@ public double getGameScore(int playerId) {
return Arrays.stream(playerResults).filter(status -> status == CoreConstants.GameResult.LOSE_GAME).count() + 1;
}
- @Override
- public int getOrdinalPosition(int playerId) {
- if (playerResults[playerId] == CoreConstants.GameResult.WIN_GAME)
- return 1;
- if (playerResults[playerId] == CoreConstants.GameResult.LOSE_GAME)
- return getNPlayers() - orderOfPlayerDeath[playerId] + 1;
- return 1; // anyone still alive is jointly winning
- }
-
- @Override
- protected boolean _equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ExplodingKittensGameState)) return false;
- if (!super.equals(o)) return false;
- ExplodingKittensGameState gameState = (ExplodingKittensGameState) o;
- return playerGettingAFavor == gameState.playerGettingAFavor &&
- Objects.equals(playerHandCards, gameState.playerHandCards) &&
- Objects.equals(drawPile, gameState.drawPile) &&
- Objects.equals(discardPile, gameState.discardPile) &&
- Objects.equals(actionStack, gameState.actionStack);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), playerHandCards, drawPile, discardPile, playerGettingAFavor, actionStack);
- }
-
- /**
- * Marks a player as dead.
- *
- * @param playerID - player who was killed in a kitten explosion.
- */
- public void killPlayer(int playerID) {
- setPlayerResult(CoreConstants.GameResult.LOSE_GAME, playerID);
- int nPlayersActive = 0;
- for (int i = 0; i < getNPlayers(); i++) {
- if (playerResults[i] == CoreConstants.GameResult.GAME_ONGOING) nPlayersActive++;
- }
- orderOfPlayerDeath[playerID] = getNPlayers() - nPlayersActive;
- if (nPlayersActive == 1) {
- this.gameStatus = CoreConstants.GameResult.GAME_END;
- for (int i = 0; i < getNPlayers(); i++) {
- // set winner
- if (playerResults[i] == CoreConstants.GameResult.GAME_ONGOING) {
- playerResults[i] = CoreConstants.GameResult.WIN_GAME;
- }
- }
- turnOrder.endGame(this);
- }
-
- ((ExplodingKittensTurnOrder) getTurnOrder()).endPlayerTurnStep(this);
-
- }
-
- // Getters, setters
- public int getPlayerGettingAFavor() {
- return playerGettingAFavor;
- }
- public void setPlayerGettingAFavor(int playerGettingAFavor) {
- this.playerGettingAFavor = playerGettingAFavor;
+ public PartialObservableDeck getPlayerHand(int playerId) {
+ return playerHandCards.get(playerId);
}
-
public PartialObservableDeck getDrawPile() {
return drawPile;
}
-
- protected void setDrawPile(PartialObservableDeck drawPile) {
- this.drawPile = drawPile;
- }
-
public Deck getDiscardPile() {
return discardPile;
}
-
- // Protected, only accessible in this package and subclasses
- protected void setDiscardPile(Deck discardPile) {
- this.discardPile = discardPile;
- }
-
- public Stack getActionStack() {
- return actionStack;
- }
-
- protected void setActionStack(Stack actionStack) {
- this.actionStack = actionStack;
- }
-
- public List> getPlayerHandCards() {
- return playerHandCards;
- }
-
- protected void setPlayerHandCards(List> playerHandCards) {
- this.playerHandCards = playerHandCards;
+ public Deck getInPlay() {
+ return inPlay;
}
- public void printToConsole() {
- System.out.println(toString());
+ @Override
+ protected boolean _equals(Object o) {
+ if (o instanceof ExplodingKittensGameState other) {
+ return discardPile.equals(other.discardPile) &&
+ Arrays.equals(orderOfPlayerDeath, other.orderOfPlayerDeath) &&
+ playerHandCards.equals(other.playerHandCards) &&
+ currentPlayerTurnsLeft == other.currentPlayerTurnsLeft &&
+ nextAttackLevel == other.nextAttackLevel &&
+ inPlay.equals(other.inPlay) &&
+ drawPile.equals(other.drawPile);
+ };
+ return false;
}
-
- // Printing functions for the game state and decks.
-
@Override
- public String toString() {
- String s = "============================\n";
-
- int currentPlayer = turnOrder.getCurrentPlayer(this);
-
- for (int i = 0; i < getNPlayers(); i++) {
- if (currentPlayer == i)
- s += ">>> Player " + i + ":";
- else
- s += "Player " + i + ":";
- s += playerHandCards.get(i).toString() + "\n";
- }
-
- s += "\nDrawPile: ";
- s += drawPile.toString() + "\n";
-
- s += "DiscardPile: ";
- s += discardPile.toString() + "\n";
-
- s += "Action stack: ";
- for (AbstractAction a : actionStack) {
- s += a.toString() + ",";
- }
- s = s.substring(0, s.length() - 1);
- s += "\n\n";
-
- s += "Current GamePhase: " + gamePhase + "\n";
- s += "Missing Draws: " + ((ExplodingKittensTurnOrder) turnOrder).requiredDraws + "\n";
- s += "============================\n";
- return s;
+ public int hashCode() {
+ return Objects.hash(discardPile, playerHandCards, drawPile, inPlay, nextAttackLevel, currentPlayerTurnsLeft) + 31 * Arrays.hashCode(orderOfPlayerDeath);
}
- // Exploding kittens adds 4 phases on top of default ones.
- public enum ExplodingKittensGamePhase implements IGamePhase {
- Nope,
- Defuse,
- Favor,
- SeeTheFuture
- }
}
diff --git a/src/main/java/games/explodingkittens/ExplodingKittensHeuristic.java b/src/main/java/games/explodingkittens/ExplodingKittensHeuristic.java
index 180d69ba6..9054e7df2 100644
--- a/src/main/java/games/explodingkittens/ExplodingKittensHeuristic.java
+++ b/src/main/java/games/explodingkittens/ExplodingKittensHeuristic.java
@@ -4,7 +4,6 @@
import core.CoreConstants;
import core.interfaces.IStateHeuristic;
import evaluation.optimisation.TunableParameters;
-import games.explodingkittens.actions.IsNopeable;
import games.explodingkittens.cards.ExplodingKittensCard;
public class ExplodingKittensHeuristic extends TunableParameters implements IStateHeuristic {
@@ -62,7 +61,6 @@ public double evaluateState(AbstractGameState gs, int playerId) {
return cardValues / (ekgs.playerHandCards.get(playerId).getSize() + 1);
}
- // TODO: check state more
double getCardValue(ExplodingKittensGameState ekgs, ExplodingKittensCard card) {
switch (card.cardType) {
case EXPLODING_KITTEN:
@@ -70,7 +68,7 @@ public double evaluateState(AbstractGameState gs, int playerId) {
case DEFUSE:
return defuseValue;
case NOPE:
- if (ekgs.actionStack.size() > 0 && ekgs.actionStack.get(0) instanceof IsNopeable) {
+ if (ekgs.isActionInProgress()) {
return nopeValue;
} else return 0; // Neutral
case ATTACK:
@@ -82,49 +80,22 @@ public double evaluateState(AbstractGameState gs, int playerId) {
case SHUFFLE:
return shuffleValue;
case SEETHEFUTURE:
- return seeFutureValue; // TODO: higher if future not already known, otherwise low
+ return seeFutureValue;
default:
return regularValue;
}
}
- /**
- * Return a copy of this game parameters object, with the same parameters as in the original.
- *
- * @return - new game parameters object.
- */
@Override
protected ExplodingKittensHeuristic _copy() {
- ExplodingKittensHeuristic retValue = new ExplodingKittensHeuristic();
- retValue.explodingValue = explodingValue;
- retValue.defuseValue = defuseValue;
- retValue.regularValue = regularValue;
- retValue.seeFutureValue = seeFutureValue;
- retValue.nopeValue = nopeValue;
- retValue.attackValue = attackValue;
- retValue.skipValue = skipValue;
- retValue.favorValue = favorValue;
- retValue.shuffleValue = shuffleValue;
- return retValue;
+ return new ExplodingKittensHeuristic();
+ // copying of parameterisable values is done in the super class
}
- /**
- * Checks if the given object is the same as the current.
- *
- * @param o - other object to test equals for.
- * @return true if the two objects are equal, false otherwise
- */
@Override
protected boolean _equals(Object o) {
- if (o instanceof ExplodingKittensHeuristic) {
- ExplodingKittensHeuristic other = (ExplodingKittensHeuristic) o;
- return other.explodingValue == explodingValue && other.defuseValue == defuseValue &&
- other.regularValue == regularValue && other.seeFutureValue == seeFutureValue &&
- other.nopeValue == nopeValue && other.attackValue == attackValue &&
- other.skipValue == skipValue && other.favorValue == favorValue &&
- other.shuffleValue == shuffleValue;
- }
- return false;
+ return o instanceof ExplodingKittensHeuristic;
+ // checking of parameterisable values is done in the super class
}
/**
@@ -133,7 +104,7 @@ protected boolean _equals(Object o) {
*/
@Override
public ExplodingKittensHeuristic instantiate() {
- return this._copy();
+ return (ExplodingKittensHeuristic) this.copy();
}
diff --git a/src/main/java/games/explodingkittens/ExplodingKittensParameters.java b/src/main/java/games/explodingkittens/ExplodingKittensParameters.java
index 59895cb9a..eef76f8ed 100644
--- a/src/main/java/games/explodingkittens/ExplodingKittensParameters.java
+++ b/src/main/java/games/explodingkittens/ExplodingKittensParameters.java
@@ -6,15 +6,13 @@
import games.GameType;
import games.explodingkittens.cards.ExplodingKittensCard;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Objects;
+import java.util.*;
public class ExplodingKittensParameters extends TunableParameters {
String dataPath = "data/explodingkittens/";
- HashMap cardCounts = new HashMap() {{
+ Map cardCounts = new HashMap<>() {{
put(ExplodingKittensCard.CardType.ATTACK, 4);
put(ExplodingKittensCard.CardType.SKIP, 4);
put(ExplodingKittensCard.CardType.FAVOR, 4);
@@ -43,6 +41,7 @@ public ExplodingKittensParameters() {
if (c == ExplodingKittensCard.CardType.EXPLODING_KITTEN) addTunableParameter(c.name() + " count", -1);
else addTunableParameter(c.name() + " count", cardCounts.get(c), Arrays.asList(1,2,3,4,5));
}
+ addTunableParameter("dataPath", "data/explodingkittens/");
_reset();
}
@@ -53,6 +52,7 @@ public void _reset() {
nSeeFutureCards = (int) getParameterValue("nSeeFutureCards");
nopeOwnCards = (boolean) getParameterValue("nopeOwnCards");
cardCounts.replaceAll((c, v) -> (Integer) getParameterValue(c.name() + " count"));
+ dataPath = (String) getParameterValue("dataPath");
}
public String getDataPath() {
@@ -61,25 +61,14 @@ public String getDataPath() {
@Override
protected AbstractParameters _copy() {
- return new ExplodingKittensParameters();
+ ExplodingKittensParameters ekp = new ExplodingKittensParameters();
+ ekp.cardCounts = new HashMap<>(cardCounts);
+ return ekp;
}
@Override
protected boolean _equals(Object o) {
- if (this == o) return true;
- if (!(o instanceof ExplodingKittensParameters)) return false;
- if (!super.equals(o)) return false;
- ExplodingKittensParameters that = (ExplodingKittensParameters) o;
- return nCardsPerPlayer == that.nCardsPerPlayer &&
- nDefuseCards == that.nDefuseCards &&
- nSeeFutureCards == that.nSeeFutureCards &&
- Objects.equals(dataPath, that.dataPath) &&
- Objects.equals(cardCounts, that.cardCounts);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), dataPath, cardCounts, nCardsPerPlayer, nDefuseCards, nSeeFutureCards);
+ return o instanceof ExplodingKittensParameters;
}
@Override
diff --git a/src/main/java/games/explodingkittens/actions/ChoiceOfCardToGive.java b/src/main/java/games/explodingkittens/actions/ChoiceOfCardToGive.java
new file mode 100644
index 000000000..35f7c9f3a
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/ChoiceOfCardToGive.java
@@ -0,0 +1,67 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import core.interfaces.IExtendedSequence;
+import games.explodingkittens.ExplodingKittensGameState;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ChoiceOfCardToGive implements IExtendedSequence {
+
+ public final int giver;
+ public final int recipient;
+ public boolean executed;
+
+ public ChoiceOfCardToGive(int giver, int recipient) {
+ this.giver = giver;
+ this.recipient = recipient;
+ }
+
+ @Override
+ public List _computeAvailableActions(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ // consider all cards in hand
+ List actions = state.getPlayerHand(giver).stream()
+ .map(c -> new GiveCard(giver, recipient, c.cardType))
+ .toList();
+ return actions.stream().distinct().collect(Collectors.toList());
+ }
+
+ @Override
+ public int getCurrentPlayer(AbstractGameState state) {
+ return giver;
+ }
+
+ @Override
+ public void _afterAction(AbstractGameState state, AbstractAction action) {
+ if (action instanceof GiveCard)
+ executed = true;
+ }
+
+ @Override
+ public boolean executionComplete(AbstractGameState state) {
+ return executed;
+ }
+
+ @Override
+ public ChoiceOfCardToGive copy() {
+ ChoiceOfCardToGive retValue = new ChoiceOfCardToGive(giver, recipient);
+ retValue.executed = executed;
+ return retValue;
+ }
+
+ @Override
+ public String toString() {
+ return "Choice of card to give";
+ }
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ChoiceOfCardToGive cc && cc.giver == giver && cc.recipient == recipient && cc.executed == executed;
+ }
+ @Override
+ public int hashCode() {
+ return giver * 31 + recipient * 31 * 31 + (executed ? 1 : 0) + 2901;
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/DefuseKitten.java b/src/main/java/games/explodingkittens/actions/DefuseKitten.java
new file mode 100644
index 000000000..a03d3ece5
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/DefuseKitten.java
@@ -0,0 +1,65 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import core.interfaces.IExtendedSequence;
+import games.explodingkittens.ExplodingKittensGameState;
+
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static java.util.stream.Collectors.toList;
+
+public class DefuseKitten implements IExtendedSequence {
+
+ boolean executed;
+ final int player;
+
+
+ public DefuseKitten(int player) {
+ this.player = player;
+ }
+
+ @Override
+ public List _computeAvailableActions(AbstractGameState state) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) state;
+ return IntStream.rangeClosed(0, ekgs.getDrawPile().getSize()).mapToObj(PlaceKitten::new).collect(toList());
+ }
+
+ @Override
+ public int getCurrentPlayer(AbstractGameState state) {
+ return player;
+ }
+
+ @Override
+ public void _afterAction(AbstractGameState state, AbstractAction action) {
+ if (action instanceof PlaceKitten)
+ executed = true;
+ }
+
+ @Override
+ public boolean executionComplete(AbstractGameState state) {
+ return executed;
+ }
+
+ @Override
+ public DefuseKitten copy() {
+ DefuseKitten retValue = new DefuseKitten(player);
+ retValue.executed = executed;
+ return retValue;
+ }
+
+ @Override
+ public String toString() {
+ return "Defuse Kitten";
+ }
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof DefuseKitten dk && dk.player == player && dk.executed == executed;
+ }
+
+ @Override
+ public int hashCode() {
+ return 9981 - player * 63 + (executed ? 1 : 0);
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/GiveCard.java b/src/main/java/games/explodingkittens/actions/GiveCard.java
new file mode 100644
index 000000000..71a8e4635
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/GiveCard.java
@@ -0,0 +1,66 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import games.explodingkittens.*;
+import games.explodingkittens.cards.ExplodingKittensCard;
+
+import java.util.Objects;
+
+public class GiveCard extends AbstractAction {
+
+ public int giver;
+ public int recipient;
+ public ExplodingKittensCard.CardType cardType;
+
+ public GiveCard(int giver, int recipient, ExplodingKittensCard.CardType cardType) {
+ this.giver = giver;
+ this.cardType = cardType;
+ this.recipient = recipient;
+ }
+
+ @Override
+ public boolean execute(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ ExplodingKittensCard card = state.getPlayerHand(giver).stream()
+ .filter(c -> c.cardType == cardType).findFirst()
+ .orElseThrow(() -> new AssertionError("Card not found : " + cardType));
+ state.getPlayerHand(giver).remove(card);
+ state.getPlayerHand(recipient).add(card);
+ return true;
+ }
+
+ @Override
+ public GiveCard copy() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof GiveCard gc) {
+ return gc.giver == giver && gc.recipient == recipient && gc.cardType == cardType;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(giver, recipient, cardType.ordinal());
+ }
+
+ @Override
+ public String toString() {
+ return "Give card " + cardType + " from player " + giver + " to player " + recipient;
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState, int perspectiveID) {
+ if (perspectiveID == giver || perspectiveID == recipient) return toString();
+ return getString(gameState);
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState) {
+ return "Player " + giver + " gives a card to player " + recipient;
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/Nope.java b/src/main/java/games/explodingkittens/actions/Nope.java
new file mode 100644
index 000000000..8eefd2b7f
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/Nope.java
@@ -0,0 +1,41 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import games.explodingkittens.ExplodingKittensGameState;
+import games.explodingkittens.cards.ExplodingKittensCard;
+
+public class Nope extends AbstractAction {
+
+ @Override
+ public boolean execute(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ state.setInPlay(ExplodingKittensCard.CardType.NOPE, state.getCurrentPlayer());
+ return true;
+ }
+
+ @Override
+ public AbstractAction copy() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof Nope;
+ }
+
+ @Override
+ public int hashCode() {
+ return -42943;
+ }
+
+ @Override
+ public String toString() {
+ return "Nope";
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState) {
+ return toString();
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/NopeableAction.java b/src/main/java/games/explodingkittens/actions/NopeableAction.java
new file mode 100644
index 000000000..0b3540769
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/NopeableAction.java
@@ -0,0 +1,119 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import core.components.Deck;
+import core.interfaces.IExtendedSequence;
+import games.explodingkittens.ExplodingKittensGameState;
+import games.explodingkittens.cards.ExplodingKittensCard;
+
+import java.util.List;
+import java.util.Objects;
+
+import static games.explodingkittens.cards.ExplodingKittensCard.CardType.NOPE;
+
+
+public class NopeableAction implements IExtendedSequence {
+
+ int currentInterrupter = -1;
+ int lastCardPlayedBy;
+ PlayEKCard originalAction;
+ int nopes = 0;
+
+ public NopeableAction(int player, PlayEKCard action, ExplodingKittensGameState state) {
+ this.lastCardPlayedBy = player;
+ this.originalAction = action.copy();
+ setNextInterrupter(state);
+ }
+
+
+ // private constructor for copying
+ private NopeableAction(int lastCardPlayedBy, PlayEKCard originalAction, int currentInterrupter, int nopes) {
+ this.lastCardPlayedBy = lastCardPlayedBy;
+ this.originalAction = originalAction.copy();
+ this.currentInterrupter = currentInterrupter;
+ this.nopes = nopes;
+ }
+
+ @Override
+ public List _computeAvailableActions(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ Deck hand = state.getPlayerHand(currentInterrupter);
+ if (hand.stream().anyMatch(c -> c.cardType == NOPE)) {
+ return List.of(new Pass(), new Nope());
+ }
+ throw new AssertionError(currentInterrupter + " does not have a NOPE card to play");
+ }
+
+
+ private void setNextInterrupter(ExplodingKittensGameState state) {
+ if (currentInterrupter == -1) { // initialisation
+ currentInterrupter = (lastCardPlayedBy + 1) % state.getNPlayers();
+ } else if (currentInterrupter != lastCardPlayedBy) {
+ currentInterrupter = (currentInterrupter + 1) % state.getNPlayers();
+ }
+ // Now we see if anyone has a Nope card
+ while (currentInterrupter != lastCardPlayedBy) {
+ if (state.isNotTerminalForPlayer(currentInterrupter)) {
+ Deck hand = state.getPlayerHand(currentInterrupter);
+ boolean hasNope = hand.stream().anyMatch(c -> c.cardType == NOPE);
+ if (hasNope) break;
+ }
+ currentInterrupter = (currentInterrupter + 1) % state.getNPlayers();
+ }
+ }
+
+ @Override
+ public int getCurrentPlayer(AbstractGameState state) {
+ return currentInterrupter;
+ }
+
+ @Override
+ public void _afterAction(AbstractGameState gs, AbstractAction action) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ if (action instanceof Nope) {
+ nopes++;
+ // this resets the count
+ lastCardPlayedBy = currentInterrupter;
+ currentInterrupter = -1;
+ setNextInterrupter(state);
+ }
+ if (action instanceof Pass) {
+ setNextInterrupter(state);
+ }
+ if (executionComplete(state)) {
+ // we have gone round the table
+ if (nopes % 2 == 0) {
+ // no one noped the action; execute the action
+ originalAction.cardType.execute(state, originalAction.target);
+ }
+ state.getInPlay().forEach(c -> state.getDiscardPile().add(c));
+ state.getInPlay().clear();
+ }
+ }
+
+ @Override
+ public boolean executionComplete(AbstractGameState state) {
+ return currentInterrupter == lastCardPlayedBy; // we have gone round the table
+ }
+
+ @Override
+ public IExtendedSequence copy() {
+ return new NopeableAction(lastCardPlayedBy, originalAction, currentInterrupter, nopes);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+ if (!(obj instanceof NopeableAction other)) return false;
+ return lastCardPlayedBy == other.lastCardPlayedBy &&
+ currentInterrupter == other.currentInterrupter &&
+ nopes == other.nopes &&
+ originalAction.equals(other.originalAction);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(lastCardPlayedBy, currentInterrupter, nopes, originalAction);
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/Pass.java b/src/main/java/games/explodingkittens/actions/Pass.java
new file mode 100644
index 000000000..a92653920
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/Pass.java
@@ -0,0 +1,15 @@
+package games.explodingkittens.actions;
+
+import core.actions.DoNothing;
+
+public class Pass extends DoNothing {
+ @Override
+ public String toString() {
+ return "Player passes";
+ }
+
+ @Override
+ public Pass copy() {
+ return this;
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/PlaceKitten.java b/src/main/java/games/explodingkittens/actions/PlaceKitten.java
new file mode 100644
index 000000000..573fa97eb
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/PlaceKitten.java
@@ -0,0 +1,60 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import games.explodingkittens.ExplodingKittensGameState;
+import games.explodingkittens.cards.ExplodingKittensCard;
+
+public class PlaceKitten extends AbstractAction {
+
+ public final int index;
+
+ public PlaceKitten(int atIndex) {
+ this.index = atIndex;
+ }
+
+ @Override
+ public boolean execute(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ int player = state.getCurrentPlayer();
+ ExplodingKittensCard card = state.getPlayerHand(player).stream()
+ .filter(c -> c.cardType == ExplodingKittensCard.CardType.EXPLODING_KITTEN).findFirst()
+ .orElseThrow(() -> new AssertionError("No Exploding Kitten found"));
+ state.getPlayerHand(player).remove(card);
+ boolean[] visibility = new boolean[state.getNPlayers()];
+ visibility[state.getCurrentPlayer()] = true;
+ state.getDrawPile().add(card, index, visibility);
+ state.setSkip(true);
+ return true;
+ }
+
+ @Override
+ public AbstractAction copy() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof PlaceKitten pk && pk.index == index;
+ }
+
+ @Override
+ public int hashCode() {
+ return 3805093 + index;
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState, int perspectivePlayer) {
+ return perspectivePlayer == gameState.getCurrentPlayer() ? toString() : getString(gameState);
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState) {
+ return "Places Exploding Kitten in deck somewhere";
+ }
+
+ @Override
+ public String toString() {
+ return "Place Exploding Kitten at index " + index;
+ }
+}
diff --git a/src/main/java/games/explodingkittens/actions/PlayEKCard.java b/src/main/java/games/explodingkittens/actions/PlayEKCard.java
new file mode 100644
index 000000000..323fa08be
--- /dev/null
+++ b/src/main/java/games/explodingkittens/actions/PlayEKCard.java
@@ -0,0 +1,68 @@
+package games.explodingkittens.actions;
+
+import core.AbstractGameState;
+import core.actions.AbstractAction;
+import games.explodingkittens.ExplodingKittensGameState;
+import games.explodingkittens.cards.ExplodingKittensCard.CardType;
+
+import java.util.Objects;
+
+public class PlayEKCard extends AbstractAction {
+
+ public final CardType cardType;
+ public final int target;
+
+ public PlayEKCard(CardType cardType) {
+ this.cardType = cardType;
+ target = -1;
+ }
+
+ public PlayEKCard(CardType cardType, int target) {
+ this.cardType = cardType;
+ this.target = target;
+ }
+
+ @Override
+ public boolean execute(AbstractGameState gs) {
+ ExplodingKittensGameState state = (ExplodingKittensGameState) gs;
+ state.setInPlay(cardType, state.getCurrentPlayer());
+ if (cardType.catCard)
+ state.setInPlay(cardType, state.getCurrentPlayer()); // add an extra one
+
+ if (cardType.nopeable) {
+ state.setActionInProgress(new NopeableAction(state.getCurrentPlayer(), this, state));
+ } else {
+ // if not Nopeable, execute the card
+ cardType.execute(state, target);
+ }
+
+ return true;
+ }
+
+ @Override
+ public PlayEKCard copy() {
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof PlayEKCard pek && pek.cardType == cardType && pek.target == target;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cardType, target) + 2901;
+ }
+
+ @Override
+ public String toString() {
+ if (target == -1)
+ return "Play " + cardType;
+ return "Play " + cardType + " on player " + target;
+ }
+
+ @Override
+ public String getString(AbstractGameState gameState) {
+ return toString();
+ }
+}
diff --git a/src/main/java/games/explodingkittens/cards/ExplodingKittensCard.java b/src/main/java/games/explodingkittens/cards/ExplodingKittensCard.java
index 943fa0e29..05724394f 100644
--- a/src/main/java/games/explodingkittens/cards/ExplodingKittensCard.java
+++ b/src/main/java/games/explodingkittens/cards/ExplodingKittensCard.java
@@ -1,22 +1,89 @@
package games.explodingkittens.cards;
import core.components.Card;
+import games.explodingkittens.ExplodingKittensGameState;
+import games.explodingkittens.actions.ChoiceOfCardToGive;
+import org.apache.spark.sql.catalyst.plans.logical.BinaryCommand;
+
+import java.util.function.BiConsumer;
+
public class ExplodingKittensCard extends Card {
+
+ public static int SEE_THE_FUTURE_CARDS = 3;
+ public static int ADDITIONAL_TURNS = 2;
+
public enum CardType {
- EXPLODING_KITTEN,
- DEFUSE,
- NOPE,
- ATTACK,
- SKIP,
- FAVOR,
- SHUFFLE,
- SEETHEFUTURE,
- TACOCAT,
- MELONCAT,
- FURRYCAT,
- BEARDCAT,
- RAINBOWCAT,
+ EXPLODING_KITTEN(false),
+ DEFUSE(false),
+ NOPE(false),
+ ATTACK(true, (state, target) -> {
+ int attackLevel = ADDITIONAL_TURNS;
+ state.setSkip(true);
+ if (state.getCurrentPlayerTurnsLeft() > 1) {
+ attackLevel += state.getCurrentPlayerTurnsLeft();
+ state.setCurrentPlayerTurnsLeft(1);
+ }
+ state.setNextAttackLevel(attackLevel);
+ }),
+ SKIP(true, (state, target) -> {
+ state.setSkip(true);
+ }),
+ FAVOR(true, (state, target) -> {
+ int cards = state.getPlayerHand(target).getSize();
+ if (cards > 0) { // edge cases make zero cards possible
+ state.setActionInProgress(new ChoiceOfCardToGive(target, state.getCurrentPlayer()));
+ }
+ }),
+ SHUFFLE(true, (state, target) -> {
+ state.getDrawPile().shuffle(state.getRnd());
+ }),
+ SEETHEFUTURE(true, (state, target) -> {
+ // we set the visibility of the top 3 cards of the draw pile to the current player
+ int cardsToSee = Math.min(SEE_THE_FUTURE_CARDS, state.getDrawPile().getSize());
+ for (int i = 0; i < cardsToSee; i++) {
+ state.getDrawPile().setVisibilityOfComponent(i, state.getCurrentPlayer(), true);
+ }
+ }),
+ TACOCAT(true, true),
+ MELONCAT(true, true),
+ FURRYCAT(true, true),
+ BEARDCAT(true, true),
+ RAINBOWCAT(true, true);
+
+ public final boolean nopeable;
+ public final boolean catCard;
+ private final BiConsumer lambda;
+
+ CardType(boolean nope) {
+ this(nope, (state, target) -> {
+ throw new AssertionError("Card should not be executed directly");
+ });
+ }
+
+ CardType (boolean nope, boolean catCard) {
+ this.nopeable = nope;
+ this.catCard = catCard;
+ // lambda for all cat cards; need to play a pair to take a random card from the target
+ this.lambda = (state, target) -> {
+ if (state.getPlayerHand(target).getSize() > 0) {
+ int index = state.getRnd().nextInt(state.getPlayerHand(target).getSize());
+ ExplodingKittensCard card = state.getPlayerHand(target).get(index);
+ state.getPlayerHand(target).remove(card);
+ state.getPlayerHand(state.getCurrentPlayer()).add(card);
+ }
+ };
+ }
+
+ CardType(boolean nope, BiConsumer lambda) {
+ this.nopeable = nope;
+ this.catCard = false;
+ this.lambda = lambda;
+ }
+
+ public void execute(ExplodingKittensGameState state, int target) {
+ lambda.accept(state, target);
+ }
}
public CardType cardType;
@@ -33,7 +100,7 @@ public ExplodingKittensCard(CardType cardType, int ID) {
@Override
public Card copy() {
- return new ExplodingKittensCard(cardType, componentID);
+ return this; // immutable
}
@Override
diff --git a/src/main/java/games/explodingkittens/gui/ExplodingKittensDiscardView.java b/src/main/java/games/explodingkittens/gui/ExplodingKittensDiscardView.java
index 486e8f861..2388b1f5b 100644
--- a/src/main/java/games/explodingkittens/gui/ExplodingKittensDiscardView.java
+++ b/src/main/java/games/explodingkittens/gui/ExplodingKittensDiscardView.java
@@ -1,23 +1,19 @@
package games.explodingkittens.gui;
-import core.actions.AbstractAction;
import core.components.Component;
import core.components.Deck;
-import games.explodingkittens.actions.*;
-import games.explodingkittens.actions.reactions.ChooseSeeTheFutureOrder;
+import core.interfaces.IExtendedSequence;
import games.explodingkittens.cards.ExplodingKittensCard;
import java.awt.*;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Stack;
public class ExplodingKittensDiscardView extends ExplodingKittensDeckView {
// Images for Action Stack
ArrayList catImagesBkup;
- // This view adds action stack as cards in the discard pile for display
- Stack actionStack;
-
/**
* Constructor initialising information and adding key/mouse listener for card highlight (left click or ALT + hover
* allows showing the highlighted card on top of all others).
@@ -25,9 +21,8 @@ public class ExplodingKittensDiscardView extends ExplodingKittensDeckView {
* @param visible - true if whole deck visible
* @param dataPath - path to assets
*/
- public ExplodingKittensDiscardView(Deck d, Stack actionStack, boolean visible, String dataPath) {
+ public ExplodingKittensDiscardView(Deck d, boolean visible, String dataPath) {
super(-1, d, visible, dataPath);
- this.actionStack = actionStack;
catImagesBkup = new ArrayList<>();
catImagesBkup.addAll(catImages);
}
@@ -40,13 +35,6 @@ public void drawDeck(Graphics2D g) {
Component oldComponent = component;
@SuppressWarnings("unchecked") Deck deckCopy = ((Deck) component).copy();
- // Add cards played from action stack into the copy of the deck
- for (AbstractAction aa: actionStack) {
- ExplodingKittensCard card = getStackCard(aa);
- if (card != null) {
- deckCopy.add(card);
- }
- }
// set thie copy to tbe the component we draw
component = deckCopy;
// and draw it
@@ -55,24 +43,5 @@ public void drawDeck(Graphics2D g) {
component = oldComponent;
}
- /**
- * Turns an action into a card
- * @param aa - action to turn to card
- * @return - Exploding kittens card
- */
- private ExplodingKittensCard getStackCard(AbstractAction aa) {
- if (aa instanceof AttackAction) {
- return new ExplodingKittensCard(ExplodingKittensCard.CardType.ATTACK, 0);
- } else if (aa instanceof ChooseSeeTheFutureOrder) {
- return new ExplodingKittensCard(ExplodingKittensCard.CardType.FAVOR, 1);
- } else if (aa instanceof NopeAction) {
- return new ExplodingKittensCard(ExplodingKittensCard.CardType.SEETHEFUTURE, 3);
- } else if (aa instanceof ShuffleAction) {
- return new ExplodingKittensCard(ExplodingKittensCard.CardType.SHUFFLE, 4);
- } else if (aa instanceof SkipAction) {
- return new ExplodingKittensCard(ExplodingKittensCard.CardType.SKIP, 5);
- }
- return null;
- }
}
diff --git a/src/main/java/games/explodingkittens/gui/ExplodingKittensGUIManager.java b/src/main/java/games/explodingkittens/gui/ExplodingKittensGUIManager.java
index ad8add1f6..07877430b 100644
--- a/src/main/java/games/explodingkittens/gui/ExplodingKittensGUIManager.java
+++ b/src/main/java/games/explodingkittens/gui/ExplodingKittensGUIManager.java
@@ -1,14 +1,13 @@
package games.explodingkittens.gui;
-import gui.AbstractGUIManager;
-import gui.GamePanel;
import core.AbstractGameState;
import core.AbstractPlayer;
import core.Game;
-import games.explodingkittens.ExplodingKittensParameters;
-import games.explodingkittens.ExplodingKittensGameState;
+import gui.AbstractGUIManager;
+import gui.GamePanel;
import gui.IScreenHighlight;
import players.human.ActionController;
+import games.explodingkittens.*;
import javax.swing.*;
import javax.swing.border.Border;
@@ -29,6 +28,7 @@ public class ExplodingKittensGUIManager extends AbstractGUIManager {
ExplodingKittensDeckView[] playerHands;
// Discard pile view
ExplodingKittensDiscardView discardPile;
+ ExplodingKittensDiscardView inPlayPile;
// Draw pile view
ExplodingKittensDeckView drawPile;
@@ -68,7 +68,7 @@ public ExplodingKittensGUIManager(GamePanel parent, Game game, ActionController
JPanel[] sides = new JPanel[]{new JPanel(), new JPanel(), new JPanel(), new JPanel()};
int next = 0;
for (int i = 0; i < nPlayers; i++) {
- ExplodingKittensDeckView playerHand = new ExplodingKittensDeckView(i, ekgs.getPlayerHandCards().get(i), false, ekgp.getDataPath());
+ ExplodingKittensDeckView playerHand = new ExplodingKittensDeckView(humanPlayerIds.iterator().next(), ekgs.getPlayerHand(i), false, ekgp.getDataPath());
// Get agent name
String[] split = game.getPlayers().get(i).getClass().toString().split("\\.");
@@ -92,10 +92,12 @@ public ExplodingKittensGUIManager(GamePanel parent, Game game, ActionController
// Discard and draw piles go in the center
JPanel centerArea = new JPanel();
centerArea.setLayout(new BoxLayout(centerArea, BoxLayout.Y_AXIS));
- discardPile = new ExplodingKittensDiscardView(ekgs.getDiscardPile(), ekgs.getActionStack(), true, ekgp.getDataPath());
- drawPile = new ExplodingKittensDeckView(-1, ekgs.getDrawPile(), gameState.getCoreGameParameters().alwaysDisplayFullObservable, ekgp.getDataPath());
+ discardPile = new ExplodingKittensDiscardView(ekgs.getDiscardPile(), true, ekgp.getDataPath());
+ inPlayPile = new ExplodingKittensDiscardView(ekgs.getInPlay(), true, ekgp.getDataPath());
+ drawPile = new ExplodingKittensDeckView(humanPlayerIds.iterator().next(), ekgs.getDrawPile(), gameState.getCoreGameParameters().alwaysDisplayFullObservable, ekgp.getDataPath());
centerArea.add(drawPile);
centerArea.add(discardPile);
+ centerArea.add(inPlayPile);
JPanel jp = new JPanel();
jp.setLayout(new GridBagLayout());
jp.add(centerArea);
@@ -136,9 +138,9 @@ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
// Update decks and visibility
ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
for (int i = 0; i < gameState.getNPlayers(); i++) {
- playerHands[i].updateComponent(ekgs.getPlayerHandCards().get(i));
+ playerHands[i].updateComponent(ekgs.getPlayerHand(i).copy());
if (i == gameState.getCurrentPlayer() && gameState.getCoreGameParameters().alwaysDisplayCurrentPlayer
- || humanPlayerId.contains(i)
+ || humanPlayerIds.contains(i)
|| gameState.getCoreGameParameters().alwaysDisplayFullObservable) {
playerHands[i].setFront(true);
playerHands[i].setFocusable(true);
@@ -155,11 +157,9 @@ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
playerHands[i].setBorder(playerViewBorders[i]);
}
}
- discardPile.updateComponent(ekgs.getDiscardPile());
+ discardPile.updateComponent(ekgs.getDiscardPile().copy());
discardPile.setFocusable(true);
- drawPile.updateComponent(ekgs.getDrawPile());
- if (humanPlayerId.contains(activePlayer) || gameState.getCoreGameParameters().alwaysDisplayFullObservable)
- drawPile.setFront(true);
+ drawPile.updateComponent(ekgs.getDrawPile().copy());
}
}
diff --git a/src/main/java/games/explodingkittens/ExplodingKittensFeatures.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensFeatures.java
similarity index 96%
rename from src/main/java/games/explodingkittens/ExplodingKittensFeatures.java
rename to src/main/java/games/explodingkittensOLD/ExplodingKittensFeatures.java
index 31526060b..ed6e3ea73 100644
--- a/src/main/java/games/explodingkittens/ExplodingKittensFeatures.java
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensFeatures.java
@@ -1,9 +1,9 @@
-package games.explodingkittens;
+package games.explodingkittensOLD;
import core.AbstractGameState;
import core.CoreConstants;
import core.interfaces.IStateFeatureVector;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/src/main/java/games/explodingkittensOLD/ExplodingKittensForwardModel.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensForwardModel.java
new file mode 100644
index 000000000..d83de9c64
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensForwardModel.java
@@ -0,0 +1,496 @@
+package games.explodingkittensOLD;
+
+import core.AbstractForwardModel;
+import core.AbstractGameState;
+import core.CoreConstants;
+import core.CoreConstants.VisibilityMode;
+import core.actions.AbstractAction;
+import core.actions.ActionSpace;
+import core.components.Deck;
+import core.components.PartialObservableDeck;
+import core.interfaces.ITreeActionSpace;
+import games.GameType;
+import games.explodingkittensOLD.actions.*;
+import games.explodingkittensOLD.actions.reactions.ChooseSeeTheFutureOrder;
+import games.explodingkittensOLD.actions.reactions.GiveCard;
+import games.explodingkittensOLD.actions.reactions.PassAction;
+import games.explodingkittensOLD.actions.reactions.PlaceExplodingKitten;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+import utilities.ActionTreeNode;
+
+import java.util.*;
+
+import static games.explodingkittensOLD.ExplodingKittensGameState.ExplodingKittensGamePhase.Nope;
+import static utilities.Utils.generatePermutations;
+
+public class ExplodingKittensForwardModel extends AbstractForwardModel implements ITreeActionSpace {
+ private final ArrayList cardTypes = new ArrayList<>(Arrays.asList("EXPLODING_KITTEN", "DEFUSE", "NOPE", "ATTACK", "SKIP", "FAVOR",
+ "SHUFFLE", "SEETHEFUTURE", "TACOCAT", "MELONCAT", "FURRYCAT", "BEARDCAT", "RAINBOWCAT"));
+ private final int N_CARDS_TO_CHECK = 10;
+
+ /**
+ * Performs initial game setup according to game rules.
+ * @param firstState - the state to be modified to the initial game state.
+ */
+ protected void _setup(AbstractGameState firstState) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState)firstState;
+ ExplodingKittensParameters ekp = (ExplodingKittensParameters)firstState.getGameParameters();
+ ekgs.playerHandCards = new ArrayList<>();
+ ekgs.playerGettingAFavor = -1;
+ ekgs.actionStack = null;
+ // Set up draw pile deck
+ PartialObservableDeck drawPile = new PartialObservableDeck<>("Draw Pile", -1, firstState.getNPlayers(), VisibilityMode.HIDDEN_TO_ALL);
+ ekgs.setDrawPile(drawPile);
+
+ // Add all cards but defuse and exploding kittens
+ for (HashMap.Entry entry : ekp.cardCounts.entrySet()) {
+ if (entry.getKey() == ExplodingKittensCard.CardType.DEFUSE || entry.getKey() == ExplodingKittensCard.CardType.EXPLODING_KITTEN)
+ continue;
+ for (int i = 0; i < entry.getValue(); i++) {
+ ExplodingKittensCard card = new ExplodingKittensCard(entry.getKey());
+ drawPile.add(card);
+ }
+ }
+ ekgs.getDrawPile().shuffle(ekgs.getRnd());
+
+ // Set up player hands
+ List> playerHandCards = new ArrayList<>(firstState.getNPlayers());
+ for (int i = 0; i < firstState.getNPlayers(); i++) {
+ boolean[] visible = new boolean[firstState.getNPlayers()];
+ visible[i] = true;
+ PartialObservableDeck playerCards = new PartialObservableDeck<>("Player Cards", i, visible);
+ playerHandCards.add(playerCards);
+
+ // Add defuse card
+ ExplodingKittensCard defuse = new ExplodingKittensCard(ExplodingKittensCard.CardType.DEFUSE);
+ defuse.setOwnerId(i);
+ playerCards.add(defuse);
+
+ // Add N random cards from the deck
+ for (int j = 0; j < ekp.nCardsPerPlayer; j++) {
+ ExplodingKittensCard c = ekgs.getDrawPile().draw();
+ c.setOwnerId(i);
+ playerCards.add(c);
+ }
+ }
+ ekgs.setPlayerHandCards(playerHandCards);
+ ekgs.setDiscardPile(new Deck<>("Discard Pile", VisibilityMode.VISIBLE_TO_ALL));
+
+ // Add remaining defuse cards and exploding kitten cards to the deck and shuffle again
+ for (int i = ekgs.getNPlayers(); i < ekp.nDefuseCards; i++){
+ ExplodingKittensCard defuse = new ExplodingKittensCard(ExplodingKittensCard.CardType.DEFUSE);
+ drawPile.add(defuse);
+ }
+ for (int i = 0; i < ekgs.getNPlayers() + ekp.cardCounts.get(ExplodingKittensCard.CardType.EXPLODING_KITTEN); i++){
+ ExplodingKittensCard explodingKitten = new ExplodingKittensCard(ExplodingKittensCard.CardType.EXPLODING_KITTEN);
+ drawPile.add(explodingKitten);
+ }
+ drawPile.shuffle(ekgs.getRnd());
+
+ ekgs.setActionStack(new Stack<>());
+ ekgs.orderOfPlayerDeath = new int[ekgs.getNPlayers()];
+ ekgs.setGamePhase(CoreConstants.DefaultGamePhase.Main);
+ }
+
+ /**
+ * Applies the given action to the game state and executes any other game rules.
+ * @param gameState - current game state, to be modified by the action.
+ * @param action - action requested to be played by a player.
+ */
+ @Override
+ protected void _next(AbstractGameState gameState, AbstractAction action) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+ ExplodingKittensTurnOrder ekTurnOrder = (ExplodingKittensTurnOrder) ekgs.getTurnOrder();
+ Stack actionStack = ekgs.getActionStack();
+
+ if (action instanceof IsNopeable) {
+ actionStack.add(action);
+ ((IsNopeable) action).actionPlayed(ekgs);
+
+ ekTurnOrder.registerNopeableActionByPlayer(ekgs, ekTurnOrder.getCurrentPlayer(ekgs));
+ if (action instanceof NopeAction) {
+ // Nope cards added immediately to avoid infinite nopeage
+ action.execute(ekgs);
+ } else {
+ if (ekTurnOrder.reactionsFinished()){
+ action.execute(ekgs);
+ actionStack.clear();
+ }
+ }
+ } else if (action instanceof PassAction) {
+
+ ekTurnOrder.endPlayerTurnStep(gameState);
+
+ if (ekTurnOrder.reactionsFinished()) {
+ // apply stack
+ if (actionStack.size()%2 == 0){
+ while (actionStack.size() > 1) {
+ actionStack.pop();
+ }
+ //Action was successfully noped
+ ((IsNopeable) actionStack.pop()).nopedExecute(gameState);
+// if (gameState.getCoreGameParameters().verbose) {
+// System.out.println("Action was successfully noped");
+// }
+ } else {
+// if (actionStack.size() > 2 && gameState.getCoreGameParameters().verbose) {
+// System.out.println("All nopes were noped");
+// }
+
+ while (actionStack.size() > 1) {
+ actionStack.pop();
+ }
+
+ //Action can be played
+ AbstractAction stackedAction = actionStack.get(0);
+ stackedAction.execute(gameState);
+ }
+ actionStack.clear();
+ if (ekgs.getGamePhase() == Nope) {
+ ekgs.setGamePhase(CoreConstants.DefaultGamePhase.Main);
+ }
+ }
+ } else {
+ action.execute(gameState);
+ }
+ }
+
+ @Override
+ protected List _computeAvailableActions(AbstractGameState gameState) {
+ return _computeAvailableActions(gameState, ActionSpace.Default);
+ }
+
+ /**
+ * Calculates the list of currently available actions, possibly depending on the game phase.
+ * @return - List of AbstractAction objects.
+ */
+ @Override
+ protected List _computeAvailableActions(AbstractGameState gameState, ActionSpace actionSpace) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+ ArrayList actions;
+
+ // The actions per player do not change a lot in between two turns
+ // Could update an existing list instead of generating a new list every time we query this function
+
+ // Find actions for the player depending on current game phase
+ int player = ekgs.getCurrentPlayer();
+ if (CoreConstants.DefaultGamePhase.Main.equals(ekgs.getGamePhase())) {
+ actions = playerActions(ekgs, player);
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse.equals(ekgs.getGamePhase())) {
+ actions = placeKittenActions(ekgs, player);
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Nope.equals(ekgs.getGamePhase())) {
+ actions = nopeActions(ekgs, player);
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Favor.equals(ekgs.getGamePhase())) {
+ actions = favorActions(ekgs, player);
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture.equals(ekgs.getGamePhase())) {
+ actions = seeTheFutureActions(ekgs, player);
+ } else {
+ actions = new ArrayList<>();
+ }
+
+ return actions;
+ }
+
+ @Override
+ protected AbstractForwardModel _copy() {
+ return new ExplodingKittensForwardModel();
+ }
+
+ @Override
+ protected void endPlayerTurn(AbstractGameState state) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) state;
+ ekgs.getTurnOrder().endPlayerTurn(ekgs);
+ }
+
+ private ArrayList playerActions(ExplodingKittensGameState ekgs, int playerID){
+ ArrayList actions = new ArrayList<>();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+// ActionTreeNode playNode = root.findChildrenByName("PLAY");
+
+ HashSet types = new HashSet<>();
+ for (int c = 0; c < playerDeck.getSize(); c++) {
+ ExplodingKittensCard card = playerDeck.get(c);
+ if (types.contains(card.cardType)) continue;
+ types.add(card.cardType);
+
+ switch (card.cardType) {
+ case DEFUSE:
+ case MELONCAT:
+ case RAINBOWCAT:
+ case FURRYCAT:
+ case BEARDCAT:
+ case TACOCAT:
+ case NOPE:
+ case EXPLODING_KITTEN:
+ break;
+ case SKIP:
+// playNode.setValue(1);
+ actions.add(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+// playNode.findChildrenByName("SKIP").setAction(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ case FAVOR:
+ for (int player = 0; player < ekgs.getNPlayers(); player++) {
+ if (player == playerID)
+ continue;
+ if (ekgs.playerHandCards.get(player).getSize() > 0) {
+ actions.add(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
+// playNode.setValue(1);
+// playNode.findChildrenByName("FAVOR").setAction(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
+ }
+ }
+ break;
+ case ATTACK:
+ for (int targetPlayer = 0; targetPlayer < ekgs.getNPlayers(); targetPlayer++) {
+
+ if (targetPlayer == playerID || ekgs.getPlayerResults()[targetPlayer] != CoreConstants.GameResult.GAME_ONGOING)
+ continue;
+
+ actions.add(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
+// playNode.setValue(1);
+// playNode.findChildrenByName("ATTACK").setAction(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
+ }
+ break;
+ case SHUFFLE:
+ actions.add(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+// playNode.setValue(1);
+// playNode.findChildrenByName("SHUFFLE").setAction(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ case SEETHEFUTURE:
+// actions.add(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
+// playNode.setValue(1);
+// playNode.findChildrenByName("SEETHEFUTURE").setAction(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
+ break;
+ default:
+ System.out.println("No actions known for cardtype: " + card.cardType);
+ }
+ }
+ /* todo add special combos
+ // can take any card from anyone
+ for (int i = 0; i < nPlayers; i++){
+ if (i != activePlayer){
+ Deck otherDeck = (Deck)this.areas.get(activePlayer).getComponent(playerHandHash);
+ for (Card card: otherDeck.getCards()){
+ core.actions.add(new TakeCard(card, i));
+ }
+ }
+ }*/
+
+ // add end turn by drawing a card
+ actions.add(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
+// root.findChildrenByName("DRAW").setAction(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
+ return actions;
+ }
+
+ private ArrayList placeKittenActions(ExplodingKittensGameState ekgs, int playerID){
+ ArrayList actions = new ArrayList<>();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+ int explodingKittenCard = -1;
+ for (int i = 0; i < playerDeck.getSize(); i++) {
+ if (playerDeck.getComponents().get(i).cardType == ExplodingKittensCard.CardType.EXPLODING_KITTEN) {
+ explodingKittenCard = i;
+ break;
+ }
+ }
+ if (explodingKittenCard != -1) {
+ for (int i = 0; i <= ekgs.drawPile.getSize(); i++) {
+ actions.add(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
+// if (i < N_CARDS_TO_CHECK){
+// root.findChildrenByName("DEFUSE").findChildrenByName("PUT" + i, true).setAction(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
+// }
+ }
+ }
+ return actions;
+ }
+
+ private ArrayList nopeActions(ExplodingKittensGameState ekgs, int playerID){
+ ArrayList actions = new ArrayList<>();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+ for (int c = 0; c < playerDeck.getSize(); c++) {
+ if (playerDeck.getComponents().get(c).cardType == ExplodingKittensCard.CardType.NOPE) {
+ actions.add(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+// root.findChildrenByName("NOPE").findChildrenByName("NOPE").setAction(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ }
+ }
+// root.findChildrenByName("NOPE").findChildrenByName("PASS").setAction(new PassAction());
+ actions.add(new PassAction());
+ return actions;
+ }
+
+ private ArrayList favorActions(ExplodingKittensGameState ekgs, int playerID){
+ ArrayList actions = new ArrayList<>();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+ Deck receiverDeck = ekgs.playerHandCards.get(ekgs.playerGettingAFavor);
+ // todo this requires mapping the card type to indices
+ for (int card = 0; card < playerDeck.getSize(); card++) {
+ actions.add(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
+// root.findChildrenByName("FAVOR").findChildrenByName(playerDeck.get(card).toString()).setAction(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
+ }
+ if (actions.isEmpty()) // the target has no cards.
+ actions.add(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), -1));
+ return actions;
+ }
+
+ private ArrayList seeTheFutureActions(ExplodingKittensGameState ekgs, int playerID){
+ ArrayList actions = new ArrayList<>();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+
+ int cardIdx = -1;
+ for (int c = 0; c < playerDeck.getSize(); c++) {
+ if (playerDeck.get(c).cardType == ExplodingKittensCard.CardType.SEETHEFUTURE) {
+ cardIdx = c;
+ break;
+ }
+ }
+
+ if (cardIdx != -1) {
+ int numberOfCards = ekgs.drawPile.getSize();
+ int n = Math.min(((ExplodingKittensParameters) ekgs.getGameParameters()).nSeeFutureCards, numberOfCards);
+ if (n > 0) {
+
+ ArrayList permutations = new ArrayList<>();
+ int[] order = new int[n];
+ for (int i = 0; i < n; i++) {
+ order[i] = i;
+ }
+ generatePermutations(n, order, permutations);
+ for (int[] perm : permutations) {
+ actions.add(new ChooseSeeTheFutureOrder(playerDeck.getComponentID(),
+ ekgs.discardPile.getComponentID(), cardIdx, ekgs.drawPile.getComponentID(), perm));
+ }
+ }
+ } else {
+ throw new AssertionError("Player " + playerID + " does not have a SeeTheFuture card in hand.");
+ }
+
+ return actions;
+ }
+
+ @Override
+ public ActionTreeNode updateActionTree(ActionTreeNode root, AbstractGameState gameState) {
+ root.resetTree();
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+
+ int playerID = ekgs.getCurrentPlayer();
+ Deck playerDeck = ekgs.playerHandCards.get(playerID);
+ if (CoreConstants.DefaultGamePhase.Main.equals(ekgs.getGamePhase())) {
+
+ ActionTreeNode playNode = root.findChildrenByName("PLAY");
+
+ HashSet types = new HashSet<>();
+ for (int c = 0; c < playerDeck.getSize(); c++) {
+ ExplodingKittensCard card = playerDeck.get(c);
+ if (types.contains(card.cardType)) continue;
+ types.add(card.cardType);
+
+ switch (card.cardType) {
+ case SKIP:
+ playNode.setValue(1);
+ playNode.findChildrenByName("SKIP").setAction(new SkipAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ case FAVOR:
+ for (int player = 0; player < ekgs.getNPlayers(); player++) {
+ if (player == playerID)
+ continue;
+ if (ekgs.playerHandCards.get(player).getSize() > 0) {
+ playNode.setValue(1);
+ playNode.findChildrenByName("FAVOR").setAction(new FavorAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, player));
+ }
+ }
+ break;
+ case ATTACK:
+ for (int targetPlayer = 0; targetPlayer < ekgs.getNPlayers(); targetPlayer++) {
+
+ if (targetPlayer == playerID || ekgs.getPlayerResults()[targetPlayer] != CoreConstants.GameResult.GAME_ONGOING)
+ continue;
+
+ playNode.setValue(1);
+ playNode.findChildrenByName("ATTACK").setAction(new AttackAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, targetPlayer));
+ }
+ break;
+ case SHUFFLE:
+ playNode.setValue(1);
+ playNode.findChildrenByName("SHUFFLE").setAction(new ShuffleAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ case SEETHEFUTURE:
+ // todo this action not implemented correctly
+ // actions.add(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
+// playNode.setValue(1);
+ // playNode.findChildrenByName("SEETHEFUTURE").setAction(new SeeTheFuture(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c, playerID));
+ break;
+// default:
+// System.out.println("No actions known for cardtype: " + card.cardType);
+ }
+ }
+
+ // add end turn by drawing a card
+ root.findChildrenByName("DRAW").setAction(new DrawExplodingKittenCard(ekgs.drawPile.getComponentID(), playerDeck.getComponentID()));
+
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse.equals(ekgs.getGamePhase())) {
+ int explodingKittenCard = -1;
+ for (int i = 0; i < playerDeck.getSize(); i++) {
+ if (playerDeck.getComponents().get(i).cardType == ExplodingKittensCard.CardType.EXPLODING_KITTEN) {
+ explodingKittenCard = i;
+ break;
+ }
+ }
+ if (explodingKittenCard != -1) {
+ for (int i = 0; i <= ekgs.drawPile.getSize(); i++) {
+ if (i < N_CARDS_TO_CHECK){
+ root.findChildrenByName("DEFUSE").findChildrenByName("PUT" + i, true).setAction(new PlaceExplodingKitten(playerDeck.getComponentID(), ekgs.drawPile.getComponentID(), explodingKittenCard, i));
+ }
+ }
+ }
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Nope.equals(ekgs.getGamePhase())) {
+ for (int c = 0; c < playerDeck.getSize(); c++) {
+ if (playerDeck.getComponents().get(c).cardType == ExplodingKittensCard.CardType.NOPE) {
+ root.findChildrenByName("NOPE").findChildrenByName("NOPE").setAction(new NopeAction(playerDeck.getComponentID(), ekgs.discardPile.getComponentID(), c));
+ break;
+ }
+ }
+ root.findChildrenByName("NOPE").findChildrenByName("PASS").setAction(new PassAction());
+ System.out.println("got boht nope actions");
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.Favor.equals(ekgs.getGamePhase())) {
+ Deck receiverDeck = ekgs.playerHandCards.get(ekgs.playerGettingAFavor);
+ // this requires mapping the card type to indices
+ for (int card = 0; card < playerDeck.getSize(); card++) {
+ root.findChildrenByName("FAVOR").findChildrenByName(playerDeck.get(card).toString()).setAction(new GiveCard(playerDeck.getComponentID(), receiverDeck.getComponentID(), card));
+ }
+ } else if (ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture.equals(ekgs.getGamePhase())) {
+ // todo this is not correctly implemented
+ }
+
+ return root;
+ }
+
+ public ActionTreeNode initActionTree(AbstractGameState gs){
+ // set up the action tree
+ ActionTreeNode tree = new ActionTreeNode(0, "root");
+ tree.addChild(0, "DRAW");
+ ActionTreeNode play = tree.addChild(0, "PLAY");
+ ActionTreeNode favor = tree.addChild(0, "FAVOR");
+ ActionTreeNode nope = tree.addChild(0, "NOPE");
+ ActionTreeNode defuse = tree.addChild(0, "DEFUSE");
+
+ for (String cardType : cardTypes) {
+ ActionTreeNode cardTypeNode = play.addChild(0, cardType);
+ // can ask a favor from any other player
+ if (cardType.equals("FAVOR")) {
+ for (int j = 0; j < GameType.ExplodingKittens.getMaxPlayers(); j++) {
+ cardTypeNode.addChild(0, "PLAYER" + j);
+ }
+ }
+ favor.addChild(0, cardType);
+ }
+ // if player has a nope card it can decide if it wants to play it or not
+ nope.addChild(0, "NOPE");
+ nope.addChild(0, "PASS");
+
+ // allow to put the back the defuse card to these positions
+ for (int i =0; i < N_CARDS_TO_CHECK; i++){
+ defuse.addChild(0, "PUT"+i);
+
+ }
+ return tree;
+ }
+}
diff --git a/src/main/java/games/explodingkittensOLD/ExplodingKittensGameState.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensGameState.java
new file mode 100644
index 000000000..003cfc0eb
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensGameState.java
@@ -0,0 +1,290 @@
+package games.explodingkittensOLD;
+
+import core.AbstractGameStateWithTurnOrder;
+import core.AbstractParameters;
+import core.CoreConstants;
+import core.actions.AbstractAction;
+import core.components.Component;
+import core.components.Deck;
+import core.components.PartialObservableDeck;
+import core.interfaces.IGamePhase;
+import core.interfaces.IPrintable;
+import core.turnorders.TurnOrder;
+import games.GameType;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+
+import java.util.*;
+
+import static core.CoreConstants.VisibilityMode;
+
+public class ExplodingKittensGameState extends AbstractGameStateWithTurnOrder implements IPrintable {
+
+ // Cards in each player's hand, index corresponds to player ID
+ List> playerHandCards;
+ // Cards in the draw pile
+ PartialObservableDeck drawPile;
+ // Cards in the discard pile
+ Deck discardPile;
+ // Player ID of the player currently getting a favor
+ int playerGettingAFavor;
+ // Current stack of actions
+ Stack actionStack;
+ int[] orderOfPlayerDeath;
+ public ExplodingKittensGameState(AbstractParameters gameParameters, int nPlayers) {
+ super(gameParameters, nPlayers);
+ playerGettingAFavor = -1;
+ playerHandCards = new ArrayList<>();
+ }
+ @Override
+ protected TurnOrder _createTurnOrder(int nPlayers) {
+ return new ExplodingKittensTurnOrder(nPlayers);
+ }
+
+ @Override
+ protected GameType _getGameType() {
+ return GameType.ExplodingKittens;
+ }
+
+ @Override
+ protected List _getAllComponents() {
+ return new ArrayList() {{
+ add(drawPile);
+ add(discardPile);
+ addAll(playerHandCards);
+ }};
+ }
+
+ @Override
+ protected AbstractGameStateWithTurnOrder __copy(int playerId) {
+ ExplodingKittensGameState ekgs = new ExplodingKittensGameState(gameParameters.copy(), getNPlayers());
+ ekgs.discardPile = discardPile.copy();
+ ekgs.playerGettingAFavor = playerGettingAFavor;
+ ekgs.actionStack = new Stack<>();
+ for (AbstractAction a : actionStack) {
+ ekgs.actionStack.add(a.copy());
+ }
+ ekgs.orderOfPlayerDeath = orderOfPlayerDeath.clone();
+ ekgs.playerHandCards = new ArrayList<>();
+ for (PartialObservableDeck d : playerHandCards) {
+ ekgs.playerHandCards.add(d.copy());
+ }
+ ekgs.drawPile = drawPile.copy();
+ if (getCoreGameParameters().partialObservable && playerId != -1) {
+ // Other player hands + draw deck are hidden, combine in draw pile and shuffle
+ // Note: this considers the agent to track opponent's cards that are known to him by itself
+ // e.g. in case the agent has previously given a favor card to its opponent
+ for (int i = 0; i < getNPlayers(); i++) {
+ if (i != playerId) {
+ // Take all cards the player can't see from other players and put them in the draw pile.
+ ArrayList cs = new ArrayList<>();
+ for (int j = 0; j < ekgs.playerHandCards.get(i).getSize(); j++) {
+ if (!ekgs.playerHandCards.get(i).isComponentVisible(j, playerId)) {
+ ExplodingKittensCard c = ekgs.playerHandCards.get(i).get(j);
+ ekgs.drawPile.add(c, ekgs.playerHandCards.get(i).getVisibilityOfComponent(j).clone());
+ cs.add(c);
+ }
+ }
+ for (ExplodingKittensCard c : cs) {
+ ekgs.playerHandCards.get(i).remove(c);
+ }
+ }
+ }
+
+ // Shuffles only hidden cards in draw pile, if player knows what's on top those will stay in place
+ ekgs.drawPile.redeterminiseUnknown(redeterminisationRnd, playerId);
+ Deck explosive = new Deck<>("tmp", VisibilityMode.HIDDEN_TO_ALL);
+ for (int i = 0; i < getNPlayers(); i++) {
+ if (i != playerId) {
+ for (int j = 0; j < playerHandCards.get(i).getSize(); j++) {
+ // Add back random cards for all components not visible to this player
+ if (playerHandCards.get(i).isComponentVisible(j, playerId)) continue;
+ boolean added = false;
+ int cardIndex = 0;
+ while (!added) {
+ // if the card is visible to the player we cannot move it somewhere else
+ if (ekgs.drawPile.getVisibilityForPlayer(cardIndex, playerId)) {
+ cardIndex++;
+ continue;
+ }
+ ExplodingKittensCard card = ekgs.drawPile.pick(cardIndex);
+ if (card.cardType != ExplodingKittensCard.CardType.EXPLODING_KITTEN) {
+ ekgs.playerHandCards.get(i).add(card);
+ added = true;
+ } else {
+ explosive.add(card);
+ }
+ }
+ }
+ }
+ }
+ ekgs.drawPile.add(explosive);
+ }
+ return ekgs;
+ }
+
+ @Override
+ protected double _getHeuristicScore(int playerId) {
+ return new ExplodingKittensHeuristic().evaluateState(this, playerId);
+ }
+
+ /**
+ * This provides the current score in game turns. This will only be relevant for games that have the concept
+ * of victory points, etc.
+ * If a game does not support this directly, then just return 0.0
+ *
+ * @param playerId
+ * @return - double, score of current state
+ */
+ @Override
+ public double getGameScore(int playerId) {
+ if (playerResults[playerId] == CoreConstants.GameResult.LOSE_GAME)
+ // knocked out
+ return orderOfPlayerDeath[playerId];
+ // otherwise our current score is the number knocked out + 1
+ return Arrays.stream(playerResults).filter(status -> status == CoreConstants.GameResult.LOSE_GAME).count() + 1;
+ }
+
+ @Override
+ public int getOrdinalPosition(int playerId) {
+ if (playerResults[playerId] == CoreConstants.GameResult.WIN_GAME)
+ return 1;
+ if (playerResults[playerId] == CoreConstants.GameResult.LOSE_GAME)
+ return getNPlayers() - orderOfPlayerDeath[playerId] + 1;
+ return 1; // anyone still alive is jointly winning
+ }
+
+ @Override
+ protected boolean _equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ExplodingKittensGameState)) return false;
+ if (!super.equals(o)) return false;
+ ExplodingKittensGameState gameState = (ExplodingKittensGameState) o;
+ return playerGettingAFavor == gameState.playerGettingAFavor &&
+ Objects.equals(playerHandCards, gameState.playerHandCards) &&
+ Objects.equals(drawPile, gameState.drawPile) &&
+ Objects.equals(discardPile, gameState.discardPile) &&
+ Objects.equals(actionStack, gameState.actionStack);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), playerHandCards, drawPile, discardPile, playerGettingAFavor, actionStack);
+ }
+
+ /**
+ * Marks a player as dead.
+ *
+ * @param playerID - player who was killed in a kitten explosion.
+ */
+ public void killPlayer(int playerID) {
+ setPlayerResult(CoreConstants.GameResult.LOSE_GAME, playerID);
+ int nPlayersActive = 0;
+ for (int i = 0; i < getNPlayers(); i++) {
+ if (playerResults[i] == CoreConstants.GameResult.GAME_ONGOING) nPlayersActive++;
+ }
+ orderOfPlayerDeath[playerID] = getNPlayers() - nPlayersActive;
+ if (nPlayersActive == 1) {
+ this.gameStatus = CoreConstants.GameResult.GAME_END;
+ for (int i = 0; i < getNPlayers(); i++) {
+ // set winner
+ if (playerResults[i] == CoreConstants.GameResult.GAME_ONGOING) {
+ playerResults[i] = CoreConstants.GameResult.WIN_GAME;
+ }
+ }
+ turnOrder.endGame(this);
+ }
+
+ ((ExplodingKittensTurnOrder) getTurnOrder()).endPlayerTurnStep(this);
+
+ }
+
+ // Getters, setters
+ public int getPlayerGettingAFavor() {
+ return playerGettingAFavor;
+ }
+
+ public void setPlayerGettingAFavor(int playerGettingAFavor) {
+ this.playerGettingAFavor = playerGettingAFavor;
+ }
+
+ public PartialObservableDeck getDrawPile() {
+ return drawPile;
+ }
+
+ protected void setDrawPile(PartialObservableDeck drawPile) {
+ this.drawPile = drawPile;
+ }
+
+ public Deck getDiscardPile() {
+ return discardPile;
+ }
+
+ // Protected, only accessible in this package and subclasses
+ protected void setDiscardPile(Deck discardPile) {
+ this.discardPile = discardPile;
+ }
+
+ public Stack getActionStack() {
+ return actionStack;
+ }
+
+ protected void setActionStack(Stack actionStack) {
+ this.actionStack = actionStack;
+ }
+
+ public List> getPlayerHandCards() {
+ return playerHandCards;
+ }
+
+ protected void setPlayerHandCards(List> playerHandCards) {
+ this.playerHandCards = playerHandCards;
+ }
+
+ public void printToConsole() {
+ System.out.println(toString());
+ }
+
+
+ // Printing functions for the game state and decks.
+
+ @Override
+ public String toString() {
+ String s = "============================\n";
+
+ int currentPlayer = turnOrder.getCurrentPlayer(this);
+
+ for (int i = 0; i < getNPlayers(); i++) {
+ if (currentPlayer == i)
+ s += ">>> Player " + i + ":";
+ else
+ s += "Player " + i + ":";
+ s += playerHandCards.get(i).toString() + "\n";
+ }
+
+ s += "\nDrawPile: ";
+ s += drawPile.toString() + "\n";
+
+ s += "DiscardPile: ";
+ s += discardPile.toString() + "\n";
+
+ s += "Action stack: ";
+ for (AbstractAction a : actionStack) {
+ s += a.toString() + ",";
+ }
+ s = s.substring(0, s.length() - 1);
+ s += "\n\n";
+
+ s += "Current GamePhase: " + gamePhase + "\n";
+ s += "Missing Draws: " + ((ExplodingKittensTurnOrder) turnOrder).requiredDraws + "\n";
+ s += "============================\n";
+ return s;
+ }
+
+ // Exploding kittens adds 4 phases on top of default ones.
+ public enum ExplodingKittensGamePhase implements IGamePhase {
+ Nope,
+ Defuse,
+ Favor,
+ SeeTheFuture
+ }
+}
diff --git a/src/main/java/games/explodingkittensOLD/ExplodingKittensHeuristic.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensHeuristic.java
new file mode 100644
index 000000000..09043dc60
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensHeuristic.java
@@ -0,0 +1,111 @@
+package games.explodingkittensOLD;
+
+import core.AbstractGameState;
+import core.CoreConstants;
+import core.interfaces.IStateHeuristic;
+import evaluation.optimisation.TunableParameters;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+
+public class ExplodingKittensHeuristic extends TunableParameters implements IStateHeuristic {
+
+ double explodingValue = -1;
+ double defuseValue = 1;
+ double regularValue = -0.01;
+ double seeFutureValue = -0.3; // Play it
+ double nopeValue = -0.5; // Play it
+ double attackValue = -0.4; // Play it
+ double skipValue = 0.2;
+ double favorValue = -0.1;
+ double shuffleValue = -0.2;
+
+ public ExplodingKittensHeuristic() {
+ addTunableParameter("explodingValue", -1.0);
+ addTunableParameter("defuseValue", 1.0);
+ addTunableParameter("regularValue", -0.01);
+ addTunableParameter("seeFutureValue", -0.3);
+ addTunableParameter("nopeValue", -0.5);
+ addTunableParameter("attackValue", -0.4);
+ addTunableParameter("skipValue", 0.2);
+ addTunableParameter("favorValue", -0.1);
+ addTunableParameter("shuffleValue", -0.2);
+ }
+
+ @Override
+ public void _reset() {
+ explodingValue = (double) getParameterValue("explodingValue");
+ defuseValue = (double) getParameterValue("defuseValue");
+ regularValue = (double) getParameterValue("regularValue");
+ seeFutureValue = (double) getParameterValue("seeFutureValue");
+ nopeValue = (double) getParameterValue("nopeValue");
+ attackValue = (double) getParameterValue("attackValue");
+ skipValue = (double) getParameterValue("skipValue");
+ favorValue = (double) getParameterValue("favorValue");
+ shuffleValue = (double) getParameterValue("shuffleValue");
+ }
+
+ @Override
+ public double evaluateState(AbstractGameState gs, int playerId) {
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState)gs;
+ CoreConstants.GameResult playerResult = ekgs.getPlayerResults()[playerId];
+
+ if (playerResult == CoreConstants.GameResult.LOSE_GAME)
+ return -1;
+ if (playerResult == CoreConstants.GameResult.WIN_GAME)
+ return 1;
+
+ double cardValues = 0.0;
+ for (ExplodingKittensCard card : ekgs.playerHandCards.get(playerId).getComponents()) {
+ cardValues += getCardValue(ekgs, card);
+ }
+
+ return cardValues / (ekgs.playerHandCards.get(playerId).getSize() + 1);
+ }
+
+ double getCardValue(ExplodingKittensGameState ekgs, ExplodingKittensCard card) {
+ switch (card.cardType) {
+ case EXPLODING_KITTEN:
+ return explodingValue;
+ case DEFUSE:
+ return defuseValue;
+ case NOPE:
+ if (ekgs.isActionInProgress()) {
+ return nopeValue;
+ } else return 0; // Neutral
+ case ATTACK:
+ return attackValue;
+ case SKIP:
+ return skipValue;
+ case FAVOR:
+ return favorValue;
+ case SHUFFLE:
+ return shuffleValue;
+ case SEETHEFUTURE:
+ return seeFutureValue;
+ default:
+ return regularValue;
+ }
+ }
+
+ @Override
+ protected ExplodingKittensHeuristic _copy() {
+ return new ExplodingKittensHeuristic();
+ // copying of parameterisable values is done in the super class
+ }
+
+ @Override
+ protected boolean _equals(Object o) {
+ return o instanceof ExplodingKittensHeuristic;
+ // checking of parameterisable values is done in the super class
+ }
+
+ /**
+ * @return Returns Tuned Parameters corresponding to the current settings
+ * (will use all defaults if setParameterValue has not been called at all)
+ */
+ @Override
+ public ExplodingKittensHeuristic instantiate() {
+ return (ExplodingKittensHeuristic) this.copy();
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/java/games/explodingkittensOLD/ExplodingKittensParameters.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensParameters.java
new file mode 100644
index 000000000..d9eced0da
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensParameters.java
@@ -0,0 +1,89 @@
+package games.explodingkittensOLD;
+
+import core.AbstractParameters;
+import core.Game;
+import evaluation.optimisation.TunableParameters;
+import games.GameType;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Objects;
+
+public class ExplodingKittensParameters extends TunableParameters {
+
+ String dataPath = "data/explodingkittens/";
+
+ HashMap cardCounts = new HashMap() {{
+ put(ExplodingKittensCard.CardType.ATTACK, 4);
+ put(ExplodingKittensCard.CardType.SKIP, 4);
+ put(ExplodingKittensCard.CardType.FAVOR, 4);
+ put(ExplodingKittensCard.CardType.SHUFFLE, 4);
+ put(ExplodingKittensCard.CardType.SEETHEFUTURE, 5);
+ put(ExplodingKittensCard.CardType.TACOCAT, 4);
+ put(ExplodingKittensCard.CardType.MELONCAT, 4);
+ put(ExplodingKittensCard.CardType.BEARDCAT, 4);
+ put(ExplodingKittensCard.CardType.RAINBOWCAT, 4);
+ put(ExplodingKittensCard.CardType.FURRYCAT, 4);
+ put(ExplodingKittensCard.CardType.NOPE, 5);
+ put(ExplodingKittensCard.CardType.DEFUSE, 6);
+ put(ExplodingKittensCard.CardType.EXPLODING_KITTEN, -1);
+ }};
+ public int nCardsPerPlayer = 7;
+ public int nDefuseCards = 6;
+ public int nSeeFutureCards = 3;
+ public boolean nopeOwnCards = true;
+
+ public ExplodingKittensParameters() {
+ addTunableParameter("nCardsPerPlayer", 7, Arrays.asList(3,5,7,10,15));
+ addTunableParameter("nDefuseCards", 6, Arrays.asList(1,2,3,6,9));
+ addTunableParameter("nSeeFutureCards", 3, Arrays.asList(1,3,5,7));
+ addTunableParameter("nopeOwnCards", true, Arrays.asList(false, true));
+ for (ExplodingKittensCard.CardType c: cardCounts.keySet()) {
+ if (c == ExplodingKittensCard.CardType.EXPLODING_KITTEN) addTunableParameter(c.name() + " count", -1);
+ else addTunableParameter(c.name() + " count", cardCounts.get(c), Arrays.asList(1,2,3,4,5));
+ }
+ _reset();
+ }
+
+ @Override
+ public void _reset() {
+ nCardsPerPlayer = (int) getParameterValue("nCardsPerPlayer");
+ nDefuseCards = (int) getParameterValue("nDefuseCards");
+ nSeeFutureCards = (int) getParameterValue("nSeeFutureCards");
+ nopeOwnCards = (boolean) getParameterValue("nopeOwnCards");
+ cardCounts.replaceAll((c, v) -> (Integer) getParameterValue(c.name() + " count"));
+ }
+
+ public String getDataPath() {
+ return dataPath;
+ }
+
+ @Override
+ protected AbstractParameters _copy() {
+ return new ExplodingKittensParameters();
+ }
+
+ @Override
+ protected boolean _equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ExplodingKittensParameters)) return false;
+ if (!super.equals(o)) return false;
+ ExplodingKittensParameters that = (ExplodingKittensParameters) o;
+ return nCardsPerPlayer == that.nCardsPerPlayer &&
+ nDefuseCards == that.nDefuseCards &&
+ nSeeFutureCards == that.nSeeFutureCards &&
+ Objects.equals(dataPath, that.dataPath) &&
+ Objects.equals(cardCounts, that.cardCounts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), dataPath, cardCounts, nCardsPerPlayer, nDefuseCards, nSeeFutureCards);
+ }
+
+ @Override
+ public Object instantiate() {
+ return new Game(GameType.ExplodingKittens, new ExplodingKittensForwardModel(), new ExplodingKittensGameState(this, GameType.ExplodingKittens.getMinPlayers()));
+ }
+}
diff --git a/src/main/java/games/explodingkittens/ExplodingKittensTurnOrder.java b/src/main/java/games/explodingkittensOLD/ExplodingKittensTurnOrder.java
similarity index 95%
rename from src/main/java/games/explodingkittens/ExplodingKittensTurnOrder.java
rename to src/main/java/games/explodingkittensOLD/ExplodingKittensTurnOrder.java
index 81fca5ecc..5b854da02 100644
--- a/src/main/java/games/explodingkittens/ExplodingKittensTurnOrder.java
+++ b/src/main/java/games/explodingkittensOLD/ExplodingKittensTurnOrder.java
@@ -1,16 +1,16 @@
-package games.explodingkittens;
+package games.explodingkittensOLD;
import core.AbstractGameState;
import core.CoreConstants;
import core.turnorders.ReactiveTurnOrder;
import core.turnorders.TurnOrder;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Objects;
-import static games.explodingkittens.ExplodingKittensGameState.ExplodingKittensGamePhase.Nope;
+import static games.explodingkittensOLD.ExplodingKittensGameState.ExplodingKittensGamePhase.Nope;
import static core.CoreConstants.GameResult.GAME_ONGOING;
public class ExplodingKittensTurnOrder extends ReactiveTurnOrder {
diff --git a/src/main/java/games/explodingkittens/actions/AttackAction.java b/src/main/java/games/explodingkittensOLD/actions/AttackAction.java
similarity index 91%
rename from src/main/java/games/explodingkittens/actions/AttackAction.java
rename to src/main/java/games/explodingkittensOLD/actions/AttackAction.java
index ae68a13af..777cdfd49 100644
--- a/src/main/java/games/explodingkittens/actions/AttackAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/AttackAction.java
@@ -1,13 +1,13 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.actions.AbstractAction;
import core.actions.DrawCard;
import core.AbstractGameState;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.ExplodingKittensTurnOrder;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
import java.util.Objects;
diff --git a/src/main/java/games/explodingkittens/actions/DrawExplodingKittenCard.java b/src/main/java/games/explodingkittensOLD/actions/DrawExplodingKittenCard.java
similarity index 88%
rename from src/main/java/games/explodingkittens/actions/DrawExplodingKittenCard.java
rename to src/main/java/games/explodingkittensOLD/actions/DrawExplodingKittenCard.java
index cb48bae60..d8479eec8 100644
--- a/src/main/java/games/explodingkittens/actions/DrawExplodingKittenCard.java
+++ b/src/main/java/games/explodingkittensOLD/actions/DrawExplodingKittenCard.java
@@ -1,15 +1,15 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.actions.AbstractAction;
import core.actions.DrawCard;
import core.AbstractGameState;
import core.components.Deck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensTurnOrder;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
-import static games.explodingkittens.ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse;
+import static games.explodingkittensOLD.ExplodingKittensGameState.ExplodingKittensGamePhase.Defuse;
public class DrawExplodingKittenCard extends DrawCard implements IPrintable {
diff --git a/src/main/java/games/explodingkittens/actions/FavorAction.java b/src/main/java/games/explodingkittensOLD/actions/FavorAction.java
similarity index 88%
rename from src/main/java/games/explodingkittens/actions/FavorAction.java
rename to src/main/java/games/explodingkittensOLD/actions/FavorAction.java
index d93dab24a..99e99a454 100644
--- a/src/main/java/games/explodingkittens/actions/FavorAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/FavorAction.java
@@ -1,18 +1,18 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.actions.AbstractAction;
import core.actions.DrawCard;
import core.AbstractGameState;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensTurnOrder;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
import java.util.Objects;
-import static games.explodingkittens.ExplodingKittensGameState.ExplodingKittensGamePhase.Favor;
+import static games.explodingkittensOLD.ExplodingKittensGameState.ExplodingKittensGamePhase.Favor;
public class FavorAction extends DrawCard implements IsNopeable, IPrintable {
final int target;
diff --git a/src/main/java/games/explodingkittens/actions/IsNopeable.java b/src/main/java/games/explodingkittensOLD/actions/IsNopeable.java
similarity index 78%
rename from src/main/java/games/explodingkittens/actions/IsNopeable.java
rename to src/main/java/games/explodingkittensOLD/actions/IsNopeable.java
index e59ed28ed..a08227534 100644
--- a/src/main/java/games/explodingkittens/actions/IsNopeable.java
+++ b/src/main/java/games/explodingkittensOLD/actions/IsNopeable.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.AbstractGameState;
diff --git a/src/main/java/games/explodingkittens/actions/NopeAction.java b/src/main/java/games/explodingkittensOLD/actions/NopeAction.java
similarity index 96%
rename from src/main/java/games/explodingkittens/actions/NopeAction.java
rename to src/main/java/games/explodingkittensOLD/actions/NopeAction.java
index 952a25971..c31dabdd6 100644
--- a/src/main/java/games/explodingkittens/actions/NopeAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/NopeAction.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.AbstractGameState;
import core.actions.AbstractAction;
diff --git a/src/main/java/games/explodingkittens/actions/SeeTheFuture.java b/src/main/java/games/explodingkittensOLD/actions/SeeTheFuture.java
similarity index 88%
rename from src/main/java/games/explodingkittens/actions/SeeTheFuture.java
rename to src/main/java/games/explodingkittensOLD/actions/SeeTheFuture.java
index 97bfd28b4..4fe57ed4b 100644
--- a/src/main/java/games/explodingkittens/actions/SeeTheFuture.java
+++ b/src/main/java/games/explodingkittensOLD/actions/SeeTheFuture.java
@@ -1,17 +1,17 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.actions.AbstractAction;
import core.AbstractGameState;
import core.actions.DrawCard;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.ExplodingKittensParameters;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.ExplodingKittensParameters;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
-import static games.explodingkittens.ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture;
+import static games.explodingkittensOLD.ExplodingKittensGameState.ExplodingKittensGamePhase.SeeTheFuture;
public class SeeTheFuture extends DrawCard implements IsNopeable, IPrintable {
diff --git a/src/main/java/games/explodingkittens/actions/ShuffleAction.java b/src/main/java/games/explodingkittensOLD/actions/ShuffleAction.java
similarity index 91%
rename from src/main/java/games/explodingkittens/actions/ShuffleAction.java
rename to src/main/java/games/explodingkittensOLD/actions/ShuffleAction.java
index 599c6a318..abd63b2e9 100644
--- a/src/main/java/games/explodingkittens/actions/ShuffleAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/ShuffleAction.java
@@ -1,15 +1,14 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.actions.AbstractAction;
import core.actions.DrawCard;
import core.AbstractGameState;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
-import java.util.Random;
public class ShuffleAction extends DrawCard implements IsNopeable, IPrintable {
diff --git a/src/main/java/games/explodingkittens/actions/SkipAction.java b/src/main/java/games/explodingkittensOLD/actions/SkipAction.java
similarity index 90%
rename from src/main/java/games/explodingkittens/actions/SkipAction.java
rename to src/main/java/games/explodingkittensOLD/actions/SkipAction.java
index 4a44dc442..3a07b73ca 100644
--- a/src/main/java/games/explodingkittens/actions/SkipAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/SkipAction.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions;
+package games.explodingkittensOLD.actions;
import core.CoreConstants;
import core.actions.AbstractAction;
@@ -6,9 +6,9 @@
import core.AbstractGameState;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.ExplodingKittensTurnOrder;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
diff --git a/src/main/java/games/explodingkittens/actions/reactions/ChooseSeeTheFutureOrder.java b/src/main/java/games/explodingkittensOLD/actions/reactions/ChooseSeeTheFutureOrder.java
similarity index 91%
rename from src/main/java/games/explodingkittens/actions/reactions/ChooseSeeTheFutureOrder.java
rename to src/main/java/games/explodingkittensOLD/actions/reactions/ChooseSeeTheFutureOrder.java
index d2eb8a9cf..1a7751eca 100644
--- a/src/main/java/games/explodingkittens/actions/reactions/ChooseSeeTheFutureOrder.java
+++ b/src/main/java/games/explodingkittensOLD/actions/reactions/ChooseSeeTheFutureOrder.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions.reactions;
+package games.explodingkittensOLD.actions.reactions;
import core.AbstractGameState;
import core.CoreConstants;
@@ -6,8 +6,8 @@
import core.actions.RearrangeDeckOfCards;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
import java.util.Arrays;
diff --git a/src/main/java/games/explodingkittens/actions/reactions/GiveCard.java b/src/main/java/games/explodingkittensOLD/actions/reactions/GiveCard.java
similarity index 88%
rename from src/main/java/games/explodingkittens/actions/reactions/GiveCard.java
rename to src/main/java/games/explodingkittensOLD/actions/reactions/GiveCard.java
index 9c9ab825d..ff47f9a6f 100644
--- a/src/main/java/games/explodingkittens/actions/reactions/GiveCard.java
+++ b/src/main/java/games/explodingkittensOLD/actions/reactions/GiveCard.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions.reactions;
+package games.explodingkittensOLD.actions.reactions;
import core.CoreConstants;
import core.actions.AbstractAction;
@@ -7,9 +7,9 @@
import core.components.Card;
import core.components.Deck;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensTurnOrder;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.cards.ExplodingKittensCard;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
public class GiveCard extends DrawCard implements IPrintable {
diff --git a/src/main/java/games/explodingkittens/actions/reactions/PassAction.java b/src/main/java/games/explodingkittensOLD/actions/reactions/PassAction.java
similarity index 92%
rename from src/main/java/games/explodingkittens/actions/reactions/PassAction.java
rename to src/main/java/games/explodingkittensOLD/actions/reactions/PassAction.java
index eb99930a1..8463a0a63 100644
--- a/src/main/java/games/explodingkittens/actions/reactions/PassAction.java
+++ b/src/main/java/games/explodingkittensOLD/actions/reactions/PassAction.java
@@ -1,4 +1,4 @@
-package games.explodingkittens.actions.reactions;
+package games.explodingkittensOLD.actions.reactions;
import core.AbstractGameState;
import core.actions.AbstractAction;
diff --git a/src/main/java/games/explodingkittens/actions/reactions/PlaceExplodingKitten.java b/src/main/java/games/explodingkittensOLD/actions/reactions/PlaceExplodingKitten.java
similarity index 90%
rename from src/main/java/games/explodingkittens/actions/reactions/PlaceExplodingKitten.java
rename to src/main/java/games/explodingkittensOLD/actions/reactions/PlaceExplodingKitten.java
index 65729fee9..c3792d9a9 100644
--- a/src/main/java/games/explodingkittens/actions/reactions/PlaceExplodingKitten.java
+++ b/src/main/java/games/explodingkittensOLD/actions/reactions/PlaceExplodingKitten.java
@@ -1,12 +1,12 @@
-package games.explodingkittens.actions.reactions;
+package games.explodingkittensOLD.actions.reactions;
import core.AbstractGameState;
import core.CoreConstants;
import core.actions.AbstractAction;
import core.actions.DrawCard;
import core.interfaces.IPrintable;
-import games.explodingkittens.ExplodingKittensGameState;
-import games.explodingkittens.ExplodingKittensTurnOrder;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import games.explodingkittensOLD.ExplodingKittensTurnOrder;
public class PlaceExplodingKitten extends DrawCard implements IPrintable {
diff --git a/src/main/java/games/explodingkittensOLD/cards/ExplodingKittensCard.java b/src/main/java/games/explodingkittensOLD/cards/ExplodingKittensCard.java
new file mode 100644
index 000000000..f0e29d2ec
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/cards/ExplodingKittensCard.java
@@ -0,0 +1,43 @@
+package games.explodingkittensOLD.cards;
+
+import core.components.Card;
+
+public class ExplodingKittensCard extends Card {
+ public enum CardType {
+ EXPLODING_KITTEN,
+ DEFUSE,
+ NOPE,
+ ATTACK,
+ SKIP,
+ FAVOR,
+ SHUFFLE,
+ SEETHEFUTURE,
+ TACOCAT,
+ MELONCAT,
+ FURRYCAT,
+ BEARDCAT,
+ RAINBOWCAT,
+ }
+
+ public CardType cardType;
+
+ public ExplodingKittensCard(CardType cardType) {
+ super(cardType.toString());
+ this.cardType = cardType;
+ }
+
+ public ExplodingKittensCard(CardType cardType, int ID) {
+ super(cardType.toString(), ID);
+ this.cardType = cardType;
+ }
+
+ @Override
+ public Card copy() {
+ return new ExplodingKittensCard(cardType, componentID);
+ }
+
+ @Override
+ public String toString() {
+ return cardType.name();
+ }
+}
diff --git a/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDeckView.java b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDeckView.java
new file mode 100644
index 000000000..767fe6a99
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDeckView.java
@@ -0,0 +1,96 @@
+package games.explodingkittensOLD.gui;
+
+import core.components.Deck;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+import gui.views.CardView;
+import gui.views.DeckView;
+import utilities.ImageIO;
+
+import java.awt.*;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Random;
+
+import static games.explodingkittensOLD.gui.ExplodingKittensGUIManager.*;
+
+
+public class ExplodingKittensDeckView extends DeckView {
+
+ // Back of card image
+ Image backOfCard;
+ // Path to assets
+ String dataPath;
+
+ // Card images
+ HashMap cardCatImageMapping;
+ ArrayList catImages;
+ Random rnd = new Random(); // This doesn't need to use the game random seed
+
+ /**
+ * Constructor initialising information and adding key/mouse listener for card highlight (left click or ALT + hover
+ * allows showing the highlighted card on top of all others).
+ * @param d - deck to draw
+ * @param visible - true if whole deck visible
+ * @param dataPath - path to assets
+ */
+ public ExplodingKittensDeckView(int player, Deck d, boolean visible, String dataPath) {
+ super(player, d, visible, ekCardWidth, ekCardHeight, new Rectangle(5, 5, playerAreaWidth, playerAreaHeight));
+ backOfCard = ImageIO.GetInstance().getImage(dataPath + "CardBack.png");
+ this.dataPath = dataPath;
+ cardCatImageMapping = new HashMap<>();
+
+ // Get card Images
+ File dir = new File(dataPath + "cats/");
+ File[] files = dir.listFiles();
+ catImages = new ArrayList<>();
+ if (files != null) {
+ for (File f : files) {
+ catImages.add(f.getAbsolutePath());
+ }
+ }
+ }
+
+ /**
+ * Draws the specified component at the specified place
+ *
+ * @param g Graphics object
+ * @param rect Where the item is to be drawn
+ * @param card The item itself
+ * @param visible true if the item is visible (e.g. the card details); false if only the card-back
+ */
+ @Override
+ public void drawComponent(Graphics2D g, Rectangle rect, ExplodingKittensCard card, boolean visible) {
+ drawCat(g, card, rect, visible);
+ }
+
+ /**
+ * Draws an Exploding Kittens card, with a random cat icon.
+ * @param g - Graphics object
+ * @param card - card to draw on
+ * @param r - rectangle in which card is to be drawn
+ * @param visible - if the card is visible or not
+ */
+ private void drawCat(Graphics2D g, ExplodingKittensCard card, Rectangle r, boolean visible) {
+ Image cardFace = ImageIO.GetInstance().getImage(dataPath + card.cardType.name().toLowerCase() + ".png");
+ CardView.drawCard(g, r, card, cardFace, backOfCard, visible);
+
+ if (visible) {
+ // Draw decorative cat image if card is visible
+ Image catImg;
+ if (cardCatImageMapping.containsKey(card.getComponentID())) {
+ catImg = cardCatImageMapping.get(card.getComponentID());
+ } else {
+ // Random selection from those available
+ int choice = rnd.nextInt(catImages.size());
+ catImg = ImageIO.GetInstance().getImage(catImages.get(choice));
+ catImages.remove(choice);
+ cardCatImageMapping.put(card.getComponentID(), catImg);
+ }
+ double scaleW = 1.0 * defaultItemSize / catImg.getWidth(null);
+ int height = (int) (catImg.getHeight(null) * scaleW);
+ g.drawImage(catImg, r.x + ekCardWidth / 2 - defaultItemSize / 2, r.y + ekCardHeight / 2, defaultItemSize, height, null);
+ }
+ }
+
+}
diff --git a/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDiscardView.java b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDiscardView.java
new file mode 100644
index 000000000..33d0a0bb1
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensDiscardView.java
@@ -0,0 +1,78 @@
+package games.explodingkittensOLD.gui;
+
+import core.actions.AbstractAction;
+import core.components.Component;
+import core.components.Deck;
+import games.explodingkittensOLD.actions.*;
+import games.explodingkittensOLD.actions.reactions.ChooseSeeTheFutureOrder;
+import games.explodingkittensOLD.cards.ExplodingKittensCard;
+
+import java.awt.*;
+import java.util.*;
+
+public class ExplodingKittensDiscardView extends ExplodingKittensDeckView {
+
+ // Images for Action Stack
+ ArrayList catImagesBkup;
+
+ // This view adds action stack as cards in the discard pile for display
+ Stack actionStack;
+
+ /**
+ * Constructor initialising information and adding key/mouse listener for card highlight (left click or ALT + hover
+ * allows showing the highlighted card on top of all others).
+ * @param d - deck to draw
+ * @param visible - true if whole deck visible
+ * @param dataPath - path to assets
+ */
+ public ExplodingKittensDiscardView(Deck d, Stack actionStack, boolean visible, String dataPath) {
+ super(-1, d, visible, dataPath);
+ this.actionStack = actionStack;
+ catImagesBkup = new ArrayList<>();
+ catImagesBkup.addAll(catImages);
+ }
+
+ /**
+ * In the case of ExplodingKittens Discard we also display the contents of the action stack
+ * @param g - Graphics object
+ */
+ public void drawDeck(Graphics2D g) {
+ Component oldComponent = component;
+ @SuppressWarnings("unchecked") Deck deckCopy = ((Deck) component).copy();
+
+ // Add cards played from action stack into the copy of the deck
+ for (AbstractAction aa: actionStack) {
+ ExplodingKittensCard card = getStackCard(aa);
+ if (card != null) {
+ deckCopy.add(card);
+ }
+ }
+ // set thie copy to tbe the component we draw
+ component = deckCopy;
+ // and draw it
+ super.drawDeck(g);
+ // then reset to the original
+ component = oldComponent;
+ }
+
+ /**
+ * Turns an action into a card
+ * @param aa - action to turn to card
+ * @return - Exploding kittens card
+ */
+ private ExplodingKittensCard getStackCard(AbstractAction aa) {
+ if (aa instanceof AttackAction) {
+ return new ExplodingKittensCard(ExplodingKittensCard.CardType.ATTACK, 0);
+ } else if (aa instanceof ChooseSeeTheFutureOrder) {
+ return new ExplodingKittensCard(ExplodingKittensCard.CardType.FAVOR, 1);
+ } else if (aa instanceof NopeAction) {
+ return new ExplodingKittensCard(ExplodingKittensCard.CardType.SEETHEFUTURE, 3);
+ } else if (aa instanceof ShuffleAction) {
+ return new ExplodingKittensCard(ExplodingKittensCard.CardType.SHUFFLE, 4);
+ } else if (aa instanceof SkipAction) {
+ return new ExplodingKittensCard(ExplodingKittensCard.CardType.SKIP, 5);
+ }
+ return null;
+ }
+
+}
diff --git a/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensGUIManager.java b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensGUIManager.java
new file mode 100644
index 000000000..528bffb0d
--- /dev/null
+++ b/src/main/java/games/explodingkittensOLD/gui/ExplodingKittensGUIManager.java
@@ -0,0 +1,167 @@
+package games.explodingkittensOLD.gui;
+
+import gui.AbstractGUIManager;
+import gui.GamePanel;
+import core.AbstractGameState;
+import core.AbstractPlayer;
+import core.Game;
+import games.explodingkittensOLD.ExplodingKittensParameters;
+import games.explodingkittensOLD.ExplodingKittensGameState;
+import gui.IScreenHighlight;
+import players.human.ActionController;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.EtchedBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.util.Set;
+
+
+public class ExplodingKittensGUIManager extends AbstractGUIManager {
+ // Settings for display areas
+ final static int playerAreaWidth = 300;
+ final static int playerAreaHeight = 135;
+ final static int ekCardWidth = 90;
+ final static int ekCardHeight = 110;
+
+ // List of player hand views
+ ExplodingKittensDeckView[] playerHands;
+ // Discard pile view
+ ExplodingKittensDiscardView discardPile;
+ // Draw pile view
+ ExplodingKittensDeckView drawPile;
+
+ // Currently active player
+ int activePlayer = -1;
+ // Border highlight of active player
+ Border highlightActive = BorderFactory.createLineBorder(new Color(220, 169, 11), 3);
+ Border[] playerViewBorders;
+
+ public ExplodingKittensGUIManager(GamePanel parent, Game game, ActionController ac, Set humanID) {
+ super(parent, game, ac, humanID);
+
+ if (game != null) {
+ AbstractGameState gameState = game.getGameState();
+ if (gameState != null) {
+ // Initialise active player
+ activePlayer = gameState.getCurrentPlayer();
+
+ // Find required size of window
+ int nPlayers = gameState.getNPlayers();
+ int nHorizAreas = 1 + (nPlayers <= 3 ? 2 : nPlayers == 4 ? 3 : nPlayers <= 8 ? 4 : 5);
+ double nVertAreas = 5;
+ this.width = playerAreaWidth * nHorizAreas;
+ this.height = (int) (playerAreaHeight * nVertAreas) + 20;
+
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+ ExplodingKittensParameters ekgp = (ExplodingKittensParameters) gameState.getGameParameters();
+
+ // Create main game area that will hold all game views
+ playerHands = new ExplodingKittensDeckView[nPlayers];
+ playerViewBorders = new Border[nPlayers];
+ JPanel mainGameArea = new JPanel();
+ mainGameArea.setLayout(new BorderLayout());
+
+ // Player hands go on the edges
+ String[] locations = new String[]{BorderLayout.NORTH, BorderLayout.EAST, BorderLayout.SOUTH, BorderLayout.WEST};
+ JPanel[] sides = new JPanel[]{new JPanel(), new JPanel(), new JPanel(), new JPanel()};
+ int next = 0;
+ for (int i = 0; i < nPlayers; i++) {
+ ExplodingKittensDeckView playerHand = new ExplodingKittensDeckView(i, ekgs.getPlayerHandCards().get(i), false, ekgp.getDataPath());
+
+ // Get agent name
+ String[] split = game.getPlayers().get(i).getClass().toString().split("\\.");
+ String agentName = split[split.length - 1];
+
+ // Create border, layouts and keep track of this view
+ TitledBorder title = BorderFactory.createTitledBorder(
+ BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), "Player " + i + " [" + agentName + "]",
+ TitledBorder.CENTER, TitledBorder.BELOW_BOTTOM);
+ playerViewBorders[i] = title;
+ playerHand.setBorder(title);
+ sides[next].add(playerHand);
+ sides[next].setLayout(new GridBagLayout());
+ next = (next + 1) % (locations.length);
+ playerHands[i] = playerHand;
+ }
+ for (int i = 0; i < locations.length; i++) {
+ mainGameArea.add(sides[i], locations[i]);
+ }
+
+ // Discard and draw piles go in the center
+ JPanel centerArea = new JPanel();
+ centerArea.setLayout(new BoxLayout(centerArea, BoxLayout.Y_AXIS));
+ discardPile = new ExplodingKittensDiscardView(ekgs.getDiscardPile(), ekgs.getActionStack(), true, ekgp.getDataPath());
+ drawPile = new ExplodingKittensDeckView(-1, ekgs.getDrawPile(), gameState.getCoreGameParameters().alwaysDisplayFullObservable, ekgp.getDataPath());
+ centerArea.add(drawPile);
+ centerArea.add(discardPile);
+ JPanel jp = new JPanel();
+ jp.setLayout(new GridBagLayout());
+ jp.add(centerArea);
+ mainGameArea.add(jp, BorderLayout.CENTER);
+
+ // Top area will show state information
+ JPanel infoPanel = createGameStateInfoPanel("Exploding Kittens", gameState, width, defaultInfoPanelHeight);
+ // Bottom area will show actions available
+ JComponent actionPanel = createActionPanel(new IScreenHighlight[0], width, defaultActionPanelHeight, false, true, null, null, null);
+
+ // Add all views to frame
+ parent.setLayout(new BorderLayout());
+ parent.add(mainGameArea, BorderLayout.CENTER);
+ parent.add(infoPanel, BorderLayout.NORTH);
+ parent.add(actionPanel, BorderLayout.SOUTH);
+
+ parent.revalidate();
+ parent.setVisible(true);
+ parent.repaint();
+ }
+ }
+
+ }
+
+ @Override
+ public int getMaxActionSpace() {
+ return 25;
+ }
+
+ @Override
+ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
+ if (gameState != null) {
+ if (gameState.getCurrentPlayer() != activePlayer) {
+ playerHands[activePlayer].setCardHighlight(-1);
+ activePlayer = gameState.getCurrentPlayer();
+ }
+
+ // Update decks and visibility
+ ExplodingKittensGameState ekgs = (ExplodingKittensGameState) gameState;
+ for (int i = 0; i < gameState.getNPlayers(); i++) {
+ playerHands[i].updateComponent(ekgs.getPlayerHandCards().get(i));
+ if (i == gameState.getCurrentPlayer() && gameState.getCoreGameParameters().alwaysDisplayCurrentPlayer
+ || humanPlayerIds.contains(i)
+ || gameState.getCoreGameParameters().alwaysDisplayFullObservable) {
+ playerHands[i].setFront(true);
+ playerHands[i].setFocusable(true);
+ } else {
+ playerHands[i].setFront(false);
+ }
+
+ // Highlight active player
+ if (i == gameState.getCurrentPlayer()) {
+ Border compound = BorderFactory.createCompoundBorder(
+ highlightActive, playerViewBorders[i]);
+ playerHands[i].setBorder(compound);
+ } else {
+ playerHands[i].setBorder(playerViewBorders[i]);
+ }
+ }
+ discardPile.updateComponent(ekgs.getDiscardPile());
+ discardPile.setFocusable(true);
+ drawPile.updateComponent(ekgs.getDrawPile());
+ if (humanPlayerIds.contains(activePlayer) || gameState.getCoreGameParameters().alwaysDisplayFullObservable)
+ drawPile.setFront(true);
+
+ }
+ }
+
+}
diff --git a/src/main/java/games/hearts/HeartsGameState.java b/src/main/java/games/hearts/HeartsGameState.java
index 25b5e77c6..0b434acb4 100644
--- a/src/main/java/games/hearts/HeartsGameState.java
+++ b/src/main/java/games/hearts/HeartsGameState.java
@@ -67,6 +67,9 @@ public Deck getDrawDeck() {
return drawDeck;
}
+ /**
+ * returns a List in playerID order of the player hands
+ */
public List> getPlayerDecks() {
return playerDecks;
}
@@ -110,6 +113,7 @@ else if (card.equals(params.qosCard)) {
}
}
+ // The player's points from all previous hands
public int getPlayerPoints(int playerID) {
return playerPoints.getOrDefault(playerID, 0);
}
@@ -193,10 +197,7 @@ protected double _getHeuristicScore(int playerId) {
}
/**
- * For Hearts a lower score is better than a high one. So we return the negative of the player's score.
- *
- * @param playerId - player observing the state.
- * @return
+ * For Hearts a lower score is better than a high one. So we return the negative of the player's points.
*/
@Override
public double getGameScore(int playerId) {
@@ -247,6 +248,10 @@ public int hashCode() {
return result;
}
+ /**
+ * A player's trick deck is the Deck of cards they have won (only the ones with points) in
+ * previous tricks this round.
+ */
public List> getPlayerTrickDecks() {
return trickDecks;
}
diff --git a/src/main/java/games/loveletter/LoveLetterForwardModel.java b/src/main/java/games/loveletter/LoveLetterForwardModel.java
index dd8222ac1..2347b6902 100644
--- a/src/main/java/games/loveletter/LoveLetterForwardModel.java
+++ b/src/main/java/games/loveletter/LoveLetterForwardModel.java
@@ -8,6 +8,7 @@
import core.interfaces.ITreeActionSpace;
import games.GameType;
import games.loveletter.actions.PlayCard;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import utilities.ActionTreeNode;
@@ -58,7 +59,7 @@ private void setupRound(LoveLetterGameState llgs, Set previousWinners)
// Add all cards to the draw pile
llgs.drawPile.clear();
- for (HashMap.Entry entry : llp.cardCounts.entrySet()) {
+ for (HashMap.Entry entry : llp.cardCounts.entrySet()) {
for (int i = 0; i < entry.getValue(); i++) {
LoveLetterCard card = new LoveLetterCard(entry.getKey());
llgs.drawPile.add(card);
@@ -326,19 +327,19 @@ public List _computeAvailableActions(AbstractGameState gameState
Deck playerDeck = llgs.playerHandCards.get(playerID);
// in case a player holds the countess and either the king or the prince, the countess needs to be played
- LoveLetterCard.CardType cardTypeForceCountess = llgs.needToForceCountess(playerDeck);
+ CardType cardTypeForceCountess = llgs.needToForceCountess(playerDeck);
// We create the respective actions for each card on the player's hand
for (int card = 0; card < playerDeck.getSize(); card++) {
- LoveLetterCard.CardType cardType = playerDeck.getComponents().get(card).cardType;
- if (cardType != LoveLetterCard.CardType.Countess && cardTypeForceCountess != null) continue;
+ CardType cardType = playerDeck.getComponents().get(card).cardType;
+ if (cardType != CardType.Countess && cardTypeForceCountess != null) continue;
int cardIdx;
if (actionSpace.context == ActionSpace.Context.Dependent) cardIdx = card;
else cardIdx = -1; // Independent and default
if (actionSpace.structure == ActionSpace.Structure.Flat || actionSpace.structure == ActionSpace.Structure.Default) {
- actions.addAll(cardType.getFlatActions(llgs, cardIdx, playerID, true));
+ actions.addAll(cardType.flatActions(llgs, cardIdx, playerID, true));
} else if (actionSpace.structure == ActionSpace.Structure.Deep) {
- actions.addAll(cardType.getDeepActions(llgs, cardIdx, playerID, true));
+ actions.addAll(cardType.deepActions(llgs, cardIdx, playerID, true));
}
}
@@ -355,10 +356,10 @@ public ActionTreeNode updateActionTree(ActionTreeNode root, AbstractGameState ga
PlayCard llAction = (PlayCard) action;
// TODO - Probaly a better way to get the card names
- LoveLetterCard.CardType[] soloCards = new LoveLetterCard.CardType[]{
- LoveLetterCard.CardType.Handmaid,
- LoveLetterCard.CardType.Countess,
- LoveLetterCard.CardType.Princess
+ CardType[] soloCards = new CardType[]{
+ CardType.Handmaid,
+ CardType.Countess,
+ CardType.Princess
};
// Actions stored in the card type layer (Layer 1)
@@ -370,7 +371,7 @@ public ActionTreeNode updateActionTree(ActionTreeNode root, AbstractGameState ga
else {
ActionTreeNode cardNode = root.findChildrenByName(llAction.getCardType().toString().toLowerCase());
ActionTreeNode playerNode = cardNode.findChildrenByName("player" + llAction.getTargetPlayer());
- if (llAction.getCardType() == LoveLetterCard.CardType.Guard) {
+ if (llAction.getCardType() == CardType.Guard) {
if (llAction.getTargetCardType() == null) {
playerNode.getChildren().get(0).setAction(action);
} else {
diff --git a/src/main/java/games/loveletter/LoveLetterGameState.java b/src/main/java/games/loveletter/LoveLetterGameState.java
index ae08b65d5..b595e69c6 100644
--- a/src/main/java/games/loveletter/LoveLetterGameState.java
+++ b/src/main/java/games/loveletter/LoveLetterGameState.java
@@ -9,6 +9,7 @@
import core.interfaces.IPrintable;
import evaluation.metrics.Event;
import games.GameType;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.*;
@@ -116,6 +117,7 @@ protected double _getHeuristicScore(int playerId) {
}
@Override
+ /** Returns the score of the player (the number of affection tokens) */
public double getGameScore(int playerId) {
return affectionTokens[playerId];
}
@@ -156,10 +158,10 @@ void updateComponents() {
* @param playerDeck - deck of player to check
* @return - card type of the card that forces the countess to be played, null if countess not forced
*/
- public LoveLetterCard.CardType needToForceCountess(Deck playerDeck) {
+ public CardType needToForceCountess(Deck playerDeck) {
boolean ownsCountess = false;
for (LoveLetterCard card : playerDeck.getComponents()) {
- if (card.cardType == LoveLetterCard.CardType.Countess) {
+ if (card.cardType == CardType.Countess) {
ownsCountess = true;
break;
}
@@ -167,7 +169,7 @@ public LoveLetterCard.CardType needToForceCountess(Deck playerDe
if (ownsCountess) {
for (LoveLetterCard card : playerDeck.getComponents()) {
- if (card.cardType == LoveLetterCard.CardType.Prince || card.cardType == LoveLetterCard.CardType.King) {
+ if (card.cardType == CardType.Prince || card.cardType == CardType.King) {
return card.cardType;
}
}
@@ -182,7 +184,7 @@ public LoveLetterCard.CardType needToForceCountess(Deck playerDe
* @param targetPlayer - ID of player killed
* @param cardType - card used to kill
*/
- public void killPlayer(int whoKill, int targetPlayer, LoveLetterCard.CardType cardType) {
+ public void killPlayer(int whoKill, int targetPlayer, CardType cardType) {
setPlayerResult(CoreConstants.GameResult.LOSE_ROUND, targetPlayer);
// a losing player needs to discard all cards
@@ -201,6 +203,7 @@ public Deck getReserveCards() {
return reserveCards;
}
+ /** Player is protected by the Handmaid */
public boolean isProtected(int playerID) {
return effectProtection[playerID];
}
@@ -209,6 +212,7 @@ public void setProtection(int playerID, boolean protection) {
effectProtection[playerID] = protection;
}
+ /** Number of cards left in the draw deck */
public int getRemainingCards() {
return drawPile.getSize();
}
@@ -225,6 +229,10 @@ public PartialObservableDeck getDrawPile() {
return drawPile;
}
+ /**
+ * Returns the affection tokens for each player in an array.
+ * The index of the array corresponds to the player ID.
+ */
public int[] getAffectionTokens() {
return affectionTokens;
}
diff --git a/src/main/java/games/loveletter/LoveLetterHeuristic.java b/src/main/java/games/loveletter/LoveLetterHeuristic.java
index f483307ba..08418f5c2 100644
--- a/src/main/java/games/loveletter/LoveLetterHeuristic.java
+++ b/src/main/java/games/loveletter/LoveLetterHeuristic.java
@@ -5,6 +5,7 @@
import core.components.PartialObservableDeck;
import core.interfaces.IStateHeuristic;
import evaluation.optimisation.TunableParameters;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import utilities.Utils;
@@ -12,7 +13,7 @@
import java.util.Set;
import java.util.stream.IntStream;
-import static games.loveletter.cards.LoveLetterCard.CardType.*;
+import static games.loveletter.cards.CardType.*;
public class LoveLetterHeuristic extends TunableParameters implements IStateHeuristic {
@@ -70,7 +71,7 @@ public double evaluateState(AbstractGameState gs, int playerId) {
}
double cardValues = 0;
- Set cardTypes = new HashSet<>();
+ Set cardTypes = new HashSet<>();
for (LoveLetterCard card : llgs.getPlayerHandCards().get(playerId).getComponents()) {
cardValues += card.cardType.getValue();
cardTypes.add(card.cardType);
diff --git a/src/main/java/games/loveletter/LoveLetterParameters.java b/src/main/java/games/loveletter/LoveLetterParameters.java
index 1c60dabdd..696faf42b 100644
--- a/src/main/java/games/loveletter/LoveLetterParameters.java
+++ b/src/main/java/games/loveletter/LoveLetterParameters.java
@@ -4,7 +4,7 @@
import core.Game;
import evaluation.optimisation.TunableParameters;
import games.GameType;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.Arrays;
import java.util.HashMap;
@@ -18,15 +18,15 @@ public class LoveLetterParameters extends TunableParameters {
String dataPath = "data/loveletter/";
// Occurrence count for each card
- public HashMap cardCounts = new HashMap() {{
- put(LoveLetterCard.CardType.Princess, 1);
- put(LoveLetterCard.CardType.Countess, 1);
- put(LoveLetterCard.CardType.King, 1);
- put(LoveLetterCard.CardType.Prince, 2);
- put(LoveLetterCard.CardType.Handmaid, 2);
- put(LoveLetterCard.CardType.Baron, 2);
- put(LoveLetterCard.CardType.Priest, 2);
- put(LoveLetterCard.CardType.Guard, 5);
+ public HashMap cardCounts = new HashMap() {{
+ put(CardType.Princess, 1);
+ put(CardType.Countess, 1);
+ put(CardType.King, 1);
+ put(CardType.Prince, 2);
+ put(CardType.Handmaid, 2);
+ put(CardType.Baron, 2);
+ put(CardType.Priest, 2);
+ put(CardType.Guard, 5);
}};
// How many cards each player draws
@@ -44,7 +44,7 @@ public LoveLetterParameters() {
addTunableParameter("nTokensWin2", 7, Arrays.asList(3,4,5,6,7,8,9,10));
addTunableParameter("nTokensWin3", 5, Arrays.asList(3,4,5,6,7,8,9,10));
addTunableParameter("nTokensWin4", 4, Arrays.asList(3,4,5,6,7,8,9,10));
- for (LoveLetterCard.CardType c: cardCounts.keySet()) {
+ for (CardType c: cardCounts.keySet()) {
addTunableParameter(c.name() + " count", cardCounts.get(c), Arrays.asList(1,2,3,4,5));
}
_reset();
diff --git a/src/main/java/games/loveletter/actions/BaronAction.java b/src/main/java/games/loveletter/actions/BaronAction.java
index 54cba9334..7669e8921 100644
--- a/src/main/java/games/loveletter/actions/BaronAction.java
+++ b/src/main/java/games/loveletter/actions/BaronAction.java
@@ -5,17 +5,18 @@
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
/**
* The Baron lets two players compare their hand card. The player with the lesser valued card is removed from the game.
*/
public class BaronAction extends PlayCard implements IPrintable {
- private transient LoveLetterCard.CardType playerCard;
- private transient LoveLetterCard.CardType opponentCard;
+ private transient CardType playerCard;
+ private transient CardType opponentCard;
public BaronAction(int cardIdx, int playerID, int opponentID, boolean canExecuteEffect, boolean discard) {
- super(LoveLetterCard.CardType.Baron, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
+ super(CardType.Baron, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/GuardAction.java b/src/main/java/games/loveletter/actions/GuardAction.java
index f0edb210b..b9d70453a 100644
--- a/src/main/java/games/loveletter/actions/GuardAction.java
+++ b/src/main/java/games/loveletter/actions/GuardAction.java
@@ -4,6 +4,7 @@
import core.components.Deck;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
/**
@@ -12,8 +13,8 @@
*/
public class GuardAction extends PlayCard implements IPrintable {
- public GuardAction(int cardIdx, int playerID, int opponentID, LoveLetterCard.CardType cardtype, boolean canExecuteEffect, boolean discard) {
- super(LoveLetterCard.CardType.Guard, cardIdx, playerID, opponentID, cardtype, null, canExecuteEffect, discard);
+ public GuardAction(int cardIdx, int playerID, int opponentID, CardType cardtype, boolean canExecuteEffect, boolean discard) {
+ super(CardType.Guard, cardIdx, playerID, opponentID, cardtype, null, canExecuteEffect, discard);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/HandmaidAction.java b/src/main/java/games/loveletter/actions/HandmaidAction.java
index 87a8aff7d..9567c12bb 100644
--- a/src/main/java/games/loveletter/actions/HandmaidAction.java
+++ b/src/main/java/games/loveletter/actions/HandmaidAction.java
@@ -1,9 +1,8 @@
package games.loveletter.actions;
-import core.AbstractGameState;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
/**
* The handmaid protects the player from any targeted effects until the next turn.
@@ -11,7 +10,7 @@
public class HandmaidAction extends PlayCard implements IPrintable {
public HandmaidAction(int cardIdx, int playerID) {
- super(LoveLetterCard.CardType.Handmaid, cardIdx, playerID, -1, null, null, true, true);
+ super(CardType.Handmaid, cardIdx, playerID, -1, null, null, true, true);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/KingAction.java b/src/main/java/games/loveletter/actions/KingAction.java
index fd0cce54c..0711d379c 100644
--- a/src/main/java/games/loveletter/actions/KingAction.java
+++ b/src/main/java/games/loveletter/actions/KingAction.java
@@ -5,6 +5,7 @@
import core.components.Deck;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
@@ -14,7 +15,7 @@
public class KingAction extends PlayCard implements IPrintable {
public KingAction(int cardidx, int playerID, int opponentID, boolean canExecuteEffect, boolean discard) {
- super(LoveLetterCard.CardType.King, cardidx, playerID, opponentID, null, null, canExecuteEffect, discard);
+ super(CardType.King, cardidx, playerID, opponentID, null, null, canExecuteEffect, discard);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/PlayCard.java b/src/main/java/games/loveletter/actions/PlayCard.java
index 2b48cd519..398e12d19 100644
--- a/src/main/java/games/loveletter/actions/PlayCard.java
+++ b/src/main/java/games/loveletter/actions/PlayCard.java
@@ -5,6 +5,7 @@
import core.components.Deck;
import core.components.PartialObservableDeck;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.Objects;
@@ -12,15 +13,15 @@
public class PlayCard extends AbstractAction {
protected final int playerID;
final int targetPlayer;
- protected final LoveLetterCard.CardType cardType;
- final LoveLetterCard.CardType forcedCountessCardType;
+ protected final CardType cardType;
+ final CardType forcedCountessCardType;
final boolean canExecuteEffect;
final boolean discard;
protected final int cardIdx;
- LoveLetterCard.CardType targetCardType, otherCardInHand;
+ CardType targetCardType, otherCardInHand;
- public PlayCard(LoveLetterCard.CardType cardType, int cardIdx, int playerID, int targetPlayer, LoveLetterCard.CardType targetCardType, LoveLetterCard.CardType forcedCountessCardType, boolean canExecuteEffect, boolean discard) {
+ public PlayCard(CardType cardType, int cardIdx, int playerID, int targetPlayer, CardType targetCardType, CardType forcedCountessCardType, boolean canExecuteEffect, boolean discard) {
this.cardType = cardType;
this.playerID = playerID;
this.targetPlayer = targetPlayer;
@@ -30,7 +31,7 @@ public PlayCard(LoveLetterCard.CardType cardType, int cardIdx, int playerID, int
this.discard = discard;
this.cardIdx = cardIdx;
}
- public PlayCard(int cardIdx, int playerID, boolean discard, LoveLetterCard.CardType targetCardType) {
+ public PlayCard(int cardIdx, int playerID, boolean discard, CardType targetCardType) {
this.cardType = null;
this.playerID = playerID;
this.targetPlayer = -1;
@@ -137,15 +138,15 @@ public int getTargetPlayer() {
return targetPlayer;
}
- public LoveLetterCard.CardType getCardType() {
+ public CardType getCardType() {
return cardType;
}
- public LoveLetterCard.CardType getForcedCountessCardType() {
+ public CardType getForcedCountessCardType() {
return forcedCountessCardType;
}
- public LoveLetterCard.CardType getTargetCardType() {
+ public CardType getTargetCardType() {
return targetCardType;
}
@@ -157,7 +158,7 @@ public boolean isDiscard() {
return discard;
}
- public LoveLetterCard.CardType getOtherCardInHand() {
+ public CardType getOtherCardInHand() {
return otherCardInHand;
}
diff --git a/src/main/java/games/loveletter/actions/PriestAction.java b/src/main/java/games/loveletter/actions/PriestAction.java
index 9c9a9139c..9efc8c5c1 100644
--- a/src/main/java/games/loveletter/actions/PriestAction.java
+++ b/src/main/java/games/loveletter/actions/PriestAction.java
@@ -1,10 +1,10 @@
package games.loveletter.actions;
import core.AbstractGameState;
-import core.CoreConstants;
import core.components.PartialObservableDeck;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
/**
@@ -14,7 +14,7 @@
public class PriestAction extends PlayCard implements IPrintable {
public PriestAction(int cardIdx, int playerID, int opponentID, boolean canExecuteEffect, boolean discard) {
- super(LoveLetterCard.CardType.Priest, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
+ super(CardType.Priest, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/PrinceAction.java b/src/main/java/games/loveletter/actions/PrinceAction.java
index 703fd2b48..ded638f6b 100644
--- a/src/main/java/games/loveletter/actions/PrinceAction.java
+++ b/src/main/java/games/loveletter/actions/PrinceAction.java
@@ -4,10 +4,9 @@
import core.components.Deck;
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
-import java.util.Objects;
-
/**
* The targeted player discards its current and draws a new one.
* In case the discarded card is a princess, the targeted player is removed from the game.
@@ -15,7 +14,7 @@
public class PrinceAction extends PlayCard implements IPrintable {
public PrinceAction(int cardIdx, int playerID, int opponentID, boolean canExecuteEffect, boolean discard) {
- super(LoveLetterCard.CardType.Prince, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
+ super(CardType.Prince, cardIdx, playerID, opponentID, null, null, canExecuteEffect, discard);
}
@Override
@@ -29,7 +28,7 @@ protected boolean _execute(LoveLetterGameState llgs) {
// if the discarded card is a princess, the targeted player loses the game
targetCardType = card.cardType;
- if (targetCardType == LoveLetterCard.CardType.Princess) {
+ if (targetCardType == CardType.Princess) {
llgs.killPlayer(playerID, targetPlayer, cardType);
if (llgs.getCoreGameParameters().recordEventHistory) {
llgs.recordHistory("Player " + targetPlayer + " discards Princess and loses!");
diff --git a/src/main/java/games/loveletter/actions/deep/DeepBaronAction.java b/src/main/java/games/loveletter/actions/deep/DeepBaronAction.java
index 76037492c..67c9a5741 100644
--- a/src/main/java/games/loveletter/actions/deep/DeepBaronAction.java
+++ b/src/main/java/games/loveletter/actions/deep/DeepBaronAction.java
@@ -3,7 +3,7 @@
import core.AbstractGameState;
import core.actions.AbstractAction;
import games.loveletter.LoveLetterGameState;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.List;
@@ -13,7 +13,7 @@
public class DeepBaronAction extends PlayCardDeep {
public DeepBaronAction(int cardIdx, int playerID) {
- super(LoveLetterCard.CardType.Baron, cardIdx, playerID);
+ super(CardType.Baron, cardIdx, playerID);
}
@Override
@@ -24,7 +24,7 @@ public boolean equals(Object obj) {
@Override
public List _computeAvailableActions(AbstractGameState state) {
assert cardType != null;
- return cardType.getFlatActions((LoveLetterGameState) state, cardIdx, playerID, false);
+ return cardType.flatActions((LoveLetterGameState) state, cardIdx, playerID, false);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/deep/DeepGuardAction.java b/src/main/java/games/loveletter/actions/deep/DeepGuardAction.java
index 1cf95f529..f860799c1 100644
--- a/src/main/java/games/loveletter/actions/deep/DeepGuardAction.java
+++ b/src/main/java/games/loveletter/actions/deep/DeepGuardAction.java
@@ -7,7 +7,7 @@
import core.interfaces.IPrintable;
import games.loveletter.LoveLetterGameState;
import games.loveletter.actions.PlayCard;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.ArrayList;
import java.util.List;
@@ -27,7 +27,7 @@ enum Step {
private Step step;
public DeepGuardAction(int cardIdx, int playerID) {
- super(LoveLetterCard.CardType.Guard, cardIdx, playerID);
+ super(CardType.Guard, cardIdx, playerID);
step = Step.TargetPlayer;
targetPlayer = -1;
}
@@ -61,7 +61,7 @@ public List _computeAvailableActions(AbstractGameState state) {
if (cardActions.size() == 0) cardActions.add(new ChoosePlayer(-1));
} else {
// Complete actions
- cardActions.addAll(LoveLetterCard.CardType.Guard.getFlatActions(gs, new PlayCard(cardIdx, playerID, false, targetPlayer)));
+ cardActions.addAll(CardType.Guard.flatActions(gs, new PlayCard(cardIdx, playerID, false, targetPlayer)));
}
return cardActions;
}
diff --git a/src/main/java/games/loveletter/actions/deep/DeepKingAction.java b/src/main/java/games/loveletter/actions/deep/DeepKingAction.java
index 8c06c1b17..e17541d30 100644
--- a/src/main/java/games/loveletter/actions/deep/DeepKingAction.java
+++ b/src/main/java/games/loveletter/actions/deep/DeepKingAction.java
@@ -3,7 +3,7 @@
import core.AbstractGameState;
import core.actions.AbstractAction;
import games.loveletter.LoveLetterGameState;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.List;
@@ -13,7 +13,7 @@
public class DeepKingAction extends PlayCardDeep {
public DeepKingAction(int cardIdx, int playerID) {
- super(LoveLetterCard.CardType.King, cardIdx, playerID);
+ super(CardType.King, cardIdx, playerID);
}
@Override
@@ -24,7 +24,7 @@ public boolean equals(Object obj) {
@Override
public List _computeAvailableActions(AbstractGameState state) {
assert cardType != null;
- return cardType.getFlatActions((LoveLetterGameState) state, cardIdx, playerID, false);
+ return cardType.flatActions((LoveLetterGameState) state, cardIdx, playerID, false);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/deep/DeepPriestAction.java b/src/main/java/games/loveletter/actions/deep/DeepPriestAction.java
index ed922cbf8..132513d75 100644
--- a/src/main/java/games/loveletter/actions/deep/DeepPriestAction.java
+++ b/src/main/java/games/loveletter/actions/deep/DeepPriestAction.java
@@ -3,7 +3,7 @@
import core.AbstractGameState;
import core.actions.AbstractAction;
import games.loveletter.LoveLetterGameState;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.List;
@@ -14,7 +14,7 @@
public class DeepPriestAction extends PlayCardDeep {
public DeepPriestAction(int cardIdx, int playerID) {
- super(LoveLetterCard.CardType.Priest, cardIdx, playerID);
+ super(CardType.Priest, cardIdx, playerID);
}
@Override
@@ -25,7 +25,7 @@ public boolean equals(Object obj) {
@Override
public List _computeAvailableActions(AbstractGameState state) {
assert cardType != null;
- return cardType.getFlatActions((LoveLetterGameState) state, cardIdx, playerID, false);
+ return cardType.flatActions((LoveLetterGameState) state, cardIdx, playerID, false);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/deep/DeepPrinceAction.java b/src/main/java/games/loveletter/actions/deep/DeepPrinceAction.java
index 7d7f76334..0d355016d 100644
--- a/src/main/java/games/loveletter/actions/deep/DeepPrinceAction.java
+++ b/src/main/java/games/loveletter/actions/deep/DeepPrinceAction.java
@@ -3,7 +3,7 @@
import core.AbstractGameState;
import core.actions.AbstractAction;
import games.loveletter.LoveLetterGameState;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.List;
@@ -14,13 +14,13 @@
public class DeepPrinceAction extends PlayCardDeep {
public DeepPrinceAction(int cardIdx, int playerId) {
- super(LoveLetterCard.CardType.Prince, cardIdx, playerId);
+ super(CardType.Prince, cardIdx, playerId);
}
@Override
public List _computeAvailableActions(AbstractGameState state) {
assert cardType != null;
- return cardType.getFlatActions((LoveLetterGameState) state, cardIdx, playerID, false);
+ return cardType.flatActions((LoveLetterGameState) state, cardIdx, playerID, false);
}
@Override
diff --git a/src/main/java/games/loveletter/actions/deep/PlayCardDeep.java b/src/main/java/games/loveletter/actions/deep/PlayCardDeep.java
index 417869d3d..5de126bd2 100644
--- a/src/main/java/games/loveletter/actions/deep/PlayCardDeep.java
+++ b/src/main/java/games/loveletter/actions/deep/PlayCardDeep.java
@@ -4,14 +4,14 @@
import core.actions.AbstractAction;
import core.interfaces.IExtendedSequence;
import games.loveletter.actions.PlayCard;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.Objects;
public abstract class PlayCardDeep extends PlayCard implements IExtendedSequence {
private boolean executed;
- public PlayCardDeep(LoveLetterCard.CardType cardType, int cardIdx, int playerID) {
+ public PlayCardDeep(CardType cardType, int cardIdx, int playerID) {
super(cardType, cardIdx, playerID, -1, null, null, false, true);
}
diff --git a/src/main/java/games/loveletter/cards/CardType.java b/src/main/java/games/loveletter/cards/CardType.java
new file mode 100644
index 000000000..d5c87e727
--- /dev/null
+++ b/src/main/java/games/loveletter/cards/CardType.java
@@ -0,0 +1,196 @@
+package games.loveletter.cards;
+
+import core.CoreConstants;
+import core.actions.AbstractAction;
+import games.loveletter.LoveLetterGameState;
+import games.loveletter.LoveLetterParameters;
+import games.loveletter.actions.*;
+import games.loveletter.actions.deep.*;
+
+import java.util.*;
+import java.util.function.*;
+
+// each card consists of a type and a value (between 1 and 8). the card type defines the actions available to the player
+public enum CardType {
+ Princess(8, "In case the princess is discarded or played the player is immediately removed from the game."),
+ Countess(7, "The Countess needs to be discarded in case the player also hold a King or a Prince card."),
+ King(6, "The King lets two players swap their hand cards."),
+ Prince(5, "The targeted player discards its current and draws a new one."),
+ Handmaid(4, "The handmaid protects the player from any targeted effects until the next turn."),
+ Baron(3, "The Baron lets two players compare their hand card. The player with the lesser valued card is removed from the game."),
+ Priest(2, "The Priest allows a player to see another player's hand cards."),
+ Guard(1, "The guard allows to attempt guessing another player's card. If the guess is correct, the targeted opponent is removed from the game.");
+
+ private final String cardText;
+ private final int value;
+
+ CardType(int value, String text) {
+ this.value = value;
+ this.cardText = text;
+ }
+
+ /** Numeric value of card from 1 to 8 */
+ public int getValue() {
+ return value;
+ }
+
+ // The value of the highest card in the game
+ public static int getMaxCardValue() {
+ return 8;
+ }
+
+ // Summarises the functionality of the card
+ public String getCardText(LoveLetterParameters params) {
+ return this.name() + " (" + value + "; x" + params.cardCounts.get(this) + "): " + cardText;
+ }
+
+ // Action factory
+ private BiFunction> generateFlatActions, generateDeepActions;
+
+ static {
+ Princess.generateFlatActions = (gs, play) -> Collections.singletonList(new PlayCard(CardType.Princess, play.getCardIdx(), play.getPlayerID(), -1, null, null, true, true));
+ Handmaid.generateFlatActions = (gs, play) -> Collections.singletonList(new HandmaidAction(play.getCardIdx(), play.getPlayerID()));
+ Countess.generateFlatActions = (gs, play) -> Collections.singletonList(
+ new PlayCard(Countess, play.getCardIdx(), play.getPlayerID(), -1, null, gs.needToForceCountess(gs.getPlayerHandCards().get(play.getPlayerID())), true, play.isDiscard()));
+ Priest.generateFlatActions = (gs, play) -> {
+ int p = play.getPlayerID();
+ boolean discard = play.isDiscard();
+ List cardActions = new ArrayList<>();
+ for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
+ if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
+ continue;
+ cardActions.add(new PriestAction(play.getCardIdx(), p, targetPlayer, true, discard));
+ }
+ if (cardActions.isEmpty()) cardActions.add(new PriestAction(play.getCardIdx(), p, p, false, discard));
+ return cardActions;
+ };
+ Priest.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepPriestAction(play.getCardIdx(), play.getPlayerID()));
+ King.generateFlatActions = (gs, play) -> {
+ int p = play.getPlayerID();
+ boolean discard = play.isDiscard();
+ List cardActions = new ArrayList<>();
+ for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
+ if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
+ continue;
+ cardActions.add(new KingAction(play.getCardIdx(), p, targetPlayer, true, discard));
+ }
+ if (cardActions.isEmpty()) cardActions.add(new KingAction(play.getCardIdx(), p, p, false, discard));
+ return cardActions;
+ };
+ King.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepKingAction(play.getCardIdx(), play.getPlayerID()));
+ Baron.generateFlatActions = (gs, play) -> {
+ int p = play.getPlayerID();
+ boolean discard = play.isDiscard();
+ List cardActions = new ArrayList<>();
+ for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
+ if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
+ continue;
+ cardActions.add(new BaronAction(play.getCardIdx(), p, targetPlayer, true, discard));
+ }
+ if (cardActions.isEmpty()) cardActions.add(new BaronAction(play.getCardIdx(), p, p, false, discard));
+ return cardActions;
+ };
+ Baron.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepBaronAction(play.getCardIdx(), play.getPlayerID()));
+ Prince.generateFlatActions = (gs, play) -> {
+ int p = play.getPlayerID();
+ boolean discard = play.isDiscard();
+ List cardActions = new ArrayList<>();
+ for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
+ if (gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
+ continue;
+ cardActions.add(new PrinceAction(play.getCardIdx(), p, targetPlayer, true, discard));
+ }
+ return cardActions;
+ };
+ Prince.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepPrinceAction(play.getCardIdx(), play.getPlayerID()));
+ Guard.generateFlatActions = (gs, play) -> {
+ int p = play.getPlayerID();
+ boolean discard = play.isDiscard();
+ int target = play.getTargetPlayer();
+
+ List cardActions = new ArrayList<>();
+ if (target == -1) {
+ for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
+ if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
+ continue;
+ for (CardType type : CardType.values()) {
+ if (type != CardType.Guard) {
+ cardActions.add(new GuardAction(play.getCardIdx(), p, targetPlayer, type, true, discard));
+ }
+ }
+ }
+ } else {
+ for (CardType type : CardType.values()) {
+ if (type != CardType.Guard) {
+ cardActions.add(new GuardAction(play.getCardIdx(), p, target, type, true, discard));
+ }
+ }
+ }
+ if (cardActions.isEmpty()) {
+ // Target yourself, but don't guess yourself out of the game
+ cardActions.add(new GuardAction(play.getCardIdx(), p, p, Guard, false, discard));
+ }
+ return cardActions;
+ };
+ Guard.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepGuardAction(play.getCardIdx(), play.getPlayerID()));
+ }
+
+ public List flatActions(LoveLetterGameState gs, PlayCard play) {
+ if (generateFlatActions != null) return generateFlatActions.apply(gs, play);
+ return new ArrayList<>();
+ }
+
+ public List flatActions(LoveLetterGameState gs, int cardIdx, int playerId, boolean discard) {
+ if (generateFlatActions != null) return generateFlatActions.apply(gs, new PlayCard(cardIdx, playerId, discard));
+ return new ArrayList<>();
+ }
+
+ public List deepActions(LoveLetterGameState gs, int cardIdx, int playerId, boolean discard) {
+ PlayCard play = new PlayCard(cardIdx, playerId, discard);
+ if (generateDeepActions != null) return generateDeepActions.apply(gs, play);
+ return flatActions(gs, play);
+ }
+
+ // To string
+ private Function generateString;
+
+ static {
+ Princess.generateString = play -> "Princess (" + play.getPlayerID() + " loses the game)";
+ Countess.generateString = play -> {
+ if (play.getForcedCountessCardType() == null) return "Countess (no effect)";
+ return "Countess (auto discard with " + play.getForcedCountessCardType() + ")";
+ };
+ King.generateString = play -> "King (" + play.getPlayerID() + " trades hands with " + play.getTargetPlayer() + ")";
+ Prince.generateString = play -> "Prince (" + play.getTargetPlayer() + " discards " + (play.getTargetCardType() != null ? play.getTargetCardType() : "card") + " and draws a new card)";
+ Handmaid.generateString = play -> "Handmaid (" + play.getPlayerID() + " is protected until their next turn)";
+ Baron.generateString = play -> {
+ if (play.getTargetCardType() == null) {
+ return "Baron (" + play.getPlayerID() + " compares cards with " + play.getTargetPlayer() + ")";
+ } else {
+ return "Baron (" + play.getPlayerID() + " " + play.getOtherCardInHand() + " vs " + play.getTargetPlayer() + " " + play.getTargetCardType() + ")";
+ }
+ };
+ Priest.generateString = play -> "Priest (" + play.getPlayerID() + " sees " + (play.getTargetCardType() != null ? play.getTargetCardType() : "card") + " of " + play.getTargetPlayer() + ")";
+ Guard.generateString = play -> "Guard (" + play.getPlayerID() + " guess " + play.getTargetPlayer() + " holds card " + play.getTargetCardType().name() + ")";
+ }
+
+ public String getString(PlayCard play) {
+ if (generateString != null) {
+ return generateString.apply(play);
+ } else return "";
+ }
+
+ // Execute
+ private BiConsumer execute;
+
+ public void execute(LoveLetterGameState gs, PlayCard play) {
+ if (execute != null) {
+ execute.accept(gs, play);
+ }
+ }
+
+ static {
+ Princess.execute = (gs, play) -> gs.killPlayer(play.getPlayerID(), play.getPlayerID(), Princess);
+
+ }
+}
diff --git a/src/main/java/games/loveletter/cards/LoveLetterCard.java b/src/main/java/games/loveletter/cards/LoveLetterCard.java
index 46596d923..8d43e40fc 100644
--- a/src/main/java/games/loveletter/cards/LoveLetterCard.java
+++ b/src/main/java/games/loveletter/cards/LoveLetterCard.java
@@ -1,186 +1,9 @@
package games.loveletter.cards;
-import core.CoreConstants;
-import core.actions.AbstractAction;
import core.components.Card;
-import games.loveletter.LoveLetterGameState;
-import games.loveletter.LoveLetterParameters;
-import games.loveletter.actions.*;
-import games.loveletter.actions.deep.*;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Function;
public class LoveLetterCard extends Card {
- // each card consists of a type and a value. the card type defines the actions available to the player
- public enum CardType {
- Princess(8, "In case the princess is discarded or played the player is immediately removed from the game."),
- Countess(7, "The Countess needs to be discarded in case the player also hold a King or a Prince card."),
- King(6, "The King lets two players swap their hand cards."),
- Prince(5, "The targeted player discards its current and draws a new one."),
- Handmaid(4, "The handmaid protects the player from any targeted effects until the next turn."),
- Baron(3, "The Baron lets two players compare their hand card. The player with the lesser valued card is removed from the game."),
- Priest(2, "The Priest allows a player to see another player's hand cards."),
- Guard(1, "The guard allows to attempt guessing another player's card. If the guess is correct, the targeted opponent is removed from the game.");
-
- private final String cardText;
- private final int value;
- CardType(int value, String text){
- this.value = value;
- this.cardText = text;
- }
- public int getValue(){ return value;}
- public static int getMaxCardValue() { return 8; }
- public String getCardText(LoveLetterParameters params) {
- return this.name() + " (" + value + "; x" + params.cardCounts.get(this) + "): " + cardText;
- }
-
- // Action factory
- private BiFunction> generateFlatActions, generateDeepActions;
- static {
- Princess.generateFlatActions = (gs, play) -> Collections.singletonList(new PlayCard(LoveLetterCard.CardType.Princess, play.getCardIdx(), play.getPlayerID(), -1, null, null, true, true));
- Handmaid.generateFlatActions = (gs, play) -> Collections.singletonList(new HandmaidAction(play.getCardIdx(), play.getPlayerID()));
- Countess.generateFlatActions = (gs, play) -> Collections.singletonList(
- new PlayCard(Countess, play.getCardIdx(), play.getPlayerID(), -1, null, gs.needToForceCountess(gs.getPlayerHandCards().get(play.getPlayerID())), true, play.isDiscard()));
- Priest.generateFlatActions = (gs, play) -> {
- int p = play.getPlayerID();
- boolean discard = play.isDiscard();
- List cardActions = new ArrayList<>();
- for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
- if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
- continue;
- cardActions.add(new PriestAction(play.getCardIdx(), p, targetPlayer, true, discard));
- }
- if (cardActions.isEmpty()) cardActions.add(new PriestAction(play.getCardIdx(), p, p, false, discard));
- return cardActions;
- };
- Priest.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepPriestAction(play.getCardIdx(), play.getPlayerID()));
- King.generateFlatActions = (gs, play) -> {
- int p = play.getPlayerID();
- boolean discard = play.isDiscard();
- List cardActions = new ArrayList<>();
- for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
- if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
- continue;
- cardActions.add(new KingAction(play.getCardIdx(), p, targetPlayer, true, discard));
- }
- if (cardActions.isEmpty()) cardActions.add(new KingAction(play.getCardIdx(), p, p, false, discard));
- return cardActions;
- };
- King.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepKingAction(play.getCardIdx(), play.getPlayerID()));
- Baron.generateFlatActions = (gs, play) -> {
- int p = play.getPlayerID();
- boolean discard = play.isDiscard();
- List cardActions = new ArrayList<>();
- for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
- if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
- continue;
- cardActions.add(new BaronAction(play.getCardIdx(), p, targetPlayer, true, discard));
- }
- if (cardActions.isEmpty()) cardActions.add(new BaronAction(play.getCardIdx(), p, p, false, discard));
- return cardActions;};
- Baron.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepBaronAction(play.getCardIdx(), play.getPlayerID()));
- Prince.generateFlatActions = (gs, play) -> {
- int p = play.getPlayerID();
- boolean discard = play.isDiscard();
- List cardActions = new ArrayList<>();
- for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
- if (gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
- continue;
- cardActions.add(new PrinceAction(play.getCardIdx(), p, targetPlayer, true, discard));
- }
- return cardActions;};
- Prince.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepPrinceAction(play.getCardIdx(), play.getPlayerID()));
- Guard.generateFlatActions = (gs, play) -> {
- int p = play.getPlayerID();
- boolean discard = play.isDiscard();
- int target = play.getTargetPlayer();
-
- List cardActions = new ArrayList<>();
- if (target == -1) {
- for (int targetPlayer = 0; targetPlayer < gs.getNPlayers(); targetPlayer++) {
- if (targetPlayer == p || gs.getPlayerResults()[targetPlayer] == CoreConstants.GameResult.LOSE_ROUND || gs.isProtected(targetPlayer))
- continue;
- for (LoveLetterCard.CardType type : LoveLetterCard.CardType.values()) {
- if (type != LoveLetterCard.CardType.Guard) {
- cardActions.add(new GuardAction(play.getCardIdx(), p, targetPlayer, type, true, discard));
- }
- }
- }
- } else {
- for (LoveLetterCard.CardType type : LoveLetterCard.CardType.values()) {
- if (type != LoveLetterCard.CardType.Guard) {
- cardActions.add(new GuardAction(play.getCardIdx(), p, target, type, true, discard));
- }
- }
- }
- if (cardActions.isEmpty()) {
- // Target yourself, but don't guess yourself out of the game
- cardActions.add(new GuardAction(play.getCardIdx(), p, p, Guard, false, discard));
- }
- return cardActions;
- };
- Guard.generateDeepActions = (gs, play) -> Collections.singletonList(new DeepGuardAction(play.getCardIdx(), play.getPlayerID()));
- }
- public List getFlatActions(LoveLetterGameState gs, PlayCard play) {
- if (generateFlatActions != null) return generateFlatActions.apply(gs, play);
- return new ArrayList<>();
- }
- public List getFlatActions(LoveLetterGameState gs, int cardIdx, int playerId, boolean discard) {
- if (generateFlatActions != null) return generateFlatActions.apply(gs, new PlayCard(cardIdx, playerId, discard));
- return new ArrayList<>();
- }
- public List getDeepActions(LoveLetterGameState gs, int cardIdx, int playerId, boolean discard) {
- PlayCard play = new PlayCard(cardIdx, playerId, discard);
- if (generateDeepActions != null) return generateDeepActions.apply(gs, play);
- return getFlatActions(gs, play);
- }
-
- // To string
- private Function generateString;
- static {
- Princess.generateString = play -> "Princess (" + play.getPlayerID() + " loses the game)";
- Countess.generateString = play -> {
- if (play.getForcedCountessCardType() == null) return "Countess (no effect)";
- return "Countess (auto discard with " + play.getForcedCountessCardType() + ")";
- };
- King.generateString = play -> "King (" + play.getPlayerID() + " trades hands with " + play.getTargetPlayer() + ")";
- Prince.generateString = play -> "Prince (" + play.getTargetPlayer() + " discards " + (play.getTargetCardType() != null? play.getTargetCardType() : "card") + " and draws a new card)";
- Handmaid.generateString = play -> "Handmaid (" + play.getPlayerID() + " is protected until their next turn)";
- Baron.generateString = play -> {
- if (play.getTargetCardType() == null) {
- return "Baron (" + play.getPlayerID() + " compares cards with " + play.getTargetPlayer() + ")";
- } else {
- return "Baron (" + play.getPlayerID() + " " + play.getOtherCardInHand() + " vs " + play.getTargetPlayer() + " " + play.getTargetCardType() + ")";
- }
- };
- Priest.generateString = play -> "Priest (" + play.getPlayerID() + " sees " + (play.getTargetCardType() != null? play.getTargetCardType() : "card") + " of " + play.getTargetPlayer() + ")";
- Guard.generateString = play -> "Guard (" + play.getPlayerID() + " guess " + play.getTargetPlayer() + " holds card " + play.getTargetCardType().name() + ")";
- }
- public String getString(PlayCard play) {
- if (generateString != null) {
- return generateString.apply(play);
- } else return "";
- }
-
- // Execute
- private BiConsumer execute;
- public void execute(LoveLetterGameState gs, PlayCard play) {
- if (execute != null) {
- execute.accept(gs, play);
- }
- }
- static {
- Princess.execute = (gs, play) -> gs.killPlayer(play.getPlayerID(), play.getPlayerID(), Princess);
-
- }
- }
-
public final CardType cardType;
public LoveLetterCard(CardType cardType) {
diff --git a/src/main/java/games/loveletter/features/LLActionFeaturesLarge.java b/src/main/java/games/loveletter/features/LLActionFeaturesLarge.java
index 01c196836..912b8e9d6 100644
--- a/src/main/java/games/loveletter/features/LLActionFeaturesLarge.java
+++ b/src/main/java/games/loveletter/features/LLActionFeaturesLarge.java
@@ -6,12 +6,12 @@
import core.interfaces.IActionFeatureVector;
import games.loveletter.LoveLetterGameState;
import games.loveletter.actions.*;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.ArrayList;
import java.util.List;
-import static java.util.stream.Collectors.toList;
import static utilities.Utils.enumNames;
import static utilities.Utils.enumToOneHot;
@@ -19,16 +19,16 @@ public class LLActionFeaturesLarge implements IActionFeatureVector {
final String[] localNames;
- final int featuresPerGroup = LoveLetterCard.CardType.values().length;
+ final int featuresPerGroup = CardType.values().length;
public LLActionFeaturesLarge() {
List allNames = new ArrayList<>();
// For each card type, is this the card played
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_PLAY").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_PLAY").toList());
// For each card type, is this the card guessed (where that is relevant - for GUARD)
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_GUESS").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_GUESS").toList());
// For each card type, does the target player have this card (to our knowledge)
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_HAS").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_HAS").toList());
// current position of target player
allNames.add("TARGET_ORDINAL");
localNames = allNames.toArray(new String[0]);
@@ -45,8 +45,8 @@ public double[] featureVector(AbstractAction a, AbstractGameState state, int pla
LoveLetterGameState llgs = (LoveLetterGameState) state;
if (!(a instanceof PlayCard))
return retValue;
- LoveLetterCard.CardType cardPlayed = ((PlayCard) a).getCardType();
- LoveLetterCard.CardType cardGuessed = ((PlayCard) a).getTargetCardType();
+ CardType cardPlayed = ((PlayCard) a).getCardType();
+ CardType cardGuessed = ((PlayCard) a).getTargetCardType();
System.arraycopy(enumToOneHot(cardPlayed), 0, retValue, 0, featuresPerGroup);
if (cardGuessed != null)
System.arraycopy(enumToOneHot(cardGuessed), 0, retValue, featuresPerGroup, featuresPerGroup);
diff --git a/src/main/java/games/loveletter/features/LLActionFeaturesMedium.java b/src/main/java/games/loveletter/features/LLActionFeaturesMedium.java
index 289604fb6..3ca2e8536 100644
--- a/src/main/java/games/loveletter/features/LLActionFeaturesMedium.java
+++ b/src/main/java/games/loveletter/features/LLActionFeaturesMedium.java
@@ -5,7 +5,7 @@
import core.interfaces.IActionFeatureVector;
import games.loveletter.LoveLetterGameState;
import games.loveletter.actions.PlayCard;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
import java.util.ArrayList;
import java.util.List;
@@ -17,14 +17,14 @@
public class LLActionFeaturesMedium implements IActionFeatureVector {
final String[] localNames;
- final int featuresPerGroup = LoveLetterCard.CardType.values().length;
+ final int featuresPerGroup = CardType.values().length;
public LLActionFeaturesMedium() {
List allNames = new ArrayList<>();
// For each card type, is this the card played
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_PLAY").collect(toList()));
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_PLAY").collect(toList()));
// For each card type, is this the card guessed (where that is relevant - for GUARD)
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_GUESS").collect(toList()));
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_GUESS").collect(toList()));
localNames = allNames.toArray(new String[0]);
}
@@ -39,8 +39,8 @@ public double[] featureVector(AbstractAction a, AbstractGameState state, int pla
LoveLetterGameState llgs = (LoveLetterGameState) state;
if (!(a instanceof PlayCard))
return retValue;
- LoveLetterCard.CardType cardPlayed = ((PlayCard) a).getCardType();
- LoveLetterCard.CardType cardGuessed = ((PlayCard) a).getTargetCardType();
+ CardType cardPlayed = ((PlayCard) a).getCardType();
+ CardType cardGuessed = ((PlayCard) a).getTargetCardType();
System.arraycopy(enumToOneHot(cardPlayed), 0, retValue, 0, featuresPerGroup);
if (cardGuessed != null)
System.arraycopy(enumToOneHot(cardGuessed), 0, retValue, featuresPerGroup, featuresPerGroup);
diff --git a/src/main/java/games/loveletter/features/LLActionFeaturesTiny.java b/src/main/java/games/loveletter/features/LLActionFeaturesTiny.java
index 0b2e53f8c..c64211d0d 100644
--- a/src/main/java/games/loveletter/features/LLActionFeaturesTiny.java
+++ b/src/main/java/games/loveletter/features/LLActionFeaturesTiny.java
@@ -4,23 +4,19 @@
import core.actions.AbstractAction;
import core.interfaces.IActionFeatureVector;
import games.loveletter.actions.PlayCard;
-import games.loveletter.cards.LoveLetterCard;
+import games.loveletter.cards.CardType;
-import java.util.ArrayList;
-import java.util.List;
-
-import static java.util.stream.Collectors.toList;
import static utilities.Utils.enumNames;
import static utilities.Utils.enumToOneHot;
public class LLActionFeaturesTiny implements IActionFeatureVector {
final String[] localNames;
- final int featuresPerGroup = LoveLetterCard.CardType.values().length;
+ final int featuresPerGroup = CardType.values().length;
public LLActionFeaturesTiny() {
// For each card type, is this the card played
- localNames = enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_PLAY").toArray(String[]::new);
+ localNames = enumNames(CardType.class).stream().map(s -> s + "_PLAY").toArray(String[]::new);
}
@Override
@@ -33,7 +29,7 @@ public double[] featureVector(AbstractAction a, AbstractGameState state, int pla
double[] retValue = new double[names().length];
if (!(a instanceof PlayCard))
return retValue;
- LoveLetterCard.CardType cardPlayed = ((PlayCard) a).getCardType();
+ CardType cardPlayed = ((PlayCard) a).getCardType();
System.arraycopy(enumToOneHot(cardPlayed), 0, retValue, 0, featuresPerGroup);
return retValue;
}
diff --git a/src/main/java/games/loveletter/features/LLStateFeatures.java b/src/main/java/games/loveletter/features/LLStateFeatures.java
index 6ecc83fc3..01ff9d678 100644
--- a/src/main/java/games/loveletter/features/LLStateFeatures.java
+++ b/src/main/java/games/loveletter/features/LLStateFeatures.java
@@ -4,6 +4,7 @@
import core.components.PartialObservableDeck;
import core.interfaces.IComponentContainer;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import players.heuristics.AbstractStateFeature;
@@ -12,9 +13,6 @@
import java.util.List;
import java.util.stream.IntStream;
-import static games.loveletter.cards.LoveLetterCard.CardType.Guard;
-import static games.loveletter.cards.LoveLetterCard.CardType.getMaxCardValue;
-import static java.util.stream.Collectors.toList;
import static utilities.Utils.enumNames;
public class LLStateFeatures extends AbstractStateFeature {
@@ -25,13 +23,13 @@ public class LLStateFeatures extends AbstractStateFeature {
public LLStateFeatures() {
List allNames = new ArrayList<>(Arrays.asList("PROTECTED", "HIDDEN", "CARDS", "DRAW_DECK"));
// For each card type, do we have one in hand
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_HAND").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_HAND").toList());
// Card is in Hand and is also Known to at least one other player
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_KNOWN").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_KNOWN").toList());
// Percentage of card type in Discard
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_DISCARD").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_DISCARD").toList());
// Card is known to be in hand of another player
- allNames.addAll(enumNames(LoveLetterCard.CardType.class).stream().map(s -> s + "_OTHER").toList());
+ allNames.addAll(enumNames(CardType.class).stream().map(s -> s + "_OTHER").toList());
localNames = allNames.toArray(new String[0]);
}
@@ -55,7 +53,7 @@ protected double[] localFeatureVector(AbstractGameState gs, int playerID) {
LoveLetterGameState state = (LoveLetterGameState) gs;
double[] retValue = new double[localNames.length];
- int featuresPerGroup = LoveLetterCard.CardType.values().length;
+ int featuresPerGroup = CardType.values().length;
double cardValues = 0;
PartialObservableDeck hand = state.getPlayerHandCards().get(playerID);
diff --git a/src/main/java/games/loveletter/features/LLStateFeaturesReduced.java b/src/main/java/games/loveletter/features/LLStateFeaturesReduced.java
index 579792fbf..512e4ea91 100644
--- a/src/main/java/games/loveletter/features/LLStateFeaturesReduced.java
+++ b/src/main/java/games/loveletter/features/LLStateFeaturesReduced.java
@@ -5,13 +5,14 @@
import core.interfaces.IStateFeatureVector;
import games.loveletter.LoveLetterGameState;
import games.loveletter.LoveLetterParameters;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.IntStream;
-import static games.loveletter.cards.LoveLetterCard.CardType.*;
+import static games.loveletter.cards.CardType.*;
/**
* A set of features designed to tie in exactly with those used in LoveLetterHeuristic
@@ -31,7 +32,7 @@ public double[] featureVector(AbstractGameState gs, int playerId) {
double cardValues = 0;
- Set cardTypes = new HashSet<>();
+ Set cardTypes = new HashSet<>();
for (LoveLetterCard card : llgs.getPlayerHandCards().get(playerId).getComponents()) {
cardValues += card.cardType.getValue();
cardTypes.add(card.cardType);
diff --git a/src/main/java/games/loveletter/features/LLStateFeaturesTunable.java b/src/main/java/games/loveletter/features/LLStateFeaturesTunable.java
index 71cda8c49..3d639dcfc 100644
--- a/src/main/java/games/loveletter/features/LLStateFeaturesTunable.java
+++ b/src/main/java/games/loveletter/features/LLStateFeaturesTunable.java
@@ -4,6 +4,7 @@
import core.components.PartialObservableDeck;
import evaluation.features.TunableStateFeatures;
import games.loveletter.LoveLetterGameState;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.HashSet;
@@ -11,7 +12,7 @@
import java.util.stream.IntStream;
import static core.CoreConstants.GameResult.LOSE_ROUND;
-import static games.loveletter.cards.LoveLetterCard.CardType.*;
+import static games.loveletter.cards.CardType.*;
/**
* A set of features designed to tie in exactly with those used in LoveLetterHeuristic
@@ -37,7 +38,7 @@ public double[] fullFeatureVector(AbstractGameState gs, int playerId) {
double cardValues = 0;
- Set cardTypes = new HashSet<>();
+ Set cardTypes = new HashSet<>();
if (active[0]) {
for (LoveLetterCard card : llgs.getPlayerHandCards().get(playerId).getComponents()) {
cardValues += card.cardType.getValue();
diff --git a/src/main/java/games/loveletter/gui/LoveLetterGUIManager.java b/src/main/java/games/loveletter/gui/LoveLetterGUIManager.java
index 5b9d32e55..3f60b6c3b 100644
--- a/src/main/java/games/loveletter/gui/LoveLetterGUIManager.java
+++ b/src/main/java/games/loveletter/gui/LoveLetterGUIManager.java
@@ -321,7 +321,7 @@ protected void _update(AbstractPlayer player, AbstractGameState gameState) {
llgs = (LoveLetterGameState)gameState.copy();
for (int i = 0; i < gameState.getNPlayers(); i++) {
boolean front = i == gameState.getCurrentPlayer() && gameState.getCoreGameParameters().alwaysDisplayCurrentPlayer
- || humanPlayerId.contains(i)
+ || humanPlayerIds.contains(i)
|| gameState.getCoreGameParameters().alwaysDisplayFullObservable;
playerHands[i].update(llgs, front);
diff --git a/src/main/java/games/loveletter/stats/LoveLetterMetrics.java b/src/main/java/games/loveletter/stats/LoveLetterMetrics.java
index 07292a097..49a97be6e 100644
--- a/src/main/java/games/loveletter/stats/LoveLetterMetrics.java
+++ b/src/main/java/games/loveletter/stats/LoveLetterMetrics.java
@@ -12,6 +12,7 @@
import games.loveletter.LoveLetterGameState;
import games.loveletter.actions.PlayCard;
import games.loveletter.actions.PrinceAction;
+import games.loveletter.cards.CardType;
import games.loveletter.cards.LoveLetterCard;
import java.util.*;
@@ -41,12 +42,12 @@ protected boolean _run(MetricsGameListener listener, Event e, Map getDefaultEventTypes() {
public static class CardStatsGameEvent extends AbstractMetric {
Set