diff --git a/client/src/main/java/object/EntropyAchievementsTracker.java b/client/src/main/java/object/EntropyAchievementsTracker.java index f8e72860..691c3c5b 100644 --- a/client/src/main/java/object/EntropyAchievementsTracker.java +++ b/client/src/main/java/object/EntropyAchievementsTracker.java @@ -1,6 +1,7 @@ package object; import achievement.AchievementUtilKt; +import game.EntropyBidAction; import game.Suit; import util.AchievementsUtil; import util.Registry; @@ -85,11 +86,10 @@ public void unlockPerfectBidAchievements(boolean earnedPsychic) earnedGardener, earnedSpaceman, earnedPsychic); } - public void updatePerfectBidVariables(Bid lastBid) + public void updatePerfectBidVariables(EntropyBidAction lastBid) { - EntropyBid entropyBid = (EntropyBid)lastBid; - var bidSuit = entropyBid.getBidSuit(); - + var bidSuit = lastBid.getSuit(); + if (bidSuit == Suit.Spades) { earnedGardener = true; @@ -116,26 +116,23 @@ else if (bidSuit == Suit.Stars) } } - public void update(Bid bid) + public void update(EntropyBidAction bid) { - EntropyBid entropyBid = (EntropyBid)bid; - - updateCardReveal(entropyBid); - updateMonotone(entropyBid); + updateCardReveal(bid); + updateMonotone(bid); } - private void updateCardReveal(EntropyBid bidMade) + private void updateCardReveal(EntropyBidAction bidMade) { String card = bidMade.getCardToReveal(); - if (card.isEmpty()) + if (card == null) { return; } cardsRevealed++; - - var bidSuit = bidMade.getBidSuit(); - if (isCardRelevant(card, bidSuit)) + + if (isCardRelevant(card, bidMade.getSuit())) { revealedSameSuit = true; } @@ -144,17 +141,17 @@ private void updateCardReveal(EntropyBid bidMade) revealedDifferentSuit = true; } } - - private void updateMonotone(EntropyBid bid) + + private void updateMonotone(EntropyBidAction bid) { - var suitCode = bid.getBidSuit(); + var suit = bid.getSuit(); if (firstSuitBid == null) { - firstSuitBid = suitCode; + firstSuitBid = suit; return; } - if (suitCode != firstSuitBid) + if (suit != firstSuitBid) { deviatedFromFirstSuit = true; } diff --git a/client/src/main/java/online/screen/EntropyRoom.java b/client/src/main/java/online/screen/EntropyRoom.java index 2449aebc..dd57a1c8 100644 --- a/client/src/main/java/online/screen/EntropyRoom.java +++ b/client/src/main/java/online/screen/EntropyRoom.java @@ -3,19 +3,19 @@ import java.awt.BorderLayout; import java.util.UUID; +import game.EntropyBidAction; import game.GameSettings; -import game.Suit; -import object.Bid; import object.EntropyAchievementsTracker; import object.EntropyBid; import screen.EntropyBidPanel; +import util.ClientUtil; import util.Registry; import util.ReplayConstants; import static game.CardsUtilKt.countSuit; import static game.CardsUtilKt.extractCards; -public class EntropyRoom extends GameRoom +public class EntropyRoom extends GameRoom { private EntropyAchievementsTracker achievementTracker = new EntropyAchievementsTracker(); @@ -23,7 +23,7 @@ public EntropyRoom(UUID id, String roomName, GameSettings settings, int players) { super(id, roomName, settings, players); - bidPanel = new EntropyBidPanel(); + bidPanel = new EntropyBidPanel(ClientUtil.getUsername(), handPanel); leftPaneSouth.add(bidPanel, BorderLayout.CENTER); bidPanel.addBidListener(this); } @@ -37,7 +37,7 @@ public void doSpecificResetGameVariables() @Override public void resetBids() { - lastBid = new EntropyBid(Suit.Clubs, 0); + lastBid = null; hmBidByPlayerNumber.clear(); } @@ -46,7 +46,7 @@ public void updateScreenForChallengeOrIllegal() { if (isVisible()) { - var lastBidSuit = ((EntropyBid)lastBid).getBidSuit(); + var lastBidSuit = lastBid.getSuit(); handPanel.displayAndHighlightHands(lastBidSuit); achievementTracker.unlockPerfectBidAchievements(earnedPsychic); @@ -69,18 +69,18 @@ public void saveModeSpecificVariablesForReplay() { int roundsSoFar = replay.getInt(Registry.REPLAY_INT_ROUNDS_SO_FAR, 0); replay.putInt(Registry.REPLAY_INT_GAME_MODE, ReplayConstants.GAME_MODE_ENTROPY_ONLINE); - replay.put(roundsSoFar + Registry.REPLAY_STRING_LAST_BID_SUIT_NAME, ((EntropyBid)lastBid).getBidSuit().name()); + replay.put(roundsSoFar + Registry.REPLAY_STRING_LAST_BID_SUIT_NAME, lastBid.getSuit().name()); replayDialog.roundAdded(); } @Override - public void updatePerfectBidVariables(Bid bid) + public void updatePerfectBidVariables(EntropyBidAction bid) { achievementTracker.updatePerfectBidVariables(bid); } @Override - public void updateAchievementVariables(Bid bid) + public void updateAchievementVariables(EntropyBidAction bid) { achievementTracker.update(bid); } diff --git a/client/src/main/java/online/screen/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index 2f64f4a5..249fa533 100644 --- a/client/src/main/java/online/screen/GameRoom.java +++ b/client/src/main/java/online/screen/GameRoom.java @@ -1,7 +1,6 @@ package online.screen; -import game.GameMode; -import game.GameSettings; +import game.*; import http.dto.RoomSummary; import object.*; import online.util.XmlBuilderClient; @@ -23,6 +22,7 @@ import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.prefs.Preferences; +import java.util.stream.Collectors; import static utils.ColourUtilKt.getColourForPlayerNumber; import static utils.CoreGlobals.logger; @@ -30,10 +30,10 @@ /** * This is an actual room as seen by the player */ -public abstract class GameRoom extends JFrame +public abstract class GameRoom> extends JFrame implements WindowListener, ActionListener, - BidListener, + BidListener, RevealListener, Registry { @@ -60,18 +60,18 @@ public abstract class GameRoom extends JFrame public boolean hasOverbid = false; private boolean seenRoundStart = false; - public Bid lastBid = null; + public B lastBid = null; private ConcurrentHashMap hmPlayerByAdjustedPlayerNumber = new ConcurrentHashMap<>(); public ConcurrentHashMap> hmHandByAdjustedPlayerNumber = new ConcurrentHashMap<>(); - public ConcurrentHashMap hmBidByPlayerNumber = new ConcurrentHashMap<>(); + public ConcurrentHashMap hmBidByPlayerNumber = new ConcurrentHashMap<>(); private int personToStartLocal = -1; private int personToStart = -1; public int lastPlayerToAct = 0; private OnlineChatPanel chatPanel = null; public Preferences replay = null; public ReplayDialog replayDialog = new ReplayDialog(); - public BidPanel bidPanel = null; + public BidPanel bidPanel = null; public GameRoom(UUID id, String roomName, GameSettings settings, int players) { @@ -205,8 +205,8 @@ else if (mode == GameMode.Vectropy) public abstract void resetBids(); public abstract void doSpecificResetGameVariables(); public abstract void saveModeSpecificVariablesForReplay(); - public abstract void updatePerfectBidVariables(Bid bid); - public abstract void updateAchievementVariables(Bid bid); + public abstract void updatePerfectBidVariables(B bid); + public abstract void updateAchievementVariables(B bid); public abstract void unlockEndOfGameAchievements(); private void setIcon() @@ -767,7 +767,7 @@ public void startObserving(int personToStart, int lastPlayerToAct) handPanel.displayHandsOnline(hmHandByAdjustedPlayerNumber); handPanel.setInitted(true); - Bid lastBid = hmBidByPlayerNumber.get(lastPlayerToAct); + PlayerAction lastBid = hmBidByPlayerNumber.get(lastPlayerToAct); if (lastBid != null) { handleBid(lastPlayerToAct, lastBid); @@ -779,39 +779,39 @@ public void clearHands() hmHandByAdjustedPlayerNumber.clear(); } - public void handleBid(int playerNumber, Bid bid) + public void handleBid(int playerNumber, PlayerAction action) { int playerNumberAdjusted = adjustForMe(playerNumber); Player player = hmPlayerByAdjustedPlayerNumber.get(playerNumberAdjusted); - if (player != null) - { - bid.setPlayer(player); - } - addBidToBidBox(playerNumberAdjusted, bid); + addBidToBidBox(playerNumberAdjusted, action); handPanel.selectPlayerInAwtThread(playerNumberAdjusted, false); - if (bid.isChallenge() - || bid.isIllegal()) + if (action instanceof ChallengeAction + || action instanceof IllegalAction) { processChallengeOrIllegal(); return; } + + if (!(action instanceof BidAction)) { + throw new RuntimeException("Wtf"); + } lastPlayerToAct = playerNumber; - lastBid = bid; - hmBidByPlayerNumber.put(playerNumber, bid); + lastBid = (B)action; + hmBidByPlayerNumber.put(playerNumber, lastBid); if (playerNumber != this.playerNumber && !observer) { - bidPanel.adjust(bid); + bidPanel.adjust(lastBid); } if (settings.getCardReveal()) { - String card = bid.getCardToReveal(); - if (!card.isEmpty()) + String card = lastBid.getCardToReveal(); + if (card != null) { handPanel.revealCard(card); player.addRevealedCard(card); @@ -994,21 +994,25 @@ else if (playerNumberAdjusted == playerNumberLocal) } } } - - private int getTotalFromHands() - { - int total = 0; - + + protected List allCards() { + ArrayList cards = new ArrayList<>(); + for (int i=0; i hand = hmHandByAdjustedPlayerNumber.get(i); if (hand != null) { - total += hand.size(); + cards.addAll(hand); } } - - return total; + + return cards; + } + + private int getTotalFromHands() + { + return allCards().size(); } public void setObserver(boolean observer) @@ -1373,12 +1377,10 @@ public void requestFocus() * BidListener */ @Override - public void bidMade(Bid bid) + public void bidMade(B bid) { - //1. Set the player on the bid - Player player = hmPlayerByAdjustedPlayerNumber.get(0); - bid.setPlayer(player); lastBid = bid; + Player player = hmPlayerByAdjustedPlayerNumber.get(0); //2. Disable the bid panel enableBidPanel(false); @@ -1405,8 +1407,8 @@ private void processPlayerBid() //5. Unlock achievements, including specific perfect bid ones updateAchievementVariables(lastBid); - if (lastBid.isPerfect(hmHandByAdjustedPlayerNumber, settings) - && lastBid.isOverAchievementThreshold()) + if (lastBid.isPerfect(allCards(), settings) + && lastBid.overAchievementThreshold()) { updatePerfectBidVariables(lastBid); @@ -1416,7 +1418,7 @@ private void processPlayerBid() } } - boolean overBid = lastBid.isOverbid(hmHandByAdjustedPlayerNumber, settings.getJokerValue()); + boolean overBid = lastBid.isOverbid(allCards(), settings); if (overBid) { hasOverbid = true; diff --git a/client/src/main/java/online/screen/VectropyRoom.java b/client/src/main/java/online/screen/VectropyRoom.java index c6f1c4f4..1baf8a54 100644 --- a/client/src/main/java/online/screen/VectropyRoom.java +++ b/client/src/main/java/online/screen/VectropyRoom.java @@ -1,18 +1,21 @@ package online.screen; -import java.awt.BorderLayout; -import java.util.UUID; - import game.GameSettings; -import object.Bid; -import object.VectropyBid; +import game.VectropyBidAction; import screen.VectropyBidPanel; -import util.*; +import util.AchievementsUtil; +import util.ClientUtil; +import util.Registry; +import util.ReplayConstants; + +import java.awt.*; +import java.util.UUID; import static game.CardsUtilKt.extractCards; import static game.RenderingUtilKt.getVectropyResult; -public class VectropyRoom extends GameRoom + +public class VectropyRoom extends GameRoom { private boolean earnedMathematician = false; @@ -20,7 +23,7 @@ public VectropyRoom(UUID id, String roomName, GameSettings settings, int players { super(id, roomName, settings, players); - bidPanel = new VectropyBidPanel(); + bidPanel = new VectropyBidPanel(ClientUtil.getUsername(), handPanel); leftPaneSouth.add(bidPanel, BorderLayout.CENTER); bidPanel.addBidListener(this); } @@ -34,12 +37,12 @@ public void doSpecificResetGameVariables() @Override public void resetBids() { - lastBid = VectropyBid.factoryEmpty(getIncludeMoons(), getIncludeStars()); + lastBid = null; hmBidByPlayerNumber.clear(); } @Override - public void updatePerfectBidVariables(Bid bid) + public void updatePerfectBidVariables(VectropyBidAction bid) { earnedMathematician = true; } @@ -71,7 +74,7 @@ public void unlockEndOfGameAchievements() } @Override - public void updateAchievementVariables(Bid bid) + public void updateAchievementVariables(VectropyBidAction bid) { //do nothing } diff --git a/client/src/main/java/screen/BidPanel.java b/client/src/main/java/screen/BidPanel.java index 627b4cc5..989fbedf 100644 --- a/client/src/main/java/screen/BidPanel.java +++ b/client/src/main/java/screen/BidPanel.java @@ -2,12 +2,21 @@ import java.util.prefs.Preferences; +import game.BidAction; import object.Bid; import util.BidListener; -public abstract class BidPanel extends TransparentPanel +public abstract class BidPanel> extends TransparentPanel { - public BidListener listener = null; + protected final String playerName; + protected final HandPanelMk2 handPanel; + + BidPanel(String playerName, HandPanelMk2 handPanel) { + this.playerName = playerName; + this.handPanel = handPanel; + } + + public BidListener listener = null; public int maxBid = -1; private boolean logging = false; @@ -19,7 +28,7 @@ public abstract class BidPanel extends TransparentPanel public abstract void saveState(Preferences savedGame); public abstract void fireAppearancePreferencesChange(); public abstract void init(int maxBid, int totalNumberOfCards, boolean online, boolean includeMoons, boolean includeStars, boolean illegalAllowed); - public abstract void adjust(Bid bid); + public abstract void adjust(B bid); public void addBidListener(BidListener listener) { diff --git a/client/src/main/java/screen/EntropyBidPanel.java b/client/src/main/java/screen/EntropyBidPanel.java index 622bd8b1..2620bd5e 100644 --- a/client/src/main/java/screen/EntropyBidPanel.java +++ b/client/src/main/java/screen/EntropyBidPanel.java @@ -24,19 +24,17 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import game.EntropyBidAction; import game.Suit; -import object.Bid; import object.EntropyBid; import util.Debug; import util.Registry; -public class EntropyBidPanel extends BidPanel +public class EntropyBidPanel extends BidPanel implements ActionListener, ChangeListener, Registry { - private String suitSelected = Suit.Clubs.getUnicodeStr(); - private Suit bidSuit = Suit.Clubs; private Suit lastBidSuit = Suit.Clubs; private int lastBidAmount = 0; @@ -45,8 +43,10 @@ public class EntropyBidPanel extends BidPanel private boolean includeStars = false; private boolean online = false; - public EntropyBidPanel() + public EntropyBidPanel(String playerName, HandPanelMk2 handPanel) { + super(playerName, handPanel); + setPreferredSize(new Dimension(550, 150)); bidGroup.add(btnClubs); bidGroup.add(btnDiamonds); @@ -179,9 +179,7 @@ public void init(int maxBid, int totalNumberOfCards, boolean online, boolean inc bidSlider.setValue(1); btnClubs.setSelected(true); - suitSelected = Suit.Clubs.getUnicodeStr(); - bidAmountDisplay.setText("1 " + suitSelected); - setBidAmountDisplayColour(); + updateBidAmountDisplay(); setBidButtonColours(); totalCardsLabel.setText("x " + totalNumberOfCards); @@ -246,17 +244,16 @@ public void enableChallenge(boolean enable) } @Override - public void adjust(Bid bid) + public void adjust(EntropyBidAction bid) { - EntropyBid entropyBid = (EntropyBid)bid; - int lastBidAmount = entropyBid.getBidAmount(); + int lastBidAmount = bid.getAmount(); if (lastBidAmount == 0) { return; } - this.lastBidSuit = entropyBid.getBidSuit(); - this.lastBidAmount = entropyBid.getBidAmount(); + this.lastBidSuit = bid.getSuit(); + this.lastBidAmount = bid.getAmount(); updateSelectionForLastBidSuit(); } @@ -273,12 +270,14 @@ public void fireAppearancePreferencesChange() String back = prefs.get(PREFERENCES_STRING_CARD_BACKS, Registry.BACK_CODE_CLASSIC_BLUE); smallCardIcon.setIcon(new ImageIcon(EntropyScreen.class.getResource("/backs/" + back + "Small.png"))); - setBidAmountDisplayColour(); + updateBidAmountDisplay(); setBidButtonColours(); } - private void setBidAmountDisplayColour() + private void updateBidAmountDisplay() { + String spaceStr = bidSuit == Suit.Moons ? "":" "; + bidAmountDisplay.setText(bidSlider.getValue() + spaceStr + bidSuit.getUnicodeStr()); bidAmountDisplay.setForeground(bidSuit.getColour()); } @@ -359,7 +358,7 @@ public void actionPerformed(ActionEvent arg0) { if (listener != null) { - EntropyBid bid = new EntropyBid(bidSuit, bidSlider.getValue()); + var bid = new EntropyBidAction(playerName, handPanel.isPlayingBlind(), bidSlider.getValue(), bidSuit); listener.bidMade(bid); } } @@ -414,16 +413,13 @@ private void actionPerformedBidButton(Suit suit) { bidSlider.setMinimum(lastBidAmount + 1); } - suitSelected = suit.getUnicodeStr(); - stateChanged(null); - setBidAmountDisplayColour(); + updateBidAmountDisplay(); } @Override public void stateChanged(ChangeEvent arg0) { - String spaceStr = bidSuit == Suit.Moons ? "":" "; - bidAmountDisplay.setText(bidSlider.getValue() + spaceStr + suitSelected); + updateBidAmountDisplay(); } } diff --git a/client/src/main/java/screen/EntropyScreen.java b/client/src/main/java/screen/EntropyScreen.java index 22dc02d8..1102ba20 100644 --- a/client/src/main/java/screen/EntropyScreen.java +++ b/client/src/main/java/screen/EntropyScreen.java @@ -2,6 +2,8 @@ import java.awt.BorderLayout; +import com.fasterxml.jackson.core.JsonProcessingException; +import game.EntropyBidAction; import game.GameMode; import game.Suit; import object.EntropyAchievementsTracker; @@ -12,8 +14,9 @@ import static game.CheatUtilKt.getMaxBidString; import static game.EntropyUtilKt.perfectBidAmount; import static game.EntropyUtilKt.perfectBidSuit; +import static utils.CoreGlobals.jsonMapper; -public class EntropyScreen extends GameScreen +public class EntropyScreen extends GameScreen { private static final long serialVersionUID = 1L; @@ -22,7 +25,7 @@ public class EntropyScreen extends GameScreen public EntropyScreen() { setFocusable(true); - bidPanel = new EntropyBidPanel(); + bidPanel = new EntropyBidPanel(player.getName(), handPanel); bidPanel.showBidPanel(false); setLayout(new BorderLayout(0, 0)); @@ -33,13 +36,11 @@ public EntropyScreen() bidPanel.setLogging(true); bidPanel.addBidListener(this); } - -//David added this comment! @Override public void showResult() { - var lastBidSuit = ((EntropyBid)lastBid).getBidSuit(); + var lastBidSuit = lastBid.getSuit(); handPanel.displayAndHighlightHands(lastBidSuit); int total = countSuit(lastBidSuit, getConcatenatedHands(), settings.getJokerValue()); @@ -63,15 +64,14 @@ protected void initVariablesForNewGame() } @Override - public void saveGame() + public void saveGame() throws JsonProcessingException { super.saveGame(); //save bid amounts and bid suits if (lastBid != null) { - savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID_SUIT_NAME, ((EntropyBid)lastBid).getBidSuit().name()); - savedGame.putInt(Registry.SAVED_GAME_INT_LAST_BID_AMOUNT, ((EntropyBid)lastBid).getBidAmount()); + savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID, jsonMapper.writeValueAsString(lastBid)); } //other booleans @@ -82,23 +82,22 @@ public void saveGame() } @Override - protected void saveRoundForReplay() + protected void saveRoundForReplay() throws JsonProcessingException { int roundsSoFar = inGameReplay.getInt(Registry.REPLAY_INT_ROUNDS_SO_FAR, 0) + 1; - inGameReplay.put(roundsSoFar + Registry.REPLAY_STRING_LAST_BID_SUIT_NAME, ((EntropyBid)lastBid).getBidSuit().name()); + inGameReplay.put(roundsSoFar + Registry.REPLAY_STRING_LAST_BID_SUIT_NAME, lastBid.getSuit().name()); super.saveRoundForReplay(); } @Override - public void loadLastBid() + public void loadLastBid() throws JsonProcessingException { - String lastBidSuitName = savedGame.get(Registry.SAVED_GAME_STRING_LAST_BID_SUIT_NAME, null); - int lastBidAmount = savedGame.getInt(Registry.SAVED_GAME_INT_LAST_BID_AMOUNT, -1); - - if (lastBidSuitName != null) - { - lastBid = new EntropyBid(Suit.valueOf(lastBidSuitName), lastBidAmount); - } + String lastBidStr = savedGame.get(Registry.SAVED_GAME_STRING_LAST_BID, ""); + + if (!lastBidStr.isEmpty()) + { + lastBid = jsonMapper.readValue(lastBidStr, EntropyBidAction.class); + } } @Override @@ -150,7 +149,7 @@ public GameMode getGameMode() @Override public Suit getLastBidSuit() { - return ((EntropyBid)lastBid).getBidSuit(); + return lastBid.getSuit(); } @Override diff --git a/client/src/main/java/screen/GameScreen.java b/client/src/main/java/screen/GameScreen.java index 1f1513a0..34199699 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -1,14 +1,14 @@ package screen; import achievement.AchievementSetting; -import game.GameMode; -import game.GameSettings; -import game.Suit; +import com.fasterxml.jackson.core.JsonProcessingException; +import game.*; import object.Bid; import object.ChallengeBid; import object.IllegalBid; import object.Player; import util.*; +import utils.CoreGlobals; import javax.swing.*; import java.util.Timer; @@ -23,7 +23,7 @@ import static util.ClientGlobals.achievementStore; import static utils.CoreGlobals.logger; -public abstract class GameScreen extends TransparentPanel +public abstract class GameScreen> extends TransparentPanel implements BidListener, RevealListener, Registry @@ -34,8 +34,8 @@ public abstract class GameScreen extends TransparentPanel private int personToStart = -1; private Player currentPlayer = null; private int handicapAmount; - - public Bid lastBid = null; + + protected B lastBid = null; private boolean playBlind; private boolean playWithHandicap; @@ -44,7 +44,7 @@ public abstract class GameScreen extends TransparentPanel private boolean logging = true; public boolean hasOverbid; public boolean hasActedBlindThisGame = false; - + private boolean earnedSpectator = false; public boolean earnedPsychic = false; private boolean currentlyOnChallenge = false; @@ -62,7 +62,7 @@ public abstract class GameScreen extends TransparentPanel public HandPanelMk2 handPanel = new HandPanelMk2(this); //Abstract methods - public abstract void loadLastBid(); + public abstract void loadLastBid() throws JsonProcessingException; public abstract void loadSpecificVariables(); public abstract void showResult(); @@ -211,7 +211,7 @@ private void initVariables() int maxBid = GameUtil.getMaxBid(settings, totalNumberOfCards); bidPanel.init(maxBid, totalNumberOfCards, false, settings.getIncludeMoons(), settings.getIncludeStars(), false); - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); listmodel.removeAllElements(); player.resetHand(); @@ -421,7 +421,12 @@ else if (winningPlayer == 0) protected void roundEnded(int playerLastToAct) { ScreenCache.get(MainScreen.class).enableNewGameOption(true); - saveRoundForReplay(); + + try { + saveRoundForReplay(); + } catch (Exception e) { + logger.error("replay.error", "Failed to save round for replay", e); + } firstRound = false; @@ -453,7 +458,7 @@ protected void roundEnded(int playerLastToAct) } } - protected void saveRoundForReplay() + protected void saveRoundForReplay() throws JsonProcessingException { inGameReplay.putInt(REPLAY_INT_GAME_MODE, ReplayConstantsKt.toReplayConstant(getGameMode())); inGameReplay.put(REPLAY_STRING_OPPONENT_ONE_STRATEGY, opponentOne.getStrategy()); @@ -464,13 +469,13 @@ protected void saveRoundForReplay() inGameReplay.putInt(REPLAY_INT_ROUNDS_SO_FAR, roundsSoFar); //save the listmodel - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); int historySize = listmodel.size(); inGameReplay.putInt(roundsSoFar + REPLAY_INT_HISTORY_SIZE, historySize); for (int i = 0; i < historySize; i++) { - Bid bid = listmodel.get(i); - inGameReplay.put(roundsSoFar + REPLAY_STRING_LISTMODEL + i, bid.toXmlString()); + PlayerAction bid = listmodel.get(i); + inGameReplay.put(roundsSoFar + REPLAY_STRING_LISTMODEL + i, CoreGlobals.jsonMapper.writeValueAsString(bid)); } inGameReplay.putBoolean(REPLAY_BOOLEAN_PLAY_BLIND, playBlind); @@ -512,22 +517,22 @@ protected void saveRoundForReplay() ScreenCache.getReplayDialog(IN_GAME_REPLAY).roundAdded(); } - protected void saveGame() + protected void saveGame() throws JsonProcessingException { savedGame.putBoolean(SAVED_GAME_BOOLEAN_IS_GAME_TO_CONTINUE, true); savedGame.put(SAVED_GAME_STRING_GAME_MODE, getGameMode().name()); //settings settings.exportToRegistry(savedGame); - + //save the listmodel - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); int historySize = listmodel.size(); savedGame.putInt(SAVED_GAME_INT_HISTORY_SIZE, historySize); for (int i=0; i listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); int historySize = savedGame.getInt(SAVED_GAME_INT_HISTORY_SIZE, 0); for (int i = 0; i < historySize; i++) { String modelItem = savedGame.get(SAVED_GAME_STRING_LISTMODEL + i, ""); - Bid bid = Bid.factoryFromXmlString(modelItem, settings.getIncludeMoons(), settings.getIncludeStars()); + PlayerAction bid = CoreGlobals.jsonMapper.readValue(modelItem, PlayerAction.class); listmodel.addElement(bid); } @@ -748,7 +754,7 @@ public void cancelNewRound() nextRoundTimer.cancel(); nextRoundTimer = new Timer("Timer-NextRound"); } - + protected void randomlyReplaceCardsWithJokers() { if (!currentlyOnChallenge) @@ -826,7 +832,6 @@ private void processPlayerBid() boolean actedBlind = handPanel.isPlayingBlind(); hasActedBlindThisGame &= actedBlind; - lastBid.setBlind(actedBlind); addToListmodel(lastBid); updateAchievementVariables(); @@ -835,7 +840,7 @@ private void processPlayerBid() handlePerfectBid(lastBid); } - if (lastBid.isOverbid(getConcatenatedHands(), settings.getJokerValue())) + if (lastBid.isOverbid(getConcatenatedHands(), settings)) { hasOverbid = true; } @@ -843,10 +848,10 @@ private void processPlayerBid() processNextTurn(0); } - private void handlePerfectBid(Bid bid) + private void handlePerfectBid(B bid) { Debug.append("Player made a perfect bid.", logging); - if (bid.isOverAchievementThreshold()) + if (bid.overAchievementThreshold()) { if (handPanel.isPlayingBlind()) { @@ -865,7 +870,7 @@ public void processChallenge(Player challenger) unlockPerfectBidAchievements(); Player playerChallenged = lastBid.getPlayer(); - if (!lastBid.isOverbid(getConcatenatedHands(), settings.getJokerValue())) + if (!lastBid.isOverbid(getConcatenatedHands(), settings)) { Debug.append("not an overbid", logging); setCardsToSubtract(challenger); @@ -1005,7 +1010,7 @@ private void displayHands() handPanel.revealCard(card); } } - + public StrategyParams factoryStrategyParms(Player opponent) { var cardsOnShow = Stream.of(player, opponentOne, opponentTwo, opponentThree) @@ -1026,10 +1031,9 @@ public void fireAppearancePreferencesChange() * BidListener */ @Override - public void bidMade(Bid bid) + public void bidMade(BidAction bid) { bidPanel.enableBidPanel(false); - bid.setPlayer(player); lastBid = bid; if (settings.getCardReveal() @@ -1049,10 +1053,8 @@ public void challengeMade() Debug.append("Player challenged.", logging); boolean actedBlind = handPanel.isPlayingBlind(); hasActedBlindThisGame &= actedBlind; - - Bid bid = new ChallengeBid(); - bid.setPlayer(player); - bid.setBlind(actedBlind); + + ChallengeAction bid = new ChallengeAction(player.getName(), actedBlind); addToListmodel(bid); processChallenge(player); @@ -1064,10 +1066,8 @@ public void illegalCalled() Debug.append("Player called Illegal!", logging); boolean actedBlind = handPanel.isPlayingBlind(); hasActedBlindThisGame &= actedBlind; - - Bid bid = new IllegalBid(); - bid.setPlayer(player); - bid.setBlind(actedBlind); + + var bid = new IllegalAction(player.getName(), actedBlind); addToListmodel(bid); processIllegal(player); @@ -1085,9 +1085,9 @@ public void cardRevealed(String card) processPlayerBid(); } - private void addToListmodel(Bid bid) + private void addToListmodel(PlayerAction bid) { - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); listmodel.add(0, bid); } @@ -1130,38 +1130,38 @@ public void run() Debug.appendBanner("Opponent " + opponent, logging); StrategyParams parms = factoryStrategyParms(opponent); - Bid bid = CpuStrategies.processOpponentTurn(parms, opponent); - if (bid == null) + PlayerAction action = CpuStrategies.processOpponentTurn(parms, opponent); + if (action == null) { //Something's gone wrong - probably an API strategy that timed out or did something invalid. String info = opponent.getName() + " has had their strategy reset to " + CpuStrategies.STRATEGY_BASIC; DialogUtil.showInfo(info); opponent.setStrategy(CpuStrategies.STRATEGY_BASIC); - bid = CpuStrategies.processOpponentTurn(parms, opponent); + action = CpuStrategies.processOpponentTurn(parms, opponent); } - if (bid == null) + if (action == null) { //Something's gone very wrong... handPanel.selectPlayerInAwtThread(opponent.getPlayerNumber(), false); ScreenCache.get(MainScreen.class).enableNewGameOption(true); return; } + + addToListmodel(action); - bid.setPlayer(opponent); - addToListmodel(bid); - - if (bid.isChallenge()) + if (action instanceof ChallengeAction) { processChallenge(opponent); } - else if (bid.isIllegal()) + else if (action instanceof IllegalAction) { processIllegal(opponent); } else { + var bid = (B)action; lastBid = bid; if (settings.getCardReveal()) diff --git a/client/src/main/java/screen/MainScreen.java b/client/src/main/java/screen/MainScreen.java index 4448ff44..9a35db51 100644 --- a/client/src/main/java/screen/MainScreen.java +++ b/client/src/main/java/screen/MainScreen.java @@ -4,6 +4,7 @@ import achievement.AchievementUtilKt; import bean.AbstractDevScreen; import game.GameMode; +import game.PlayerAction; import object.Bid; import object.BidListCellRenderer; import object.Player; @@ -199,9 +200,9 @@ public MainScreen() private final JMenuItem mntmViewLogs = new JMenuItem("View logs..."); //Screen - private final DefaultListModel listmodel = new DefaultListModel<>(); + private final DefaultListModel listmodel = new DefaultListModel<>(); private final JScrollPane scrollPane = new JScrollPane(); - private final JList history = new JList<>(listmodel); + private final JList history = new JList<>(listmodel); private final JLabel lblBidHistory = new JLabel("Bid History"); private final JButton btnNextRound = new JButton("Next"); private final JTextPane lblResult = new JTextPane(); @@ -798,7 +799,7 @@ private void checkForCoward() } } - public DefaultListModel getListmodel() + public DefaultListModel getListmodel() { return listmodel; } diff --git a/client/src/main/java/screen/SimulationDialog.java b/client/src/main/java/screen/SimulationDialog.java index 37e41252..0007a504 100644 --- a/client/src/main/java/screen/SimulationDialog.java +++ b/client/src/main/java/screen/SimulationDialog.java @@ -250,7 +250,7 @@ public void run() final ProgressDialog dialog = ProgressDialog.factory("Simulating games...", "games remaining", numberOfGames); dialog.showCancel(true); dialog.setVisibleLater(); - + Debug.appendBanner("Starting simulation for " + numberOfGames + " games"); GameSimulator simulator = new GameSimulator(parms); @@ -549,9 +549,9 @@ private SimulationParams factorySimulationParms() String opponentOneStrategy = (String) opponentOneStrat.getSelectedItem(); String opponentTwoStrategy = (String) opponentTwoStrat.getSelectedItem(); String opponentThreeStrategy = (String) opponentThreeStrat.getSelectedItem(); + boolean enableLogging = cbLogging.getState(); boolean forceStart = cbForceStart.getState(); boolean randomiseOrder = cbRandomiseOrder.getState(); - boolean enableLogging = cbLogging.getState(); var settings = new GameSettings(gameMode, numberOfCards, jokerQuantity, jokerValue, includeMoons, includeStars, negativeJacks, cardReveal, false); diff --git a/client/src/main/java/screen/VectropyBidPanel.java b/client/src/main/java/screen/VectropyBidPanel.java index 09d4dcb6..c0d6dca9 100644 --- a/client/src/main/java/screen/VectropyBidPanel.java +++ b/client/src/main/java/screen/VectropyBidPanel.java @@ -23,6 +23,7 @@ import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; +import game.VectropyBidAction; import game.Suit; import object.Bid; import object.VectropyBid; @@ -30,19 +31,23 @@ import util.EntropyColour; import util.Registry; -public class VectropyBidPanel extends BidPanel +import static utils.ColourUtilKt.*; + +public class VectropyBidPanel extends BidPanel implements ActionListener, ChangeListener, Registry { - private VectropyBid lastBid = VectropyBid.factoryEmpty(false, false); + private VectropyBidAction lastBid = null; private boolean illegalAllowed = false; private boolean includeMoons = false; private boolean includeStars = false; private boolean online = false; - public VectropyBidPanel() + public VectropyBidPanel(String playerName, HandPanelMk2 handPanel) { + super(playerName, handPanel); + setLayout(new BorderLayout(0, 0)); updateSpinnerColours(); @@ -159,7 +164,7 @@ public VectropyBidPanel() starLabel.setOpaque(false); starLabel.setBorder(BorderFactory.createEmptyBorder()); starLabel.setBackground(new Color(0,0,0,0)); - starLabel.setForeground(EntropyColour.COLOUR_SUIT_GOLD); + starLabel.setForeground(COLOUR_SUIT_GOLD); starsPanel.add(starLabel); starSpinner.setFont(new Font("Tahoma", Font.PLAIN, 16)); @@ -225,14 +230,13 @@ public void init(int maxBid, int totalNumberOfCards, boolean online, boolean inc String back = prefs.get(PREFERENCES_STRING_CARD_BACKS, Registry.BACK_CODE_CLASSIC_BLUE); smallCardIcon.setIcon(new ImageIcon(EntropyScreen.class.getResource("/backs/" + back + "Small.png"))); - lastBid = VectropyBid.factoryEmpty(includeMoons, includeStars); + lastBid = null; adjust(lastBid); } @Override - public void adjust(Bid bid) + public void adjust(VectropyBidAction bid) { - VectropyBid lastBid = (VectropyBid)bid; this.lastBid = lastBid; int clubs = lastBid.getClubs(); @@ -267,46 +271,35 @@ private void updateSpinnerColours() if (fourColours) { - clubLabel.setForeground(EntropyColour.COLOUR_SUIT_GREEN); + clubLabel.setForeground(COLOUR_SUIT_GREEN); diamondLabel.setForeground(Color.BLUE); - moonLabel.setForeground(EntropyColour.COLOUR_SUIT_PURPLE); + moonLabel.setForeground(COLOUR_SUIT_PURPLE); } else { clubLabel.setForeground(Color.BLACK); diamondLabel.setForeground(Color.RED); - moonLabel.setForeground(EntropyColour.COLOUR_SUIT_GOLD); + moonLabel.setForeground(COLOUR_SUIT_GOLD); } } private void setBidButtonState() { - VectropyBid currentSelection = getBidFromSpinners(); + VectropyBidAction currentSelection = getBidFromSpinners(); boolean enabled = currentSelection.higherThan(lastBid); btnBid.setEnabled(enabled); } - private VectropyBid getBidFromSpinners() + private VectropyBidAction getBidFromSpinners() { int clubs = (int)clubSpinner.getValue(); int diamonds = (int)diamondSpinner.getValue(); int hearts = (int)heartSpinner.getValue(); - - int moons = 0; - if (includeMoons) - { - moons = (int)moonSpinner.getValue(); - } - + int moons = (int)moonSpinner.getValue(); int spades = (int)spadeSpinner.getValue(); - - int stars = 0; - if (includeStars) - { - stars = (int)starSpinner.getValue(); - } - - return new VectropyBid(clubs, diamonds, hearts, moons, spades, stars, includeMoons, includeStars); + int stars = (int)starSpinner.getValue(); + + return new VectropyBidAction(playerName, handPanel.isPlayingBlind(), clubs, diamonds, hearts, includeMoons ? moons : null, spades, includeStars ? stars : null); } @Override diff --git a/client/src/main/java/screen/VectropyScreen.java b/client/src/main/java/screen/VectropyScreen.java index 65ba025e..06cb78ab 100644 --- a/client/src/main/java/screen/VectropyScreen.java +++ b/client/src/main/java/screen/VectropyScreen.java @@ -2,10 +2,10 @@ import java.awt.BorderLayout; +import com.fasterxml.jackson.core.JsonProcessingException; import game.GameMode; +import game.VectropyBidAction; import game.Suit; -import object.Bid; -import object.VectropyBid; import util.AchievementsUtil; import util.Debug; import util.Registry; @@ -13,7 +13,10 @@ import static game.CheatUtilKt.getMaxBidString; import static game.RenderingUtilKt.getVectropyResult; -public class VectropyScreen extends GameScreen +import static utils.CoreGlobals.jsonMapper; +import static utils.CoreGlobals.logger; + +public class VectropyScreen extends GameScreen { private static final long serialVersionUID = 1L; @@ -27,7 +30,7 @@ public VectropyScreen() setLayout(new BorderLayout(0, 0)); add(handPanel, BorderLayout.CENTER); handPanel.setOpaque(false); - bidPanel = new VectropyBidPanel(); + bidPanel = new VectropyBidPanel(player.getName(), handPanel); add(bidPanel, BorderLayout.SOUTH); bidPanel.showBidPanel(false); @@ -56,14 +59,14 @@ protected void initVariablesForNewGame() } @Override - public void saveGame() + public void saveGame() throws JsonProcessingException { super.saveGame(); //save bid amounts and bid suits if (lastBid != null) { - savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID, lastBid.toXmlString()); + savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID, jsonMapper.writeValueAsString(lastBid)); } //other booleans @@ -74,12 +77,12 @@ public void saveGame() } @Override - public void loadLastBid() + public void loadLastBid() throws JsonProcessingException { String lastBidStr = savedGame.get(Registry.SAVED_GAME_STRING_LAST_BID, ""); if (!lastBidStr.isEmpty()) { - lastBid = Bid.factoryFromXmlString(lastBidStr, settings.getIncludeMoons(), settings.getIncludeStars()); + lastBid = jsonMapper.readValue(lastBidStr, VectropyBidAction.class); } } @@ -119,11 +122,6 @@ else if (command.equals("rainingjokers")) /* * Get/sets */ - public void setLastBid(VectropyBid lastBid) - { - this.lastBid = lastBid; - } - @Override public GameMode getGameMode() { diff --git a/client/src/main/java/util/AchievementsUtil.java b/client/src/main/java/util/AchievementsUtil.java index 744d3a65..6a7c3961 100644 --- a/client/src/main/java/util/AchievementsUtil.java +++ b/client/src/main/java/util/AchievementsUtil.java @@ -1,7 +1,9 @@ package util; import achievement.AchievementSetting; +import game.BidAction; import game.GameMode; +import game.PlayerAction; import object.Bid; import object.Player; import online.screen.EntropyLobby; @@ -302,7 +304,7 @@ private static int getTotalGamesPlayed() public static void unlockSecondThoughts(String roomId) { - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); if (!roomId.isEmpty()) { //We're online, so need a different listmodel... @@ -313,8 +315,8 @@ public static void unlockSecondThoughts(String roomId) int size = listmodel.size(); for (int i=0; i> { - public void bidMade(Bid bid); + public void bidMade(B bid); public void challengeMade(); public void illegalCalled(); } diff --git a/client/src/main/java/util/CpuStrategies.java b/client/src/main/java/util/CpuStrategies.java index 14960ab6..ba0033e9 100644 --- a/client/src/main/java/util/CpuStrategies.java +++ b/client/src/main/java/util/CpuStrategies.java @@ -1,8 +1,6 @@ package util; -import game.GameMode; -import game.GameSettings; -import game.Suit; +import game.*; import object.*; import java.util.ArrayList; @@ -62,11 +60,11 @@ private static Vector getFixedStrategies(boolean entropy) /** * Entry-point for strategy code */ - public static Bid processOpponentTurn(StrategyParams parms, Player opponent) + public static PlayerAction processOpponentTurn(StrategyParams parms, Player opponent) { var settings = parms.getSettings(); boolean entropy = settings.getMode() == GameMode.Entropy; - Bid bid = getOpponentBid(parms, opponent, entropy); + PlayerAction bid = getOpponentBid(parms, opponent, entropy); if (bid == null) { return bid; @@ -110,7 +108,7 @@ public static Bid processOpponentTurn(StrategyParams parms, Player opponent) return bid; } - private static Bid getOpponentBid(StrategyParams parms, Player opponent, boolean entropy) + private static PlayerAction getOpponentBid(StrategyParams parms, Player opponent, boolean entropy) { if (opponent.isApiStrategy()) { @@ -131,11 +129,16 @@ else if (entropy) * then just pick one at random. This is what most built-in strategies will do, and implementing for API too * so that worrying about revealing cards is optional. */ - private static void setRandomCardToRevealIfNecessary(Player opponent, Bid bid, GameSettings settings) + private static void setRandomCardToRevealIfNecessary(Player opponent, PlayerAction action, GameSettings settings) { + if (!(action instanceof BidAction)) { + return; + } + + var bid = (BidAction)action; if (settings.getCardReveal() && opponent.hasMoreCardsToReveal() - && bid.getCardToReveal().isEmpty()) + && bid.getCardToReveal() == null) { //Pick a card at random to reveal. ArrayList cardsNotOnShow = opponent.getCardsNotOnShow(); @@ -148,34 +151,35 @@ private static void setRandomCardToRevealIfNecessary(Player opponent, Bid bid, G } } - private static String validateBid(Player opponent, Bid bid, StrategyParams params) + private static String validateBid(Player opponent, PlayerAction action, StrategyParams params) { - var settings = params.getSettings(); - if (bid.isChallenge() - || bid.isIllegal()) - { - return validateChallengeOrIllegal(bid, params); - } - - if (bid instanceof EntropyBid) + var settings = params.getSettings(); + if (action instanceof ChallengeAction + || action instanceof IllegalAction) + { + return validateChallengeOrIllegal(action, params); + } + + var bid = (BidAction)action; + if (action instanceof EntropyBidAction) { - String error = validateEntropyBid((EntropyBid)bid, settings); + String error = validateEntropyBid((EntropyBidAction)action, settings); if (error != null) { return error; } } - if (bid instanceof VectropyBid) + if (action instanceof VectropyBidAction) { - String error = validateVectropyBid((VectropyBid)bid); + String error = validateVectropyBid((VectropyBidAction)action); if (error != null) { return error; } } - - Bid lastBid = params.getLastBid(); + + BidAction lastBid = params.getLastBid(); if (lastBid != null && !bid.higherThan(lastBid)) { @@ -207,12 +211,12 @@ private static String validateBid(Player opponent, Bid bid, StrategyParams param return null; } - private static String validateChallengeOrIllegal(Bid bid, StrategyParams parms) + private static String validateChallengeOrIllegal(PlayerAction bid, StrategyParams parms) { - Bid lastBid = parms.getLastBid(); + var lastBid = parms.getLastBid(); if (lastBid == null) { - if (bid.isChallenge()) + if (bid instanceof ChallengeAction) { return "Challenged as an opening bid."; } @@ -223,11 +227,11 @@ private static String validateChallengeOrIllegal(Bid bid, StrategyParams parms) return null; } - private static String validateEntropyBid(EntropyBid bid, GameSettings settings) + private static String validateEntropyBid(EntropyBidAction bid, GameSettings settings) { - Suit bidSuit = bid.getBidSuit(); + var bidSuit = bid.getSuit(); - int bidAmount = bid.getBidAmount(); + int bidAmount = bid.getAmount(); if (bidAmount < 1) { return "Invalid bidAmount: " + bidAmount; @@ -248,19 +252,22 @@ private static String validateEntropyBid(EntropyBid bid, GameSettings settings) return null; } - private static String validateVectropyBid(VectropyBid bid) + private static String validateVectropyBid(VectropyBidAction bid) { if (bid.getTotal() < 1) { return "Elements sum to less than 1."; } - + + var moons = bid.getMoons(); + var stars = bid.getStars(); + if (bid.getClubs() < 0 || bid.getDiamonds() < 0 || bid.getHearts() < 0 - || bid.getMoons() < 0 + || (moons != null && moons < 0) || bid.getSpades() < 0 - || bid.getStars() < 0) + || (stars != null && stars < 0)) { return "Negative amount specified for a suit."; } diff --git a/client/src/main/java/util/EntCpuStrategies.java b/client/src/main/java/util/EntCpuStrategies.java index 5de9bd7b..f0872667 100644 --- a/client/src/main/java/util/EntCpuStrategies.java +++ b/client/src/main/java/util/EntCpuStrategies.java @@ -1,18 +1,17 @@ package util; -import java.util.*; - +import game.ChallengeAction; +import game.EntropyBidAction; +import game.PlayerAction; import game.Suit; -import object.Bid; import object.ChallengeBid; -import object.EntropyBid; import object.Player; import strategy.MarkStrategySuitWrapper; +import java.util.*; + import static game.CardsUtilKt.countSuit; -import static game.EntropyUtilKt.amountRequiredToBid; -import static game.StrategyUtilKt.*; -import static strategy.MarkStrategySuitWrapperKt.factoryMarkStrategySuitWrapper; +import static game.StrategyUtilKt.getEvMap; import static utils.CoreGlobals.logger; public class EntCpuStrategies @@ -32,12 +31,12 @@ public static Vector getAllStrategies() return allStrategies; } - public static Bid processOpponentTurn(Player opponent, StrategyParams parms) + public static PlayerAction processOpponentTurn(Player opponent, StrategyParams parms) { String strategy = opponent.getStrategy(); return processOpponentTurn(strategy, opponent, parms); } - private static Bid processOpponentTurn(String strategy, Player opponent, StrategyParams parms) + private static PlayerAction processOpponentTurn(String strategy, Player opponent, StrategyParams parms) { if (strategy.equals(CpuStrategies.STRATEGY_BASIC)) { @@ -61,10 +60,10 @@ else if (strategy.equals(CpuStrategies.STRATEGY_EV)) } } - private static Bid processMarkTurn(Player opponent, StrategyParams parms) + private static PlayerAction processMarkTurn(Player opponent, StrategyParams parms) { - var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); + var settings = parms.getSettings(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); Random rand = new Random(); List hand = opponent.getHand(); @@ -96,7 +95,7 @@ private static Bid processMarkTurn(Player opponent, StrategyParams parms) var suit = getSuitForMarkBid(suitWrapper, logging); int bidAmount = markBid(halfThreshold, (int)totalCards, suitsInPlay); - return new EntropyBid(suit, bidAmount); + return new EntropyBidAction(opponent.getName(), false, bidAmount, suit); } else { @@ -206,12 +205,12 @@ else if (minBiddableSuitCount == 1) if (bidAmountFacedWith > threshold) { log("Auto-challenging because " + bidAmountFacedWith + " > " + threshold, logging); - return new ChallengeBid(); + return new ChallengeAction(); } else if (bidAmountFacedWith > bestSuitCount + bidSuitCount + quarterThreshold) { log("Auto-challenging because bestSuitCount + bidSuitCount + quarterThreshold = " + (bestSuitCount + bidSuitCount + quarterThreshold), logging); - return new ChallengeBid(); + return new ChallengeAction(); } else if (bidAmountFacedWith > 1 + quarterThreshold) { @@ -224,7 +223,7 @@ else if (bidAmountFacedWith > 1 + quarterThreshold) else { log("Challenging (25%)", logging); - return new ChallengeBid(); + return new ChallengeAction(); } } else @@ -295,11 +294,11 @@ private static int markBid(int halfThreshold, int totalCards, int suitsInPlay) } } - private static Bid processBasicTurn(Player opponent, StrategyParams parms) + private static PlayerAction processBasicTurn(Player opponent, StrategyParams parms) { //Parms - var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); + var settings = parms.getSettings(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); boolean includeMoons = settings.getIncludeMoons(); boolean includeStars = settings.getIncludeStars(); int jokerValue = settings.getJokerValue(); @@ -318,7 +317,7 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) var suit = Suit.random(includeMoons, includeStars); int bidAmount = Math.max(1, countSuit(suit, hand, jokerValue) + coin.nextInt(2)); - return new EntropyBid(suit, bidAmount); + return new EntropyBidAction(opponent.getName(), false, bidAmount, suit); } else { @@ -365,12 +364,12 @@ else if (bidAmountFacedWith < bidSuitCount + thirdThreshold) else if (bidAmountFacedWith > halfThreshold) { log("Opponent " + opponent + " auto-challenged", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (decision == 0) { log("Opponent " + opponent + " flip-challenged", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else { @@ -388,19 +387,19 @@ else if (decision == 0) } } - private static Bid processEvTurnAndRevealCard(Player opponent, StrategyParams parms) + private static PlayerAction processEvTurnAndRevealCard(Player opponent, StrategyParams parms) { - Bid bid = processEvTurn(opponent, parms); + PlayerAction bid = processEvTurn(opponent, parms); CpuStrategies.setCardToReveal(bid, parms.getSettings(), opponent); return bid; } - private static Bid processEvTurn(Player opponent, StrategyParams parms) + private static PlayerAction processEvTurn(Player opponent, StrategyParams parms) { //Parms - var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); - int totalCards = parms.getCardsInPlay(); + var settings = parms.getSettings(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); + int totalCards = settings.getCardsInPlay(); boolean includeMoons = settings.getIncludeMoons(); boolean includeStars = settings.getIncludeStars(); int jokerValue = settings.getJokerValue(); @@ -423,7 +422,7 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) int randomAmount = coin.nextInt(5) - 4; //-4, -3, -2, 1, 0 int bidAmount = Math.max(1, suitEvRounded + randomAmount); - return new EntropyBid(suit, bidAmount); + return new EntropyBidAction(opponent.getName(), false, bidAmount, suit); } else { @@ -433,15 +432,15 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) int bidAmountFacedWith = bid.getBidAmount(); var bidSuitFacedWith = bid.getBidSuit(); double expectedValueForBid = hmEvBySuit.get(bidSuitFacedWith); - + log("EV calculation for bid of " + bidAmountFacedWith + " " + bidSuitFacedWith.getDescription(bidAmountFacedWith) + ": " + expectedValueForBid, logging); double maxEv = getMaxValue(hmEvBySuit); var suitsWithMax = getSuitsWithMostPositiveValue(hmEvBySuit); - + if (bidAmountFacedWith > expectedValueForBid + 1) { - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else { @@ -468,52 +467,44 @@ else if (maxEv > bidAmountFacedWith - 1) else { log("Bidding would've needed >1 in one card, so challenged", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } } else { log("Couldn't bid anything 'safely', so challenged.", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } } } } - private static EntropyBid opponentMinBid(Suit bidSuitFacedWith, int bidAmount, + private static EntropyBidAction opponentMinBid(Player opponent, Suit bidSuitFacedWith, int bidAmount, boolean includeMoons, boolean includeStars) { var nextSuit = bidSuitFacedWith.next(includeMoons, includeStars); var myAmount = nextSuit.lessThan(bidSuitFacedWith) ? bidAmount + 1 : bidAmount; - return new EntropyBid(nextSuit, myAmount); + return new EntropyBidAction(opponent.getName(), false, myAmount, nextSuit); } - private static EntropyBid opponentOneUp(Suit bidSuitFacedWith, int bidAmount, boolean logging) + private static EntropyBidAction opponentOneUp(Player opponent, Suit bidSuitFacedWith, int bidAmount, boolean logging) { log("One Up", logging); - return new EntropyBid(bidSuitFacedWith, bidAmount + 1); + return new EntropyBidAction(opponent.getName(), false, bidAmount + 1, bidSuitFacedWith); } - private static EntropyBid opponentMinBidSuit(Suit bidSuitFacedWith, int bidAmountFacedWith, Suit suitToBid, boolean logging) + private static EntropyBidAction opponentMinBidSuit(Player opponent, Suit bidSuitFacedWith, int bidAmountFacedWith, Suit desiredSuit, boolean logging) { - log("MinBidSuit " + suitToBid, logging); + log("MinBidSuit " + desiredSuit, logging); - int bidAmount = 0; - if (bidSuitFacedWith.lessThan(suitToBid)) - { - bidAmount = bidAmountFacedWith; - } - else - { - bidAmount = bidAmountFacedWith + 1; - } + var bidAmount = desiredSuit.lessThan(bidSuitFacedWith) ? bidAmountFacedWith + 1 : bidAmountFacedWith; - return new EntropyBid(suitToBid, bidAmount); + return new EntropyBidAction(opponent.getName(), false, bidAmount, desiredSuit); } - private static void log(String text, boolean logging) { - if (logging) { - logger.info("strategy.debug", text); - } - } + private static void log(String text, boolean logging) { + if (logging) { + logger.info("strategy.debug", text); + } + } } \ No newline at end of file diff --git a/client/src/main/java/util/EntropyColour.java b/client/src/main/java/util/EntropyColour.java index d0424801..93431e3c 100644 --- a/client/src/main/java/util/EntropyColour.java +++ b/client/src/main/java/util/EntropyColour.java @@ -4,10 +4,6 @@ public class EntropyColour { - public static final Color COLOUR_SUIT_PURPLE = Color.getHSBColor((float)7/9, 1, 1); - public static final Color COLOUR_SUIT_GOLD = Color.getHSBColor((float)5/36, 1, (float)0.9); - public static final Color COLOUR_SUIT_GREEN = Color.getHSBColor((float)1/3, 1, (float)0.5); - public static final Color COLOUR_REPLAY_VICTORY = new Color(10, 175, 40); public static final Color COLOUR_REPLAY_UNKNOWN_OUTCOME = new Color(175, 175, 175); public static final Color COLOUR_REPLAY_UNFINISHED_AND_LOST = new Color(233, 133, 133); diff --git a/client/src/main/java/util/VectCpuStrategies.java b/client/src/main/java/util/VectCpuStrategies.java index 4379e289..c4cf5ea6 100644 --- a/client/src/main/java/util/VectCpuStrategies.java +++ b/client/src/main/java/util/VectCpuStrategies.java @@ -1,18 +1,19 @@ package util; -import java.util.*; - import game.Suit; +import game.VectropyBidAction; import object.Bid; import object.ChallengeBid; import object.Player; import object.VectropyBid; +import java.util.*; + import static game.CardsUtilKt.countSuit; import static game.StrategyUtilKt.*; import static utils.CoreGlobals.logger; -public class VectCpuStrategies +public class VectCpuStrategies { public static final String STRATEGY_RANDOMISE_PER_MOVE = "Randomise (per move)"; private static final String[] STRATEGIES_TO_CHOOSE_AT_RANDOM = {CpuStrategies.STRATEGY_BASIC, CpuStrategies.STRATEGY_EV}; @@ -62,7 +63,7 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) //Get the variables we're interested in var settings = parms.getSettings(); List hand = opponent.getHand(); - VectropyBid lastBid = (VectropyBid)parms.getLastBid(); + VectropyBidAction lastBid = (VectropyBidAction)parms.getLastBid(); double totalCards = parms.getCardsInPlay(); int jokerValue = settings.getJokerValue(); boolean includeMoons = settings.getIncludeMoons(); diff --git a/client/src/main/kotlin/help/FundamentalsTheDeck.kt b/client/src/main/kotlin/help/FundamentalsTheDeck.kt index 9e95debf..e53a1967 100644 --- a/client/src/main/kotlin/help/FundamentalsTheDeck.kt +++ b/client/src/main/kotlin/help/FundamentalsTheDeck.kt @@ -13,6 +13,8 @@ import javax.swing.JTextPane import javax.swing.SwingConstants import util.EntropyColour import util.Registry +import utils.COLOUR_SUIT_GOLD +import utils.COLOUR_SUIT_PURPLE class FundamentalsTheDeck : HelpPanel(), Registry { override val nodeName = "The Deck" @@ -71,7 +73,7 @@ class FundamentalsTheDeck : HelpPanel(), Registry { heartLabel.setBounds(240, 360, 65, 60) add(heartLabel) moonLabel.horizontalAlignment = SwingConstants.CENTER - moonLabel.foreground = EntropyColour.COLOUR_SUIT_PURPLE + moonLabel.foreground = COLOUR_SUIT_PURPLE moonLabel.font = Font("Segoe UI Symbol", Font.PLAIN, 32) moonLabel.setBounds(240, 360, 65, 60) add(moonLabel) @@ -94,7 +96,7 @@ class FundamentalsTheDeck : HelpPanel(), Registry { starLabel.horizontalAlignment = SwingConstants.CENTER starLabel.font = Font("Segoe UI Symbol", Font.PLAIN, 40) starLabel.setBounds(400, 360, 65, 60) - starLabel.foreground = EntropyColour.COLOUR_SUIT_GOLD + starLabel.foreground = COLOUR_SUIT_GOLD add(starLabel) finaliseComponents() @@ -154,11 +156,11 @@ class FundamentalsTheDeck : HelpPanel(), Registry { if (fourColours) { clubLabel.foreground = Color(0, 128, 0) diamondLabel.foreground = Color.BLUE - moonLabel.foreground = EntropyColour.COLOUR_SUIT_PURPLE + moonLabel.foreground = COLOUR_SUIT_PURPLE } else { clubLabel.foreground = Color.black diamondLabel.foreground = Color(255, 0, 0) - moonLabel.foreground = EntropyColour.COLOUR_SUIT_GOLD + moonLabel.foreground = COLOUR_SUIT_GOLD } } diff --git a/client/src/main/kotlin/http/RoomApi.kt b/client/src/main/kotlin/http/RoomApi.kt index c8fc116a..f0c4b941 100644 --- a/client/src/main/kotlin/http/RoomApi.kt +++ b/client/src/main/kotlin/http/RoomApi.kt @@ -11,7 +11,7 @@ import screen.ScreenCache import util.DialogUtilNew class RoomApi(private val httpClient: HttpClient) { - fun joinRoom(room: GameRoom) { + fun joinRoom(room: GameRoom<*>) { val response = httpClient.doCall( HttpMethod.POST, @@ -25,7 +25,7 @@ class RoomApi(private val httpClient: HttpClient) { } } - private fun handleJoinRoom(room: GameRoom, response: JoinRoomResponse) { + private fun handleJoinRoom(room: GameRoom<*>, response: JoinRoomResponse) { room.username = ScreenCache.get().username room.observer = true room.isVisible = true @@ -34,7 +34,7 @@ class RoomApi(private val httpClient: HttpClient) { room.chatPanel.updateChatBox(response.chatHistory) } - fun sitDown(room: GameRoom, seat: Int) { + fun sitDown(room: GameRoom<*>, seat: Int) { val response = httpClient.doCall( HttpMethod.POST, @@ -52,7 +52,7 @@ class RoomApi(private val httpClient: HttpClient) { } } - private fun handleSitDown(room: GameRoom, seat: Int, response: RoomStateResponse) { + private fun handleSitDown(room: GameRoom<*>, seat: Int, response: RoomStateResponse) { room.setObserver(false) room.setPlayerNumber(seat) room.init(true) @@ -66,7 +66,7 @@ class RoomApi(private val httpClient: HttpClient) { } } - fun standUp(room: GameRoom) { + fun standUp(room: GameRoom<*>) { val response = httpClient.doCall( HttpMethod.POST, @@ -80,7 +80,7 @@ class RoomApi(private val httpClient: HttpClient) { } } - fun leaveRoom(room: GameRoom) { + fun leaveRoom(room: GameRoom<*>) { val response = httpClient.doCall(HttpMethod.POST, Routes.LEAVE_ROOM, SimpleRoomRequest(room.id)) @@ -90,7 +90,7 @@ class RoomApi(private val httpClient: HttpClient) { } } - private fun handleStandUp(room: GameRoom, response: RoomStateResponse) { + private fun handleStandUp(room: GameRoom<*>, response: RoomStateResponse) { room.setObserver(true) room.init(true) room.synchronisePlayers(response.players, response.formerPlayers) diff --git a/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt b/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt index 6cf90968..b603c213 100644 --- a/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt +++ b/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt @@ -1,8 +1,8 @@ package strategy +import game.EntropyBidAction import game.Suit import game.countSuit -import `object`.EntropyBid data class MarkStrategySuitWrapper( val applicableSuits: List, @@ -24,7 +24,7 @@ fun factoryMarkStrategySuitWrapper( jokerValue: Int, includeMoons: Boolean, includeStars: Boolean, - bid: EntropyBid?, + bid: EntropyBidAction?, ): MarkStrategySuitWrapper { val applicableSuits = Suit.filter(includeMoons, includeStars) @@ -36,7 +36,7 @@ fun factoryMarkStrategySuitWrapper( else { applicableSuits.filter { suit -> val myCount = countSuit(suit, hand, jokerValue) - myCount > bid.bidAmount || (myCount == bid.bidAmount && suit > bid.bidSuit) + myCount > bid.amount || (myCount == bid.amount && suit > bid.suit) } } diff --git a/client/src/main/kotlin/util/GameSimulator.kt b/client/src/main/kotlin/util/GameSimulator.kt index ebf0f589..97a4678e 100644 --- a/client/src/main/kotlin/util/GameSimulator.kt +++ b/client/src/main/kotlin/util/GameSimulator.kt @@ -1,7 +1,8 @@ package util import game.createAndShuffleDeck -import `object`.Bid +import game.BidAction +import game.ChallengeAction import `object`.Player import screen.ScreenCache.get import screen.SimulationDialog @@ -10,7 +11,7 @@ import utils.CoreGlobals.logger class GameSimulator(private val params: SimulationParams) { private var personToStart = 0 - private var lastBid: Bid? = null + private var lastBid: BidAction<*>? = null val opponentZero: Player = Player(0, "").also { it.name = "0" } val opponentOne: Player = Player(1, "").also { it.name = "1" } @@ -110,10 +111,10 @@ class GameSimulator(private val params: SimulationParams) { ?: // Abort the simulation, something's gone wrong throw SimulationException("Simulation error") - if (action.isChallenge) { + if (action is ChallengeAction) { processChallenge(opponent) } else { - lastBid = action + lastBid = action as BidAction<*> processOpponentTurn(nextPlayer(opponent)) } } @@ -139,7 +140,7 @@ class GameSimulator(private val params: SimulationParams) { val allCards = allPlayers().flatMap { it.hand } val dialog = get(SimulationDialog::class.java) - if (!lastBid.isOverbid(allCards, params.settings.jokerValue)) { + if (!lastBid.isOverbid(allCards, params.settings)) { log("not overbid") dialog.recordChallenge(challenger.playerNumber, false) @@ -150,7 +151,8 @@ class GameSimulator(private val params: SimulationParams) { log("overbid") dialog.recordChallenge(challenger.playerNumber, true) - val bidder = lastBid.player + val name = lastBid.playerName + val bidder = getPlayer(name.toInt()) bidder.cardsToSubtract = 1 bidder.doSubtraction() personToStart = bidder.playerNumber diff --git a/client/src/main/kotlin/util/StrategyParams.kt b/client/src/main/kotlin/util/StrategyParams.kt index 4e483fdf..1a2a0806 100644 --- a/client/src/main/kotlin/util/StrategyParams.kt +++ b/client/src/main/kotlin/util/StrategyParams.kt @@ -1,12 +1,12 @@ package util +import game.BidAction import game.GameSettings -import `object`.Bid data class StrategyParams( val settings: GameSettings, val cardsInPlay: Int, val opponentCardsOnShow: List, - val lastBid: Bid?, + val lastBid: BidAction<*>?, val logging: Boolean, ) diff --git a/core/src/main/java/object/ExtendedConcurrentHashMap.java b/core/src/main/java/object/ExtendedConcurrentHashMap.java deleted file mode 100644 index 758206a2..00000000 --- a/core/src/main/java/object/ExtendedConcurrentHashMap.java +++ /dev/null @@ -1,42 +0,0 @@ -package object; - -import java.util.Iterator; -import java.util.concurrent.ConcurrentHashMap; - -import util.Debug; - -/** - * Extension of ConcurrentHashMap so I can add useful methods - */ -public class ExtendedConcurrentHashMap extends ConcurrentHashMap -{ - public ExtendedConcurrentHashMap factoryCopy() - { - ExtendedConcurrentHashMap hmCopy = new ExtendedConcurrentHashMap<>(); - - Iterator it = keySet().iterator(); - for (; it.hasNext();) - { - KeyType key = it.next(); - ValueType value = get(key); - hmCopy.put(key, value); - } - - return hmCopy; - } - - public KeyType getOnlyKey() - { - if (size() != 1) - { - Debug.stackTrace("Calling getOnlyKey() but size is " + size() + ". HashMap: " + this); - if (size() == 0) - { - return null; - } - } - - Iterator it = keySet().iterator(); - return it.next(); - } -} diff --git a/core/src/main/java/util/Registry.java b/core/src/main/java/util/Registry.java index c0927732..e3018da4 100644 --- a/core/src/main/java/util/Registry.java +++ b/core/src/main/java/util/Registry.java @@ -29,7 +29,7 @@ public interface Registry public static final String SHARED_INT_JOKER_VALUE = "jokerValue"; public static final String SHARED_INT_JOKER_QUANTITY = "jokerQuantity"; public static final String SHARED_INT_NUMBER_OF_CARDS = "numberOfCards"; - + //prefs public static final String PREFERENCES_STRING_REPLAY_DIRECTORY = "replayDirectory"; public static final String PREFERENCES_STRING_DECK_DIRECTORY = "deckDirectory"; @@ -99,7 +99,7 @@ public interface Registry public static final String REPLAY_STRING_OPPONENT_THREE_STRATEGY = "opponentThreeStrategy"; public static final String REPLAY_STRING_ROOM_NAME = "roomName"; public static final String REPLAY_STRING_LAST_BID_SUIT_NAME = "LAST_BID_SUIT_NAME"; - + public static final String REPLAY_BOOLEAN_HAS_ACTED_BLIND_THIS_GAME = "hasActedBlindThisGame"; public static final String REPLAY_BOOLEAN_HAS_VIEWED_HAND_THIS_GAME = "hasViewedHandThisGame"; public static final String REPLAY_BOOLEAN_PLAY_WITH_HANDICAP = "playWithHandicap"; diff --git a/core/src/main/kotlin/game/BidAction.kt b/core/src/main/kotlin/game/BidAction.kt new file mode 100644 index 00000000..ea503d54 --- /dev/null +++ b/core/src/main/kotlin/game/BidAction.kt @@ -0,0 +1,17 @@ +package game + +abstract class BidAction> : PlayerAction() { + + var cardToReveal: String? = null + + abstract fun higherThan(other: B): Boolean + + abstract fun overAchievementThreshold(): Boolean + + abstract fun isPerfect(cards: List, settings: GameSettings): Boolean + + abstract fun isOverbid(cards: List, settings: GameSettings): Boolean + + override fun toString() = + plainString() + if (cardToReveal != null) " (Shows: $cardToReveal)" else "" +} diff --git a/core/src/main/kotlin/game/ChallengeAction.kt b/core/src/main/kotlin/game/ChallengeAction.kt new file mode 100644 index 00000000..ef7f7f5f --- /dev/null +++ b/core/src/main/kotlin/game/ChallengeAction.kt @@ -0,0 +1,6 @@ +package game + +data class ChallengeAction(override val playerName: String, override val blind: Boolean) : + PlayerAction() { + override fun plainString() = "Challenge" +} diff --git a/core/src/main/kotlin/game/EntropyBidAction.kt b/core/src/main/kotlin/game/EntropyBidAction.kt new file mode 100644 index 00000000..3d3a54a3 --- /dev/null +++ b/core/src/main/kotlin/game/EntropyBidAction.kt @@ -0,0 +1,34 @@ +package game + +data class EntropyBidAction( + override val playerName: String, + override val blind: Boolean, + val amount: Int, + val suit: Suit, +) : BidAction() { + + override fun overAchievementThreshold() = amount >= 5 + + override fun isPerfect(cards: List, settings: GameSettings): Boolean { + val perfectAmount = perfectBidAmount(cards, settings.jokerValue) + val perfectSuit = perfectBidSuit(cards, settings.jokerValue, settings.includeStars) + + return amount == perfectAmount && suit == perfectSuit + } + + override fun isOverbid(cards: List, settings: GameSettings) = + amount > countSuit(suit, cards, settings.jokerValue) + + override fun higherThan(other: EntropyBidAction): Boolean { + if (amount > other.amount) { + return true + } + + return amount == other.amount && suit > other.suit + } + + override fun plainString() = "$amount ${suit.getDescription(amount)}" + + override fun htmlString() = + "$amount${suit.unicodeStr}" +} diff --git a/core/src/main/kotlin/game/IllegalAction.kt b/core/src/main/kotlin/game/IllegalAction.kt new file mode 100644 index 00000000..87b68ff3 --- /dev/null +++ b/core/src/main/kotlin/game/IllegalAction.kt @@ -0,0 +1,6 @@ +package game + +data class IllegalAction(override val playerName: String, override val blind: Boolean) : + PlayerAction() { + override fun plainString() = "Illegal!" +} diff --git a/core/src/main/kotlin/game/LeaveAction.kt b/core/src/main/kotlin/game/LeaveAction.kt new file mode 100644 index 00000000..e8f0a4af --- /dev/null +++ b/core/src/main/kotlin/game/LeaveAction.kt @@ -0,0 +1,7 @@ +package game + +data class LeaveAction(override val playerName: String) : PlayerAction() { + override val blind = false + + override fun plainString() = "Left" +} diff --git a/core/src/main/kotlin/game/PlayerAction.kt b/core/src/main/kotlin/game/PlayerAction.kt new file mode 100644 index 00000000..3c9adbc4 --- /dev/null +++ b/core/src/main/kotlin/game/PlayerAction.kt @@ -0,0 +1,25 @@ +package game + +import com.fasterxml.jackson.annotation.JsonSubTypes +import com.fasterxml.jackson.annotation.JsonTypeInfo + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "actionType", +) +@JsonSubTypes( + JsonSubTypes.Type(value = ChallengeAction::class, name = "CHALLENGE"), + JsonSubTypes.Type(value = IllegalAction::class, name = "ILLEGAL"), + JsonSubTypes.Type(value = LeaveAction::class, name = "LEAVE"), + JsonSubTypes.Type(value = EntropyBidAction::class, name = "ENTROPY_BID"), + JsonSubTypes.Type(value = VectropyBidAction::class, name = "VECTROPY_BID"), +) +abstract class PlayerAction { + abstract val playerName: String + abstract val blind: Boolean + + abstract fun plainString(): String + + open fun htmlString() = plainString() +} diff --git a/core/src/main/kotlin/game/VectropyBidAction.kt b/core/src/main/kotlin/game/VectropyBidAction.kt new file mode 100644 index 00000000..22fa8299 --- /dev/null +++ b/core/src/main/kotlin/game/VectropyBidAction.kt @@ -0,0 +1,61 @@ +package game + +data class VectropyBidAction( + override val playerName: String, + override val blind: Boolean, + val clubs: Int, + val diamonds: Int, + val hearts: Int, + val moons: Int?, + val spades: Int, + val stars: Int?, +) : BidAction() { + + fun getTotal() = clubs + diamonds + hearts + spades + (moons ?: 0) + (stars ?: 0) + + override fun higherThan(other: VectropyBidAction) = + getTotal() > other.getTotal() && + isAtLeast(other, VectropyBidAction::clubs) && + isAtLeast(other, VectropyBidAction::diamonds) && + isAtLeast(other, VectropyBidAction::hearts) && + isAtLeast(other, VectropyBidAction::moons) && + isAtLeast(other, VectropyBidAction::spades) && + isAtLeast(other, VectropyBidAction::stars) + + private fun isAtLeast(other: VectropyBidAction, suitSelector: (VectropyBidAction) -> Int?) = + suitSelector(this)?.let { it >= suitSelector(other)!! } ?: true + + override fun overAchievementThreshold() = getTotal() >= 5 + + override fun isPerfect(cards: List, settings: GameSettings): Boolean = + evaluateAllSuits(cards, settings, ::isPerfect).all { it } + + private fun isPerfect(cards: List, amount: Int?, suit: Suit, settings: GameSettings) = + amount?.let { amount == countSuit(suit, cards, settings.jokerValue) } ?: true + + override fun isOverbid(cards: List, settings: GameSettings): Boolean = + evaluateAllSuits(cards, settings, ::isOverbid).none { it } + + private fun isOverbid(cards: List, amount: Int?, suit: Suit, settings: GameSettings) = + amount?.let { amount > countSuit(suit, cards, settings.jokerValue) } ?: false + + private fun evaluateAllSuits( + cards: List, + settings: GameSettings, + condition: (List, Int?, Suit, GameSettings) -> Boolean, + ) = + listOf( + condition(cards, clubs, Suit.Clubs, settings), + condition(cards, diamonds, Suit.Diamonds, settings), + condition(cards, hearts, Suit.Hearts, settings), + condition(cards, moons, Suit.Moons, settings), + condition(cards, spades, Suit.Spades, settings), + condition(cards, stars, Suit.Stars, settings), + ) + + override fun plainString(): String { + fun optional(amount: Int?) = if (amount == null) " " else " $amount," + + return "($clubs, $diamonds, $hearts,${optional(moons)} $spades,${optional(stars)})" + } +} diff --git a/core/src/test/kotlin/game/EntropyBidActionTest.kt b/core/src/test/kotlin/game/EntropyBidActionTest.kt new file mode 100644 index 00000000..cfa81d33 --- /dev/null +++ b/core/src/test/kotlin/game/EntropyBidActionTest.kt @@ -0,0 +1,64 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest +import testCore.makeEntropyBidAction +import testCore.makeGameSettings +import utils.CoreGlobals + +class EntropyBidActionTest : AbstractTest() { + @Test + fun `Should serialise and deserialise correctly`() { + val bid: PlayerAction = EntropyBidAction("Suzie", false, 5, Suit.Moons) + val json = CoreGlobals.jsonMapper.writeValueAsString(bid) + val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) + deserialized shouldBe bid + } + + @Test + fun `Over achievement threshold`() { + makeEntropyBidAction(amount = 4).overAchievementThreshold() shouldBe false + makeEntropyBidAction(amount = 5).overAchievementThreshold() shouldBe true + makeEntropyBidAction(amount = 6).overAchievementThreshold() shouldBe true + } + + @Test + fun `Should detect a perfect bid`() { + val settings = makeGameSettings(jokerValue = 2) + val cards = listOf("As", "3s", "Jo1", "4h") + + makeEntropyBidAction(4, Suit.Hearts).isPerfect(cards, settings) shouldBe false + makeEntropyBidAction(5, Suit.Hearts).isPerfect(cards, settings) shouldBe false + + makeEntropyBidAction(4, Suit.Spades).isPerfect(cards, settings) shouldBe false + makeEntropyBidAction(5, Suit.Spades).isPerfect(cards, settings) shouldBe true + makeEntropyBidAction(6, Suit.Spades).isPerfect(cards, settings) shouldBe false + } + + @Test + fun `Should detect overbids`() { + val settings = makeGameSettings(jokerValue = 3) + val cards = listOf("As", "3s", "Jo1", "4h") + + makeEntropyBidAction(6, Suit.Spades).isOverbid(cards, settings) shouldBe false + makeEntropyBidAction(7, Suit.Spades).isOverbid(cards, settings) shouldBe true + + makeEntropyBidAction(4, Suit.Clubs).isOverbid(cards, settings) shouldBe false + makeEntropyBidAction(5, Suit.Clubs).isOverbid(cards, settings) shouldBe true + } + + @Test + fun `Should determine which bid is higher`() { + val twoDiamonds = makeEntropyBidAction(2, Suit.Diamonds) + + twoDiamonds.higherThan(makeEntropyBidAction(1, Suit.Diamonds)) shouldBe true + twoDiamonds.higherThan(makeEntropyBidAction(1, Suit.Stars)) shouldBe true + twoDiamonds.higherThan(makeEntropyBidAction(2, Suit.Clubs)) shouldBe true + + twoDiamonds.higherThan(makeEntropyBidAction(2, Suit.Diamonds)) shouldBe false + + twoDiamonds.higherThan(makeEntropyBidAction(2, Suit.Hearts)) shouldBe false + twoDiamonds.higherThan(makeEntropyBidAction(3, Suit.Clubs)) shouldBe false + } +} diff --git a/server/src/main/java/object/BidHistory.java b/server/src/main/java/object/BidHistory.java index 43d95671..722f9876 100644 --- a/server/src/main/java/object/BidHistory.java +++ b/server/src/main/java/object/BidHistory.java @@ -36,36 +36,6 @@ public boolean addBidForPlayer(int playerNumber, Bid bid) } } - public Bid getNextBidForPlayer(int playerNumber, Bid currentBid) - { - ArrayList bids = hmBidVectorByPlayerNumber.get(playerNumber); - int size = bids.size(); - - if (currentBid == null) - { - return size>0? bids.get(0):null; - } - - for (int i=0; i bids = hmBidVectorByPlayerNumber.get(playerNumber); diff --git a/server/src/main/java/object/Game.kt b/server/src/main/java/object/Game.kt new file mode 100644 index 00000000..8ef3f987 --- /dev/null +++ b/server/src/main/java/object/Game.kt @@ -0,0 +1,30 @@ +package `object` + +typealias Millis = Long + +data class Game( + val gameId: String, + val rounds: Map, + val currentPlayer: Int, + val countdownStart: Millis?, + val gameStart: Millis?, + val gameEnd: Millis?, +) { + fun startCountdown() = copy(countdownStart = System.currentTimeMillis()) + + fun cancelCountdown() = copy(countdownStart = null) + + val countdownTimeRemaining = + countdownStart?.let { + val timeElapsed = System.currentTimeMillis() - countdownStart + if (timeElapsed > COUNTDOWN_TIME_MILLIS) { + 0 + } else { + COUNTDOWN_TIME_MILLIS - timeElapsed + } + } ?: 0 + + companion object { + private const val COUNTDOWN_TIME_MILLIS = 5000 + } +} diff --git a/server/src/main/java/object/GameWrapper.java b/server/src/main/java/object/GameWrapper.java index aeaeef3e..607f1d2a 100644 --- a/server/src/main/java/object/GameWrapper.java +++ b/server/src/main/java/object/GameWrapper.java @@ -93,23 +93,6 @@ public int getPersonToStart(int roundNumber) BidHistory history = hmBidHistoryByRoundNumber.get(roundNumber); return history.getPersonToStart(); } - public Bid getNextBidForPlayer(int playerNumber, int roundNumber, Bid latestBid) - { - BidHistory history = hmBidHistoryByRoundNumber.get(roundNumber); - if (history == null) - { - //Returning null here will be like 'waiting for player' - logger.error("noBidHistory", "Got NULL bid history for gameId " + gameId + " and round " + roundNumber + ". Dump follows."); - debugDump("Game"); - return null; - } - - return history.getNextBidForPlayer(playerNumber, latestBid); - } - public long getCountdownStartMillis() - { - return countdownStartMillis; - } public long getCountdownTimeRemaining() { long timeElapsed = System.currentTimeMillis() - countdownStartMillis; @@ -199,6 +182,5 @@ private void dumpRoundInformation(StringBuilder sb, int roundNumber) sb.append("\n Last player to act: " + history.getLastPlayerToAct()); sb.append("\n BidHistory: " + history); sb.append("\n " + details.getHandsForLogging()); - sb.append("\n hmHandSizeByPlayerNumber: " + details.getHandSizes()); } } diff --git a/server/src/main/java/object/HandDetails.java b/server/src/main/java/object/HandDetails.java index d0a0ae03..cd926fec 100644 --- a/server/src/main/java/object/HandDetails.java +++ b/server/src/main/java/object/HandDetails.java @@ -6,16 +6,7 @@ public class HandDetails { private ConcurrentHashMap> hmHandByPlayerNumber = new ConcurrentHashMap<>(); - private ExtendedConcurrentHashMap hmHandSizeByPlayerNumber = new ExtendedConcurrentHashMap<>(); - public ExtendedConcurrentHashMap getHandSizes() - { - return hmHandSizeByPlayerNumber; - } - public void setHandSizes(ExtendedConcurrentHashMap hmHandSizeByPlayerNumber) - { - this.hmHandSizeByPlayerNumber = hmHandSizeByPlayerNumber; - } public ConcurrentHashMap> getHands() { return hmHandByPlayerNumber; diff --git a/server/src/main/java/object/Round.kt b/server/src/main/java/object/Round.kt new file mode 100644 index 00000000..ad3ba8f5 --- /dev/null +++ b/server/src/main/java/object/Round.kt @@ -0,0 +1,3 @@ +package `object` + +data class Round(val hands: Map>, val bidHistory: Map) diff --git a/server/src/main/kotlin/room/Room.kt b/server/src/main/kotlin/room/Room.kt index 625ae8c2..0f648f13 100644 --- a/server/src/main/kotlin/room/Room.kt +++ b/server/src/main/kotlin/room/Room.kt @@ -11,7 +11,6 @@ import java.util.concurrent.ConcurrentHashMap import kotlin.math.max import `object`.Bid import `object`.BidHistory -import `object`.ExtendedConcurrentHashMap import `object`.GameWrapper import `object`.HandDetails import `object`.LeftBid @@ -32,7 +31,7 @@ data class Room( val capacity: Int, private val index: Int = 1, ) : IHasId { - private val hmPlayerByPlayerNumber = ExtendedConcurrentHashMap() + private val hmPlayerByPlayerNumber = ConcurrentHashMap() private val hmFormerPlayerByPlayerNumber: ConcurrentHashMap = ConcurrentHashMap() val chatHistory: MutableList = mutableListOf() private val currentPlayers: MutableList = mutableListOf() @@ -114,7 +113,7 @@ data class Room( if (currentGame.gameStartMillis == -1L) { // Unset the countdown if it's going, reset current capacity and get out of this // madness - currentGame.countdownStartMillis = -1 + currentGame.setCountdownStartMillis(-1) resetCurrentPlayers(fireLobbyChanged) return } @@ -129,16 +128,17 @@ data class Room( val history: BidHistory = currentGame.currentBidHistory history.addBidForPlayer(playerNumber, bid) + // TODO - Hand size thing // Moved this into here as otherwise we set it to 0 incorrectly and a person // ends up with no cards! - val details: HandDetails = currentGame.currentRoundDetails - val hmHandSizeByPlayerNumber = details.handSizes - hmHandSizeByPlayerNumber[playerNumber] = 0 + // val details: HandDetails = currentGame.currentRoundDetails + // val hmHandSizeByPlayerNumber = details.handSizes + // hmHandSizeByPlayerNumber[playerNumber] = 0 } val playerSize: Int = hmPlayerByPlayerNumber.size if (playerSize == 1) { - val remainingPlayerNumber: Int = hmPlayerByPlayerNumber.onlyKey + val remainingPlayerNumber: Int = hmPlayerByPlayerNumber.keys.first() finishCurrentGame(remainingPlayerNumber) } else if (playerSize == 0) { resetCurrentPlayers(fireLobbyChanged) @@ -232,14 +232,10 @@ data class Room( val newGame = GameWrapper(gameId) val details = HandDetails() - val hmHandSizeByPlayerNumber = ExtendedConcurrentHashMap() - for (i in 0.. + if (playerNumber == losingPlayerNumber) max(0, hand.size - 1) else hand.size + } - val potentialWinner: Int = getWinningPlayer(hmHandSizeByPlayerNumberForNextRound) + val potentialWinner: Int = getWinningPlayer(handSizesForNextRound) if (potentialWinner > -1) { finishCurrentGame(potentialWinner) } else { nextRoundDetails = HandDetails() - nextRoundDetails.handSizes = hmHandSizeByPlayerNumberForNextRound val hmHandByPlayerNumber: ConcurrentHashMap> = - dealHandsHashMap(hmHandSizeByPlayerNumberForNextRound) + dealHandsHashMap(handSizesForNextRound) nextRoundDetails.hands = hmHandByPlayerNumber currentGame.setDetailsForRound(currentRoundNumber + 1, nextRoundDetails) @@ -334,7 +329,7 @@ data class Room( } private fun dealHandsHashMap( - hmHandSizeByPlayerNumber: ExtendedConcurrentHashMap + hmHandSizeByPlayerNumber: Map ): ConcurrentHashMap> { val hmHandByPlayerNumber: ConcurrentHashMap> = ConcurrentHashMap() @@ -350,7 +345,7 @@ data class Room( return hmHandByPlayerNumber } - private fun getWinningPlayer(hmHandSizeByPlayerNumber: ConcurrentHashMap): Int { + private fun getWinningPlayer(hmHandSizeByPlayerNumber: Map): Int { var activePlayers = 0 var potentialWinner = 0 diff --git a/test-core/src/main/kotlin/testCore/DtoFactory.kt b/test-core/src/main/kotlin/testCore/DtoFactory.kt index dab85227..07fc0de4 100644 --- a/test-core/src/main/kotlin/testCore/DtoFactory.kt +++ b/test-core/src/main/kotlin/testCore/DtoFactory.kt @@ -1,7 +1,9 @@ package testCore +import game.EntropyBidAction import game.GameMode import game.GameSettings +import game.Suit import http.dto.JoinRoomResponse import http.dto.OnlineMessage import http.dto.RoomStateResponse @@ -56,3 +58,11 @@ fun makeRoomStateResponse( players: Map = mapOf(1 to "Alyssa"), formerPlayers: Map = emptyMap(), ) = RoomStateResponse(players, formerPlayers) + +fun makeEntropyBidAction( + amount: Int = 3, + suit: Suit = Suit.Spades, + player: String = "Alyssa", + cardToReveal: String? = null, + blind: Boolean = false, +) = EntropyBidAction(player, blind, amount, suit).also { it.cardToReveal = cardToReveal }