From aaddbdf614fabfc2fc6954eeb3458b77173270b5 Mon Sep 17 00:00:00 2001 From: alyssa Date: Tue, 15 Jul 2025 08:54:52 +0100 Subject: [PATCH 01/12] introduction of player action --- core/src/main/kotlin/game/BidAction.kt | 17 +++ core/src/main/kotlin/game/ChallengeAction.kt | 6 + core/src/main/kotlin/game/EntropyBidAction.kt | 34 ++++++ core/src/main/kotlin/game/IllegalAction.kt | 6 + core/src/main/kotlin/game/LeaveAction.kt | 7 ++ core/src/main/kotlin/game/PlayerAction.kt | 25 ++++ .../src/main/kotlin/game/VectropyBidAction.kt | 62 ++++++++++ .../test/kotlin/game/ChallengeActionTest.kt | 16 +++ .../test/kotlin/game/EntropyBidActionTest.kt | 64 ++++++++++ .../src/test/kotlin/game/IllegalActionTest.kt | 16 +++ core/src/test/kotlin/game/LeaveActionTest.kt | 16 +++ .../test/kotlin/game/VectropyBidActionTest.kt | 111 ++++++++++++++++++ .../src/main/kotlin/testCore/DtoFactory.kt | 10 ++ 13 files changed, 390 insertions(+) create mode 100644 core/src/main/kotlin/game/BidAction.kt create mode 100644 core/src/main/kotlin/game/ChallengeAction.kt create mode 100644 core/src/main/kotlin/game/EntropyBidAction.kt create mode 100644 core/src/main/kotlin/game/IllegalAction.kt create mode 100644 core/src/main/kotlin/game/LeaveAction.kt create mode 100644 core/src/main/kotlin/game/PlayerAction.kt create mode 100644 core/src/main/kotlin/game/VectropyBidAction.kt create mode 100644 core/src/test/kotlin/game/ChallengeActionTest.kt create mode 100644 core/src/test/kotlin/game/EntropyBidActionTest.kt create mode 100644 core/src/test/kotlin/game/IllegalActionTest.kt create mode 100644 core/src/test/kotlin/game/LeaveActionTest.kt create mode 100644 core/src/test/kotlin/game/VectropyBidActionTest.kt diff --git a/core/src/main/kotlin/game/BidAction.kt b/core/src/main/kotlin/game/BidAction.kt new file mode 100644 index 0000000..ea503d5 --- /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 0000000..ef7f7f5 --- /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 0000000..3d3a54a --- /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 0000000..87b68ff --- /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 0000000..e8f0a4a --- /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 0000000..3c9adbc --- /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 0000000..19ae5b1 --- /dev/null +++ b/core/src/main/kotlin/game/VectropyBidAction.kt @@ -0,0 +1,62 @@ +package game + +data class VectropyBidAction( + override val playerName: String, + override val blind: Boolean, + val amounts: Map, +) : BidAction() { + + constructor( + playerName: String, + blind: Boolean, + clubs: Int, + diamonds: Int, + hearts: Int, + moons: Int?, + spades: Int, + stars: Int?, + ) : this(playerName, blind, constructMap(clubs, diamonds, hearts, moons, spades, stars)) + + fun getTotal() = amounts.values.sum() + + fun getAmount(suit: Suit) = amounts[suit] + + override fun higherThan(other: VectropyBidAction) = + getTotal() > other.getTotal() && + amounts.all { (suit, myAmount) -> myAmount >= other.getAmount(suit)!! } + + override fun overAchievementThreshold() = getTotal() >= 5 + + override fun isPerfect(cards: List, settings: GameSettings): Boolean = + amounts.all { (suit, amount) -> amount == countSuit(suit, cards, settings.jokerValue) } + + override fun isOverbid(cards: List, settings: GameSettings): Boolean = + amounts.any { (suit, amount) -> amount > countSuit(suit, cards, settings.jokerValue) } + + override fun plainString(): String { + val suits = amounts.keys.sorted().map(::getAmount) + return "(${suits.joinToString()})" + } +} + +private fun constructMap( + clubs: Int, + diamonds: Int, + hearts: Int, + moons: Int?, + spades: Int, + stars: Int?, +): Map { + val map = + mutableMapOf( + Suit.Clubs to clubs, + Suit.Diamonds to diamonds, + Suit.Hearts to hearts, + Suit.Spades to spades, + ) + + moons?.let { map[Suit.Moons] = moons } + stars?.let { map[Suit.Stars] = stars } + + return map.toMap() +} diff --git a/core/src/test/kotlin/game/ChallengeActionTest.kt b/core/src/test/kotlin/game/ChallengeActionTest.kt new file mode 100644 index 0000000..dad37c2 --- /dev/null +++ b/core/src/test/kotlin/game/ChallengeActionTest.kt @@ -0,0 +1,16 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest +import utils.CoreGlobals + +class ChallengeActionTest : AbstractTest() { + @Test + fun `Should serialise and deserialise correctly`() { + val action: PlayerAction = ChallengeAction("Mike", false) + val json = CoreGlobals.jsonMapper.writeValueAsString(action) + val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) + deserialized shouldBe action + } +} diff --git a/core/src/test/kotlin/game/EntropyBidActionTest.kt b/core/src/test/kotlin/game/EntropyBidActionTest.kt new file mode 100644 index 0000000..cfa81d3 --- /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/core/src/test/kotlin/game/IllegalActionTest.kt b/core/src/test/kotlin/game/IllegalActionTest.kt new file mode 100644 index 0000000..6572dd4 --- /dev/null +++ b/core/src/test/kotlin/game/IllegalActionTest.kt @@ -0,0 +1,16 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest +import utils.CoreGlobals + +class IllegalActionTest : AbstractTest() { + @Test + fun `Should serialise and deserialise correctly`() { + val action: PlayerAction = IllegalAction("Susan", false) + val json = CoreGlobals.jsonMapper.writeValueAsString(action) + val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) + deserialized shouldBe action + } +} diff --git a/core/src/test/kotlin/game/LeaveActionTest.kt b/core/src/test/kotlin/game/LeaveActionTest.kt new file mode 100644 index 0000000..aecce47 --- /dev/null +++ b/core/src/test/kotlin/game/LeaveActionTest.kt @@ -0,0 +1,16 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest +import utils.CoreGlobals + +class LeaveActionTest : AbstractTest() { + @Test + fun `Should serialise and deserialise correctly`() { + val action: PlayerAction = LeaveAction("Wally") + val json = CoreGlobals.jsonMapper.writeValueAsString(action) + val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) + deserialized shouldBe action + } +} diff --git a/core/src/test/kotlin/game/VectropyBidActionTest.kt b/core/src/test/kotlin/game/VectropyBidActionTest.kt new file mode 100644 index 0000000..2dc4861 --- /dev/null +++ b/core/src/test/kotlin/game/VectropyBidActionTest.kt @@ -0,0 +1,111 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import testCore.AbstractTest +import testCore.makeGameSettings +import utils.CoreGlobals + +class VectropyBidActionTest : AbstractTest() { + @Test + fun `Should serialise and deserialise correctly`() { + val action: PlayerAction = + VectropyBidAction("", false, 0, 1, 2, 3, 4, 5).apply { cardToReveal = "Ac" } + val json = CoreGlobals.jsonMapper.writeValueAsString(action) + val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) + deserialized shouldBe action + } + + @Test + fun `Should be able to construct with suit values`() { + val action = VectropyBidAction("", false, 0, 1, 2, 3, 4, 5) + action.getAmount(Suit.Clubs) shouldBe 0 + action.getAmount(Suit.Moons) shouldBe 3 + action.getAmount(Suit.Stars) shouldBe 5 + + val nullsAction = VectropyBidAction("", false, 0, 1, 2, null, 3, null) + nullsAction.getAmount(Suit.Clubs) shouldBe 0 + nullsAction.getAmount(Suit.Moons) shouldBe null + nullsAction.getAmount(Suit.Spades) shouldBe 3 + nullsAction.getAmount(Suit.Stars) shouldBe null + } + + @Test + fun `Should be able to get total`() { + val action = VectropyBidAction("", false, 0, 1, 2, 3, 4, 5) + action.getTotal() shouldBe 15 + + val nullsAction = VectropyBidAction("", false, 0, 1, 2, null, 3, null) + nullsAction.getTotal() shouldBe 6 + } + + @Test + fun `Should be able to compare bids`() { + val baseAmounts = Suit.entries.associateWith { 2 } + val highAmounts = Suit.entries.associateWith { 5 } + val baseBid = VectropyBidAction("", false, baseAmounts) + baseBid.higherThan(baseBid) shouldBe false + + Suit.entries.forEach { suit -> + val higherBid = VectropyBidAction("", false, baseAmounts.plus(suit to 3)) + higherBid.higherThan(baseBid) shouldBe true + baseBid.higherThan(higherBid) shouldBe false + + val lowerBid = VectropyBidAction("", false, highAmounts.plus(suit to 1)) + lowerBid.higherThan(baseBid) shouldBe false + baseBid.higherThan(lowerBid) shouldBe false + } + } + + @Test + fun `Should check if over achievement threshold`() { + VectropyBidAction("", false, 1, 1, 1, 1, 0, 1).overAchievementThreshold() shouldBe true + VectropyBidAction("", false, 1, 1, 1, null, 1, null).overAchievementThreshold() shouldBe + false + } + + @Test + fun `Should detect a perfect bid`() { + val cards = listOf("Ad", "3c", "Jo1") + val settings = makeGameSettings(jokerValue = 2) + val baseMap = Suit.entries.associateWith { 0 } + + val checkPerfect: (map: Map, expected: Boolean) -> Unit = { map, expected -> + VectropyBidAction("", false, map).isPerfect(cards, settings) shouldBe expected + } + checkPerfect(baseMap + (Suit.Clubs to 5), false) + checkPerfect(baseMap + (Suit.Clubs to 3), false) + + val perfect = mapOf(Suit.Clubs to 4, Suit.Diamonds to 4, Suit.Hearts to 3, Suit.Spades to 3) + checkPerfect(perfect, true) + } + + @Test + fun `Should detect an overbid`() { + val cards = listOf("Ad", "3c", "Jo1") + val settings = makeGameSettings(jokerValue = 2) + val baseMap = Suit.entries.associateWith { 0 } + + val checkOverbid: (map: Map, expected: Boolean) -> Unit = { map, expected -> + VectropyBidAction("", false, map).isOverbid(cards, settings) shouldBe expected + } + checkOverbid(baseMap + (Suit.Clubs to 5), true) + checkOverbid(baseMap + (Suit.Diamonds to 5), true) + checkOverbid(baseMap + (Suit.Hearts to 4), true) + checkOverbid(baseMap + (Suit.Spades to 4), true) + + val perfect = mapOf(Suit.Clubs to 4, Suit.Diamonds to 4, Suit.Hearts to 3, Suit.Spades to 3) + checkOverbid(perfect, false) + } + + @Test + fun `Should render to a plainString`() { + val coreSuits = + mapOf(Suit.Clubs to 3, Suit.Diamonds to 0, Suit.Hearts to 2, Suit.Spades to 1) + VectropyBidAction("Alyssa", false, coreSuits).plainString() shouldBe "(3, 0, 2, 1)" + + val allSuits = coreSuits + mapOf(Suit.Moons to 7, Suit.Stars to 9) + VectropyBidAction("", false, allSuits).plainString() shouldBe "(3, 0, 2, 7, 1, 9)" + VectropyBidAction("", false, allSuits).htmlString() shouldBe "(3, 0, 2, 7, 1, 9)" + } +} diff --git a/test-core/src/main/kotlin/testCore/DtoFactory.kt b/test-core/src/main/kotlin/testCore/DtoFactory.kt index dab8522..07fc0de 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 } From 0540bd63d5d9ebd448fe83aea0bd5e3f581fda48 Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 16 Jul 2025 08:51:24 +0100 Subject: [PATCH 02/12] wip plugging in --- .../main/java/online/screen/EntropyRoom.java | 20 ++-- .../src/main/java/online/screen/GameRoom.java | 108 ++++++++---------- .../java/online/util/ResponseHandler.java | 16 ++- .../java/online/util/XmlBuilderClient.java | 39 ++++--- client/src/main/java/screen/BidPanel.java | 17 ++- .../src/main/java/screen/EntropyBidPanel.java | 8 +- client/src/main/java/util/BidListener.java | 5 +- client/src/main/kotlin/http/RoomApi.kt | 14 +-- 8 files changed, 122 insertions(+), 105 deletions(-) diff --git a/client/src/main/java/online/screen/EntropyRoom.java b/client/src/main/java/online/screen/EntropyRoom.java index 2449aeb..dfd418e 100644 --- a/client/src/main/java/online/screen/EntropyRoom.java +++ b/client/src/main/java/online/screen/EntropyRoom.java @@ -3,19 +3,21 @@ 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 +25,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 +39,7 @@ public void doSpecificResetGameVariables() @Override public void resetBids() { - lastBid = new EntropyBid(Suit.Clubs, 0); + lastBid = null; hmBidByPlayerNumber.clear(); } @@ -46,7 +48,7 @@ public void updateScreenForChallengeOrIllegal() { if (isVisible()) { - var lastBidSuit = ((EntropyBid)lastBid).getBidSuit(); + var lastBidSuit = lastBid.getSuit(); handPanel.displayAndHighlightHands(lastBidSuit); achievementTracker.unlockPerfectBidAchievements(earnedPsychic); @@ -69,20 +71,20 @@ 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 action) { - achievementTracker.updatePerfectBidVariables(bid); + achievementTracker.updatePerfectBidVariables(action); } @Override - public void updateAchievementVariables(Bid bid) + public void updateAchievementVariables(EntropyBidAction action) { - achievementTracker.update(bid); + achievementTracker.update(action); } @Override diff --git a/client/src/main/java/online/screen/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index 2f64f4a..12186f6 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; @@ -30,10 +29,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 +59,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 +204,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() @@ -532,9 +531,7 @@ private void removePlayer(int playerNumberAdjusted, boolean formerPlayer) //Move on to the next player if we're in actual play if (!formerPlayer) { - LeftBid bid = new LeftBid(); - bid.setPlayer(removedPlayer); - addBidToBidBox(playerNumberAdjusted, bid); + addBidToBidBox(new LeaveAction(removedPlayer.getName())); boolean wasPlayersTurn = handPanel.playerIsSelected(playerNumberAdjusted); if (wasPlayersTurn) @@ -593,16 +590,11 @@ private void processChallengeOrIllegal() } } - private void addBidToBidBox(int playerNumberAdjusted, Bid bid) + private void addBidToBidBox(PlayerAction action) { - boolean blind = handPanel.isPlayingBlind() - && playerNumberAdjusted == playerNumberLocal - && !(bid instanceof LeftBid); + hasActedBlindThisGame |= action.getBlind(); - hasActedBlindThisGame |= blind; - bid.setBlind(blind); - - listmodel.add(0, bid); + listmodel.add(0, action); } public void showResult(String result) @@ -767,7 +759,7 @@ public void startObserving(int personToStart, int lastPlayerToAct) handPanel.displayHandsOnline(hmHandByAdjustedPlayerNumber); handPanel.setInitted(true); - Bid lastBid = hmBidByPlayerNumber.get(lastPlayerToAct); + B lastBid = hmBidByPlayerNumber.get(lastPlayerToAct); if (lastBid != null) { handleBid(lastPlayerToAct, lastBid); @@ -779,39 +771,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(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("Unexpected action type: " + action); + } 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 +986,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 +1369,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 +1399,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 +1410,7 @@ private void processPlayerBid() } } - boolean overBid = lastBid.isOverbid(hmHandByAdjustedPlayerNumber, settings.getJokerValue()); + boolean overBid = lastBid.isOverbid(allCards(), settings); if (overBid) { hasOverbid = true; @@ -1434,13 +1428,12 @@ public void cardRevealed(String card) public void challengeMade() { enableBidPanel(false); - - Bid bid = new ChallengeBid(); + Player player = hmPlayerByAdjustedPlayerNumber.get(0); - bid.setPlayer(player); + var action = new ChallengeAction(player.getName(), handPanel.isPlayingBlind()); Document challenge = XmlBuilderClient.factoryBidXml(roomName, getUsername(), getGameId(), - getRoundNumber(), bid, lastPlayerToAct); + getRoundNumber(), action, lastPlayerToAct); MessageUtil.sendMessage(challenge, 0); } @@ -1448,13 +1441,12 @@ public void challengeMade() public void illegalCalled() { enableBidPanel(false); - - Bid bid = new IllegalBid(); + Player player = hmPlayerByAdjustedPlayerNumber.get(0); - bid.setPlayer(player); + var action = new IllegalAction(player.getName(), handPanel.isPlayingBlind()); Document illegal = XmlBuilderClient.factoryBidXml(roomName, getUsername(), getGameId(), - getRoundNumber(), bid, lastPlayerToAct); + getRoundNumber(), action, lastPlayerToAct); MessageUtil.sendMessage(illegal, 0); } diff --git a/client/src/main/java/online/util/ResponseHandler.java b/client/src/main/java/online/util/ResponseHandler.java index a6a7199..1804836 100644 --- a/client/src/main/java/online/util/ResponseHandler.java +++ b/client/src/main/java/online/util/ResponseHandler.java @@ -1,6 +1,8 @@ package online.util; +import com.fasterxml.jackson.core.JsonProcessingException; +import game.PlayerAction; import object.Bid; import online.screen.EntropyLobby; import online.screen.GameRoom; @@ -11,6 +13,7 @@ import org.w3c.dom.NodeList; import screen.ScreenCache; import util.*; +import utils.CoreGlobals; import java.util.ArrayList; import java.util.HashMap; @@ -189,12 +192,13 @@ private static void handleNewBid(Element root, EntropyLobby lobby) GameRoom gameRoom = lobby.getGameRoomForName(id); int playerNumber = XmlUtil.getAttributeInt(root, "PlayerNumber"); String bidStr = root.getAttribute("Bid"); - - boolean includeMoons = gameRoom.getIncludeMoons(); - boolean includeStars = gameRoom.getIncludeStars(); - Bid bid = Bid.factoryFromXmlString(bidStr, includeMoons, includeStars); - - gameRoom.handleBid(playerNumber, bid); + + try { + PlayerAction action = CoreGlobals.jsonMapper.readValue(bidStr, PlayerAction.class); + gameRoom.handleBid(playerNumber, action); + } catch (JsonProcessingException jpe) { + throw new RuntimeException("Failed to deserialise bid " + bidStr, jpe); + } } private static void handleGameOverResponse(Element root, EntropyLobby lobby) diff --git a/client/src/main/java/online/util/XmlBuilderClient.java b/client/src/main/java/online/util/XmlBuilderClient.java index d773de6..71a7f2e 100644 --- a/client/src/main/java/online/util/XmlBuilderClient.java +++ b/client/src/main/java/online/util/XmlBuilderClient.java @@ -1,5 +1,7 @@ package online.util; +import com.fasterxml.jackson.core.JsonProcessingException; +import game.PlayerAction; import object.Bid; import org.w3c.dom.Document; @@ -7,6 +9,7 @@ import util.XmlConstants; import util.XmlUtil; +import utils.CoreGlobals; public class XmlBuilderClient implements XmlConstants { @@ -40,24 +43,28 @@ public static Document factoryNewGameRequest(String roomId, String gameId, Strin return document; } - public static Document factoryBidXml(String roomId, String username, String gameId, int roundNumber, Bid bid, int previousBidder) + public static Document factoryBidXml(String roomId, String username, String gameId, int roundNumber, PlayerAction action, int previousBidder) { - Document document = XmlUtil.factoryNewDocument(); - Element rootElement = document.createElement(ROOT_TAG_BID); - - rootElement.setAttribute("RoomId", roomId); - rootElement.setAttribute("Username", username); - rootElement.setAttribute("GameId", gameId); - rootElement.setAttribute("RoundNumber", "" + roundNumber); - rootElement.setAttribute("Bid", bid.toXmlString()); - - if (previousBidder > -1) - { - rootElement.setAttribute("PreviousBidder", "" + previousBidder); + try { + Document document = XmlUtil.factoryNewDocument(); + Element rootElement = document.createElement(ROOT_TAG_BID); + + rootElement.setAttribute("RoomId", roomId); + rootElement.setAttribute("Username", username); + rootElement.setAttribute("GameId", gameId); + rootElement.setAttribute("RoundNumber", "" + roundNumber); + rootElement.setAttribute("Bid", CoreGlobals.jsonMapper.writeValueAsString(action)); + + if (previousBidder > -1) + { + rootElement.setAttribute("PreviousBidder", "" + previousBidder); + } + + document.appendChild(rootElement); + return document; + } catch (JsonProcessingException jpe) { + throw new RuntimeException("Couldn't write bid as JSON: " + action, jpe); } - - document.appendChild(rootElement); - return document; } public static Document factoryLeaderboardRequest(String username) diff --git a/client/src/main/java/screen/BidPanel.java b/client/src/main/java/screen/BidPanel.java index 627b4cc..8089620 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,9 +28,9 @@ 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) + public void addBidListener(BidListener listener) { this.listener = listener; } diff --git a/client/src/main/java/screen/EntropyBidPanel.java b/client/src/main/java/screen/EntropyBidPanel.java index 622bd8b..ad69ebf 100644 --- a/client/src/main/java/screen/EntropyBidPanel.java +++ b/client/src/main/java/screen/EntropyBidPanel.java @@ -24,13 +24,14 @@ 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 @@ -44,9 +45,10 @@ public class EntropyBidPanel extends BidPanel private boolean includeMoons = false; 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); diff --git a/client/src/main/java/util/BidListener.java b/client/src/main/java/util/BidListener.java index d31fd37..71502f4 100644 --- a/client/src/main/java/util/BidListener.java +++ b/client/src/main/java/util/BidListener.java @@ -1,10 +1,11 @@ package util; +import game.BidAction; import object.Bid; -public interface BidListener +public interface BidListener > { - public void bidMade(Bid bid); + public void bidMade(B bid); public void challengeMade(); public void illegalCalled(); } diff --git a/client/src/main/kotlin/http/RoomApi.kt b/client/src/main/kotlin/http/RoomApi.kt index c8fc116..f0c4b94 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) From 0ef6eb3e2fd9d420c9c1b7b99a87d65629960830 Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 16 Jul 2025 18:44:32 +0100 Subject: [PATCH 03/12] wip plugging in --- .../object/EntropyAchievementsTracker.java | 28 +++-- .../src/main/java/online/screen/GameRoom.java | 20 ++-- .../main/java/online/screen/VectropyRoom.java | 24 ++-- .../src/main/java/screen/EntropyBidPanel.java | 34 +++--- .../src/main/java/screen/EntropyScreen.java | 15 +-- client/src/main/java/screen/GameScreen.java | 107 ++++++------------ client/src/main/java/screen/MainScreen.java | 7 +- client/src/main/java/screen/ReplayDialog.java | 19 ++-- .../main/java/screen/VectropyBidPanel.java | 99 +++++++--------- .../src/main/java/screen/VectropyScreen.java | 2 +- .../src/main/java/util/AchievementsUtil.java | 10 +- client/src/main/java/util/ReplayFileUtil.java | 8 +- client/src/main/kotlin/game/RegistryUtil.kt | 32 ++++++ core/src/main/java/util/Registry.java | 5 +- core/src/main/kotlin/game/PlayerAction.kt | 3 + 15 files changed, 191 insertions(+), 222 deletions(-) create mode 100644 client/src/main/kotlin/game/RegistryUtil.kt diff --git a/client/src/main/java/object/EntropyAchievementsTracker.java b/client/src/main/java/object/EntropyAchievementsTracker.java index f8e7286..09e6a59 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,10 +86,9 @@ 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) { @@ -116,25 +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(); + var bidSuit = bidMade.getSuit(); if (isCardRelevant(card, bidSuit)) { revealedSameSuit = true; @@ -145,16 +143,16 @@ private void updateCardReveal(EntropyBid bidMade) } } - 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/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index 12186f6..0bb2092 100644 --- a/client/src/main/java/online/screen/GameRoom.java +++ b/client/src/main/java/online/screen/GameRoom.java @@ -7,6 +7,7 @@ import org.w3c.dom.Document; import screen.*; import util.*; +import utils.CoreGlobals; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -23,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.prefs.Preferences; +import static game.RegistryUtilKt.writeActions; import static utils.ColourUtilKt.getColourForPlayerNumber; import static utils.CoreGlobals.logger; @@ -159,8 +161,8 @@ public GameRoom(UUID id, String roomName, GameSettings settings, int players) private final TransparentPanel rightPane = new TransparentPanel(); public final HandPanelMk2 handPanel = new HandPanelMk2(this); private final JScrollPane scrollPane = new JScrollPane(); - private final DefaultListModel listmodel = new DefaultListModel<>(); - private final JList bidBox = new JList<>(listmodel); + private final DefaultListModel listmodel = new DefaultListModel<>(); + private final JList bidBox = new JList<>(listmodel); private final TransparentPanel rightCenter = new TransparentPanel(); private final JTextPane lblResult = new JTextPane(); private final JPanel panelInformation = new JPanel(); @@ -1044,7 +1046,7 @@ public void setGameId(String gameId) { this.gameId = gameId; } - public DefaultListModel getListmodel() + public DefaultListModel getListmodel() { return listmodel; } @@ -1063,13 +1065,7 @@ public void saveRoundForReplay() replay.putInt(REPLAY_INT_ROUNDS_SO_FAR, roundsSoFar); //save the listmodel - int historySize = listmodel.size(); - replay.putInt(roundsSoFar + REPLAY_INT_HISTORY_SIZE, historySize); - for (int i=0; i { private boolean earnedMathematician = false; @@ -20,7 +22,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 +36,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 +73,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/EntropyBidPanel.java b/client/src/main/java/screen/EntropyBidPanel.java index ad69ebf..7588085 100644 --- a/client/src/main/java/screen/EntropyBidPanel.java +++ b/client/src/main/java/screen/EntropyBidPanel.java @@ -36,8 +36,6 @@ public class EntropyBidPanel extends BidPanel ChangeListener, Registry { - private String suitSelected = Suit.Clubs.getUnicodeStr(); - private Suit bidSuit = Suit.Clubs; private Suit lastBidSuit = Suit.Clubs; private int lastBidAmount = 0; @@ -181,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); @@ -248,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(); } @@ -274,13 +269,15 @@ 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()); } @@ -361,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); } } @@ -416,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 22dc02d..653d9b3 100644 --- a/client/src/main/java/screen/EntropyScreen.java +++ b/client/src/main/java/screen/EntropyScreen.java @@ -2,6 +2,7 @@ import java.awt.BorderLayout; +import game.EntropyBidAction; import game.GameMode; import game.Suit; import object.EntropyAchievementsTracker; @@ -13,7 +14,7 @@ import static game.EntropyUtilKt.perfectBidAmount; import static game.EntropyUtilKt.perfectBidSuit; -public class EntropyScreen extends GameScreen +public class EntropyScreen extends GameScreen { private static final long serialVersionUID = 1L; @@ -22,7 +23,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)); @@ -39,7 +40,7 @@ public EntropyScreen() @Override public void showResult() { - var lastBidSuit = ((EntropyBid)lastBid).getBidSuit(); + var lastBidSuit = lastBid.getSuit(); handPanel.displayAndHighlightHands(lastBidSuit); int total = countSuit(lastBidSuit, getConcatenatedHands(), settings.getJokerValue()); @@ -70,8 +71,8 @@ public void 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_SUIT_NAME, lastBid.getSuit().name()); + savedGame.putInt(Registry.SAVED_GAME_INT_LAST_BID_AMOUNT, lastBid.getAmount()); } //other booleans @@ -85,7 +86,7 @@ public void saveGame() protected void saveRoundForReplay() { 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(); } @@ -150,7 +151,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 1f1513a..c45d408 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -1,14 +1,13 @@ package screen; import achievement.AchievementSetting; -import game.GameMode; -import game.GameSettings; -import game.Suit; +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; @@ -19,12 +18,14 @@ import static game.CardsUtilKt.countSuit; import static game.CardsUtilKt.createAndShuffleDeck; import static game.CheatUtilKt.containsNonJoker; +import static game.RegistryUtilKt.populateActions; +import static game.RegistryUtilKt.writeActions; import static screen.ScreenCacheKt.IN_GAME_REPLAY; import static util.ClientGlobals.achievementStore; import static utils.CoreGlobals.logger; -public abstract class GameScreen extends TransparentPanel - implements BidListener, +public abstract class GameScreen> extends TransparentPanel + implements BidListener, RevealListener, Registry { @@ -35,7 +36,7 @@ public abstract class GameScreen extends TransparentPanel private Player currentPlayer = null; private int handicapAmount; - public Bid lastBid = null; + public B lastBid = null; private boolean playBlind; private boolean playWithHandicap; @@ -58,7 +59,7 @@ public abstract class GameScreen extends TransparentPanel public Player opponentTwo = null; public Player opponentThree = null; - public BidPanel bidPanel = null; + public BidPanel bidPanel = null; public HandPanelMk2 handPanel = new HandPanelMk2(this); //Abstract methods @@ -75,25 +76,18 @@ public abstract class GameScreen extends TransparentPanel public abstract void updateAchievementVariables(); public void startNewGame() - { - try - { - Debug.appendBanner("New Game", logging); - cancelNewRound(); - - boolean playerEnabled = player != null && player.isEnabled(); - AchievementsUtil.unlockCoward(gameOver, playerEnabled, firstRound); - ScreenCache.get(MainScreen.class).dismissCurrentReplay(); + { + Debug.appendBanner("New Game", logging); + cancelNewRound(); - initVariablesForNewGame(); - initVariables(); - - startRound(); - } - catch (Throwable e) - { - Debug.stackTrace(e); - } + boolean playerEnabled = player != null && player.isEnabled(); + AchievementsUtil.unlockCoward(gameOver, playerEnabled, firstRound); + ScreenCache.get(MainScreen.class).dismissCurrentReplay(); + + initVariablesForNewGame(); + initVariables(); + + startRound(); } public void startNewRound() @@ -211,7 +205,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(); + var listmodel = ScreenCache.get(MainScreen.class).getListmodel(); listmodel.removeAllElements(); player.resetHand(); @@ -464,14 +458,7 @@ protected void saveRoundForReplay() inGameReplay.putInt(REPLAY_INT_ROUNDS_SO_FAR, roundsSoFar); //save the listmodel - 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()); - } + writeActions(inGameReplay, ScreenCache.get(MainScreen.class).getListmodel(), roundsSoFar); inGameReplay.putBoolean(REPLAY_BOOLEAN_PLAY_BLIND, playBlind); inGameReplay.putBoolean(REPLAY_BOOLEAN_PLAY_WITH_HANDICAP, playWithHandicap); @@ -521,14 +508,7 @@ protected void saveGame() settings.exportToRegistry(savedGame); //save the listmodel - 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(); - 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()); - listmodel.addElement(bid); - } + populateActions(savedGame, ScreenCache.get(MainScreen.class).getListmodel(), null); //get who is enabled player.setEnabled(savedGame.getBoolean(SAVED_GAME_BOOLEAN_PLAYER_ENABLED, false)); @@ -826,7 +799,6 @@ private void processPlayerBid() boolean actedBlind = handPanel.isPlayingBlind(); hasActedBlindThisGame &= actedBlind; - lastBid.setBlind(actedBlind); addToListmodel(lastBid); updateAchievementVariables(); @@ -835,7 +807,7 @@ private void processPlayerBid() handlePerfectBid(lastBid); } - if (lastBid.isOverbid(getConcatenatedHands(), settings.getJokerValue())) + if (lastBid.isOverbid(getConcatenatedHands(), settings)) { hasOverbid = true; } @@ -843,10 +815,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 +837,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); @@ -1026,10 +998,9 @@ public void fireAppearancePreferencesChange() * BidListener */ @Override - public void bidMade(Bid bid) + public void bidMade(B bid) { bidPanel.enableBidPanel(false); - bid.setPlayer(player); lastBid = bid; if (settings.getCardReveal() @@ -1049,11 +1020,9 @@ public void challengeMade() Debug.append("Player challenged.", logging); boolean actedBlind = handPanel.isPlayingBlind(); hasActedBlindThisGame &= actedBlind; - - Bid bid = new ChallengeBid(); - bid.setPlayer(player); - bid.setBlind(actedBlind); - addToListmodel(bid); + + var challenge = new ChallengeAction(player.getName(), actedBlind); + addToListmodel(challenge); processChallenge(player); } @@ -1064,11 +1033,9 @@ 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); - addToListmodel(bid); + + var illegal = new IllegalAction(player.getName(), actedBlind); + addToListmodel(illegal); processIllegal(player); } @@ -1085,10 +1052,10 @@ public void cardRevealed(String card) processPlayerBid(); } - private void addToListmodel(Bid bid) + private void addToListmodel(PlayerAction action) { - DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); - listmodel.add(0, bid); + DefaultListModel listmodel = ScreenCache.get(MainScreen.class).getListmodel(); + listmodel.add(0, action); } /** diff --git a/client/src/main/java/screen/MainScreen.java b/client/src/main/java/screen/MainScreen.java index 4448ff4..9a35db5 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/ReplayDialog.java b/client/src/main/java/screen/ReplayDialog.java index e0e6754..6fe27cd 100644 --- a/client/src/main/java/screen/ReplayDialog.java +++ b/client/src/main/java/screen/ReplayDialog.java @@ -33,6 +33,7 @@ import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; +import game.PlayerAction; import game.Suit; import object.Bid; import object.BidListCellRenderer; @@ -44,6 +45,7 @@ import static game.CardsUtilKt.countSuit; import static game.CardsUtilKt.isCardRelevant; +import static game.RegistryUtilKt.populateActions; import static game.RenderingUtilKt.getVectropyResult; import static utils.CoreGlobals.logger; @@ -301,9 +303,9 @@ public ReplayDialog() private final JLabel[] opponentTwoCards = {opponentTwoCard1, opponentTwoCard2, opponentTwoCard3, opponentTwoCard4, opponentTwoCard5}; private final JLabel[] opponentOneCards = {opponentCard1, opponentCard2, opponentCard3, opponentCard4, opponentCard5}; private final JLabel[] playerCards = {playerCard5, playerCard4, playerCard3, playerCard2, playerCard1}; - 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 PlayerLabel lblOpponentOne = new PlayerLabel("Mark"); private final PlayerLabel lblPlayer = new PlayerLabel("Player"); @@ -575,10 +577,10 @@ private void setLabelVisibility(PlayerLabel label, String name, boolean enabled, private boolean playerLeftThisRound(String name) { - int historySize = replay.getInt(roundNumber + REPLAY_INT_HISTORY_SIZE, 0); + int historySize = replay.getInt(roundNumber + SHARED_INT_HISTORY_SIZE, 0); for (int i = 0; i < historySize; i++) { - String modelItem = replay.get(roundNumber + REPLAY_STRING_LISTMODEL + i, ""); + String modelItem = replay.get(roundNumber + SHARED_STRING_LISTMODEL + i, ""); if (modelItem.contains(name + " left")) { return true; @@ -690,14 +692,7 @@ private void highlightHand(Suit suit, List hand, JLabel[] cards) private void populateBidHistory() { - listmodel.clear(); - int historySize = replay.getInt(roundNumber + REPLAY_INT_HISTORY_SIZE, 0); - for (int i = 0; i < historySize; i++) - { - String modelItem = replay.get(roundNumber + REPLAY_STRING_LISTMODEL + i, ""); - Bid bid = Bid.factoryFromXmlString(modelItem, includeMoons, includeStars); - listmodel.addElement(bid); - } + populateActions(replay, listmodel, roundNumber); } private void showResult(Suit suit) diff --git a/client/src/main/java/screen/VectropyBidPanel.java b/client/src/main/java/screen/VectropyBidPanel.java index 09d4dcb..c8cbe49 100644 --- a/client/src/main/java/screen/VectropyBidPanel.java +++ b/client/src/main/java/screen/VectropyBidPanel.java @@ -9,6 +9,7 @@ import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.HashMap; import java.util.prefs.Preferences; import javax.swing.BorderFactory; @@ -24,25 +25,35 @@ import javax.swing.event.ChangeListener; import game.Suit; +import game.VectropyBidAction; import object.Bid; import object.VectropyBid; import util.Debug; import util.EntropyColour; import util.Registry; -public class VectropyBidPanel extends BidPanel +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() + private HashMap suitSpinners = new HashMap<>() {{ + put(Suit.Clubs, clubSpinner); + put(Suit.Diamonds, diamondSpinner); + put(Suit.Hearts, heartSpinner); + put(Suit.Spades, spadeSpinner); + put(Suit.Moons, moonSpinner); + put(Suit.Stars, starSpinner); + }}; + + public VectropyBidPanel(String playerName, HandPanelMk2 handPanel) { + super(playerName, handPanel); setLayout(new BorderLayout(0, 0)); updateSpinnerColours(); @@ -224,30 +235,23 @@ public void init(int maxBid, int totalNumberOfCards, boolean online, boolean inc totalCardsLabel.setText("x " + totalNumberOfCards); 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(); - int diamonds = lastBid.getDiamonds(); - int hearts = lastBid.getHearts(); - int moons = lastBid.getMoons(); - int spades = lastBid.getSpades(); - int stars = lastBid.getStars(); - - clubSpinner.setModel(new SpinnerNumberModel(clubs, clubs, maxBid, 1)); - diamondSpinner.setModel(new SpinnerNumberModel(diamonds, diamonds, maxBid, 1)); - heartSpinner.setModel(new SpinnerNumberModel(hearts, hearts, maxBid, 1)); - moonSpinner.setModel(new SpinnerNumberModel(moons, moons, maxBid, 1)); - spadeSpinner.setModel(new SpinnerNumberModel(spades, spades, maxBid, 1)); - starSpinner.setModel(new SpinnerNumberModel(stars, stars, maxBid, 1)); + this.lastBid = bid; + + suitSpinners.forEach((suit, spinner) -> { + var amount = lastBid.getAmount(suit); + if (amount != null) { + int min = (int)amount; + spinner.setModel(new SpinnerNumberModel(min, min, maxBid, 1)); + } + }); } private void setIllegalButtonState() @@ -262,51 +266,28 @@ private void setIllegalButtonState() private void updateSpinnerColours() { - String numberOfColoursStr = prefs.get(PREFERENCES_STRING_NUMBER_OF_COLOURS, Registry.TWO_COLOURS); - boolean fourColours = (numberOfColoursStr.equals(Registry.FOUR_COLOURS)); - - if (fourColours) - { - clubLabel.setForeground(EntropyColour.COLOUR_SUIT_GREEN); - diamondLabel.setForeground(Color.BLUE); - moonLabel.setForeground(EntropyColour.COLOUR_SUIT_PURPLE); - } - else - { - clubLabel.setForeground(Color.BLACK); - diamondLabel.setForeground(Color.RED); - moonLabel.setForeground(EntropyColour.COLOUR_SUIT_GOLD); - } + clubLabel.setForeground(Suit.Clubs.getColour()); + diamondLabel.setForeground(Suit.Diamonds.getColour()); + moonLabel.setForeground(Suit.Moons.getColour()); } 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 spades = (int)spadeSpinner.getValue(); - - int stars = 0; - if (includeStars) - { - stars = (int)starSpinner.getValue(); - } - - return new VectropyBid(clubs, diamonds, hearts, moons, spades, stars, includeMoons, includeStars); + var map = new HashMap(); + var suits = Suit.filter(includeMoons, includeStars); + suits.forEach(suit -> { + var amount = (int)suitSpinners.get(suit).getValue(); + map.put(suit, amount); + }); + + return new VectropyBidAction(playerName, handPanel.isPlayingBlind(), map); } @Override diff --git a/client/src/main/java/screen/VectropyScreen.java b/client/src/main/java/screen/VectropyScreen.java index 65ba025..a248b20 100644 --- a/client/src/main/java/screen/VectropyScreen.java +++ b/client/src/main/java/screen/VectropyScreen.java @@ -27,7 +27,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); diff --git a/client/src/main/java/util/AchievementsUtil.java b/client/src/main/java/util/AchievementsUtil.java index 744d3a6..2119150 100644 --- a/client/src/main/java/util/AchievementsUtil.java +++ b/client/src/main/java/util/AchievementsUtil.java @@ -2,6 +2,7 @@ import achievement.AchievementSetting; import game.GameMode; +import game.PlayerAction; import object.Bid; import object.Player; import online.screen.EntropyLobby; @@ -302,19 +303,18 @@ 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... - GameRoom room = ScreenCache.get(EntropyLobby.class).getGameRoomForName(roomId); - listmodel = room.getListmodel(); + listmodel = ScreenCache.get(EntropyLobby.class).getGameRoomForName(roomId).getListmodel(); } int size = listmodel.size(); for (int i=0; i, round: Int?) { + val prefix = round?.let { "$round" } ?: "" + val historySize: Int = model.size() + node.putInt(prefix + Registry.SHARED_INT_HISTORY_SIZE, historySize) + for (i in 0 until historySize) { + val bid: PlayerAction = model.get(i) + node.put( + prefix + Registry.SHARED_STRING_LISTMODEL + i, + CoreGlobals.jsonMapper.writeValueAsString(bid), + ) + } +} + +fun populateActions(node: Preferences, model: DefaultListModel, round: Int?) { + model.clear() + + val prefix = round?.let { "$round" } ?: "" + val historySize: Int = node.getInt(prefix + Registry.SHARED_INT_HISTORY_SIZE, 0) + for (i in 0 until historySize) { + val modelItem: String = node.get(prefix + Registry.SHARED_STRING_LISTMODEL + i, "") + val action = CoreGlobals.jsonMapper.readValue(modelItem) + model.addElement(action) + } +} diff --git a/core/src/main/java/util/Registry.java b/core/src/main/java/util/Registry.java index c092773..4f5a266 100644 --- a/core/src/main/java/util/Registry.java +++ b/core/src/main/java/util/Registry.java @@ -22,10 +22,12 @@ public interface Registry public static final String INSTANCE_STRING_DEVICE_ID = "deviceId"; // shared + public static final String SHARED_STRING_LISTMODEL = "listmodel"; public static final String SHARED_BOOLEAN_INCLUDE_MOONS = "includeMoons"; public static final String SHARED_BOOLEAN_INCLUDE_STARS = "includeStars"; public static final String SHARED_BOOLEAN_NEGATIVE_JACKS = "negativeJacks"; public static final String SHARED_BOOLEAN_CARD_REVEAL = "cardReveal"; + public static final String SHARED_INT_HISTORY_SIZE = "historySize"; 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"; @@ -86,7 +88,6 @@ public interface Registry public static final String REPLAY_STRING_OPPONENT_TWO_NAME = "opponentTwoName"; public static final String REPLAY_STRING_OPPONENT_ONE_NAME = "opponentOneName"; public static final String REPLAY_STRING_PLAYER_NAME = "playerName"; - public static final String REPLAY_STRING_LISTMODEL = "listmodel"; public static final String REPLAY_STRING_PLAYER_COLOUR = "playerColour"; public static final String REPLAY_STRING_OPPONENT_ONE_COLOUR = "opponentOneColour"; public static final String REPLAY_STRING_OPPONENT_TWO_COLOUR = "opponentTwoColour"; @@ -119,7 +120,6 @@ public interface Registry public static final String REPLAY_INT_OPPONENT_TWO_NUMBER_OF_CARDS = "opponentTwoNumberOfCards"; public static final String REPLAY_INT_OPPONENT_ONE_NUMBER_OF_CARDS = "opponentOneNumberOfCards"; public static final String REPLAY_INT_PLAYER_NUMBER_OF_CARDS = "playerNumberOfCards"; - public static final String REPLAY_INT_HISTORY_SIZE = "historySize"; public static final String REPLAY_INT_ROUNDS_SO_FAR = "roundsSoFar"; public static final String REPLAY_INT_GAME_COMPLETE = "gameComplete"; public static final String REPLAY_INT_PLAYER_WON = "playerWon"; @@ -204,7 +204,6 @@ public interface Registry public static final String SAVED_GAME_INT_OPPONENT_TWO_NUMBER_OF_CARDS = "opponentTwoNumberOfCards"; public static final String SAVED_GAME_INT_OPPONENT_ONE_NUMBER_OF_CARDS = "opponentOneNumberOfCards"; public static final String SAVED_GAME_INT_PLAYER_NUMBER_OF_CARDS = "playerNumberOfCards"; - public static final String SAVED_GAME_INT_HISTORY_SIZE = "historySize"; public static final String SAVED_GAME_STRING_GAME_MODE = "gameMode"; //statics for default values etc diff --git a/core/src/main/kotlin/game/PlayerAction.kt b/core/src/main/kotlin/game/PlayerAction.kt index 3c9adbc..2170ab9 100644 --- a/core/src/main/kotlin/game/PlayerAction.kt +++ b/core/src/main/kotlin/game/PlayerAction.kt @@ -2,6 +2,7 @@ package game import com.fasterxml.jackson.annotation.JsonSubTypes import com.fasterxml.jackson.annotation.JsonTypeInfo +import utils.CoreGlobals @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -22,4 +23,6 @@ abstract class PlayerAction { abstract fun plainString(): String open fun htmlString() = plainString() + + fun toJsonString(): String = CoreGlobals.jsonMapper.writeValueAsString(this) } From 9bab6a4162ea9b06ecf278279ee891ef68f7ad8b Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 23 Jul 2025 08:43:54 +0100 Subject: [PATCH 04/12] wip plugging in --- .../src/main/java/screen/EntropyBidPanel.java | 9 +- .../src/main/java/screen/EntropyScreen.java | 11 +-- client/src/main/java/screen/GameScreen.java | 42 ++++---- .../src/main/java/screen/VectropyScreen.java | 14 +-- client/src/main/java/util/ApiUtil.java | 7 +- client/src/main/java/util/CpuStrategies.java | 97 +++++++++---------- .../src/main/java/util/EntCpuStrategies.java | 19 ++-- .../src/main/java/util/VectCpuStrategies.java | 9 +- client/src/main/kotlin/game/StrategyUtil.kt | 4 +- client/src/main/kotlin/util/GameSimulator.kt | 18 ++-- client/src/main/kotlin/util/StrategyParams.kt | 4 +- core/src/main/java/util/Registry.java | 5 +- core/src/main/kotlin/game/EntropyBidAction.kt | 8 ++ .../src/main/kotlin/game/VectropyBidAction.kt | 13 +++ 14 files changed, 140 insertions(+), 120 deletions(-) diff --git a/client/src/main/java/screen/EntropyBidPanel.java b/client/src/main/java/screen/EntropyBidPanel.java index 7588085..4622451 100644 --- a/client/src/main/java/screen/EntropyBidPanel.java +++ b/client/src/main/java/screen/EntropyBidPanel.java @@ -328,10 +328,11 @@ public void loadState(Preferences savedGame) setBidButtonColours(); - lastBidAmount = savedGame.getInt(SAVED_GAME_INT_LAST_BID_AMOUNT, -1); - var lastBid = savedGame.get(SAVED_GAME_STRING_LAST_BID_SUIT_NAME, null); - if (lastBid != null) { - lastBidSuit = Suit.valueOf(lastBid); + var lastBidStr = savedGame.get(SHARED_STRING_LAST_BID, null); + if (lastBidStr != null) { + var lastBid = EntropyBidAction.fromJson(lastBidStr); + lastBidAmount = lastBid.getAmount(); + lastBidSuit = lastBid.getSuit(); } updateSelectionForLastBidSuit(); diff --git a/client/src/main/java/screen/EntropyScreen.java b/client/src/main/java/screen/EntropyScreen.java index 653d9b3..77e5090 100644 --- a/client/src/main/java/screen/EntropyScreen.java +++ b/client/src/main/java/screen/EntropyScreen.java @@ -6,7 +6,6 @@ import game.GameMode; import game.Suit; import object.EntropyAchievementsTracker; -import object.EntropyBid; import util.Registry; import static game.CardsUtilKt.countSuit; @@ -71,8 +70,7 @@ public void saveGame() //save bid amounts and bid suits if (lastBid != null) { - savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID_SUIT_NAME, lastBid.getSuit().name()); - savedGame.putInt(Registry.SAVED_GAME_INT_LAST_BID_AMOUNT, lastBid.getAmount()); + savedGame.put(Registry.SHARED_STRING_LAST_BID, lastBid.toJsonString()); } //other booleans @@ -93,12 +91,11 @@ protected void saveRoundForReplay() @Override public void loadLastBid() { - 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); + String lastBidStr = savedGame.get(Registry.SHARED_STRING_LAST_BID, null); - if (lastBidSuitName != null) + if (lastBidStr != null) { - lastBid = new EntropyBid(Suit.valueOf(lastBidSuitName), lastBidAmount); + lastBid = EntropyBidAction.fromJson(lastBidStr); } } diff --git a/client/src/main/java/screen/GameScreen.java b/client/src/main/java/screen/GameScreen.java index c45d408..44e5469 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -835,8 +835,7 @@ public void processChallenge(Player challenger) Debug.append("Challenger: " + challenger, logging); unlockPerfectBidAchievements(); - - Player playerChallenged = lastBid.getPlayer(); + if (!lastBid.isOverbid(getConcatenatedHands(), settings)) { Debug.append("not an overbid", logging); @@ -845,7 +844,8 @@ public void processChallenge(Player challenger) else { Debug.append("overbid", logging); - setCardsToSubtract(playerChallenged); + + setCardsToSubtract(getPlayer(lastBid)); } bidPanel.enableBidPanel(false); @@ -858,8 +858,7 @@ public void processIllegal(Player illegaller) Debug.appendBanner("Processing Illegal", logging); unlockPerfectBidAchievements(); - Player bidder = lastBid.getPlayer(); - Debug.append("Bidder: " + bidder, logging); + Debug.append("Bidder: " + lastBid.getPlayerName(), logging); if (lastBid.isPerfect(getConcatenatedHands(), settings)) { @@ -870,7 +869,7 @@ public void processIllegal(Player illegaller) AchievementsUtil.unlockCitizensArrest(); } - setCardsToSubtract(bidder); + setCardsToSubtract(getPlayer(lastBid)); } else { @@ -894,6 +893,16 @@ public void processCpuTurn(int opponentNumber) cpuTurn.schedule(new DelayedOpponentTurn(currentPlayer), gameSpeed); } + + private Player getPlayer(B bid) { + var name = bid.getPlayerName(); + var found = Stream.of(player, opponentOne, opponentTwo, opponentThree).filter((p) -> p.getName().equals(name)).findFirst(); + if (found.isEmpty()) { + throw new RuntimeException("Couldn't find player for bid. Player name: " + name); + } + + return found.get(); + } private Player getPlayer(int playerNumber) { @@ -1097,43 +1106,42 @@ 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 { - lastBid = bid; + lastBid = (B)action; if (settings.getCardReveal()) { - String card = bid.getCardToReveal(); + String card = lastBid.getCardToReveal(); handPanel.revealCard(card); } diff --git a/client/src/main/java/screen/VectropyScreen.java b/client/src/main/java/screen/VectropyScreen.java index a248b20..30ddcc9 100644 --- a/client/src/main/java/screen/VectropyScreen.java +++ b/client/src/main/java/screen/VectropyScreen.java @@ -4,8 +4,7 @@ import game.GameMode; import game.Suit; -import object.Bid; -import object.VectropyBid; +import game.VectropyBidAction; import util.AchievementsUtil; import util.Debug; import util.Registry; @@ -63,7 +62,7 @@ public void saveGame() //save bid amounts and bid suits if (lastBid != null) { - savedGame.put(Registry.SAVED_GAME_STRING_LAST_BID, lastBid.toXmlString()); + savedGame.put(Registry.SHARED_STRING_LAST_BID, lastBid.toJsonString()); } //other booleans @@ -76,10 +75,10 @@ public void saveGame() @Override public void loadLastBid() { - String lastBidStr = savedGame.get(Registry.SAVED_GAME_STRING_LAST_BID, ""); + String lastBidStr = savedGame.get(Registry.SHARED_STRING_LAST_BID, ""); if (!lastBidStr.isEmpty()) { - lastBid = Bid.factoryFromXmlString(lastBidStr, settings.getIncludeMoons(), settings.getIncludeStars()); + lastBid = VectropyBidAction.fromJson(lastBidStr); } } @@ -119,11 +118,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/ApiUtil.java b/client/src/main/java/util/ApiUtil.java index 2bce756..b2e3446 100644 --- a/client/src/main/java/util/ApiUtil.java +++ b/client/src/main/java/util/ApiUtil.java @@ -1,5 +1,6 @@ package util; +import game.BidAction; import game.GameMode; import game.GameSettings; import object.*; @@ -146,7 +147,7 @@ private static String factoryXmlApiMessage(StrategyParams parms, Player player) boolean includeStars = settings.getIncludeStars(); boolean negativeJacks = settings.getNegativeJacks(); boolean cardReveal = settings.getCardReveal(); - Bid lastBid = parms.getLastBid(); + BidAction lastBid = parms.getLastBid(); rootElement.setAttribute("GameMode", gameMode.name()); @@ -203,9 +204,7 @@ private static String factoryXmlApiMessage(StrategyParams parms, Player player) if (lastBid != null) { - Element bidElement = document.createElement("LastBid"); - lastBid.populateXmlTag(bidElement); - rootElement.appendChild(bidElement); + rootElement.setAttribute("LastBid", lastBid.toJsonString()); } document.appendChild(rootElement); diff --git a/client/src/main/java/util/CpuStrategies.java b/client/src/main/java/util/CpuStrategies.java index 14960ab..87d84b0 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,37 +60,36 @@ 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); - if (bid == null) + PlayerAction action = getOpponentBid(parms, opponent, entropy); + if (action == null) { - return bid; + return action; } //Set a card to reveal if we need to - specifying this is optional for the API - bid.setPlayer(opponent); - setRandomCardToRevealIfNecessary(opponent, bid, settings); + setRandomCardToRevealIfNecessary(opponent, action, settings); //validate the bid... - String error = validateBid(opponent, bid, parms); + String error = validateAction(opponent, action, parms); if (error != null) { if (opponent.isApiStrategy()) { //Show an error message to help diagnosing. - String msg = "The bid sent back by the third-party software [" + bid + "] " + String msg = "The action sent back by the third-party software [" + action + "] " + "failed validation with the following error:\n\n" + error; String strategyStr = opponent.getStrategy(); ApiUtil.saveStrategyErrorAndUnsetStrategies(ApiUtil.getApiStrategy(strategyStr), msg); - DialogUtil.showError(msg); + DialogUtilNew.showError(msg); } else { - logger.error("invalidBid", "Error validating bid [" + bid + "]. Error: " + error); + logger.error("invalidBid", "Error validating action [" + action + "]. Error: " + error); } return null; @@ -101,13 +98,14 @@ public static Bid processOpponentTurn(StrategyParams parms, Player opponent) //Add the revealed card on the opponent object. Do this here so we don't have to duplicate the logic //in the simulator & actual game if (parms.getSettings().getCardReveal() - && opponent.hasMoreCardsToReveal()) + && opponent.hasMoreCardsToReveal() + && action instanceof BidAction) { - String cardToReveal = bid.getCardToReveal(); + String cardToReveal = ((BidAction)action).getCardToReveal(); opponent.addRevealedCard(cardToReveal); } - return bid; + return action; } private static Bid getOpponentBid(StrategyParams parms, Player opponent, boolean entropy) @@ -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,36 @@ private static void setRandomCardToRevealIfNecessary(Player opponent, Bid bid, G } } - private static String validateBid(Player opponent, Bid bid, StrategyParams params) + private static String validateAction(Player opponent, PlayerAction action, StrategyParams params) { var settings = params.getSettings(); - if (bid.isChallenge() - || bid.isIllegal()) + if (action instanceof ChallengeAction + || action instanceof IllegalAction) { - return validateChallengeOrIllegal(bid, params); + return validateChallengeOrIllegal(action, params); } - if (bid instanceof EntropyBid) + 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(); + + var bid = (BidAction)action; + + BidAction lastBid = params.getLastBid(); if (lastBid != null && !bid.higherThan(lastBid)) { @@ -187,7 +192,7 @@ private static String validateBid(Player opponent, Bid bid, StrategyParams param && opponent.hasMoreCardsToReveal()) { String cardToReveal = bid.getCardToReveal(); - if (cardToReveal.isEmpty()) + if (cardToReveal == null) { return "A card was not specified to be revealed."; } @@ -207,27 +212,22 @@ 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 action, StrategyParams parms) { - Bid lastBid = parms.getLastBid(); + var lastBid = parms.getLastBid(); if (lastBid == null) { - if (bid.isChallenge()) - { - return "Challenged as an opening bid."; - } - - return "Called 'Illegal!' as an opening bid."; + return "Called " + action.plainString() + " as an opening bid."; } return null; } - private static String validateEntropyBid(EntropyBid bid, GameSettings settings) + private static String validateEntropyBid(EntropyBidAction bid, GameSettings settings) { - Suit bidSuit = bid.getBidSuit(); + Suit bidSuit = bid.getSuit(); - int bidAmount = bid.getBidAmount(); + int bidAmount = bid.getAmount(); if (bidAmount < 1) { return "Invalid bidAmount: " + bidAmount; @@ -248,21 +248,18 @@ 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."; } - - if (bid.getClubs() < 0 - || bid.getDiamonds() < 0 - || bid.getHearts() < 0 - || bid.getMoons() < 0 - || bid.getSpades() < 0 - || bid.getStars() < 0) - { - return "Negative amount specified for a suit."; + + for (var suit : Suit.getEntries()) { + var amount = bid.getAmount(suit); + if (amount != null && amount < 0) { + return "Negative amount specified for suit " + suit + "."; + } } return null; diff --git a/client/src/main/java/util/EntCpuStrategies.java b/client/src/main/java/util/EntCpuStrategies.java index 5de9bd7..f6fb398 100644 --- a/client/src/main/java/util/EntCpuStrategies.java +++ b/client/src/main/java/util/EntCpuStrategies.java @@ -2,6 +2,7 @@ import java.util.*; +import game.EntropyBidAction; import game.Suit; import object.Bid; import object.ChallengeBid; @@ -64,7 +65,7 @@ else if (strategy.equals(CpuStrategies.STRATEGY_EV)) private static Bid processMarkTurn(Player opponent, StrategyParams parms) { var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); Random rand = new Random(); List hand = opponent.getHand(); @@ -103,8 +104,8 @@ private static Bid processMarkTurn(Player opponent, StrategyParams parms) //Set the 'hand' variable to be everything I can see. hand = CpuStrategies.getCombinedArrayOfCardsICanSee(hand, parms); - int bidAmountFacedWith = bid.getBidAmount(); - var bidSuitFacedWith = bid.getBidSuit(); + int bidAmountFacedWith = bid.getAmount(); + var bidSuitFacedWith = bid.getSuit(); int bidSuitCount = countSuit(bidSuitFacedWith, hand, jokerValue); int minBiddableSuitCount = suitWrapper.getSuitsPossibleToMinBid().size(); @@ -299,7 +300,7 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) { //Parms var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); boolean includeMoons = settings.getIncludeMoons(); boolean includeStars = settings.getIncludeStars(); int jokerValue = settings.getJokerValue(); @@ -325,8 +326,8 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) //Set the 'hand' variable to be everything I can see. hand = CpuStrategies.getCombinedArrayOfCardsICanSee(hand, parms); - int bidAmountFacedWith = bid.getBidAmount(); - var bidSuitFacedWith = bid.getBidSuit(); + int bidAmountFacedWith = bid.getAmount(); + var bidSuitFacedWith = bid.getSuit(); int bidSuitCount = countSuit(bidSuitFacedWith, hand, jokerValue); double totalCards = parms.getCardsInPlay(); @@ -399,7 +400,7 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) { //Parms var settings = parms.getSettings(); - EntropyBid bid = (EntropyBid)parms.getLastBid(); + EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); int totalCards = parms.getCardsInPlay(); boolean includeMoons = settings.getIncludeMoons(); boolean includeStars = settings.getIncludeStars(); @@ -430,8 +431,8 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) hand = CpuStrategies.getCombinedArrayOfCardsICanSee(hand, parms); Map hmEvBySuit = getEvMap(hand, parms.getSettings(), parms.getCardsInPlay()); - int bidAmountFacedWith = bid.getBidAmount(); - var bidSuitFacedWith = bid.getBidSuit(); + int bidAmountFacedWith = bid.getAmount(); + var bidSuitFacedWith = bid.getSuit(); double expectedValueForBid = hmEvBySuit.get(bidSuitFacedWith); log("EV calculation for bid of " + bidAmountFacedWith + " " + bidSuitFacedWith.getDescription(bidAmountFacedWith) + ": " + expectedValueForBid, logging); diff --git a/client/src/main/java/util/VectCpuStrategies.java b/client/src/main/java/util/VectCpuStrategies.java index 4379e28..d30fcec 100644 --- a/client/src/main/java/util/VectCpuStrategies.java +++ b/client/src/main/java/util/VectCpuStrategies.java @@ -3,6 +3,7 @@ import java.util.*; import game.Suit; +import game.VectropyBidAction; import object.Bid; import object.ChallengeBid; import object.Player; @@ -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(); @@ -188,7 +189,7 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) List hand = opponent.getHand(); //Parms - VectropyBid lastBid = (VectropyBid)parms.getLastBid(); + VectropyBidAction lastBid = (VectropyBidAction)parms.getLastBid(); boolean includeMoons = parms.getSettings().getIncludeMoons(); boolean includeStars = parms.getSettings().getIncludeStars(); @@ -287,9 +288,9 @@ else if (adjustmentChoice < 18) return Math.max(evFloor, 0); } - private static VectropyBid opponentMinBidSuit(VectropyBid lastBid, Suit suit) + private static VectropyBidAction opponentMinBidSuit(VectropyBidAction lastBid, Suit suit) { - return lastBid.incrementSuitAndGet(suit); + return lastBid.incrementSuit(suit); } private static void log(String text, boolean logging) { diff --git a/client/src/main/kotlin/game/StrategyUtil.kt b/client/src/main/kotlin/game/StrategyUtil.kt index 52ec9cb..89e33fd 100644 --- a/client/src/main/kotlin/game/StrategyUtil.kt +++ b/client/src/main/kotlin/game/StrategyUtil.kt @@ -23,14 +23,14 @@ fun getEvMap( } fun getDifferenceMap( - bid: VectropyBid, + bid: VectropyBidAction, hand: List, jokerValue: Int, includeMoons: Boolean, includeStars: Boolean, ): Map { val suits = Suit.filter(includeMoons, includeStars) - return suits.associateWith { countSuit(it, hand, jokerValue) - bid.getAmount(it) } + return suits.associateWith { countSuit(it, hand, jokerValue) - bid.getAmount(it)!! } } fun > getSuitWithMostPositiveValue(map: Map) = map.maxBy { it.value }.key diff --git a/client/src/main/kotlin/util/GameSimulator.kt b/client/src/main/kotlin/util/GameSimulator.kt index ebf0f58..4b2c610 100644 --- a/client/src/main/kotlin/util/GameSimulator.kt +++ b/client/src/main/kotlin/util/GameSimulator.kt @@ -1,7 +1,9 @@ package util +import game.BidAction +import game.ChallengeAction +import game.PlayerAction import game.createAndShuffleDeck -import `object`.Bid import `object`.Player import screen.ScreenCache.get import screen.SimulationDialog @@ -10,7 +12,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" } @@ -105,16 +107,18 @@ class GameSimulator(private val params: SimulationParams) { } val stratParms = getStrategyParms(opponent) - val action = + val action: PlayerAction = CpuStrategies.processOpponentTurn(stratParms, opponent) ?: // Abort the simulation, something's gone wrong throw SimulationException("Simulation error") - if (action.isChallenge) { + if (action is ChallengeAction) { processChallenge(opponent) - } else { + } else if (action is BidAction<*>) { lastBid = action processOpponentTurn(nextPlayer(opponent)) + } else { + throw Exception("Unexpected action type: $action") } } @@ -139,7 +143,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 +154,7 @@ class GameSimulator(private val params: SimulationParams) { log("overbid") dialog.recordChallenge(challenger.playerNumber, true) - val bidder = lastBid.player + val bidder = allPlayers().find { it.name == lastBid.playerName }!! 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 4e483fd..1a2a080 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/util/Registry.java b/core/src/main/java/util/Registry.java index 4f5a266..fd25d63 100644 --- a/core/src/main/java/util/Registry.java +++ b/core/src/main/java/util/Registry.java @@ -23,6 +23,7 @@ public interface Registry // shared public static final String SHARED_STRING_LISTMODEL = "listmodel"; + public static final String SHARED_STRING_LAST_BID = "lastbid"; public static final String SHARED_BOOLEAN_INCLUDE_MOONS = "includeMoons"; public static final String SHARED_BOOLEAN_INCLUDE_STARS = "includeStars"; public static final String SHARED_BOOLEAN_NEGATIVE_JACKS = "negativeJacks"; @@ -153,8 +154,6 @@ public interface Registry public static final String SAVED_GAME_STRING_OPPONENT_TWO_NAME = "opponentTwoName"; public static final String SAVED_GAME_STRING_OPPONENT_ONE_NAME = "opponentOneName"; public static final String SAVED_GAME_STRING_PLAYER_NAME = "playerName"; - public static final String SAVED_GAME_STRING_LISTMODEL = "listmodel"; - public static final String SAVED_GAME_STRING_LAST_BID = "lastbid"; public static final String SAVED_GAME_STRING_FIRST_SUIT_BID = "firstSuitBid"; public static final String SAVED_GAME_STRING_PLAYER_REVEALED_CARD = "playerRevealedCard"; public static final String SAVED_GAME_STRING_OPPONENT_ONE_REVEALED_CARD = "opponentOneRevealedCard"; @@ -197,8 +196,6 @@ public interface Registry public static final String SAVED_GAME_INT_CURRENT_PLAYER = "currentPlayer"; public static final String SAVED_GAME_INT_PERSON_TO_START = "personToStart"; public static final String SAVED_GAME_INT_MAX_BID = "maxBid"; - public static final String SAVED_GAME_INT_LAST_BID_AMOUNT = "lastBidAmount"; - public static final String SAVED_GAME_STRING_LAST_BID_SUIT_NAME = "lastBidSuitName"; public static final String SAVED_GAME_INT_HANDICAP_AMOUNT = "handicapAmount"; public static final String SAVED_GAME_INT_OPPONENT_THREE_NUMBER_OF_CARDS = "opponentThreeNumberOfCards"; public static final String SAVED_GAME_INT_OPPONENT_TWO_NUMBER_OF_CARDS = "opponentTwoNumberOfCards"; diff --git a/core/src/main/kotlin/game/EntropyBidAction.kt b/core/src/main/kotlin/game/EntropyBidAction.kt index 3d3a54a..906afae 100644 --- a/core/src/main/kotlin/game/EntropyBidAction.kt +++ b/core/src/main/kotlin/game/EntropyBidAction.kt @@ -1,5 +1,7 @@ package game +import utils.CoreGlobals + data class EntropyBidAction( override val playerName: String, override val blind: Boolean, @@ -31,4 +33,10 @@ data class EntropyBidAction( override fun htmlString() = "$amount${suit.unicodeStr}" + + companion object { + @JvmStatic + fun fromJson(jsonString: String): EntropyBidAction = + CoreGlobals.jsonMapper.readValue(jsonString, EntropyBidAction::class.java) + } } diff --git a/core/src/main/kotlin/game/VectropyBidAction.kt b/core/src/main/kotlin/game/VectropyBidAction.kt index 19ae5b1..a54a181 100644 --- a/core/src/main/kotlin/game/VectropyBidAction.kt +++ b/core/src/main/kotlin/game/VectropyBidAction.kt @@ -1,5 +1,7 @@ package game +import utils.CoreGlobals + data class VectropyBidAction( override val playerName: String, override val blind: Boolean, @@ -37,6 +39,17 @@ data class VectropyBidAction( val suits = amounts.keys.sorted().map(::getAmount) return "(${suits.joinToString()})" } + + fun incrementSuit(suit: Suit): VectropyBidAction { + val current = amounts.getValue(suit) + return copy(amounts = amounts + (suit to current + 1)) + } + + companion object { + @JvmStatic + fun fromJson(jsonString: String): VectropyBidAction = + CoreGlobals.jsonMapper.readValue(jsonString, VectropyBidAction::class.java) + } } private fun constructMap( From 555cad44f97d422f0cb6586b6a3ea5dd2b94f8f2 Mon Sep 17 00:00:00 2001 From: Alyssa Date: Fri, 25 Jul 2025 21:41:55 +0100 Subject: [PATCH 05/12] more WIP --- .../main/java/object/BidListCellRenderer.java | 11 +- .../main/java/online/screen/EntropyRoom.java | 3 - .../java/online/util/ResponseHandler.java | 1 - .../java/online/util/XmlBuilderClient.java | 1 - client/src/main/java/screen/BidPanel.java | 1 - .../src/main/java/screen/EntropyBidPanel.java | 2 - client/src/main/java/screen/GameScreen.java | 5 - client/src/main/java/screen/MainScreen.java | 1 - client/src/main/java/screen/ReplayDialog.java | 1 - .../main/java/screen/VectropyBidPanel.java | 2 - .../src/main/java/util/AchievementsUtil.java | 2 - client/src/main/java/util/ApiUtil.java | 66 +--- client/src/main/java/util/BidListener.java | 1 - client/src/main/java/util/CpuStrategies.java | 12 +- .../src/main/java/util/EntCpuStrategies.java | 108 +++--- .../src/main/java/util/VectCpuStrategies.java | 158 ++------- client/src/main/kotlin/game/CheatUtil.kt | 2 +- client/src/main/kotlin/game/StrategyUtil.kt | 67 +++- .../strategy/MarkStrategySuitWrapper.kt | 6 +- .../src/test/kotlin/game/StrategyUtilTest.kt | 8 +- core/src/main/java/object/Bid.java | 189 ---------- core/src/main/java/object/ChallengeBid.java | 26 -- core/src/main/java/object/EntropyBid.java | 172 --------- core/src/main/java/object/FakeBid.java | 71 ---- core/src/main/java/object/IllegalBid.java | 22 -- core/src/main/java/object/LeftBid.java | 23 -- core/src/main/java/object/VectropyBid.java | 328 ------------------ core/src/main/kotlin/game/CardsUtil.kt | 2 +- core/src/main/kotlin/game/Suit.kt | 4 + core/src/main/kotlin/game/VectropyUtil.kt | 8 - core/src/test/kotlin/game/VectropyUtilTest.kt | 21 -- server/src/main/java/object/BidHistory.java | 52 +-- server/src/main/java/object/GameWrapper.java | 13 - .../src/main/java/util/XmlBuilderServer.java | 28 +- server/src/main/kotlin/room/Room.kt | 27 +- 35 files changed, 211 insertions(+), 1233 deletions(-) delete mode 100644 core/src/main/java/object/Bid.java delete mode 100644 core/src/main/java/object/ChallengeBid.java delete mode 100644 core/src/main/java/object/EntropyBid.java delete mode 100644 core/src/main/java/object/FakeBid.java delete mode 100644 core/src/main/java/object/IllegalBid.java delete mode 100644 core/src/main/java/object/LeftBid.java delete mode 100644 core/src/main/java/object/VectropyBid.java delete mode 100644 core/src/main/kotlin/game/VectropyUtil.kt delete mode 100644 core/src/test/kotlin/game/VectropyUtilTest.kt diff --git a/client/src/main/java/object/BidListCellRenderer.java b/client/src/main/java/object/BidListCellRenderer.java index 781a0f7..d5c303e 100644 --- a/client/src/main/java/object/BidListCellRenderer.java +++ b/client/src/main/java/object/BidListCellRenderer.java @@ -1,5 +1,6 @@ package object; +import game.PlayerAction; import util.StringUtil; import java.awt.Component; @@ -16,29 +17,29 @@ public class BidListCellRenderer extends DefaultListCellRenderer public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { - Bid bid = (Bid)value; + PlayerAction bid = (PlayerAction)value; String text = toHtmlString(bid); return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus); } - public String toHtmlString(Bid bid) + public String toHtmlString(PlayerAction bid) { - String playerName = bid.getPlayer().getName(); + String playerName = bid.getPlayerName(); playerName = StringUtil.escapeHtml(playerName); String colour = bid.getPlayer().getColour(); String playerNamePrefix = playerName + ": "; - if (bid.isBlind()) + if (bid.getBlind()) { playerNamePrefix = "[" + playerName + "]: "; } String text = "" + playerNamePrefix; text += ""; - text += bid.toHtmlStringSpecific(); + text += bid.htmlString(); if (!bid.getCardToReveal().isEmpty() && !bid.isChallenge() diff --git a/client/src/main/java/online/screen/EntropyRoom.java b/client/src/main/java/online/screen/EntropyRoom.java index dfd418e..35fa0a3 100644 --- a/client/src/main/java/online/screen/EntropyRoom.java +++ b/client/src/main/java/online/screen/EntropyRoom.java @@ -5,10 +5,7 @@ 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; diff --git a/client/src/main/java/online/util/ResponseHandler.java b/client/src/main/java/online/util/ResponseHandler.java index 1804836..8ac6285 100644 --- a/client/src/main/java/online/util/ResponseHandler.java +++ b/client/src/main/java/online/util/ResponseHandler.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import game.PlayerAction; -import object.Bid; import online.screen.EntropyLobby; import online.screen.GameRoom; import online.screen.Leaderboard; diff --git a/client/src/main/java/online/util/XmlBuilderClient.java b/client/src/main/java/online/util/XmlBuilderClient.java index 71a7f2e..02d4f7c 100644 --- a/client/src/main/java/online/util/XmlBuilderClient.java +++ b/client/src/main/java/online/util/XmlBuilderClient.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import game.PlayerAction; -import object.Bid; import org.w3c.dom.Document; import org.w3c.dom.Element; diff --git a/client/src/main/java/screen/BidPanel.java b/client/src/main/java/screen/BidPanel.java index 8089620..ea9e9b1 100644 --- a/client/src/main/java/screen/BidPanel.java +++ b/client/src/main/java/screen/BidPanel.java @@ -3,7 +3,6 @@ import java.util.prefs.Preferences; import game.BidAction; -import object.Bid; import util.BidListener; public abstract class BidPanel> extends TransparentPanel diff --git a/client/src/main/java/screen/EntropyBidPanel.java b/client/src/main/java/screen/EntropyBidPanel.java index 4622451..fb637ef 100644 --- a/client/src/main/java/screen/EntropyBidPanel.java +++ b/client/src/main/java/screen/EntropyBidPanel.java @@ -26,8 +26,6 @@ import game.EntropyBidAction; import game.Suit; -import object.Bid; -import object.EntropyBid; import util.Debug; import util.Registry; diff --git a/client/src/main/java/screen/GameScreen.java b/client/src/main/java/screen/GameScreen.java index 44e5469..a4d2950 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -2,12 +2,8 @@ import achievement.AchievementSetting; 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; @@ -15,7 +11,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static game.CardsUtilKt.countSuit; import static game.CardsUtilKt.createAndShuffleDeck; import static game.CheatUtilKt.containsNonJoker; import static game.RegistryUtilKt.populateActions; diff --git a/client/src/main/java/screen/MainScreen.java b/client/src/main/java/screen/MainScreen.java index 9a35db5..cdc7e11 100644 --- a/client/src/main/java/screen/MainScreen.java +++ b/client/src/main/java/screen/MainScreen.java @@ -5,7 +5,6 @@ import bean.AbstractDevScreen; import game.GameMode; import game.PlayerAction; -import object.Bid; import object.BidListCellRenderer; import object.Player; import online.screen.EntropyLobby; diff --git a/client/src/main/java/screen/ReplayDialog.java b/client/src/main/java/screen/ReplayDialog.java index 6fe27cd..7892031 100644 --- a/client/src/main/java/screen/ReplayDialog.java +++ b/client/src/main/java/screen/ReplayDialog.java @@ -35,7 +35,6 @@ import game.PlayerAction; import game.Suit; -import object.Bid; import object.BidListCellRenderer; import http.dto.OnlineMessage; import object.PlayerLabel; diff --git a/client/src/main/java/screen/VectropyBidPanel.java b/client/src/main/java/screen/VectropyBidPanel.java index c8cbe49..cca33d5 100644 --- a/client/src/main/java/screen/VectropyBidPanel.java +++ b/client/src/main/java/screen/VectropyBidPanel.java @@ -26,8 +26,6 @@ import game.Suit; import game.VectropyBidAction; -import object.Bid; -import object.VectropyBid; import util.Debug; import util.EntropyColour; import util.Registry; diff --git a/client/src/main/java/util/AchievementsUtil.java b/client/src/main/java/util/AchievementsUtil.java index 2119150..9015094 100644 --- a/client/src/main/java/util/AchievementsUtil.java +++ b/client/src/main/java/util/AchievementsUtil.java @@ -3,10 +3,8 @@ import achievement.AchievementSetting; import game.GameMode; import game.PlayerAction; -import object.Bid; import object.Player; import online.screen.EntropyLobby; -import online.screen.GameRoom; import screen.HelpDialog; import screen.MainScreen; import screen.RewardDialog; diff --git a/client/src/main/java/util/ApiUtil.java b/client/src/main/java/util/ApiUtil.java index b2e3446..9044f02 100644 --- a/client/src/main/java/util/ApiUtil.java +++ b/client/src/main/java/util/ApiUtil.java @@ -3,10 +3,12 @@ import game.BidAction; import game.GameMode; import game.GameSettings; +import game.PlayerAction; import object.*; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; +import utils.CoreGlobals; import javax.swing.*; import java.io.BufferedReader; @@ -45,7 +47,7 @@ public static void sendTestMessage(int port, boolean xml) sendWithCatch(messageString, port, true, true); } - public static Bid processApiTurn(StrategyParams parms, Player player) + public static PlayerAction processApiTurn(StrategyParams parms, Player player) { apiStrategy = getApiStrategy(player.getStrategy()); int port = apiStrategy.getPortNumber(); @@ -60,7 +62,7 @@ public static Bid processApiTurn(StrategyParams parms, Player player) return null; } - return handleResponse(parms, responseString); + return handleResponse(responseString); } private static String sendWithCatch(String messageString, int port, boolean logging, boolean testMode) @@ -222,68 +224,16 @@ private static String factoryJsonApiMessage(StrategyParams parms, Player player) return null; } - private static Bid handleResponse(StrategyParams parms, String responseString) + private static PlayerAction handleResponse(String responseString) { - Document xmlResponse = XmlUtil.getDocumentFromXmlString(responseString); - if (xmlResponse == null) - { - Debug.append("Received unparsable response via API: " + responseString); - showMalformedResponseError(responseString); - return null; - } - - Element root = xmlResponse.getDocumentElement(); - String responseName = root.getNodeName(); - - if (responseName.equals("Bid")) - { - Bid bid = factoryBid(parms.getSettings(), root, responseString); - String cardToShow = root.getAttribute("CardToShow"); - bid.setCardToReveal(cardToShow); - return bid; - } - else if (responseName.equals("Challenge")) - { - return new ChallengeBid(); - } - else if (responseName.equals("Illegal")) - { - return new IllegalBid(); - } - else - { + try { + return CoreGlobals.jsonMapper.readValue(responseString, PlayerAction.class); + } catch (Exception e) { showMalformedResponseError(responseString); return null; } } - private static Bid factoryBid(GameSettings settings, Element root, String responseString) - { - try - { - GameMode gameMode = settings.getMode(); - if (gameMode == GameMode.Entropy) - { - return EntropyBid.factoryFromXmlTag(root); - } - else - { - return VectropyBid.factoryFromXmlTag(root, settings.getIncludeMoons(), settings.getIncludeStars()); - } - } - catch (IOException ioe) - { - String message = "The third-party software returned a message that was not successfully parsed." - + "\n\nMessage: " + responseString - + "\n\nError: " + ioe.getMessage(); - - saveStrategyErrorAndUnsetStrategies(apiStrategy, message); - - DialogUtilNew.showError(message); - return null; - } - } - private static void showMalformedResponseError(String response) { String message = "The third-party software returned an unexpected message type:" diff --git a/client/src/main/java/util/BidListener.java b/client/src/main/java/util/BidListener.java index 71502f4..6eb9087 100644 --- a/client/src/main/java/util/BidListener.java +++ b/client/src/main/java/util/BidListener.java @@ -1,7 +1,6 @@ package util; import game.BidAction; -import object.Bid; public interface BidListener > { diff --git a/client/src/main/java/util/CpuStrategies.java b/client/src/main/java/util/CpuStrategies.java index 87d84b0..54213ef 100644 --- a/client/src/main/java/util/CpuStrategies.java +++ b/client/src/main/java/util/CpuStrategies.java @@ -108,7 +108,7 @@ public static PlayerAction processOpponentTurn(StrategyParams parms, Player oppo return action; } - private static Bid getOpponentBid(StrategyParams parms, Player opponent, boolean entropy) + private static PlayerAction getOpponentBid(StrategyParams parms, Player opponent, boolean entropy) { if (opponent.isApiStrategy()) { @@ -281,11 +281,13 @@ public static List getCombinedArrayOfCardsICanSee(List hand, Str * Used by EV strategies. Slightly more refined version of card reveal - this tries to show a card * which isn't an Ace or a Joker (as these reveal more information than average) */ - public static void setCardToReveal(Bid bid, GameSettings settings, Player opponent) + public static void setCardToReveal(PlayerAction action, GameSettings settings, Player opponent) { - if (!bid.isIllegal() - && !bid.isChallenge() - && settings.getCardReveal() + if (!(action instanceof BidAction bid)) { + return; + } + + if (settings.getCardReveal() && opponent.hasMoreCardsToReveal()) { ArrayList cardsToChooseFrom = opponent.getCardsNotOnShow(); diff --git a/client/src/main/java/util/EntCpuStrategies.java b/client/src/main/java/util/EntCpuStrategies.java index f6fb398..61fe9aa 100644 --- a/client/src/main/java/util/EntCpuStrategies.java +++ b/client/src/main/java/util/EntCpuStrategies.java @@ -2,11 +2,10 @@ 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; @@ -33,12 +32,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)) { @@ -62,7 +61,7 @@ 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(); EntropyBidAction bid = (EntropyBidAction)parms.getLastBid(); @@ -97,7 +96,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 { @@ -118,7 +117,7 @@ private static Bid processMarkTurn(Player opponent, StrategyParams parms) { //0 or 1 log("One-upped (50%)", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } else if (choice == 2) { @@ -126,19 +125,19 @@ else if (choice == 2) if (bestSuit == bidSuitFacedWith) { log("BestSuit = bidSuitFacedWith = " + bestSuit + ". Bidding worstSuit: " + worstSuit, logging); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, worstSuit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, worstSuit, logging); } else { log("Bidding bestSuit as it's different from what I'm faced with.", logging); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); } } else { log("Minbid random middle suit (25%)", logging); var suitToBid = suitWrapper.getRandomMiddleSuit(); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); } } else if (minBiddableSuitCount >= 3) @@ -150,13 +149,13 @@ else if (minBiddableSuitCount >= 3) if (choice <= 1) { log("One-upping (50%)", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } else { log("Bidding random other suit (50%)", logging); var suitToBid = suitWrapper.randomSuitNot(bidSuitFacedWith); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); } } else @@ -165,13 +164,13 @@ else if (minBiddableSuitCount >= 3) if (choice <= 1) { log("Minbidding my best suit (50%)", logging); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); } else { log("Minbidding random other suit (50%)", logging); var suitToBid = suitWrapper.randomSuitNot(bestSuit); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); } } } @@ -181,21 +180,21 @@ else if (minBiddableSuitCount == 2) if (choice <= 1) { log("One-upping (50%)", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } else { ///minbid one of the two suits I can log("Minbidding one of the two suits (50%)", logging); var suitToBid = suitWrapper.randomSuitPossibleToMinBid(); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); } } else if (minBiddableSuitCount == 1) { var suitToBid = suitWrapper.randomSuitPossibleToMinBid(); log("Could only minbid suit " + suitToBid + ", so minbidding that.", logging); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suitToBid, logging); } else { @@ -207,12 +206,12 @@ else if (minBiddableSuitCount == 1) if (bidAmountFacedWith > threshold) { log("Auto-challenging because " + bidAmountFacedWith + " > " + threshold, logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (bidAmountFacedWith > bestSuitCount + bidSuitCount + quarterThreshold) { log("Auto-challenging because bestSuitCount + bidSuitCount + quarterThreshold = " + (bestSuitCount + bidSuitCount + quarterThreshold), logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (bidAmountFacedWith > 1 + quarterThreshold) { @@ -220,12 +219,12 @@ else if (bidAmountFacedWith > 1 + quarterThreshold) if (choice <= 2) { log("One-upping (75%)", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } else { log("Challenging (25%)", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } } else @@ -234,12 +233,12 @@ else if (bidAmountFacedWith > 1 + quarterThreshold) if (choice <= 1) { log("One-upping (50%)", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } else { log("Minbidding my best suit (50%)", logging); - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, bestSuit, logging); } } } @@ -296,7 +295,7 @@ 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(); @@ -319,7 +318,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 { @@ -342,12 +341,12 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) if (decisionTwo == 1) { log("Opponent " + opponent + " auto-minbid", logging); - return opponentMinBid(bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); + return opponentMinBid(opponent, bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); } else { log("Opponent " + opponent + " auto-oneupped", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } } else if (bidAmountFacedWith < bidSuitCount + thirdThreshold) @@ -355,48 +354,48 @@ else if (bidAmountFacedWith < bidSuitCount + thirdThreshold) if (decisionTwo == 1) { log("Opponent " + opponent + " auto-minbid", logging); - return opponentMinBid(bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); + return opponentMinBid(opponent, bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); } else { log("Opponent " + opponent + " auto-oneupped", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } } 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 { if (decisionTwo == 0) { log("Opponent " + opponent + " flip-minbid", logging); - return opponentMinBid(bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); + return opponentMinBid(opponent, bidSuitFacedWith, bidAmountFacedWith, includeMoons, includeStars); } else { log("Opponent " + opponent + " flip-oneupped", logging); - return opponentOneUp(bidSuitFacedWith, bidAmountFacedWith, logging); + return opponentOneUp(opponent, bidSuitFacedWith, bidAmountFacedWith, logging); } } } } - 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(); @@ -424,7 +423,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 { @@ -442,7 +441,7 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) if (bidAmountFacedWith > expectedValueForBid + 1) { - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else { @@ -453,7 +452,7 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) if (maxEv > bidAmountFacedWith - 1 && totalOpponentCards > 1) { - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suit, logging); } else if (maxEv > bidAmountFacedWith - 1) { @@ -464,52 +463,43 @@ else if (maxEv > bidAmountFacedWith - 1) if (amountRequiredInOneCard < 2) { - return opponentMinBidSuit(bidSuitFacedWith, bidAmountFacedWith, suit, logging); + return opponentMinBidSuit(opponent, bidSuitFacedWith, bidAmountFacedWith, suit, logging); } 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 suitToBid, boolean logging) { log("MinBidSuit " + suitToBid, logging); - int bidAmount = 0; - if (bidSuitFacedWith.lessThan(suitToBid)) - { - bidAmount = bidAmountFacedWith; - } - else - { - bidAmount = bidAmountFacedWith + 1; - } - - return new EntropyBid(suitToBid, bidAmount); + int bidAmount = amountRequiredToBid(suitToBid, bidSuitFacedWith, bidAmountFacedWith); + return new EntropyBidAction(opponent.getName(), false, bidAmount, suitToBid); } private static void log(String text, boolean logging) { diff --git a/client/src/main/java/util/VectCpuStrategies.java b/client/src/main/java/util/VectCpuStrategies.java index d30fcec..933d522 100644 --- a/client/src/main/java/util/VectCpuStrategies.java +++ b/client/src/main/java/util/VectCpuStrategies.java @@ -2,14 +2,12 @@ import java.util.*; +import game.ChallengeAction; +import game.PlayerAction; import game.Suit; import game.VectropyBidAction; -import object.Bid; -import object.ChallengeBid; import object.Player; -import object.VectropyBid; -import static game.CardsUtilKt.countSuit; import static game.StrategyUtilKt.*; import static utils.CoreGlobals.logger; @@ -29,12 +27,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)) { @@ -54,7 +52,7 @@ else if (strategy.equals(CpuStrategies.STRATEGY_EV)) } } - private static Bid processBasicTurn(Player opponent, StrategyParams parms) + private static PlayerAction processBasicTurn(Player opponent, StrategyParams parms) { boolean logging = parms.getLogging(); log("Basic strategy for this turn", logging); @@ -68,55 +66,11 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) int jokerValue = settings.getJokerValue(); boolean includeMoons = settings.getIncludeMoons(); boolean includeStars = settings.getIncludeStars(); - - int clubsCount = countSuit(Suit.Clubs, hand, jokerValue); - int diamondsCount = countSuit(Suit.Diamonds, hand, jokerValue); - int heartsCount = countSuit(Suit.Hearts, hand, jokerValue); - int moonsCount = countSuit(Suit.Moons, hand, jokerValue); - int spadesCount = countSuit(Suit.Spades, hand, jokerValue); - int starsCount = countSuit(Suit.Stars, hand, jokerValue); if (lastBid == null) { log("Starting this round", logging); - - if (totalCards <= 4) - { - VectropyBid bid = VectropyBid.factoryEmpty(includeMoons, includeStars); - return bid.incrementSuitAndGet(Suit.random(includeMoons, includeStars)); - } - else - { - int clubsBid = Math.max(0, clubsCount + coin.nextInt(3) - 1); - int diamondsBid = Math.max(0, diamondsCount + coin.nextInt(3) - 1); - int heartsBid = Math.max(0, heartsCount + coin.nextInt(3) - 1); - - int moonsBid = 0; - if (includeMoons) - { - moonsBid = Math.max(0, moonsCount + coin.nextInt(3) - 1); - } - - int spadesBid = Math.max(0, spadesCount + coin.nextInt(3) - 1); - - int starsBid = 0; - if (includeStars) - { - starsBid = Math.max(0, starsCount + coin.nextInt(3) - 1); - } - - VectropyBid bid = new VectropyBid(clubsBid, diamondsBid, heartsBid, moonsBid, spadesBid, - starsBid, includeMoons, includeStars); - - int newTotal = bid.getTotal(); - if (newTotal == 0) - { - var suit = Suit.random(includeMoons, includeStars); - bid = bid.incrementSuitAndGet(suit); - } - - return bid; - } + return getBasicVectropyOpening(opponent.getName(), hand, parms); } else { @@ -133,17 +87,17 @@ private static Bid processBasicTurn(Player opponent, StrategyParams parms) if (allNonNegative(diffMap)) { log("Auto-minbid as I could see everything.", logging); - return opponentMinBidSuit(lastBid, Suit.random(includeMoons, includeStars)); + return opponentMinBidSuit(opponent, lastBid, Suit.random(includeMoons, includeStars)); } else if (shouldAutoChallengeForIndividualSuit(diffMap, thirdThreshold)) { log("Auto-challenged for individual suit.", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (shouldAutoChallengeForOverall(diffMap, unseenCards)) { log("Auto-challenged for overall.", logging); - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (bidIsSensible(diffMap, unseenCards)) { @@ -151,11 +105,11 @@ else if (bidIsSensible(diffMap, unseenCards)) if (choice < 5) //0,2,3,4 { - return opponentMinBidSuit(lastBid, suitWithHighestDiff); + return opponentMinBidSuit(opponent, lastBid, suitWithHighestDiff); } else //5,6,7,8,9 { - return opponentMinBidSuit(lastBid, Suit.random(includeMoons, includeStars)); + return opponentMinBidSuit(opponent, lastBid, Suit.random(includeMoons, includeStars)); } } else @@ -164,69 +118,36 @@ else if (bidIsSensible(diffMap, unseenCards)) int choice = coin.nextInt(2); if (choice == 0) { - return opponentMinBidSuit(lastBid, Suit.random(includeMoons, includeStars)); + return opponentMinBidSuit(opponent, lastBid, Suit.random(includeMoons, includeStars)); } else { - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } } } } - private static Bid processEvTurnAndRevealCard(Player opponent, StrategyParams parms) + private static PlayerAction processEvTurnAndRevealCard(Player opponent, StrategyParams parms) { - Bid bid = processEvTurn(opponent, parms); - CpuStrategies.setCardToReveal(bid, parms.getSettings(), opponent); - return bid; + var action = processEvTurn(opponent, parms); + CpuStrategies.setCardToReveal(action, parms.getSettings(), opponent); + return action; } - private static Bid processEvTurn(Player opponent, StrategyParams parms) + private static PlayerAction processEvTurn(Player opponent, StrategyParams parms) { boolean logging = parms.getLogging(); log("EV strategy for this turn", logging); - Random coin = new Random(); List hand = opponent.getHand(); //Parms VectropyBidAction lastBid = (VectropyBidAction)parms.getLastBid(); - boolean includeMoons = parms.getSettings().getIncludeMoons(); - boolean includeStars = parms.getSettings().getIncludeStars(); if (lastBid == null) { log("Starting this round", logging); - Map hmEvBySuit = getEvMap(hand, parms.getSettings(), parms.getCardsInPlay()); - log("EV HashMap = " + hmEvBySuit, logging); - - int clubsBid = getOpeningBidForSuitBasedOnEv(Suit.Clubs, hmEvBySuit); - int diamondsBid = getOpeningBidForSuitBasedOnEv(Suit.Diamonds, hmEvBySuit); - int heartsBid = getOpeningBidForSuitBasedOnEv(Suit.Hearts, hmEvBySuit); - int moonsBid = getOpeningBidForSuitBasedOnEv(Suit.Moons, hmEvBySuit); - int spadesBid = getOpeningBidForSuitBasedOnEv(Suit.Spades, hmEvBySuit); - int starsBid = getOpeningBidForSuitBasedOnEv(Suit.Stars, hmEvBySuit); - - VectropyBid bid = new VectropyBid(clubsBid, diamondsBid, heartsBid, moonsBid, spadesBid, - starsBid, includeMoons, includeStars); - - int newTotal = bid.getTotal(); - if (newTotal == 0) - { - int choice = coin.nextInt(10); - Suit suit; - if (choice < 6) - { - suit = getSuitWithMostPositiveValue(hmEvBySuit); - } - else - { - suit = Suit.random(includeMoons, includeStars); - } - - bid = bid.incrementSuitAndGet(suit); - } - - return bid; + return getEvVectropyOpening(opponent.getName(), hand, parms); } else { @@ -243,54 +164,27 @@ private static Bid processEvTurn(Player opponent, StrategyParams parms) if (belowEvInAllSuits(hmEvDifferenceBySuit)) { log("Auto-minbid as bid is below EV in all suits.", logging); - return opponentMinBidSuit(lastBid, suitWithHighestDiff); + return opponentMinBidSuit(opponent, lastBid, suitWithHighestDiff); } else if (shouldAutoChallengeForEvDiffOfIndividualSuit(hmEvDifferenceBySuit)) { - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else if (shouldAutoChallengeForMultipleSuitsOverEv(hmEvDifferenceBySuit)) { - return new ChallengeBid(); + return new ChallengeAction(opponent.getName(), false); } else { - return opponentMinBidSuit(lastBid, suitWithHighestDiff); + return opponentMinBidSuit(opponent, lastBid, suitWithHighestDiff); } } } - - private static int getOpeningBidForSuitBasedOnEv(Suit suit, Map hmEvBySuit) - { - if (!hmEvBySuit.containsKey(suit)) { - return 0; - } - - double ev = hmEvBySuit.get(suit); - int evFloor = (int)Math.floor(ev); - - Random random = new Random(); - int adjustmentChoice = random.nextInt(20); - - if (adjustmentChoice < 11) - { - evFloor -= 1; - } - else if (adjustmentChoice < 18) - { - evFloor -= 2; - } - else - { - evFloor -= 3; - } - - return Math.max(evFloor, 0); - } - private static VectropyBidAction opponentMinBidSuit(VectropyBidAction lastBid, Suit suit) + private static VectropyBidAction opponentMinBidSuit(Player opponent, VectropyBidAction lastBid, Suit suit) { - return lastBid.incrementSuit(suit); + var map = lastBid.incrementSuit(suit).getAmounts(); + return new VectropyBidAction(opponent.getName(), false, map); } private static void log(String text, boolean logging) { diff --git a/client/src/main/kotlin/game/CheatUtil.kt b/client/src/main/kotlin/game/CheatUtil.kt index 39919a5..b829ff1 100644 --- a/client/src/main/kotlin/game/CheatUtil.kt +++ b/client/src/main/kotlin/game/CheatUtil.kt @@ -1,7 +1,7 @@ package game fun getMaxBidString(cards: List, settings: GameSettings) = - Suit.filter(settings.includeMoons, settings.includeStars).joinToString { suit -> + Suit.filter(settings).joinToString { suit -> val amount = countSuit(suit, cards, settings.jokerValue) "$amount${suit.letter}" } diff --git a/client/src/main/kotlin/game/StrategyUtil.kt b/client/src/main/kotlin/game/StrategyUtil.kt index 89e33fd..cc896ae 100644 --- a/client/src/main/kotlin/game/StrategyUtil.kt +++ b/client/src/main/kotlin/game/StrategyUtil.kt @@ -1,7 +1,9 @@ package game import kotlin.math.ceil -import `object`.VectropyBid +import kotlin.math.floor +import kotlin.random.Random +import util.StrategyParams fun getEvMap( visibleCards: List, @@ -11,7 +13,7 @@ fun getEvMap( val unknownCardsInPlay = cardsInPlay - visibleCards.size val remainingDeck = createAndShuffleDeck(settings).filterNot { visibleCards.contains(it) } - return Suit.filter(settings.includeMoons, settings.includeStars).associateWith { suit -> + return Suit.filter(settings).associateWith { suit -> val known = countSuit(suit, visibleCards, settings.jokerValue) val possibleOthers = remainingDeck.sumOf { countContribution(suit, it, settings.jokerValue) }.toDouble() @@ -58,11 +60,68 @@ fun bidIsSensible(differenceMap: Map, unseenCards: Int): Boolean { return total >= comparison } -fun computeEvDifferences(bid: VectropyBid, evMap: Map): Map = - evMap.mapValues { (suit, ev) -> ev - bid.getAmount(suit) } +fun computeEvDifferences(bid: VectropyBidAction, evMap: Map): Map = + evMap.mapValues { (suit, ev) -> ev - bid.getAmount(suit)!! } fun shouldAutoChallengeForEvDiffOfIndividualSuit(evDifferenceMap: Map) = evDifferenceMap.any { it.value < -0.5 } fun shouldAutoChallengeForMultipleSuitsOverEv(evDifferenceMap: Map) = evDifferenceMap.count { it.value < 0 } > 1 + +fun getBasicVectropyOpening( + opponentName: String, + hand: List, + strategyParams: StrategyParams, +): VectropyBidAction { + val settings = strategyParams.settings + val suits = Suit.filter(settings) + + if (strategyParams.cardsInPlay <= 4) { + val empty = suits.associateWith { 0 } + val suit = suits.random() + return VectropyBidAction(opponentName, false, empty).incrementSuit(suit) + } + + val map = + suits.associateWith { suit -> + val myCount = countSuit(suit, hand, settings.jokerValue) + maxOf(0, myCount + Random.nextInt(3)) + } + + return VectropyBidAction(opponentName, false, map) +} + +fun getEvVectropyOpening( + opponentName: String, + hand: List, + strategyParams: StrategyParams, +): VectropyBidAction { + val settings = strategyParams.settings + val hmEvBySuit = getEvMap(hand, settings, strategyParams.cardsInPlay) + + val suits = Suit.filter(settings) + val map = + suits.associateWith { suit -> + val evFloor = floor(hmEvBySuit.getValue(suit)).toInt() + + val adjustmentSwitch = Random.nextInt(20) + val adjusted = + if (adjustmentSwitch < 11) { + evFloor - 1 + } else if (adjustmentSwitch < 18) { + evFloor - 2 + } else { + evFloor - 3 + } + + maxOf(0, adjusted) + } + + val bid = VectropyBidAction(opponentName, false, map) + if (bid.getTotal() > 0) { + return bid + } + + return bid.incrementSuit(suits.random()) +} diff --git a/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt b/client/src/main/kotlin/strategy/MarkStrategySuitWrapper.kt index 6cf9096..b603c21 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/test/kotlin/game/StrategyUtilTest.kt b/client/src/test/kotlin/game/StrategyUtilTest.kt index baaeece..db60c54 100644 --- a/client/src/test/kotlin/game/StrategyUtilTest.kt +++ b/client/src/test/kotlin/game/StrategyUtilTest.kt @@ -4,7 +4,6 @@ import io.kotest.matchers.doubles.shouldBeBetween import io.kotest.matchers.maps.shouldContainAll import io.kotest.matchers.maps.shouldNotContainKeys import io.kotest.matchers.shouldBe -import `object`.VectropyBid import org.junit.jupiter.api.Test import testCore.makeGameSettings import util.AbstractClientTest @@ -51,7 +50,7 @@ class StrategyUtilTest : AbstractClientTest() { @Test fun `Should compute the difference between a vectropy bid and what can be seen`() { val cards = listOf("Ac", "3d", "4h", "6h") - val bid = VectropyBid(0, 2, 1, 0, 2, 0, false, false) + val bid = VectropyBidAction("", false, 0, 2, 1, null, 2, null) val map = getDifferenceMap(bid, cards, 1, false, false) map.shouldContainAll( mapOf(Suit.Clubs to 2, Suit.Diamonds to 0, Suit.Hearts to 2, Suit.Spades to -1) @@ -80,12 +79,13 @@ class StrategyUtilTest : AbstractClientTest() { val evs = mapOf(Suit.Clubs to 5.4, Suit.Diamonds to 8.8, Suit.Hearts to 3.7, Suit.Spades to 2.0) - val offInOneSuit = computeEvDifferences(VectropyBid(6, 0, 0, 0, 0, 0, false, false), evs) + val offInOneSuit = + computeEvDifferences(VectropyBidAction("", false, 6, 0, 0, null, 0, null), evs) shouldAutoChallengeForEvDiffOfIndividualSuit(offInOneSuit) shouldBe true shouldAutoChallengeForMultipleSuitsOverEv(offInOneSuit) shouldBe false val offInMultipleSuits = - computeEvDifferences(VectropyBid(0, 9, 4, 0, 0, 0, false, false), evs) + computeEvDifferences(VectropyBidAction("", false, 0, 9, 4, null, 0, null), evs) shouldAutoChallengeForEvDiffOfIndividualSuit(offInMultipleSuits) shouldBe false shouldAutoChallengeForMultipleSuitsOverEv(offInMultipleSuits) shouldBe true } diff --git a/core/src/main/java/object/Bid.java b/core/src/main/java/object/Bid.java deleted file mode 100644 index 69c839f..0000000 --- a/core/src/main/java/object/Bid.java +++ /dev/null @@ -1,189 +0,0 @@ - -package object; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -import game.GameSettings; -import org.w3c.dom.Element; - -import util.Debug; -import util.StringUtil; - -import static game.CardsUtilKt.extractCards; - -public abstract class Bid -{ - protected static final String XML_DELIM_CHAR = ";"; - private static final String BID_XML_VERSION = "1"; - - private Player player = null; //The player who made the bid - private String cardToReveal = ""; - private boolean blind = false; - - public abstract boolean higherThan(Bid bid); - public abstract boolean isOverAchievementThreshold(); - public abstract boolean isPerfect(List cards, GameSettings settings); - public abstract boolean isOverbid(List cards, int jokerValue); - - /** - * Used for: - * - Saving all bids to a saved game/replay - * - Serializiing bids for client/server communication - */ - public abstract String getXmlStringPrefix(); - public abstract String toXmlStringSpecific(); - public abstract void populateFromXmlStringSpecific(ArrayList toks, boolean moons, boolean stars); - public abstract String toHtmlStringSpecific(); - - public abstract String toStringSpecific(); - - /** - * Used for the API. More user-friendly form of a bid, without unnecessary "fluff" (e.g. whether it was blind and - * info on the player such as the player's colour) - */ - public abstract void populateXmlTag(Element bidElement); - - public String toXmlString() - { - String xmlStr = BID_XML_VERSION; - xmlStr += XML_DELIM_CHAR; - xmlStr += getXmlStringPrefix(); - xmlStr += XML_DELIM_CHAR; - - if (blind) - { - xmlStr += "B"; - } - else - { - xmlStr += "N"; - } - - xmlStr += XML_DELIM_CHAR; - xmlStr += player.getPlayerNumber(); - xmlStr += XML_DELIM_CHAR; - xmlStr += player.getName(); - xmlStr += XML_DELIM_CHAR; - xmlStr += player.getColour(); - xmlStr += XML_DELIM_CHAR; - xmlStr += cardToReveal; - - String specificXml = toXmlStringSpecific(); - if (!specificXml.isEmpty()) - { - xmlStr += XML_DELIM_CHAR; - xmlStr += specificXml; - } - - return xmlStr; - } - - public boolean isOverbid(ConcurrentHashMap> hmHandByPlayerNumber, int jokerValue) { - return isOverbid(extractCards(hmHandByPlayerNumber), jokerValue); - } - - public boolean isPerfect(ConcurrentHashMap> hmHandByPlayerNumber, GameSettings settings) - { - return isPerfect(extractCards(hmHandByPlayerNumber), settings); - } - - public boolean isChallenge() - { - return (this instanceof ChallengeBid); - } - public boolean isIllegal() - { - return (this instanceof IllegalBid); - } - - public boolean hasLeft() - { - return (this instanceof LeftBid); - } - public Player getPlayer() - { - return player; - } - public void setPlayer(Player player) - { - this.player = player; - } - public String getCardToReveal() - { - return cardToReveal; - } - public void setCardToReveal(String cardToReveal) - { - this.cardToReveal = cardToReveal; - } - public boolean isBlind() - { - return blind; - } - public void setBlind(boolean blind) - { - this.blind = blind; - } - - public static Bid factoryFromXmlString(String xmlStr, boolean includeMoons, boolean includeStars) - { - ArrayList toks = StringUtil.getListFromDelims(xmlStr, XML_DELIM_CHAR); - String version = toks.remove(0); - if (!version.equals(BID_XML_VERSION)) - { - Debug.stackTrace("Factorying bid from XML that isn't on current version ( " + BID_XML_VERSION + "). Bid: " + xmlStr); - return null; - } - - //Get the other generic variables... - String prefix = toks.remove(0); - String blindStr = toks.remove(0); - String playerNoStr = toks.remove(0); - String playerName = toks.remove(0); - String colour = toks.remove(0); - String cardToReveal = toks.remove(0); - - int playerNo = Integer.parseInt(playerNoStr); - Player player = new Player(playerNo, colour); - player.setName(playerName); - - boolean blind = blindStr.equals("B"); - - Bid bid = getEmptyBidBasedOnPrefix(prefix); - bid.setPlayer(player); - bid.setBlind(blind); - bid.setCardToReveal(cardToReveal); - - bid.populateFromXmlStringSpecific(toks, includeMoons, includeStars); - return bid; - } - private static Bid getEmptyBidBasedOnPrefix(String prefix) - { - Bid[] bids = {new EntropyBid(), new VectropyBid(), new ChallengeBid(), new IllegalBid(), new LeftBid()}; - for (int i=0; i"; - htmlStr += bidAmount + suitSymbol; - htmlStr += ""; - - return htmlStr; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + bidAmount; - result = prime * result + bidSuit.ordinal(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof EntropyBid)) - return false; - EntropyBid other = (EntropyBid) obj; - if (bidAmount != other.bidAmount) - return false; - if (bidSuit != other.bidSuit) - return false; - return true; - } - - /** - * Abstract methods - */ - @Override - public boolean higherThan(Bid bid) - { - if (!(bid instanceof EntropyBid)) - { - Debug.stackTrace("Comparing EntropyBid " + this + " to " + bid); - return false; - } - - EntropyBid entropyBid = (EntropyBid)bid; - if (bidAmount > entropyBid.getBidAmount()) - { - return true; - } - - if (bidAmount == entropyBid.getBidAmount() - && entropyBid.bidSuit.lessThan(bidSuit)) - { - return true; - } - - return false; - } - - @Override - public boolean isPerfect(List cards, GameSettings settings) - { - int perfectBidAmount = perfectBidAmount(cards, settings.getJokerValue()); - var perfectBidSuit = perfectBidSuit(cards, settings.getJokerValue(), settings.getIncludeStars()); - - return bidSuit == perfectBidSuit && bidAmount == perfectBidAmount; - } - - @Override - public boolean isOverbid(List cards, int jokerValue) - { - int total = countSuit(bidSuit, cards, jokerValue); - return bidAmount > total; - } - - @Override - public String getXmlStringPrefix() - { - return "E"; - } - - @Override - public String toXmlStringSpecific() - { - return bidSuit.name() + ";" + bidAmount; - } - - @Override - public void populateFromXmlStringSpecific(ArrayList toks, - boolean moons, boolean stars) - { - this.bidSuit = Suit.valueOf(toks.get(0)); - this.bidAmount = Integer.parseInt(toks.get(1)); - } - - @Override - public void populateXmlTag(Element bidElement) - { - bidElement.setAttribute("BidSuit", bidSuit.name()); - bidElement.setAttribute("BidAmount", "" + bidAmount); - } - - @Override - public boolean isOverAchievementThreshold() - { - return bidAmount >= 5; - } - - /** - * Static methods - */ - public static EntropyBid factoryFromXmlTag(Element root) throws IOException - { - String bidSuitName = XmlUtil.getCompulsoryAttribute(root, "BidSuit"); - int bidAmount = XmlUtil.getAttributeIntCompulsory(root, "BidAmount"); - return new EntropyBid(Suit.valueOf(bidSuitName), bidAmount); - } -} diff --git a/core/src/main/java/object/FakeBid.java b/core/src/main/java/object/FakeBid.java deleted file mode 100644 index 596282a..0000000 --- a/core/src/main/java/object/FakeBid.java +++ /dev/null @@ -1,71 +0,0 @@ -package object; - -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -import game.GameSettings; -import org.w3c.dom.Element; - -import util.Debug; - -public abstract class FakeBid extends Bid -{ - @Override - public boolean higherThan(Bid bid) - { - Debug.stackTrace("Calling unimplemented method: higherThan"); - return false; - } - - @Override - public boolean isPerfect(List cards, GameSettings settings) - { - Debug.stackTrace("Calling unimplemented method: isPerfect"); - return false; - } - - @Override - public boolean isOverbid(ConcurrentHashMap> hmHandByPlayerNumber, int jokerValue) - { - Debug.stackTrace("Calling unimplemented method: isOverbid"); - return false; - } - - @Override - public boolean isOverbid(List cards, int jokerValue) - { - Debug.stackTrace("Calling unimplemented method: isOverbid"); - return false; - } - - @Override - public boolean isOverAchievementThreshold() - { - Debug.stackTrace("Calling unimplemented method: isOverAchievementThreshold"); - return false; - } - - @Override - public void populateXmlTag(Element bidElement) - { - Debug.stackTrace("Calling populateXmlTag but is hasn't been implemented! BidElement: " + bidElement.getNodeName()); - } - - @Override - public String toXmlStringSpecific() - { - return ""; - } - - @Override - public void populateFromXmlStringSpecific(java.util.ArrayList toks, boolean moons, boolean stars) - { - //Do nothing... - } - - @Override - public String toHtmlStringSpecific() - { - return toStringSpecific(); - } -} diff --git a/core/src/main/java/object/IllegalBid.java b/core/src/main/java/object/IllegalBid.java deleted file mode 100644 index e390747..0000000 --- a/core/src/main/java/object/IllegalBid.java +++ /dev/null @@ -1,22 +0,0 @@ -package object; - -public class IllegalBid extends FakeBid -{ - @Override - public String toStringSpecific() - { - return "Illegal!"; - } - - @Override - public String getXmlStringPrefix() - { - return "I"; - } - - @Override - public boolean equals(Object arg0) - { - return arg0 instanceof IllegalBid; - } -} diff --git a/core/src/main/java/object/LeftBid.java b/core/src/main/java/object/LeftBid.java deleted file mode 100644 index b64480d..0000000 --- a/core/src/main/java/object/LeftBid.java +++ /dev/null @@ -1,23 +0,0 @@ -package object; - - -public class LeftBid extends FakeBid -{ - @Override - public String toStringSpecific() - { - return "Left"; - } - - @Override - public String getXmlStringPrefix() - { - return "L"; - } - - @Override - public boolean equals(Object arg0) - { - return arg0 instanceof LeftBid; - } -} diff --git a/core/src/main/java/object/VectropyBid.java b/core/src/main/java/object/VectropyBid.java deleted file mode 100644 index 975c59f..0000000 --- a/core/src/main/java/object/VectropyBid.java +++ /dev/null @@ -1,328 +0,0 @@ -package object; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import game.GameSettings; -import game.Suit; -import game.VectropyUtilKt; -import org.w3c.dom.Element; - -import util.Debug; -import util.XmlUtil; - -import static game.CardsUtilKt.countSuit; - -public class VectropyBid extends Bid -{ - private Map amounts = new HashMap<>(); - public boolean includeMoons = false; - public boolean includeStars = false; - - /** - * Empty constructor, used for factorying from XML - */ - public VectropyBid(){} - - public VectropyBid(int clubs, int diamonds, int hearts, int moons, int spades, int stars, - boolean includeMoons, boolean includeStars) - { - populateMap(clubs, diamonds, hearts, moons, spades, stars); - this.includeMoons = includeMoons; - this.includeStars = includeStars; - } - - public VectropyBid(Map amounts, boolean includeMoons, boolean includeStars) { - this.amounts = amounts; - this.includeMoons = includeMoons; - this.includeStars = includeStars; - } - - private void populateMap(int clubs, int diamonds, int hearts, int moons, int spades, int stars) { - this.amounts.put(Suit.Clubs, clubs); - this.amounts.put(Suit.Diamonds, diamonds); - this.amounts.put(Suit.Hearts, hearts); - this.amounts.put(Suit.Moons, moons); - this.amounts.put(Suit.Spades, spades); - this.amounts.put(Suit.Stars, stars); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + amounts.get(Suit.Clubs); - result = prime * result + amounts.get(Suit.Diamonds); - result = prime * result + amounts.get(Suit.Hearts); - result = prime * result + (includeMoons ? 1231 : 1237); - result = prime * result + (includeStars ? 1231 : 1237); - result = prime * result + amounts.get(Suit.Moons); - result = prime * result + amounts.get(Suit.Spades); - result = prime * result + amounts.get(Suit.Stars); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (!(obj instanceof VectropyBid)) - return false; - VectropyBid other = (VectropyBid) obj; - if (!amounts.get(Suit.Clubs).equals(other.amounts.get(Suit.Clubs))) - return false; - if (!amounts.get(Suit.Diamonds).equals(other.amounts.get(Suit.Diamonds))) - return false; - if (!amounts.get(Suit.Hearts).equals(other.amounts.get(Suit.Hearts))) - return false; - if (includeMoons != other.includeMoons) - return false; - if (includeStars != other.includeStars) - return false; - if (!amounts.get(Suit.Moons).equals(other.amounts.get(Suit.Moons))) - return false; - if (!amounts.get(Suit.Spades).equals(other.amounts.get(Suit.Spades))) - return false; - if (!amounts.get(Suit.Stars).equals(other.amounts.get(Suit.Stars))) - return false; - return true; - } - - public int getAmount(Suit suit) - { - return amounts.get(suit); - } - public int getClubs() - { - return amounts.get(Suit.Clubs); - } - public int getDiamonds() - { - return amounts.get(Suit.Diamonds); - } - public int getHearts() - { - return amounts.get(Suit.Hearts); - } - public int getMoons() - { - return amounts.get(Suit.Moons); - } - public int getSpades() - { - return amounts.get(Suit.Spades); - } - public int getStars() - { - return amounts.get(Suit.Stars); - } - - public int getTotal() - { - return amounts.values().stream().mapToInt(Integer::intValue).sum(); - } - - public VectropyBid incrementSuitAndGet(Suit suit) - { - int current = amounts.get(suit); - var newAmounts = new HashMap(); - Suit.getEntries().forEach((Suit s) -> { - if (s == suit) { - newAmounts.put(s, amounts.get(s) + 1); - } else { - newAmounts.put(s, amounts.get(s)); - } - }); - - return new VectropyBid(newAmounts, includeMoons, includeStars); - } - - @Override - public String getXmlStringPrefix() - { - return "V"; - } - - @Override - public String toXmlStringSpecific() - { - String xmlStr = getClubs() + XML_DELIM_CHAR + getDiamonds() + XML_DELIM_CHAR + getHearts() + XML_DELIM_CHAR; - - if (includeMoons) - { - xmlStr += getMoons(); - xmlStr += XML_DELIM_CHAR; - } - - xmlStr += getSpades(); - - if (includeStars) - { - xmlStr += XML_DELIM_CHAR; - xmlStr += getStars(); - } - - return xmlStr; - } - - @Override - public void populateFromXmlStringSpecific(ArrayList toks, - boolean includeMoons, boolean includeStars) - { - this.includeMoons = includeMoons; - this.includeStars = includeStars; - - var clubs = Integer.parseInt(toks.remove(0)); - var diamonds = Integer.parseInt(toks.remove(0)); - var hearts = Integer.parseInt(toks.remove(0)); - - var moons = 0; - if (includeMoons) - { - moons = Integer.parseInt(toks.remove(0)); - } - - var spades = Integer.parseInt(toks.remove(0)); - - var stars = 0; - if (includeStars) - { - stars = Integer.parseInt(toks.remove(0)); - } - - populateMap(clubs, diamonds, hearts, moons, spades, stars); - } - - @Override - public String toStringSpecific() - { - String bidStr = "(" + getClubs() + ", " + getDiamonds() + ", " + getHearts(); - if (includeMoons) - { - bidStr += ", " + getMoons(); - } - - bidStr += ", " + getSpades(); - - if (includeStars) - { - bidStr += ", " + getStars(); - } - - bidStr += ")"; - return bidStr; - } - - @Override - public String toHtmlStringSpecific() - { - return toStringSpecific(); - } - - /** - * Abstract methods - */ - @Override - public boolean higherThan(Bid bid) - { - if (!(bid instanceof VectropyBid)) - { - Debug.stackTrace("Comparing " + this + " to " + bid); - return false; - } - - VectropyBid bid2 = (VectropyBid)bid; - return getClubs() >= bid2.getClubs() - && getDiamonds() >= bid2.getDiamonds() - && getHearts() >= bid2.getHearts() - && getMoons() >= bid2.getMoons() - && getSpades() >= bid2.getSpades() - && getStars() >= bid2.getStars() - && getTotal() > bid2.getTotal(); - } - - @Override - public boolean isPerfect(List cards, GameSettings settings) - { - var jokerValue = settings.getJokerValue(); - int maxClubs = countSuit(Suit.Clubs, cards, jokerValue); - int maxDiamonds = countSuit(Suit.Diamonds, cards, jokerValue); - int maxHearts = countSuit(Suit.Hearts, cards, jokerValue); - int maxMoons = countSuit(Suit.Moons, cards, jokerValue); - int maxSpades = countSuit(Suit.Spades, cards, jokerValue); - int maxStars = countSuit(Suit.Stars, cards, jokerValue); - - return getClubs() == maxClubs - && getDiamonds() == maxDiamonds - && getHearts() == maxHearts - && (getMoons() == maxMoons || !includeMoons) - && getSpades() == maxSpades - && (getStars() == maxStars || !includeStars); - } - - @Override - public boolean isOverbid(List cards, int jokerValue) - { - return VectropyUtilKt.isOverbid(this, cards, jokerValue); - } - - @Override - public void populateXmlTag(Element bidElement) - { - bidElement.setAttribute("Clubs", "" + getClubs()); - bidElement.setAttribute("Diamonds", "" + getDiamonds()); - bidElement.setAttribute("Hearts", "" + getHearts()); - - if (includeMoons) - { - bidElement.setAttribute("Moons", "" + getMoons()); - } - - bidElement.setAttribute("Spades", "" + getSpades()); - - if (includeStars) - { - bidElement.setAttribute("Stars", "" + getStars()); - } - } - - @Override - public boolean isOverAchievementThreshold() - { - return getTotal() >= 5; - } - - /** - * Static methods - */ - public static VectropyBid factoryFromXmlTag(Element root, boolean includeMoons, boolean includeStars) throws IOException - { - int clubs = XmlUtil.getAttributeIntCompulsory(root, "Clubs"); - int diamonds = XmlUtil.getAttributeIntCompulsory(root, "Diamonds"); - int hearts = XmlUtil.getAttributeIntCompulsory(root, "Hearts"); - int spades = XmlUtil.getAttributeIntCompulsory(root, "Spades"); - - int moons = 0; - if (includeMoons) - { - moons = XmlUtil.getAttributeIntCompulsory(root, "Moons"); - } - - int stars = 0; - if (includeStars) - { - stars = XmlUtil.getAttributeIntCompulsory(root, "Stars"); - } - - return new VectropyBid(clubs, diamonds, hearts, moons, spades, stars, includeMoons, includeStars); - } - - public static VectropyBid factoryEmpty(boolean includeMoons, boolean includeStars) { - return new VectropyBid(0, 0, 0, 0, 0, 0, includeMoons, includeStars); - } -} diff --git a/core/src/main/kotlin/game/CardsUtil.kt b/core/src/main/kotlin/game/CardsUtil.kt index 9cc5248..a81ffa6 100644 --- a/core/src/main/kotlin/game/CardsUtil.kt +++ b/core/src/main/kotlin/game/CardsUtil.kt @@ -21,7 +21,7 @@ fun isCardRelevant(card: String, suit: Suit) = countContribution(suit, card, 1) @JvmOverloads fun createAndShuffleDeck(settings: GameSettings, seed: Long? = null): List { // Creating the pack of cards - val suits = Suit.filter(settings.includeMoons, settings.includeStars).map(Suit::letter) + val suits = Suit.filter(settings).map(Suit::letter) val jokers = (0.., jokerValue: Int): Boolean { - val suits = Suit.filter(bid.includeMoons, bid.includeStars) - return suits.any { bid.getAmount(it) > countSuit(it, allCards, jokerValue) } -} diff --git a/core/src/test/kotlin/game/VectropyUtilTest.kt b/core/src/test/kotlin/game/VectropyUtilTest.kt deleted file mode 100644 index cc6667b..0000000 --- a/core/src/test/kotlin/game/VectropyUtilTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package game - -import io.kotest.matchers.shouldBe -import `object`.VectropyBid -import org.junit.jupiter.api.Test -import testCore.AbstractTest - -class VectropyUtilTest : AbstractTest() { - @Test - fun `Should detect an overbid`() { - val cards = listOf("Ad", "3c", "Jo1") - val baseMap = Suit.entries.associateWith { 0 } - isOverbid(VectropyBid(baseMap + (Suit.Clubs to 5), false, false), cards, 2) shouldBe true - isOverbid(VectropyBid(baseMap + (Suit.Diamonds to 5), false, false), cards, 2) shouldBe true - isOverbid(VectropyBid(baseMap + (Suit.Hearts to 4), false, false), cards, 2) shouldBe true - isOverbid(VectropyBid(baseMap + (Suit.Spades to 4), false, false), cards, 2) shouldBe true - - val perfect = mapOf(Suit.Clubs to 4, Suit.Diamonds to 4, Suit.Hearts to 3, Suit.Spades to 3) - isOverbid(VectropyBid(perfect, false, false), cards, 2) shouldBe false - } -} diff --git a/server/src/main/java/object/BidHistory.java b/server/src/main/java/object/BidHistory.java index 43d9567..d80d6c3 100644 --- a/server/src/main/java/object/BidHistory.java +++ b/server/src/main/java/object/BidHistory.java @@ -1,5 +1,7 @@ package object; +import game.PlayerAction; + import java.util.ArrayList; import java.util.HashMap; @@ -7,21 +9,21 @@ public class BidHistory { - private HashMap> hmBidVectorByPlayerNumber = new HashMap<>(); + private HashMap> hmBidVectorByPlayerNumber = new HashMap<>(); private int lastPlayerToAct = -1; private int personToStart = -1; public BidHistory() { - hmBidVectorByPlayerNumber.put(0, new ArrayList()); - hmBidVectorByPlayerNumber.put(1, new ArrayList()); - hmBidVectorByPlayerNumber.put(2, new ArrayList()); - hmBidVectorByPlayerNumber.put(3, new ArrayList()); + hmBidVectorByPlayerNumber.put(0, new ArrayList<>()); + hmBidVectorByPlayerNumber.put(1, new ArrayList<>()); + hmBidVectorByPlayerNumber.put(2, new ArrayList<>()); + hmBidVectorByPlayerNumber.put(3, new ArrayList<>()); } - public boolean addBidForPlayer(int playerNumber, Bid bid) + public boolean addBidForPlayer(int playerNumber, PlayerAction bid) { - ArrayList bids = hmBidVectorByPlayerNumber.get(playerNumber); + ArrayList bids = hmBidVectorByPlayerNumber.get(playerNumber); if (bids.contains(bid)) { @@ -36,39 +38,9 @@ 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); + ArrayList bids = hmBidVectorByPlayerNumber.get(playerNumber); int size = bids.size(); if (size > 0) { @@ -99,7 +71,7 @@ public String toString() for (int i=0; i<4; i++) { - ArrayList bidVector = hmBidVectorByPlayerNumber.get(i); + ArrayList bidVector = hmBidVectorByPlayerNumber.get(i); if (!bidVector.isEmpty()) { if (!s.isEmpty()) diff --git a/server/src/main/java/object/GameWrapper.java b/server/src/main/java/object/GameWrapper.java index aeaeef3..bc0c463 100644 --- a/server/src/main/java/object/GameWrapper.java +++ b/server/src/main/java/object/GameWrapper.java @@ -93,19 +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; diff --git a/server/src/main/java/util/XmlBuilderServer.java b/server/src/main/java/util/XmlBuilderServer.java index cbc6c3b..c019cc8 100644 --- a/server/src/main/java/util/XmlBuilderServer.java +++ b/server/src/main/java/util/XmlBuilderServer.java @@ -1,12 +1,16 @@ package util; -import object.Bid; +import game.BidAction; +import game.ChallengeAction; +import game.IllegalAction; +import game.PlayerAction; import object.BidHistory; import object.GameWrapper; import object.HandDetails; import org.w3c.dom.Document; import org.w3c.dom.Element; import room.Room; +import utils.CoreGlobals; import java.util.List; @@ -173,10 +177,10 @@ private static void addLastBids(Room room, int roundNumber, Element rootElement) int numberOfPlayers = room.getCapacity(); for (int i=0; i, ) { val game = getGameForId(gameId) val details: HandDetails = game.getDetailsForRound(roundNumber) - val hmHandByPlayerNumber: ConcurrentHashMap> = details.hands - if (bid.isOverbid(hmHandByPlayerNumber, settings.jokerValue)) { + val hands = details.hands.values.flatten() + if (bid.isOverbid(hands, settings)) { // bidder loses setUpNextRound(challengedNumber) } else { @@ -275,12 +272,12 @@ data class Room( roundNumber: Int, playerNumber: Int, bidderNumber: Int, - bid: Bid, + bid: BidAction<*>, ) { val game = getGameForId(gameId) val details: HandDetails = game.getDetailsForRound(roundNumber) - val hmHandByPlayerNumber: ConcurrentHashMap> = details.hands - if (bid.isPerfect(hmHandByPlayerNumber, settings)) { + val hands = details.hands.values.flatten() + if (bid.isPerfect(hands, settings)) { setUpNextRound(bidderNumber) } else { setUpNextRound(playerNumber) @@ -428,7 +425,7 @@ data class Room( return currentGame } - fun getLastBidForPlayer(playerNumber: Int, roundNumber: Int): Bid? { + fun getLastBidForPlayer(playerNumber: Int, roundNumber: Int): PlayerAction? { if (playerNumber == -1) { return null } @@ -441,7 +438,7 @@ data class Room( gameId: String, playerNumber: Int, roundNumber: Int, - newBid: Bid?, + newBid: PlayerAction, ): Boolean { val game = getGameForId(gameId) From a8ddd6d3c8b386aaf78a419ea4038dd16d133bc0 Mon Sep 17 00:00:00 2001 From: alyssa Date: Sat, 26 Jul 2025 17:23:07 +0100 Subject: [PATCH 06/12] done with compile errors! --- .../main/java/object/BidListCellRenderer.java | 35 +++++++++++++------ .../main/java/online/screen/EntropyRoom.java | 2 +- .../src/main/java/online/screen/GameRoom.java | 17 +++++---- .../main/java/online/screen/VectropyRoom.java | 2 +- .../java/online/util/ResponseHandler.java | 12 ++++--- client/src/main/java/screen/GameScreen.java | 13 +++++-- client/src/main/java/screen/MainScreen.java | 7 ++-- client/src/main/java/screen/ReplayDialog.java | 14 ++++++-- .../java/server/MessageHandlerRunnable.java | 3 +- .../src/main/java/util/XmlBuilderServer.java | 11 +++--- server/src/main/kotlin/room/Room.kt | 19 ++++++---- 11 files changed, 90 insertions(+), 45 deletions(-) diff --git a/client/src/main/java/object/BidListCellRenderer.java b/client/src/main/java/object/BidListCellRenderer.java index d5c303e..4da78c8 100644 --- a/client/src/main/java/object/BidListCellRenderer.java +++ b/client/src/main/java/object/BidListCellRenderer.java @@ -1,18 +1,25 @@ package object; +import game.BidAction; import game.PlayerAction; import util.StringUtil; import java.awt.Component; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; import javax.swing.DefaultListCellRenderer; import javax.swing.JList; import static game.RenderingUtilKt.getCardHtml; import static game.SuitKt.MOONS_SYMBOL; +import static utils.ColourUtilKt.getColourForPlayerNumber; public class BidListCellRenderer extends DefaultListCellRenderer { + private Map hmNameToColour = new HashMap<>(); + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) @@ -24,27 +31,36 @@ public Component getListCellRendererComponent(JList list, Object value, cellHasFocus); } - public String toHtmlString(PlayerAction bid) + public void updateColours(Collection players) { + this.hmNameToColour.clear(); + + for (Player player : players) { + hmNameToColour.put(player.getName(), player.getColour()); + } + } + + public void updateColours(Map map) { + this.hmNameToColour = map; + } + + public String toHtmlString(PlayerAction action) { - String playerName = bid.getPlayerName(); + String playerName = action.getPlayerName(); playerName = StringUtil.escapeHtml(playerName); - String colour = bid.getPlayer().getColour(); + String colour = hmNameToColour.get(playerName); String playerNamePrefix = playerName + ": "; - if (bid.getBlind()) + if (action.getBlind()) { playerNamePrefix = "[" + playerName + "]: "; } String text = "" + playerNamePrefix; text += ""; - text += bid.htmlString(); + text += action.htmlString(); - if (!bid.getCardToReveal().isEmpty() - && !bid.isChallenge() - && !bid.isIllegal()) - { + if (action instanceof BidAction bid && bid.getCardToReveal() != null) { text += "&emsp(Shows: "; text += getCardHtml(bid.getCardToReveal()); text += ")"; @@ -54,5 +70,4 @@ public String toHtmlString(PlayerAction bid) text = text.replaceAll(MOONS_SYMBOL, "🌙"); return text; } - } diff --git a/client/src/main/java/online/screen/EntropyRoom.java b/client/src/main/java/online/screen/EntropyRoom.java index 35fa0a3..a32507a 100644 --- a/client/src/main/java/online/screen/EntropyRoom.java +++ b/client/src/main/java/online/screen/EntropyRoom.java @@ -37,7 +37,7 @@ public void doSpecificResetGameVariables() public void resetBids() { lastBid = null; - hmBidByPlayerNumber.clear(); + hmActionByPlayerNumber.clear(); } @Override diff --git a/client/src/main/java/online/screen/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index 0bb2092..ac753b5 100644 --- a/client/src/main/java/online/screen/GameRoom.java +++ b/client/src/main/java/online/screen/GameRoom.java @@ -7,7 +7,6 @@ import org.w3c.dom.Document; import screen.*; import util.*; -import utils.CoreGlobals; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -65,7 +64,7 @@ public abstract class GameRoom> extends JFrame private ConcurrentHashMap hmPlayerByAdjustedPlayerNumber = new ConcurrentHashMap<>(); public ConcurrentHashMap> hmHandByAdjustedPlayerNumber = new ConcurrentHashMap<>(); - public ConcurrentHashMap hmBidByPlayerNumber = new ConcurrentHashMap<>(); + public ConcurrentHashMap hmActionByPlayerNumber = new ConcurrentHashMap<>(); private int personToStartLocal = -1; private int personToStart = -1; public int lastPlayerToAct = 0; @@ -147,13 +146,14 @@ public GameRoom(UUID id, String roomName, GameSettings settings, int players) textPaneInfo.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); textPaneInfo.setBackground(Color.WHITE); textPaneInfo.setEditable(false); - bidBox.setCellRenderer(new BidListCellRenderer()); + bidBox.setCellRenderer(bidRenderer); btnReplay.addActionListener(this); btnStandUp.addActionListener(this); addWindowListener(this); } - + + private final BidListCellRenderer bidRenderer = new BidListCellRenderer(); private final BackgroundPanel bgPanel = new BackgroundPanel(); private final JSplitPane splitPane = new JSplitPane(); private final TransparentPanel leftPane = new TransparentPanel(); @@ -338,7 +338,7 @@ private void resetVariables(boolean hotswap) hmPlayerByAdjustedPlayerNumber = new ConcurrentHashMap<>(); hmHandByAdjustedPlayerNumber = new ConcurrentHashMap<>(); - hmBidByPlayerNumber = new ConcurrentHashMap<>(); + hmActionByPlayerNumber = new ConcurrentHashMap<>(); totalNumberOfCards = players * 5; waitingForPlayers(); gameInProgress = false; @@ -428,6 +428,9 @@ public void synchronisePlayers(Map serverHmPlayerByPlayerNumber //Remove players who we have as active but the server doesn't processPlayersLeaving(serverHmPlayerByPlayerNumber); + + // Update bid renderer + bidRenderer.updateColours(hmPlayerByAdjustedPlayerNumber.values()); //Initialise a game if we've become full int currentSize = hmPlayerByAdjustedPlayerNumber.size(); @@ -761,7 +764,7 @@ public void startObserving(int personToStart, int lastPlayerToAct) handPanel.displayHandsOnline(hmHandByAdjustedPlayerNumber); handPanel.setInitted(true); - B lastBid = hmBidByPlayerNumber.get(lastPlayerToAct); + B lastBid = hmActionByPlayerNumber.get(lastPlayerToAct); if (lastBid != null) { handleBid(lastPlayerToAct, lastBid); @@ -794,7 +797,7 @@ public void handleBid(int playerNumber, PlayerAction action) lastPlayerToAct = playerNumber; lastBid = (B)action; - hmBidByPlayerNumber.put(playerNumber, lastBid); + hmActionByPlayerNumber.put(playerNumber, lastBid); if (playerNumber != this.playerNumber && !observer) diff --git a/client/src/main/java/online/screen/VectropyRoom.java b/client/src/main/java/online/screen/VectropyRoom.java index 375b7c4..613770c 100644 --- a/client/src/main/java/online/screen/VectropyRoom.java +++ b/client/src/main/java/online/screen/VectropyRoom.java @@ -37,7 +37,7 @@ public void doSpecificResetGameVariables() public void resetBids() { lastBid = null; - hmBidByPlayerNumber.clear(); + hmActionByPlayerNumber.clear(); } @Override diff --git a/client/src/main/java/online/util/ResponseHandler.java b/client/src/main/java/online/util/ResponseHandler.java index 8ac6285..bc95c90 100644 --- a/client/src/main/java/online/util/ResponseHandler.java +++ b/client/src/main/java/online/util/ResponseHandler.java @@ -150,11 +150,13 @@ private static void setBidsFromXml(GameRoom room, Element root) { continue; } - - boolean includeMoons = room.getIncludeMoons(); - boolean includeStars = room.getIncludeStars(); - Bid bid = Bid.factoryFromXmlString(bidStr, includeMoons, includeStars); - room.hmBidByPlayerNumber.put(i, bid); + + try { + PlayerAction bid = CoreGlobals.jsonMapper.readValue(bidStr, PlayerAction.class); + room.hmActionByPlayerNumber.put(i, bid); + } catch (Exception e) { + logger.error("parseError", "Failed to parse bid from string: " + bidStr, e); + } } } diff --git a/client/src/main/java/screen/GameScreen.java b/client/src/main/java/screen/GameScreen.java index a4d2950..510a31d 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -2,6 +2,7 @@ import achievement.AchievementSetting; import game.*; +import object.BidListCellRenderer; import object.Player; import util.*; @@ -70,7 +71,7 @@ public abstract class GameScreen> extends TransparentPane public abstract void setPerfectBidBooleans(); public abstract void updateAchievementVariables(); - public void startNewGame() + public void startNewGame(BidListCellRenderer bidRenderer) { Debug.appendBanner("New Game", logging); cancelNewRound(); @@ -80,6 +81,7 @@ public void startNewGame() ScreenCache.get(MainScreen.class).dismissCurrentReplay(); initVariablesForNewGame(); + bidRenderer.updateColours(allPlayers()); initVariables(); startRound(); @@ -326,6 +328,8 @@ private void setPlayerNamesFromHandPanel() opponentOne.setName(handPanel.getOpponentOneName()); opponentTwo.setName(handPanel.getOpponentTwoName()); opponentThree.setName(handPanel.getOpponentThreeName()); + + } private void initNumberOfCards() @@ -572,7 +576,7 @@ protected void saveGame() /** * Continue Game */ - public void continueGame() + public void continueGame(BidListCellRenderer bidRenderer) { try { @@ -595,6 +599,7 @@ public void continueGame() setPlayerNames(); setPlayerHandsAndRevealedCards(); + bidRenderer.updateColours(allPlayers()); //blind, handicap etc playBlind = savedGame.getBoolean(SAVED_GAME_BOOLEAN_PLAY_BLIND, false); @@ -889,6 +894,10 @@ public void processCpuTurn(int opponentNumber) cpuTurn.schedule(new DelayedOpponentTurn(currentPlayer), gameSpeed); } + private Collection allPlayers() { + return Stream.of(player, opponentOne, opponentTwo, opponentThree).toList(); + } + private Player getPlayer(B bid) { var name = bid.getPlayerName(); var found = Stream.of(player, opponentOne, opponentTwo, opponentThree).filter((p) -> p.getName().equals(name)).findFirst(); diff --git a/client/src/main/java/screen/MainScreen.java b/client/src/main/java/screen/MainScreen.java index cdc7e11..fb98d6c 100644 --- a/client/src/main/java/screen/MainScreen.java +++ b/client/src/main/java/screen/MainScreen.java @@ -169,7 +169,7 @@ public MainScreen() splitPane.setLeftComponent(leftPanel); leftPanel.setLayout(new BorderLayout(0, 0)); leftPanel.add(commandBar, BorderLayout.SOUTH); - history.setCellRenderer(new BidListCellRenderer()); + history.setCellRenderer(bidRenderer); addKeyListener(this); commandBar.setCheatListener(this); @@ -180,6 +180,7 @@ public MainScreen() } //Menu + private final BidListCellRenderer bidRenderer = new BidListCellRenderer(); private final JMenuBar menuBar = new JMenuBar(); private final JMenu mnFile = new JMenu("File"); private final JMenuItem mntmNewGame = new JMenuItem("New Game"); @@ -265,7 +266,7 @@ public void startNewGame() btnReplay.setEnabled(false); btnNextRound.setVisible(false); lblResult.setVisible(false); - gamePanel.startNewGame(); + gamePanel.startNewGame(bidRenderer); } } catch (Throwable e) @@ -504,7 +505,7 @@ public void continueGame() btnReplay.setVisible(true); scrollPane.setVisible(true); mntmContinueGame.setEnabled(false); - gamePanel.continueGame(); + gamePanel.continueGame(bidRenderer); } catch (Throwable t) { diff --git a/client/src/main/java/screen/ReplayDialog.java b/client/src/main/java/screen/ReplayDialog.java index 7892031..117413c 100644 --- a/client/src/main/java/screen/ReplayDialog.java +++ b/client/src/main/java/screen/ReplayDialog.java @@ -11,6 +11,7 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.prefs.Preferences; @@ -46,6 +47,7 @@ import static game.CardsUtilKt.isCardRelevant; import static game.RegistryUtilKt.populateActions; import static game.RenderingUtilKt.getVectropyResult; +import static utils.ColourUtilKt.getColourForPlayerNumber; import static utils.CoreGlobals.logger; public class ReplayDialog extends JFrame @@ -263,7 +265,7 @@ public ReplayDialog() starFilter.setIcon(new ImageIcon(ReplayDialog.class.getResource("/buttons/starFilter.png"))); starFilter.setBounds(828, 275, 40, 40); getContentPane().add(starFilter); - history.setCellRenderer(new BidListCellRenderer()); + history.setCellRenderer(bidRenderer); initialiseListeners(); } @@ -272,7 +274,8 @@ public ReplayDialog() Debug.stackTrace(t); } } - + + private final BidListCellRenderer bidRenderer = new BidListCellRenderer(); private final JSeparator separator = new JSeparator(); private final Panel panelOpponentCards = new Panel(); private final JLabel opponentCard5 = new JLabel(); @@ -544,6 +547,13 @@ private void setPlayerNames() lblOpponentOne.setText(opponentOneName + opponentOneStar); lblOpponentTwo.setText(opponentTwoName + opponentTwoStar); lblOpponentThree.setText(opponentThreeName + opponentThreeStar); + + bidRenderer.updateColours(new HashMap<>() {{ + put(playerName, getColourForPlayerNumber(0)); + put(opponentOneName, getColourForPlayerNumber(1)); + put(opponentTwoName, getColourForPlayerNumber(2)); + put(opponentThreeName, getColourForPlayerNumber(3)); + }}); setLabelVisibility(lblPlayer, playerName, playerEnabled, REPLAY_STRING_PLAYER_COLOUR, "red"); setLabelVisibility(lblOpponentOne, opponentOneName, opponentOneEnabled, REPLAY_STRING_OPPONENT_ONE_COLOUR, "blue"); diff --git a/server/src/main/java/server/MessageHandlerRunnable.java b/server/src/main/java/server/MessageHandlerRunnable.java index 7b0e4a7..e4012d3 100644 --- a/server/src/main/java/server/MessageHandlerRunnable.java +++ b/server/src/main/java/server/MessageHandlerRunnable.java @@ -207,9 +207,10 @@ else if (name.equals(ROOT_TAG_BID)) String gameId = root.getAttribute("GameId"); int roundNumber = XmlUtil.getAttributeInt(root, "RoundNumber"); String bidStr = root.getAttribute("Bid"); + String bidder = root.getAttribute("Username"); int previousBidder = XmlUtil.getAttributeInt(root, "PreviousBidder", -1); - return XmlBuilderServer.getBidAck(room, gameId, roundNumber, bidStr, previousBidder); + return XmlBuilderServer.getBidAck(room, gameId, roundNumber, bidder, bidStr, previousBidder); } else if (name.equals(ROOT_TAG_LEADERBOARD_REQUEST)) { diff --git a/server/src/main/java/util/XmlBuilderServer.java b/server/src/main/java/util/XmlBuilderServer.java index c019cc8..2a7468d 100644 --- a/server/src/main/java/util/XmlBuilderServer.java +++ b/server/src/main/java/util/XmlBuilderServer.java @@ -1,5 +1,6 @@ package util; +import com.fasterxml.jackson.core.JsonProcessingException; import game.BidAction; import game.ChallengeAction; import game.IllegalAction; @@ -221,23 +222,21 @@ public static Document getNewGameResponse(Room room, String previousGameId) return response; } - public static Document getBidAck(Room room, String gameId, int roundNumber, String bidStr, int previousBidder) - { + public static Document getBidAck(Room room, String gameId, int roundNumber, String bidderName, String bidStr, int previousBidder) throws JsonProcessingException { var bid = CoreGlobals.jsonMapper.readValue(bidStr, PlayerAction.class); - int playerNumber = bid.getPlayer().getPlayerNumber(); - boolean added = room.addBidForPlayer(gameId, playerNumber, roundNumber, bid); + boolean added = room.addBidForPlayer(gameId, bidderName, roundNumber, bid); var previousBid = (BidAction)room.getLastBidForPlayer(previousBidder, roundNumber); if (bid instanceof ChallengeAction && added) { - room.handleChallenge(gameId, roundNumber, playerNumber, previousBidder, previousBid); + room.handleChallenge(gameId, roundNumber, bidderName, previousBidder, previousBid); } else if (bid instanceof IllegalAction && added) { - room.handleIllegal(gameId, roundNumber, playerNumber, previousBidder, previousBid); + room.handleIllegal(gameId, roundNumber, bidderName, previousBidder, previousBid); } return ACKNOWLEDGEMENT; diff --git a/server/src/main/kotlin/room/Room.kt b/server/src/main/kotlin/room/Room.kt index 641e10e..ec04dda 100644 --- a/server/src/main/kotlin/room/Room.kt +++ b/server/src/main/kotlin/room/Room.kt @@ -62,14 +62,16 @@ data class Room( } fun getColourForPlayer(playerName: String): String { - val playerNumber = - hmPlayerByPlayerNumber.filter { it.value == playerName }.keys.firstOrNull() + val playerNumber = getPlayerNumber(playerName) return if (playerNumber != null) { getColourForPlayerNumber(playerNumber) } else "gray" } + private fun getPlayerNumber(playerName: String): Int? = + hmPlayerByPlayerNumber.filter { it.value == playerName }.keys.firstOrNull() + fun attemptToSitDown(username: String, playerNumber: Int): Int? { synchronized(this) { val existingUsername: String? = hmPlayerByPlayerNumber[playerNumber] @@ -251,7 +253,7 @@ data class Room( fun handleChallenge( gameId: String, roundNumber: Int, - playerNumber: Int, + challenger: String, challengedNumber: Int, bid: BidAction<*>, ) { @@ -263,14 +265,14 @@ data class Room( setUpNextRound(challengedNumber) } else { // challenger loses - setUpNextRound(playerNumber) + setUpNextRound(getPlayerNumber(challenger)!!) } } fun handleIllegal( gameId: String, roundNumber: Int, - playerNumber: Int, + illegallerName: String, bidderNumber: Int, bid: BidAction<*>, ) { @@ -280,7 +282,7 @@ data class Room( if (bid.isPerfect(hands, settings)) { setUpNextRound(bidderNumber) } else { - setUpNextRound(playerNumber) + setUpNextRound(getPlayerNumber(illegallerName)!!) } } @@ -436,12 +438,15 @@ data class Room( fun addBidForPlayer( gameId: String, - playerNumber: Int, + playerName: String, roundNumber: Int, newBid: PlayerAction, ): Boolean { val game = getGameForId(gameId) + val playerNumber = + getPlayerNumber(playerName) ?: throw Exception("Player $playerName not found") + val history: BidHistory = game.getBidHistoryForRound(roundNumber) val added: Boolean = history.addBidForPlayer(playerNumber, newBid) From 36fac2c894c145864458841d5a989c9fc1602884 Mon Sep 17 00:00:00 2001 From: alyssa Date: Sat, 26 Jul 2025 17:35:07 +0100 Subject: [PATCH 07/12] fix unintended vectropy strategy changes --- client/src/main/kotlin/game/StrategyUtil.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/client/src/main/kotlin/game/StrategyUtil.kt b/client/src/main/kotlin/game/StrategyUtil.kt index cc896ae..f670bfa 100644 --- a/client/src/main/kotlin/game/StrategyUtil.kt +++ b/client/src/main/kotlin/game/StrategyUtil.kt @@ -86,10 +86,15 @@ fun getBasicVectropyOpening( val map = suits.associateWith { suit -> val myCount = countSuit(suit, hand, settings.jokerValue) - maxOf(0, myCount + Random.nextInt(3)) + maxOf(0, myCount + Random.nextInt(3) - 1) } - return VectropyBidAction(opponentName, false, map) + val bid = VectropyBidAction(opponentName, false, map) + if (bid.getTotal() > 0) { + return bid + } + + return bid.incrementSuit(suits.random()) } fun getEvVectropyOpening( @@ -123,5 +128,10 @@ fun getEvVectropyOpening( return bid } + // Just bid 1 of something, leaning towards choosing our best suit + if (Random.nextInt(10) < 6) { + return bid.incrementSuit(getSuitWithMostPositiveValue(hmEvBySuit)) + } + return bid.incrementSuit(suits.random()) } From e9cdb79a8bc5e232708eb14ebb717c98887d9283 Mon Sep 17 00:00:00 2001 From: alyssa Date: Tue, 12 Aug 2025 08:37:53 +0100 Subject: [PATCH 08/12] tweaks / tests --- .../src/main/java/online/screen/GameRoom.java | 3 +- .../src/test/kotlin/game/RegistryUtilTest.kt | 69 +++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 client/src/test/kotlin/game/RegistryUtilTest.kt diff --git a/client/src/main/java/online/screen/GameRoom.java b/client/src/main/java/online/screen/GameRoom.java index ac753b5..dc8455e 100644 --- a/client/src/main/java/online/screen/GameRoom.java +++ b/client/src/main/java/online/screen/GameRoom.java @@ -779,7 +779,6 @@ public void clearHands() public void handleBid(int playerNumber, PlayerAction action) { int playerNumberAdjusted = adjustForMe(playerNumber); - Player player = hmPlayerByAdjustedPlayerNumber.get(playerNumberAdjusted); addBidToBidBox(action); handPanel.selectPlayerInAwtThread(playerNumberAdjusted, false); @@ -811,6 +810,8 @@ public void handleBid(int playerNumber, PlayerAction action) if (card != null) { handPanel.revealCard(card); + + Player player = hmPlayerByAdjustedPlayerNumber.get(playerNumberAdjusted); player.addRevealedCard(card); ArrayList revealedCards = getOpponentsRevealedCards(); diff --git a/client/src/test/kotlin/game/RegistryUtilTest.kt b/client/src/test/kotlin/game/RegistryUtilTest.kt new file mode 100644 index 0000000..819b6c6 --- /dev/null +++ b/client/src/test/kotlin/game/RegistryUtilTest.kt @@ -0,0 +1,69 @@ +package game + +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.shouldBe +import java.util.prefs.Preferences +import javax.swing.DefaultListModel +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import util.AbstractClientTest + +class RegistryUtilTest : AbstractClientTest() { + private val testNode = Preferences.userRoot().node("RegistryUtilTest") + + @BeforeEach + fun setup() { + testNode.clear() + } + + @Test + fun `Should clear existing entries when populating`() { + val listmodel = DefaultListModel() + listmodel.add(0, EntropyBidAction("Alyssa", false, 1, Suit.Spades)) + + populateActions(testNode, listmodel, null) + + listmodel.elements().toList().shouldBeEmpty() + } + + @Test + fun `Write and read actions - no round number`() { + val listmodel = DefaultListModel() + listmodel.add(0, EntropyBidAction("Alyssa", false, 2, Suit.Clubs)) + listmodel.add(0, EntropyBidAction("David", false, 2, Suit.Spades)) + listmodel.add(0, ChallengeAction("Alyssa", false)) + + writeActions(testNode, listmodel, null) + + val newModel = DefaultListModel() + populateActions(testNode, newModel, null) + + newModel.shouldMatch(listmodel) + } + + @Test + fun `Write and read actions - with round number`() { + val roundOne = DefaultListModel() + roundOne.add(0, EntropyBidAction("David", false, 5, Suit.Spades)) + roundOne.add(0, IllegalAction("Alyssa", false)) + + val roundTwo = DefaultListModel() + roundTwo.add(0, EntropyBidAction("David", false, 2, Suit.Clubs)) + roundTwo.add(0, EntropyBidAction("Alyssa", false, 3, Suit.Diamonds)) + roundTwo.add(0, ChallengeAction("David", false)) + + writeActions(testNode, roundOne, 1) + writeActions(testNode, roundTwo, 2) + + val newModel = DefaultListModel() + populateActions(testNode, newModel, 1) + newModel.shouldMatch(roundOne) + + populateActions(testNode, newModel, 2) + newModel.shouldMatch(roundTwo) + } + + private fun DefaultListModel.shouldMatch(other: DefaultListModel) { + elements().toList() shouldBe other.elements().toList() + } +} From 14aee9212139ef6c8e29c3534d1a25a4c2dae310 Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 13 Aug 2025 08:53:38 +0100 Subject: [PATCH 09/12] start testing new StrategyUtil bits --- client/src/main/kotlin/game/StrategyUtil.kt | 10 ++++--- client/src/main/kotlin/strategy/IRandom.kt | 11 ++++++++ client/src/test/kotlin/TestUtils.kt | 26 +++++++++++++++++++ .../src/test/kotlin/game/StrategyUtilTest.kt | 16 ++++++++++++ 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 client/src/main/kotlin/strategy/IRandom.kt diff --git a/client/src/main/kotlin/game/StrategyUtil.kt b/client/src/main/kotlin/game/StrategyUtil.kt index f670bfa..6340572 100644 --- a/client/src/main/kotlin/game/StrategyUtil.kt +++ b/client/src/main/kotlin/game/StrategyUtil.kt @@ -3,6 +3,8 @@ package game import kotlin.math.ceil import kotlin.math.floor import kotlin.random.Random +import strategy.DefaultRandom +import strategy.IRandom import util.StrategyParams fun getEvMap( @@ -69,24 +71,26 @@ fun shouldAutoChallengeForEvDiffOfIndividualSuit(evDifferenceMap: Map) = evDifferenceMap.count { it.value < 0 } > 1 +@JvmOverloads fun getBasicVectropyOpening( opponentName: String, hand: List, strategyParams: StrategyParams, + random: IRandom = DefaultRandom(), ): VectropyBidAction { val settings = strategyParams.settings val suits = Suit.filter(settings) if (strategyParams.cardsInPlay <= 4) { val empty = suits.associateWith { 0 } - val suit = suits.random() - return VectropyBidAction(opponentName, false, empty).incrementSuit(suit) + val suitChoice = random.nextInt(suits.size) + return VectropyBidAction(opponentName, false, empty).incrementSuit(suits[suitChoice]) } val map = suits.associateWith { suit -> val myCount = countSuit(suit, hand, settings.jokerValue) - maxOf(0, myCount + Random.nextInt(3) - 1) + maxOf(0, myCount + random.nextInt(3) - 1) } val bid = VectropyBidAction(opponentName, false, map) diff --git a/client/src/main/kotlin/strategy/IRandom.kt b/client/src/main/kotlin/strategy/IRandom.kt new file mode 100644 index 0000000..60086ab --- /dev/null +++ b/client/src/main/kotlin/strategy/IRandom.kt @@ -0,0 +1,11 @@ +package strategy + +import kotlin.random.Random + +interface IRandom { + fun nextInt(until: Int): Int +} + +class DefaultRandom : IRandom { + override fun nextInt(until: Int) = Random.nextInt(until) +} diff --git a/client/src/test/kotlin/TestUtils.kt b/client/src/test/kotlin/TestUtils.kt index 9edfba4..4b58842 100644 --- a/client/src/test/kotlin/TestUtils.kt +++ b/client/src/test/kotlin/TestUtils.kt @@ -2,6 +2,7 @@ import com.github.alyssaburlton.swingtest.findAll import com.github.alyssaburlton.swingtest.findWindow import com.github.alyssaburlton.swingtest.flushEdt import com.github.alyssaburlton.swingtest.getChild +import game.BidAction import game.GameSettings import http.ApiResponse import http.ClientErrorCode @@ -17,9 +18,11 @@ import javax.swing.SwingUtilities import kong.unirest.HttpMethod import kong.unirest.HttpStatus import online.screen.OnlineChatPanel +import strategy.IRandom import testCore.makeGameSettings import util.CpuStrategies import util.SimulationParams +import util.StrategyParams fun getInfoDialog() = getOptionPaneDialog("Information") @@ -89,3 +92,26 @@ fun makeSimulationParams( randomiseOrder, forceStart, ) + +fun makeStrategyParams( + settings: GameSettings = makeGameSettings(), + cardsInPlay: Int = 4, + opponentCardsOnPlay: List = emptyList(), + lastBid: BidAction<*>? = null, + logging: Boolean = true, +) = StrategyParams(settings, cardsInPlay, opponentCardsOnPlay, lastBid, logging) + +class TestRandom(vararg sequence: Int) : IRandom { + private val numbers = sequence.toMutableList() + + override fun nextInt(until: Int): Int { + val result = numbers.removeFirst() + if (result >= until) { + throw IllegalStateException( + "Next choice is greater than passed limit: $result > $until" + ) + } + + return result + } +} diff --git a/client/src/test/kotlin/game/StrategyUtilTest.kt b/client/src/test/kotlin/game/StrategyUtilTest.kt index db60c54..ee7904b 100644 --- a/client/src/test/kotlin/game/StrategyUtilTest.kt +++ b/client/src/test/kotlin/game/StrategyUtilTest.kt @@ -1,9 +1,11 @@ package game +import TestRandom import io.kotest.matchers.doubles.shouldBeBetween import io.kotest.matchers.maps.shouldContainAll import io.kotest.matchers.maps.shouldNotContainKeys import io.kotest.matchers.shouldBe +import makeStrategyParams import org.junit.jupiter.api.Test import testCore.makeGameSettings import util.AbstractClientTest @@ -94,4 +96,18 @@ class StrategyUtilTest : AbstractClientTest() { private fun Map.assertEv(suit: Suit, expected: Double) { getValue(suit).shouldBeBetween(expected, expected, 0.00001) } + + @Test + fun `Basic Vectropy - Opening should bid 1 of random suit if 4 or less cards in play`() { + val strategyParams = makeStrategyParams(cardsInPlay = 4) + val random = TestRandom(0, 1) + + val openingOne = getBasicVectropyOpening("Clive", emptyList(), strategyParams, random) + val openingTwo = getBasicVectropyOpening("Clive", emptyList(), strategyParams, random) + + val emptyBid = Suit.filter(strategyParams.settings).associateWith { 0 } + + openingOne shouldBe VectropyBidAction("Clive", false, emptyBid + (Suit.Clubs to 1)) + openingTwo shouldBe VectropyBidAction("Clive", false, emptyBid + (Suit.Diamonds to 1)) + } } From 5348718599677c38fb11e6447c7d1b73c48a0db2 Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 20 Aug 2025 08:11:11 +0100 Subject: [PATCH 10/12] finish tests for vectropy openings --- client/src/main/kotlin/game/StrategyUtil.kt | 12 +-- .../src/test/kotlin/game/StrategyUtilTest.kt | 77 +++++++++++++++++++ 2 files changed, 84 insertions(+), 5 deletions(-) diff --git a/client/src/main/kotlin/game/StrategyUtil.kt b/client/src/main/kotlin/game/StrategyUtil.kt index 6340572..ad2451e 100644 --- a/client/src/main/kotlin/game/StrategyUtil.kt +++ b/client/src/main/kotlin/game/StrategyUtil.kt @@ -2,7 +2,6 @@ package game import kotlin.math.ceil import kotlin.math.floor -import kotlin.random.Random import strategy.DefaultRandom import strategy.IRandom import util.StrategyParams @@ -90,6 +89,7 @@ fun getBasicVectropyOpening( val map = suits.associateWith { suit -> val myCount = countSuit(suit, hand, settings.jokerValue) + println(myCount) maxOf(0, myCount + random.nextInt(3) - 1) } @@ -98,13 +98,15 @@ fun getBasicVectropyOpening( return bid } - return bid.incrementSuit(suits.random()) + return bid.incrementSuit(suits[random.nextInt(suits.size)]) } +@JvmOverloads fun getEvVectropyOpening( opponentName: String, hand: List, strategyParams: StrategyParams, + random: IRandom = DefaultRandom(), ): VectropyBidAction { val settings = strategyParams.settings val hmEvBySuit = getEvMap(hand, settings, strategyParams.cardsInPlay) @@ -114,7 +116,7 @@ fun getEvVectropyOpening( suits.associateWith { suit -> val evFloor = floor(hmEvBySuit.getValue(suit)).toInt() - val adjustmentSwitch = Random.nextInt(20) + val adjustmentSwitch = random.nextInt(20) val adjusted = if (adjustmentSwitch < 11) { evFloor - 1 @@ -133,9 +135,9 @@ fun getEvVectropyOpening( } // Just bid 1 of something, leaning towards choosing our best suit - if (Random.nextInt(10) < 6) { + if (random.nextInt(10) < 6) { return bid.incrementSuit(getSuitWithMostPositiveValue(hmEvBySuit)) } - return bid.incrementSuit(suits.random()) + return bid.incrementSuit(suits[random.nextInt(suits.size)]) } diff --git a/client/src/test/kotlin/game/StrategyUtilTest.kt b/client/src/test/kotlin/game/StrategyUtilTest.kt index ee7904b..f90023a 100644 --- a/client/src/test/kotlin/game/StrategyUtilTest.kt +++ b/client/src/test/kotlin/game/StrategyUtilTest.kt @@ -5,6 +5,7 @@ import io.kotest.matchers.doubles.shouldBeBetween import io.kotest.matchers.maps.shouldContainAll import io.kotest.matchers.maps.shouldNotContainKeys import io.kotest.matchers.shouldBe +import kotlin.collections.plus import makeStrategyParams import org.junit.jupiter.api.Test import testCore.makeGameSettings @@ -110,4 +111,80 @@ class StrategyUtilTest : AbstractClientTest() { openingOne shouldBe VectropyBidAction("Clive", false, emptyBid + (Suit.Clubs to 1)) openingTwo shouldBe VectropyBidAction("Clive", false, emptyBid + (Suit.Diamonds to 1)) } + + @Test + fun `Basic Vectropy - Opening should be based on hand count plus some randomness`() { + val strategyParams = makeStrategyParams(cardsInPlay = 5) + val random = TestRandom(1, 2, 0, 2) + + val hand = listOf("2c", "As", "3d", "Ad") + val opening = getBasicVectropyOpening("Clive", hand, strategyParams, random) + + val expected = + mapOf( + Suit.Clubs to 3, // 3 + 1 - 1 + Suit.Diamonds to 5, // 4 + 2 - 1 + Suit.Hearts to 1, // 2 + 0 - 1 + Suit.Spades to 4, // 3 + 2 - 1 + ) + + opening shouldBe VectropyBidAction("Clive", false, expected) + } + + @Test + fun `Basic Vectropy - Opening should bid 1 of a random suit if bid would otherwise be empty`() { + val strategyParams = makeStrategyParams(cardsInPlay = 5) + val random = TestRandom(0, 0, 0, 0, 1) + + val hand = listOf("4d") + val opening = getBasicVectropyOpening("Clive", hand, strategyParams, random) + + val emptyBid = Suit.filter(strategyParams.settings).associateWith { 0 } + opening shouldBe VectropyBidAction("Clive", false, emptyBid + (Suit.Diamonds to 1)) + } + + @Test + fun `EV Vectropy - Opening should be based on expected value plus some randomness`() { + val random = TestRandom(10, 5, 17, 19) + + val cards = listOf("As", "Jo0", "3c") + val settings = + makeGameSettings( + jokerQuantity = 1, + jokerValue = 2, + includeMoons = false, + includeStars = false, + ) + + val strategyParams = makeStrategyParams(settings = settings, cardsInPlay = 11) + + val opening = getEvVectropyOpening("Robert", cards, strategyParams, random) + val expected = + mapOf( + Suit.Clubs to 5, // floor(6.4) - 1 + Suit.Diamonds to 4, // floor(5.56) - 1 + Suit.Hearts to 3, // floor(5.56) - 2 + Suit.Spades to 3, // floor(6.4) - 3 + ) + + opening shouldBe VectropyBidAction("Robert", false, expected) + } + + @Test + fun `EV Vectropy - Opening should bid 1 of something if default approach would result in empty bid`() { + val strategyParams = makeStrategyParams(cardsInPlay = 6) + val openings = + (0..9).map { finalChoice -> + val random = TestRandom(19, 19, 19, 19, finalChoice, 0) + + getEvVectropyOpening("Robert", listOf("3h"), strategyParams, random) + } + + val emptyBid = Suit.filter(strategyParams.settings).associateWith { 0 } + val randomSuit = VectropyBidAction("Robert", false, emptyBid + (Suit.Clubs to 1)) + val strongestSuit = VectropyBidAction("Robert", false, emptyBid + (Suit.Hearts to 1)) + + openings.count() { it == randomSuit } shouldBe 4 + openings.count { it == strongestSuit } shouldBe 6 + } } From e59391ff3064764f4106027538c4025966f1869c Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 20 Aug 2025 08:30:11 +0100 Subject: [PATCH 11/12] finish action tests --- core/src/test/kotlin/game/BidActionTest.kt | 31 +++++++++++++++++++ .../test/kotlin/game/EntropyBidActionTest.kt | 13 ++++++++ .../test/kotlin/game/VectropyBidActionTest.kt | 11 +++++++ 3 files changed, 55 insertions(+) create mode 100644 core/src/test/kotlin/game/BidActionTest.kt diff --git a/core/src/test/kotlin/game/BidActionTest.kt b/core/src/test/kotlin/game/BidActionTest.kt new file mode 100644 index 0000000..ff3ed3a --- /dev/null +++ b/core/src/test/kotlin/game/BidActionTest.kt @@ -0,0 +1,31 @@ +package game + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import testCore.AbstractTest + +class BidActionTest : AbstractTest() { + @Test + fun `Should include card to reveal in description`() { + val testBid = TestBidAction("Alyssa", false) + + testBid.toString() shouldBe "5 things" + + testBid.cardToReveal = "Ac" + testBid.toString() shouldBe "5 things (Shows: Ac)" + } +} + +private class TestBidAction(override val playerName: String, override val blind: Boolean) : + BidAction() { + override fun higherThan(other: TestBidAction) = false + + override fun overAchievementThreshold() = false + + override fun isPerfect(cards: List, settings: GameSettings) = false + + override fun isOverbid(cards: List, settings: GameSettings) = false + + override fun plainString() = "5 things" +} diff --git a/core/src/test/kotlin/game/EntropyBidActionTest.kt b/core/src/test/kotlin/game/EntropyBidActionTest.kt index cfa81d3..fd27a63 100644 --- a/core/src/test/kotlin/game/EntropyBidActionTest.kt +++ b/core/src/test/kotlin/game/EntropyBidActionTest.kt @@ -1,11 +1,13 @@ package game import io.kotest.matchers.shouldBe +import java.awt.Color import org.junit.jupiter.api.Test import testCore.AbstractTest import testCore.makeEntropyBidAction import testCore.makeGameSettings import utils.CoreGlobals +import utils.toHexCode class EntropyBidActionTest : AbstractTest() { @Test @@ -14,6 +16,8 @@ class EntropyBidActionTest : AbstractTest() { val json = CoreGlobals.jsonMapper.writeValueAsString(bid) val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) deserialized shouldBe bid + + EntropyBidAction.fromJson(json) shouldBe bid } @Test @@ -61,4 +65,13 @@ class EntropyBidActionTest : AbstractTest() { twoDiamonds.higherThan(makeEntropyBidAction(2, Suit.Hearts)) shouldBe false twoDiamonds.higherThan(makeEntropyBidAction(3, Suit.Clubs)) shouldBe false } + + @Test + fun `Should have sensible description`() { + makeEntropyBidAction(1, Suit.Diamonds).plainString() shouldBe "1 diamond" + makeEntropyBidAction(3, Suit.Diamonds).plainString() shouldBe "3 diamonds" + + makeEntropyBidAction(1, Suit.Hearts).htmlString() shouldBe + "1♥" + } } diff --git a/core/src/test/kotlin/game/VectropyBidActionTest.kt b/core/src/test/kotlin/game/VectropyBidActionTest.kt index 2dc4861..6a81991 100644 --- a/core/src/test/kotlin/game/VectropyBidActionTest.kt +++ b/core/src/test/kotlin/game/VectropyBidActionTest.kt @@ -14,6 +14,8 @@ class VectropyBidActionTest : AbstractTest() { val json = CoreGlobals.jsonMapper.writeValueAsString(action) val deserialized = CoreGlobals.jsonMapper.readValue(json, PlayerAction::class.java) deserialized shouldBe action + + VectropyBidAction.fromJson(json) shouldBe action } @Test @@ -108,4 +110,13 @@ class VectropyBidActionTest : AbstractTest() { VectropyBidAction("", false, allSuits).plainString() shouldBe "(3, 0, 2, 7, 1, 9)" VectropyBidAction("", false, allSuits).htmlString() shouldBe "(3, 0, 2, 7, 1, 9)" } + + @Test + fun `Should allow incrementing a suit`() { + val start = mapOf(Suit.Clubs to 3, Suit.Diamonds to 0, Suit.Hearts to 2, Suit.Spades to 1) + val bid = VectropyBidAction("Alyssa", true, start) + + val uppedDiamonds = bid.incrementSuit(Suit.Diamonds) + uppedDiamonds shouldBe VectropyBidAction("Alyssa", true, start + (Suit.Diamonds to 1)) + } } From 14385c62ff1220c74b9504289e3077f298a3d728 Mon Sep 17 00:00:00 2001 From: alyssa Date: Wed, 20 Aug 2025 08:42:00 +0100 Subject: [PATCH 12/12] begin fixing broken stuff --- client/src/main/java/screen/EntropyScreen.java | 2 +- client/src/main/java/screen/GameScreen.java | 2 -- client/src/main/java/screen/VectropyScreen.java | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/client/src/main/java/screen/EntropyScreen.java b/client/src/main/java/screen/EntropyScreen.java index 77e5090..58cab24 100644 --- a/client/src/main/java/screen/EntropyScreen.java +++ b/client/src/main/java/screen/EntropyScreen.java @@ -22,7 +22,7 @@ public class EntropyScreen extends GameScreen public EntropyScreen() { setFocusable(true); - bidPanel = new EntropyBidPanel(player.getName(), handPanel); + bidPanel = new EntropyBidPanel(prefs.get(PREFERENCES_STRING_PLAYER_NAME, "Player"), handPanel); bidPanel.showBidPanel(false); setLayout(new BorderLayout(0, 0)); diff --git a/client/src/main/java/screen/GameScreen.java b/client/src/main/java/screen/GameScreen.java index 510a31d..e8be0f7 100644 --- a/client/src/main/java/screen/GameScreen.java +++ b/client/src/main/java/screen/GameScreen.java @@ -328,8 +328,6 @@ private void setPlayerNamesFromHandPanel() opponentOne.setName(handPanel.getOpponentOneName()); opponentTwo.setName(handPanel.getOpponentTwoName()); opponentThree.setName(handPanel.getOpponentThreeName()); - - } private void initNumberOfCards() diff --git a/client/src/main/java/screen/VectropyScreen.java b/client/src/main/java/screen/VectropyScreen.java index 30ddcc9..9d90c67 100644 --- a/client/src/main/java/screen/VectropyScreen.java +++ b/client/src/main/java/screen/VectropyScreen.java @@ -26,7 +26,7 @@ public VectropyScreen() setLayout(new BorderLayout(0, 0)); add(handPanel, BorderLayout.CENTER); handPanel.setOpaque(false); - bidPanel = new VectropyBidPanel(player.getName(), handPanel); + bidPanel = new VectropyBidPanel(prefs.get(PREFERENCES_STRING_PLAYER_NAME, "Player"), handPanel); add(bidPanel, BorderLayout.SOUTH); bidPanel.showBidPanel(false);