From 4bd5e911f41fcf8378f616db78483a43da9482ce Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 13:59:53 -0400 Subject: [PATCH 1/7] change Player start money to 800 --- src/main/java/xyz/cliserkad/consortium/Player.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/xyz/cliserkad/consortium/Player.java b/src/main/java/xyz/cliserkad/consortium/Player.java index b006d98..248a8a4 100644 --- a/src/main/java/xyz/cliserkad/consortium/Player.java +++ b/src/main/java/xyz/cliserkad/consortium/Player.java @@ -23,7 +23,7 @@ public class Player implements Serializable { public Player(GameClient controller) { position = BoardPosition.GO; - money = 1800; + money = 800; playerIndex = numPlayers++; this.controller = controller; isBankrupt = false; From 2787735032469ac381d6366c812177a68a466a8a Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 14:00:33 -0400 Subject: [PATCH 2/7] separate and adjust taxes --- .../cliserkad/consortium/BoardPosition.java | 4 ++-- .../cliserkad/consortium/IncomeTaxLogic.java | 20 ++++++++++++++++ .../cliserkad/consortium/LuxuryTaxLogic.java | 19 +++++++++++++++ .../xyz/cliserkad/consortium/TaxLogic.java | 23 ------------------- 4 files changed, 41 insertions(+), 25 deletions(-) create mode 100644 src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java create mode 100644 src/main/java/xyz/cliserkad/consortium/LuxuryTaxLogic.java delete mode 100644 src/main/java/xyz/cliserkad/consortium/TaxLogic.java diff --git a/src/main/java/xyz/cliserkad/consortium/BoardPosition.java b/src/main/java/xyz/cliserkad/consortium/BoardPosition.java index 6e331f0..3d3e9b1 100644 --- a/src/main/java/xyz/cliserkad/consortium/BoardPosition.java +++ b/src/main/java/xyz/cliserkad/consortium/BoardPosition.java @@ -18,7 +18,7 @@ public enum BoardPosition implements Serializable { MEDITERRANEAN_AVENUE(new Color(0x8B4513), new StandardLogic(60, 50, new int[] { 2, 10, 30, 90, 160, 250 })), COMMUNITY_CHEST_1(Color.WHITE, CommunityChestLogic.INSTANCE), BALTIC_AVENUE(MEDITERRANEAN_AVENUE.color, new StandardLogic(60, 50, new int[] { 4, 20, 60, 180, 320, 450 })), - INCOME_TAX(Color.WHITE, TaxLogic.INCOME_TAX), + INCOME_TAX(Color.WHITE, IncomeTaxLogic.INSTANCE), READING_RAILROAD(Color.BLACK, new RailRoadLogic()), ORIENTAL_AVENUE(new Color(0xB2FFE8), new StandardLogic(100, 50, new int[] { 6, 30, 90, 270, 400, 550 })), CHANCE_1(Color.WHITE, ChanceLogic.INSTANCE), @@ -52,7 +52,7 @@ public enum BoardPosition implements Serializable { SHORT_LINE(READING_RAILROAD.color, new RailRoadLogic()), CHANCE_3(Color.WHITE, ChanceLogic.INSTANCE), PARK_PLACE(new Color(0x1D1DE8), new StandardLogic(350, 200, new int[] { 35, 175, 500, 1100, 1300, 1500 })), - LUXURY_TAX(Color.WHITE, TaxLogic.LUXURY_TAX), + LUXURY_TAX(Color.WHITE, LuxuryTaxLogic.INSTANCE), BOARDWALK(PARK_PLACE.color, new StandardLogic(400, 200, new int[] { 50, 200, 600, 1400, 1700, 2000 })); @Serial diff --git a/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java b/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java new file mode 100644 index 0000000..dec7b31 --- /dev/null +++ b/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java @@ -0,0 +1,20 @@ +package xyz.cliserkad.consortium; + +public class IncomeTaxLogic implements PositionLogic { + + public static final IncomeTaxLogic INSTANCE = new IncomeTaxLogic(); + + public static final float INCOME_TAX_FACTOR = 0.25f; + + private IncomeTaxLogic() { + + } + + @Override + public String onLand(Player mover, GameState gameState) { + int deducted = (int) (mover.getMoney() * INCOME_TAX_FACTOR); + mover.addMoney(-deducted); + return mover.getIcon() + " paid $" + -deducted + " in Income Tax"; + } + +} diff --git a/src/main/java/xyz/cliserkad/consortium/LuxuryTaxLogic.java b/src/main/java/xyz/cliserkad/consortium/LuxuryTaxLogic.java new file mode 100644 index 0000000..84a90fa --- /dev/null +++ b/src/main/java/xyz/cliserkad/consortium/LuxuryTaxLogic.java @@ -0,0 +1,19 @@ +package xyz.cliserkad.consortium; + +public class LuxuryTaxLogic implements PositionLogic { + + public static final LuxuryTaxLogic INSTANCE = new LuxuryTaxLogic(); + + public static final int LUXURY_TAX_AMOUNT = 50; + + private LuxuryTaxLogic() { + + } + + @Override + public String onLand(Player mover, GameState gameState) { + mover.addMoney(-LUXURY_TAX_AMOUNT); + return mover.getIcon() + " paid $" + LUXURY_TAX_AMOUNT + " in Luxury Tax"; + } + +} diff --git a/src/main/java/xyz/cliserkad/consortium/TaxLogic.java b/src/main/java/xyz/cliserkad/consortium/TaxLogic.java deleted file mode 100644 index b8f4543..0000000 --- a/src/main/java/xyz/cliserkad/consortium/TaxLogic.java +++ /dev/null @@ -1,23 +0,0 @@ -package xyz.cliserkad.consortium; - -public class TaxLogic implements PositionLogic { - - public static final int LUXURY_TAX_AMOUNT = 100; - public static final int INCOME_TAX_AMOUNT = 200; - - public static final TaxLogic LUXURY_TAX = new TaxLogic(LUXURY_TAX_AMOUNT); - public static final TaxLogic INCOME_TAX = new TaxLogic(INCOME_TAX_AMOUNT); - - public final int amount; - - public TaxLogic(final int amount) { - this.amount = -amount; - } - - @Override - public String onLand(Player mover, GameState gameState) { - mover.addMoney(amount); - return mover.getIcon() + " paid $" + -amount + " in taxes"; - } - -} From 402f9c05f9730ad3db6230e800bdb570604143aa Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 14:02:35 -0400 Subject: [PATCH 3/7] increase starting bids --- src/main/java/xyz/cliserkad/consortium/Auction.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/xyz/cliserkad/consortium/Auction.java b/src/main/java/xyz/cliserkad/consortium/Auction.java index 8a63a9b..3c2d4b8 100644 --- a/src/main/java/xyz/cliserkad/consortium/Auction.java +++ b/src/main/java/xyz/cliserkad/consortium/Auction.java @@ -4,12 +4,11 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.Set; public class Auction implements Serializable { @Serial - private static final long serialVersionUID = 20240714; + private static final long serialVersionUID = 20250309; public final BoardElement property; public final List bidders; @@ -17,11 +16,16 @@ public class Auction implements Serializable { public List bids; public int bid; + // TODO: fix purchasable interface public Auction(BoardElement property, List bidders) { this.property = property; this.bidders = bidders; bids = new ArrayList<>(); - bid = 0; + if(property instanceof Purchasable purchasable) { + bid = (int) (purchasable.cost() * 0.25f); + } else { + bid = 0; + } } } From 0b3e02b6be718629cd401eba712e782a6b7a29b3 Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 20:06:24 -0400 Subject: [PATCH 4/7] better start up with config files --- pom.xml | 5 ++ .../xyz/cliserkad/consortium/GameConfig.java | 7 +++ .../xyz/cliserkad/consortium/GameServer.java | 56 +++++++++++++++---- .../xyz/cliserkad/consortium/GameState.java | 9 ++- .../consortium/GraphicalGameClient.java | 2 +- .../xyz/cliserkad/consortium/LobbyConfig.java | 8 +++ .../java/xyz/cliserkad/consortium/Main.java | 29 +++++----- .../java/xyz/cliserkad/consortium/Player.java | 4 +- 8 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 src/main/java/xyz/cliserkad/consortium/GameConfig.java create mode 100644 src/main/java/xyz/cliserkad/consortium/LobbyConfig.java diff --git a/pom.xml b/pom.xml index 0820e3b..34defdf 100644 --- a/pom.xml +++ b/pom.xml @@ -107,6 +107,11 @@ + + com.google.code.gson + gson + 2.12.1 + xyz.cliserkad smp diff --git a/src/main/java/xyz/cliserkad/consortium/GameConfig.java b/src/main/java/xyz/cliserkad/consortium/GameConfig.java new file mode 100644 index 0000000..f5fbfb6 --- /dev/null +++ b/src/main/java/xyz/cliserkad/consortium/GameConfig.java @@ -0,0 +1,7 @@ +package xyz.cliserkad.consortium; + +public class GameConfig { + + public int initialMoney = 800; + +} diff --git a/src/main/java/xyz/cliserkad/consortium/GameServer.java b/src/main/java/xyz/cliserkad/consortium/GameServer.java index 86d479a..704019a 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameServer.java +++ b/src/main/java/xyz/cliserkad/consortium/GameServer.java @@ -1,6 +1,15 @@ package xyz.cliserkad.consortium; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import xyz.cliserkad.util.Text; + +import java.io.BufferedReader; +import java.io.FileReader; import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URL; import java.util.ArrayList; import java.util.List; @@ -15,10 +24,14 @@ public class GameServer { private int turns; - public GameServer(final int playerCount) throws IOException, InterruptedException { - List> controllers = new ArrayList<>(); + public GameServer() throws IOException, InterruptedException { + GameConfig gameConfig = readConfigFile(new GameConfig(), GameConfig.class); + LobbyConfig lobbyConfig = readConfigFile(new LobbyConfig(), LobbyConfig.class); - for(int i = 0; i < playerCount; i++) { + System.out.println("Public IP: " + publicIP()); + + List> controllers = new ArrayList<>(); + for(int i = 0; i < lobbyConfig.networkClients; i++) { controllers.add(new NetworkedController<>(BASE_PORT + i, GameClient.class, true)); } @@ -32,22 +45,43 @@ public GameServer(final int playerCount) throws IOException, InterruptedExceptio System.out.println("Connections accepted..."); List clients = new ArrayList<>(); + for(int i = 0; i < lobbyConfig.autoClients; i++) { + clients.add(new AutoGameClient()); + } for(NetworkedController controller : controllers) { clients.add(controller.proxy); } - gameState = new GameState(clients); + + gameState = new GameState(clients, gameConfig); System.out.println("Game state initialized..."); } - public static void main(String[] args) throws IOException, InterruptedException { - final int playerCount; + public static String publicIP() { try { - playerCount = Integer.parseInt(args[0]); - } catch(ArrayIndexOutOfBoundsException | NumberFormatException e) { - System.err.println("Failed to parse player count from command line arguments"); - return; + URL amazonIP = new URI("http://checkip.amazonaws.com").toURL(); + BufferedReader in = new BufferedReader(new InputStreamReader(amazonIP.openStream())); + return in.readLine(); + } catch(Exception e) { + return null; } - GameServer server = new GameServer(playerCount); + } + + public static T readConfigFile(T config, Class configClass) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String fileName = Text.undoCamelCase(config.getClass().getSimpleName()) + ".json"; + try { + config = gson.fromJson(new FileReader(fileName), configClass); + } catch(IOException e) { + System.err.println(e.getMessage()); + System.err.println("Failed to read " + fileName); + } + System.out.println("Current " + config.getClass().getSimpleName() + ":"); + System.out.println(gson.toJson(config)); + return config; + } + + public static void main(String[] args) throws IOException, InterruptedException { + GameServer server = new GameServer(); while(server.run()) { } } diff --git a/src/main/java/xyz/cliserkad/consortium/GameState.java b/src/main/java/xyz/cliserkad/consortium/GameState.java index ed16b4b..1a3c396 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameState.java +++ b/src/main/java/xyz/cliserkad/consortium/GameState.java @@ -15,6 +15,7 @@ */ public class GameState implements Serializable { + private final transient GameConfig config; private final transient int[] communityCardStack = genShuffledArray(CommunityChestLogic.CommunityCard.values().length); private final transient int[] chanceCardStack = genShuffledArray(ChanceLogic.ChanceCard.values().length); private int communityCardIndex = 0; @@ -38,7 +39,9 @@ public class GameState implements Serializable { @Serial private static final long serialVersionUID = 20240805L; - public GameState(List clients) { + public GameState(List clients, GameConfig config) { + this.config = config; + boardElements = new BoardElement[BoardPosition.values().length]; for(int i = 0; i < BoardPosition.values().length; i++) { final BoardPosition position = BoardPosition.values()[i]; @@ -48,7 +51,7 @@ public GameState(List clients) { players = new Player[clients.size()]; for(int i = 0; i < clients.size(); i++) - players[i] = new Player(clients.get(i)); + players[i] = new Player(clients.get(i), config.initialMoney); for(Player player : players) { player.setPosition(BoardPosition.GO, this); @@ -64,7 +67,7 @@ public GameState(List clients) { * Creates a new game state with no players. Used as a placeholder for updating clients during game initialization. */ public GameState() { - this(new ArrayList<>()); + this(new ArrayList<>(), new GameConfig()); } /** diff --git a/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java b/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java index 05d462c..8ca3643 100644 --- a/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java +++ b/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java @@ -118,7 +118,7 @@ public GraphicalGameClient() { List self = new ArrayList<>(); self.add(this); - update(new GameState(self)); + update(new GameState(self, new GameConfig())); frame.addWindowListener(new WindowAdapter() { diff --git a/src/main/java/xyz/cliserkad/consortium/LobbyConfig.java b/src/main/java/xyz/cliserkad/consortium/LobbyConfig.java new file mode 100644 index 0000000..09c5d1b --- /dev/null +++ b/src/main/java/xyz/cliserkad/consortium/LobbyConfig.java @@ -0,0 +1,8 @@ +package xyz.cliserkad.consortium; + +public class LobbyConfig { + + public int networkClients = 2; + public int autoClients = 0; + +} diff --git a/src/main/java/xyz/cliserkad/consortium/Main.java b/src/main/java/xyz/cliserkad/consortium/Main.java index 9928fbb..21299a9 100644 --- a/src/main/java/xyz/cliserkad/consortium/Main.java +++ b/src/main/java/xyz/cliserkad/consortium/Main.java @@ -34,22 +34,23 @@ public class Main { public static final String TITLE = "Consortium " + Version.COMMIT_ID_ABBREV; public static void main(String[] args) throws IOException, InterruptedException { - if(args.length == 2) { - if(args[0].equalsIgnoreCase("server")) { - GameServer.main(new String[] { args[1] }); - } else { - try { - NetworkedResponder responder = new NetworkedResponder<>(new GraphicalGameClient(), args[0], Integer.parseInt(args[1])); - responder.start(); - } catch(NumberFormatException e) { - System.err.println("Invalid port number: " + args[1]); - } catch(IOException e) { - System.err.println("Failed to connect to server at " + args[0] + ":" + args[1] + ": " + e.getMessage()); - } + System.setProperty("user.dir", RESOURCES.resourcesRoot.getAbsolutePath()); + if(args.length == 1 && args[0].equalsIgnoreCase("server")) { + GameServer.main(args); + } else if(args.length == 2) { + try { + System.out.println("Attempting to connect to server at " + args[0] + ":" + args[1] + "..."); + NetworkedResponder responder = new NetworkedResponder<>(new GraphicalGameClient(), args[0], Integer.parseInt(args[1])); + responder.start(); + } catch(NumberFormatException e) { + System.err.println("Invalid port number: " + args[1]); + } catch(IOException e) { + System.err.println("Failed to connect to server at " + args[0] + ":" + args[1] + ": " + e.getMessage()); } + } else { + System.out.println("Run server via:\njava -jar consortium.jar server\nStarting client..."); + new GameConnector(); } - System.setProperty("user.dir", RESOURCES.resourcesRoot.getAbsolutePath()); - new GameConnector(); } public static ResourcesDescriptor describeResources() { diff --git a/src/main/java/xyz/cliserkad/consortium/Player.java b/src/main/java/xyz/cliserkad/consortium/Player.java index 248a8a4..4b4bf45 100644 --- a/src/main/java/xyz/cliserkad/consortium/Player.java +++ b/src/main/java/xyz/cliserkad/consortium/Player.java @@ -21,9 +21,9 @@ public class Player implements Serializable { private BoardPosition position; public transient final GameClient controller; - public Player(GameClient controller) { + public Player(GameClient controller, int initialMoney) { position = BoardPosition.GO; - money = 800; + money = initialMoney; playerIndex = numPlayers++; this.controller = controller; isBankrupt = false; From 03872f83eb74cca67bdea23aa33feb5e34fc6c75 Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 20:47:53 -0400 Subject: [PATCH 5/7] fix auctions --- src/main/java/xyz/cliserkad/consortium/Auction.java | 7 ++++--- .../java/xyz/cliserkad/consortium/GameConfig.java | 2 ++ .../java/xyz/cliserkad/consortium/GameState.java | 13 ++++++++----- .../xyz/cliserkad/consortium/IncomeTaxLogic.java | 2 +- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/java/xyz/cliserkad/consortium/Auction.java b/src/main/java/xyz/cliserkad/consortium/Auction.java index 3c2d4b8..641e045 100644 --- a/src/main/java/xyz/cliserkad/consortium/Auction.java +++ b/src/main/java/xyz/cliserkad/consortium/Auction.java @@ -17,13 +17,14 @@ public class Auction implements Serializable { public int bid; // TODO: fix purchasable interface - public Auction(BoardElement property, List bidders) { + public Auction(BoardElement property, List bidders, GameConfig config) { this.property = property; this.bidders = bidders; bids = new ArrayList<>(); - if(property instanceof Purchasable purchasable) { - bid = (int) (purchasable.cost() * 0.25f); + if(property.position.logic instanceof Purchasable purchasable) { + bid = (int) (purchasable.cost() * config.startingBidFactor) - config.minimumBid; } else { + System.err.println("Auction property is not purchasable."); bid = 0; } } diff --git a/src/main/java/xyz/cliserkad/consortium/GameConfig.java b/src/main/java/xyz/cliserkad/consortium/GameConfig.java index f5fbfb6..56593ee 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameConfig.java +++ b/src/main/java/xyz/cliserkad/consortium/GameConfig.java @@ -3,5 +3,7 @@ public class GameConfig { public int initialMoney = 800; + public int minimumBid = 10; + public float startingBidFactor = 0.33f; } diff --git a/src/main/java/xyz/cliserkad/consortium/GameState.java b/src/main/java/xyz/cliserkad/consortium/GameState.java index 1a3c396..1566b80 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameState.java +++ b/src/main/java/xyz/cliserkad/consortium/GameState.java @@ -247,22 +247,24 @@ private PlayerAction updateAndPoll(Player player, Class private String holdAuction(Player firstBidder, BoardElement element) { if(element.position.logic instanceof Purchasable purchasable) { // add all players who can afford the property to the auction - auction = new Auction(element, new ArrayList<>(Arrays.asList(players))); - auction.bidders.removeIf(player -> player.getMoney() < 1); + auction = new Auction(element, new ArrayList<>(Arrays.asList(players)), config); + auction.bidders.removeIf(player -> player.getMoney() < auction.bid); // abort if no players have any money :( if(auction.bidders.isEmpty()) return "No players have enough money to bid on " + element.position.niceName; + broadcast("Auction for " + element.position.niceName + " has begun! Starting bid: $" + auction.bid + config.minimumBid); + // set the current bidder to the first bidder if they can participate if(auction.bidders.contains(firstBidder)) auction.currentBidder = firstBidder; else auction.currentBidder = auction.bidders.getFirst(); - while((auction.bidders.size() == 1 && auction.bid <= 0) || auction.bidders.size() > 1) { + while(auction.bidders.size() > 1 || (auction.bidders.size() == 1 && auction.bids.isEmpty())) { PlayerAction response = updateAndPoll(auction.currentBidder, BidAction.class); - if(response instanceof BidAction bidAction && bidAction.amount() > auction.bid && auction.currentBidder.getMoney() >= bidAction.amount()) { + if(response instanceof BidAction bidAction && bidAction.amount() >= auction.bid + config.minimumBid && auction.currentBidder.getMoney() >= bidAction.amount()) { auction.bid = bidAction.amount(); auction.bids.add(bidAction); broadcast(auction.currentBidder.getIcon() + " bid $" + auction.bid + " on " + element.position.niceName); @@ -274,7 +276,8 @@ private String holdAuction(Player firstBidder, BoardElement element) { auction.currentBidder = auction.bidders.get((auction.currentBidder.playerIndex + 1) % auction.bidders.size()); } - if(!auction.bidders.isEmpty() && auction.bid > 0) { + // if any bids were placed, the property is sold to the last remaining bidder + if(!auction.bids.isEmpty() && !auction.bidders.isEmpty()) { final Player auctionWinner = auction.bidders.getFirst(); auctionWinner.addMoney(-auction.bid); element.setOwner(auctionWinner); diff --git a/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java b/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java index dec7b31..61b0892 100644 --- a/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java +++ b/src/main/java/xyz/cliserkad/consortium/IncomeTaxLogic.java @@ -14,7 +14,7 @@ private IncomeTaxLogic() { public String onLand(Player mover, GameState gameState) { int deducted = (int) (mover.getMoney() * INCOME_TAX_FACTOR); mover.addMoney(-deducted); - return mover.getIcon() + " paid $" + -deducted + " in Income Tax"; + return mover.getIcon() + " paid $" + deducted + " in Income Tax"; } } From ded69bf40730dd4b10595e32d321e36bd47049bc Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 20:50:24 -0400 Subject: [PATCH 6/7] fix bankruptcy prompt --- src/main/java/xyz/cliserkad/consortium/GameState.java | 5 ++++- .../java/xyz/cliserkad/consortium/GraphicalGameClient.java | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/xyz/cliserkad/consortium/GameState.java b/src/main/java/xyz/cliserkad/consortium/GameState.java index 1566b80..c2aba86 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameState.java +++ b/src/main/java/xyz/cliserkad/consortium/GameState.java @@ -136,7 +136,10 @@ private void endOfTurnLoop() { if(getCurrentPlayer().getMoney() < 0) broadcast(getCurrentPlayer().getIcon() + " is in debt! (" + getCurrentPlayer().getMoney() + ") They will need to raise funds or declare bankruptcy."); do { - response = updateAndPoll(getCurrentPlayer(), EndTurnAction.class); + if(getCurrentPlayer().getMoney() < 0) + response = updateAndPoll(getCurrentPlayer(), DeclareBankruptcyAction.class); + else + response = updateAndPoll(getCurrentPlayer(), EndTurnAction.class); if(response instanceof ProposeTradeAction proposeTradeAction) { broadcast("Trade proposed by " + proposeTradeAction.trade().proposer.getIcon() + " to " + proposeTradeAction.trade().acceptor.getIcon() + " " + proposeTradeAction.trade()); proposedTrade = proposeTradeAction.trade(); diff --git a/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java b/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java index 8ca3643..2cd4954 100644 --- a/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java +++ b/src/main/java/xyz/cliserkad/consortium/GraphicalGameClient.java @@ -168,7 +168,7 @@ public PlayerAction poll(Player avatar, GameState gameState, Class { yield switch(endTurnOption) { From 9392143452679165bcea0f3daf49d6e1a0d8c4e9 Mon Sep 17 00:00:00 2001 From: cliserkad <0_0@cliserkad.xyz> Date: Sun, 9 Mar 2025 22:26:34 -0400 Subject: [PATCH 7/7] fix bankruptcies. add bot trading --- .../cliserkad/consortium/AutoGameClient.java | 35 ++++++++++++------- .../consortium/BoardElementVisual.java | 12 +++---- .../xyz/cliserkad/consortium/ChanceLogic.java | 1 + .../xyz/cliserkad/consortium/GameState.java | 17 ++++++--- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/main/java/xyz/cliserkad/consortium/AutoGameClient.java b/src/main/java/xyz/cliserkad/consortium/AutoGameClient.java index c5f86b8..92bbdd9 100644 --- a/src/main/java/xyz/cliserkad/consortium/AutoGameClient.java +++ b/src/main/java/xyz/cliserkad/consortium/AutoGameClient.java @@ -7,20 +7,24 @@ */ public class AutoGameClient implements GameClient { - // The bot will bid up to 80% of the property's market value - public static final double MAX_BID_RATIO = 0.75; + // bot will bid up to 125% of the market value + public static final double MAX_BID_RATIO = 1.25; @Override public PlayerAction poll(Player avatar, GameState gameState, Class prompt) { + try { + Thread.sleep(1000); + } catch(InterruptedException e) { + // do nothing + } if(prompt == PurchaseAction.class) { - // always try to purchase property - return new PurchaseAction(avatar.getPosition()); + if(!canOthersBuy(gameState, avatar)) + return null; + else + return new PurchaseAction(avatar.getPosition()); } else if(prompt == BidAction.class) { - // bid up the property if the other players are broke - if(!canOthersWinBid(gameState, avatar)) - return new BidAction(gameState.getAuction().property.position, gameState.getAuction().bid + MIN_BID); // bid up the property if the bid is less than bid ratio * market value - else if(bidLessThanMax(gameState)) + if(bidLessThanMax(gameState)) return new BidAction(gameState.getAuction().property.position, gameState.getAuction().bid + MIN_BID); else return null; @@ -29,7 +33,14 @@ else if(bidLessThanMax(gameState)) } else if(prompt == DeclareBankruptcyAction.class) { return new DeclareBankruptcyAction(); } else if(prompt == AcceptTradeAction.class) { - return new AcceptTradeAction(gameState.getProposedTrade(), false); + // incoming value must be 2x the incoming value + int incomingValue = gameState.getProposedTrade().moneyToAcceptor; + for(BoardPosition position : gameState.getProposedTrade().positionsToAcceptor) + incomingValue += position.logic instanceof Purchasable purchasable ? purchasable.cost() : 0; + int outgoingValue = gameState.getProposedTrade().moneyToProposer; + for(BoardPosition position : gameState.getProposedTrade().positionsToProposer) + outgoingValue += position.logic instanceof Purchasable purchasable ? purchasable.cost() : 0; + return new AcceptTradeAction(gameState.getProposedTrade(), incomingValue >= 2 * outgoingValue); } else { return null; } @@ -45,11 +56,11 @@ public boolean bidLessThanMax(GameState gameState) { /** * Return false if any other player has more money than the position's market value */ - public boolean canOthersWinBid(GameState gameState, Player avatar) { + public boolean canOthersBuy(GameState gameState, Player avatar) { for(Player p : gameState.getPlayers()) { if(p != avatar) { - if(gameState.getAuction().property.position.logic instanceof Purchasable purchasable) { - if(p.getMoney() > purchasable.cost()) { + if(gameState.getBoardElement(avatar).position.logic instanceof Purchasable purchasable) { + if(p.getMoney() > purchasable.cost() - 25) { return true; } } diff --git a/src/main/java/xyz/cliserkad/consortium/BoardElementVisual.java b/src/main/java/xyz/cliserkad/consortium/BoardElementVisual.java index 24053af..20af28e 100644 --- a/src/main/java/xyz/cliserkad/consortium/BoardElementVisual.java +++ b/src/main/java/xyz/cliserkad/consortium/BoardElementVisual.java @@ -2,8 +2,6 @@ import javax.swing.*; import java.awt.*; -import java.util.ArrayList; -import java.util.List; import static xyz.cliserkad.consortium.Main.textColorForBackground; @@ -17,8 +15,6 @@ public class BoardElementVisual extends JPanel implements GameStateReceiver { private JLabel purchaseLabel; private JLabel improvementLabel; - private List playerVisuals = new ArrayList<>(); - public BoardElementVisual(BoardElement element) { super(new GridBagLayout()); @@ -75,11 +71,14 @@ public boolean update(GameState gameState) { element = gameState.getBoardElement(element.position); final int targetComponentCount; - if(element.position.logic instanceof Purchasable) { + if(element.position.logic instanceof Purchasable purchasable) { targetComponentCount = 3; if(element.owner != null) { purchaseLabel.setText("Owner: " + element.owner.getIcon()); improvementLabel.setText(improvementString()); + } else { + purchaseLabel.setText("Cost: " + purchasable.cost()); + improvementLabel.setText(improvementString()); } } else { targetComponentCount = 1; @@ -89,7 +88,7 @@ public boolean update(GameState gameState) { } for(Player player : gameState.getPlayers()) { - if(player.getPosition() == element.position) { + if(!player.isBankrupt() && player.getPosition() == element.position) { GridBagConstraints constraints = new GridBagConstraints(); constraints.gridwidth = 1; constraints.gridheight = 1; @@ -97,7 +96,6 @@ public boolean update(GameState gameState) { constraints.anchor = GridBagConstraints.SOUTHWEST; constraints.gridx = player.playerIndex; constraints.gridy = targetComponentCount + 1; - playerVisuals.add(new PlayerVisual(player)); add(new PlayerVisual(player), constraints); } } diff --git a/src/main/java/xyz/cliserkad/consortium/ChanceLogic.java b/src/main/java/xyz/cliserkad/consortium/ChanceLogic.java index 9e4ddd5..bf1860f 100644 --- a/src/main/java/xyz/cliserkad/consortium/ChanceLogic.java +++ b/src/main/java/xyz/cliserkad/consortium/ChanceLogic.java @@ -88,6 +88,7 @@ public String onLand(Player player, GameState gameState) { } } case BUILDING_LOAN -> player.addMoney(150); + // TODO: fix double auction failure when ADVANCE_TO_VERMONT is pulled case ADVANCE_TO_VERMONT -> gameState.movePlayer(player, BoardPosition.VERMONT_AVENUE, 0); } return player.getIcon() + " pulled Chance Card \"" + pulledCard.niceName + "\""; diff --git a/src/main/java/xyz/cliserkad/consortium/GameState.java b/src/main/java/xyz/cliserkad/consortium/GameState.java index c2aba86..6db8f54 100644 --- a/src/main/java/xyz/cliserkad/consortium/GameState.java +++ b/src/main/java/xyz/cliserkad/consortium/GameState.java @@ -153,6 +153,13 @@ private void endOfTurnLoop() { } } else if(response instanceof DeclareBankruptcyAction) { getCurrentPlayer().goBankrupt(); + for(BoardElement element : boardElements) { + if(element.owner == getCurrentPlayer()) { + element.improvementAmt = 0; + element.setOwner(null); + } + } + updatePlayers(); broadcast(getCurrentPlayer().getIcon() + " has declared bankruptcy"); System.out.println("Players remaining: " + Arrays.toString(players)); return; // end the turn loop forcefully @@ -251,13 +258,13 @@ private String holdAuction(Player firstBidder, BoardElement element) { if(element.position.logic instanceof Purchasable purchasable) { // add all players who can afford the property to the auction auction = new Auction(element, new ArrayList<>(Arrays.asList(players)), config); - auction.bidders.removeIf(player -> player.getMoney() < auction.bid); + auction.bidders.removeIf(player -> player.getMoney() < auction.bid + config.minimumBid); // abort if no players have any money :( if(auction.bidders.isEmpty()) return "No players have enough money to bid on " + element.position.niceName; - broadcast("Auction for " + element.position.niceName + " has begun! Starting bid: $" + auction.bid + config.minimumBid); + broadcast("Auction for " + element.position.niceName + " has begun! Starting bid: $" + (auction.bid + config.minimumBid)); // set the current bidder to the first bidder if they can participate if(auction.bidders.contains(firstBidder)) @@ -266,6 +273,8 @@ private String holdAuction(Player firstBidder, BoardElement element) { auction.currentBidder = auction.bidders.getFirst(); while(auction.bidders.size() > 1 || (auction.bidders.size() == 1 && auction.bids.isEmpty())) { + // save for later :) + final Player nextBidder = auction.bidders.get((auction.bidders.indexOf(auction.currentBidder) + 1) % auction.bidders.size()); PlayerAction response = updateAndPoll(auction.currentBidder, BidAction.class); if(response instanceof BidAction bidAction && bidAction.amount() >= auction.bid + config.minimumBid && auction.currentBidder.getMoney() >= bidAction.amount()) { auction.bid = bidAction.amount(); @@ -275,8 +284,8 @@ private String holdAuction(Player firstBidder, BoardElement element) { auction.bidders.remove(auction.currentBidder); broadcast(auction.currentBidder.getIcon() + " withdrew from bidding on " + element.position.niceName); } - if(!auction.bidders.isEmpty()) - auction.currentBidder = auction.bidders.get((auction.currentBidder.playerIndex + 1) % auction.bidders.size()); + // this is later + auction.currentBidder = nextBidder; } // if any bids were placed, the property is sold to the last remaining bidder