From 831359eea936359293281c185259d1cee95c0218 Mon Sep 17 00:00:00 2001
From: Kevin Schmidt <kevin.schmidt@hmmh.de>
Date: Wed, 12 Mar 2025 12:15:25 +0100
Subject: [PATCH] TD-50 Add Money to Matchdata

---
 .../de/towerdefence/server/match/Match.java   | 66 ++++++++++++++++++-
 .../server/match/MatchService.java            | 30 +++++++++
 .../channels/match/MatchWebsocketHandler.java | 10 +++
 .../match/money/PlayerMoneyCallback.java      |  8 +++
 .../match/money/PlayerMoneyMessage.java       | 26 ++++++++
 ws/ws.yml                                     | 22 +++++++
 6 files changed, 161 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyCallback.java
 create mode 100644 src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyMessage.java

diff --git a/src/main/java/de/towerdefence/server/match/Match.java b/src/main/java/de/towerdefence/server/match/Match.java
index 7294b7f..c69b6c3 100644
--- a/src/main/java/de/towerdefence/server/match/Match.java
+++ b/src/main/java/de/towerdefence/server/match/Match.java
@@ -1,22 +1,37 @@
 package de.towerdefence.server.match;
 
 import de.towerdefence.server.player.Player;
+import de.towerdefence.server.server.channels.match.money.PlayerMoneyCallback;
 import de.towerdefence.server.server.channels.match.placing.InvalidPlacementReason;
 import de.towerdefence.server.server.channels.match.placing.Tower;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
-@AllArgsConstructor
+import java.util.Optional;
+
 @Getter
 public class Match {
 
     public final static int MAP_SIZE_X = 10;
     public final static int MAP_SIZE_Y = 20;
+    public final static int START_MONEY = 50;
     private final String matchId;
     private final Player player1;
     private final Player player2;
+    private int player1Money = START_MONEY;
+    private int player2Money = START_MONEY;
+    private PlayerMoneyCallback player1MoneyCallback;
+    private PlayerMoneyCallback player2MoneyCallback;
     private final Tower[][] player1Map = new Tower[MAP_SIZE_X][MAP_SIZE_Y];
     private final Tower[][] player2Map = new Tower[MAP_SIZE_X][MAP_SIZE_Y];
+    private boolean isPlayer1Connected = false;
+    private boolean isPlayer2Connected = false;
+
+    public Match(String matchId, Player player1, Player player2) {
+        this.matchId = matchId;
+        this.player1 = player1;
+        this.player2 = player2;
+    }
 
     public Player getOpponent(Player player) {
         boolean isPlayer1 = player1.equals(player);
@@ -53,4 +68,53 @@ public class Match {
         }
         return getOpponent(player);
     }
+
+    // INFO: MoneyHandling
+
+    public Optional<Integer> getPlayerMoney(Player player) {
+        if (player != player1 && player != player2) {
+            return Optional.empty();
+        }
+        if (player == player1) {
+            return Optional.of(player1Money);
+        } else {
+            return Optional.of(player2Money);
+        }
+    }
+
+    public void addMoney(int money) {
+        player1Money += money;
+        player2Money += money;
+    }
+
+    public void removeMoney(Player player, int money) {
+        if (player == player1) {
+            player1Money -= money;
+        }
+        if (player == player2) {
+            player2Money -= money;
+        }
+    }
+
+    public void setPlayerMoneyCallback( Player player, PlayerMoneyCallback playerMoneyCallback) {
+        if (player == player1) {
+            this.player1MoneyCallback = playerMoneyCallback;
+        }
+        if (player == player2) {
+            this.player2MoneyCallback = playerMoneyCallback;
+        }
+    }
+
+    /**
+     * @return true if both players are connected
+     */
+    public boolean setPlayerConnected(Player player) {
+        if (player == player1) {
+            isPlayer1Connected = true;
+        }
+        if (player == player2) {
+            isPlayer2Connected = true;
+        }
+        return isPlayer1Connected && isPlayer2Connected;
+    }
 }
diff --git a/src/main/java/de/towerdefence/server/match/MatchService.java b/src/main/java/de/towerdefence/server/match/MatchService.java
index d547941..3f27222 100644
--- a/src/main/java/de/towerdefence/server/match/MatchService.java
+++ b/src/main/java/de/towerdefence/server/match/MatchService.java
@@ -1,15 +1,20 @@
 package de.towerdefence.server.match;
 
 import de.towerdefence.server.player.Player;
+import de.towerdefence.server.server.channels.match.money.PlayerMoneyCallback;
 import de.towerdefence.server.server.channels.match.placing.Tower;
 import org.springframework.stereotype.Service;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.*;
 
 @Service
 public class MatchService {
     private final Map<Player, Match> playerMatches = new HashMap<>();
+    private final Map<Match, ScheduledFuture<?>> moneyTasks = new HashMap<>();
+    private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
 
     public void createMatch(String matchId, Player player1, Player player2) {
         Match match = new Match(matchId, player1, player2);
@@ -26,6 +31,31 @@ public class MatchService {
      */
     public Player placeTower(Player player, int x, int y) throws InvalidPlacementException {
         return playerMatches.get(player).addTower(player, new Tower(), x, y);
+    }
 
+
+    public void playerConnected(Player player, PlayerMoneyCallback callback) {
+        Match match = playerMatches.get(player);
+        Optional<Integer> currentMoney = match.getPlayerMoney(player);
+        if (currentMoney.isEmpty()) {
+            return;
+        }
+        match.setPlayerMoneyCallback(player, callback);
+        callback.call(player, currentMoney.get());
+        boolean matchStarted =match.setPlayerConnected(player);
+        if (!matchStarted) {
+            return;
+        }
+        ScheduledFuture<?> moneyTask = scheduler.scheduleAtFixedRate(
+            () -> {
+                match.addMoney(3);
+                match.getPlayer1MoneyCallback().call(match.getPlayer1(), match.getPlayer1Money());
+                match.getPlayer2MoneyCallback().call(match.getPlayer2(), match.getPlayer2Money());
+            },
+            5,
+            5,
+            TimeUnit.SECONDS
+        );
+        moneyTasks.put(match, moneyTask);
     }
 }
diff --git a/src/main/java/de/towerdefence/server/server/channels/match/MatchWebsocketHandler.java b/src/main/java/de/towerdefence/server/server/channels/match/MatchWebsocketHandler.java
index aee3272..c02da6a 100644
--- a/src/main/java/de/towerdefence/server/server/channels/match/MatchWebsocketHandler.java
+++ b/src/main/java/de/towerdefence/server/server/channels/match/MatchWebsocketHandler.java
@@ -5,6 +5,7 @@ import de.towerdefence.server.match.InvalidPlacementException;
 import de.towerdefence.server.match.MatchService;
 import de.towerdefence.server.player.Player;
 import de.towerdefence.server.server.JsonWebsocketHandler;
+import de.towerdefence.server.server.channels.match.money.PlayerMoneyMessage;
 import de.towerdefence.server.server.channels.match.placing.GamePlayerMap;
 import de.towerdefence.server.server.channels.match.placing.InvalidPlacementMessage;
 import de.towerdefence.server.server.channels.match.placing.RequestTowerPlacingMessage;
@@ -35,6 +36,15 @@ public class MatchWebsocketHandler extends JsonWebsocketHandler {
     public void afterConnectionEstablished(WebSocketSession session) {
         super.afterConnectionEstablished(session);
         playerSessions.put(sessionPlayers.get(session), session);
+        matchService.playerConnected(sessionPlayers.get(session), this::onPlayerMoneyChanged);
+    }
+
+    private void onPlayerMoneyChanged(Player player, int playerMoney) {
+        WebSocketSession session = playerSessions.get(player);
+        try {
+            new PlayerMoneyMessage(playerMoney).send(session);
+        } catch (IOException ignored) {
+        }
     }
 
     @Override
diff --git a/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyCallback.java b/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyCallback.java
new file mode 100644
index 0000000..dc1fb46
--- /dev/null
+++ b/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyCallback.java
@@ -0,0 +1,8 @@
+package de.towerdefence.server.server.channels.match.money;
+
+import de.towerdefence.server.player.Player;
+
+@FunctionalInterface
+public interface PlayerMoneyCallback {
+    void call(Player player, int playerMoney);
+}
diff --git a/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyMessage.java b/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyMessage.java
new file mode 100644
index 0000000..d95b258
--- /dev/null
+++ b/src/main/java/de/towerdefence/server/server/channels/match/money/PlayerMoneyMessage.java
@@ -0,0 +1,26 @@
+package de.towerdefence.server.server.channels.match.money;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import de.towerdefence.server.server.JsonMessage;
+import lombok.AllArgsConstructor;
+
+import java.util.Map;
+
+@AllArgsConstructor
+public class PlayerMoneyMessage extends JsonMessage {
+
+    private final int playerMoney;
+
+    @Override
+    protected String getMessageId() {
+        return "PlayerMoney";
+    }
+
+    @Override
+    protected Map<String, JsonNode> getData(JsonNodeFactory factory) {
+        return Map.of(
+            "playerMoney", factory.numberNode(this.playerMoney)
+        );
+    }
+}
diff --git a/ws/ws.yml b/ws/ws.yml
index cb56075..70fb9ea 100644
--- a/ws/ws.yml
+++ b/ws/ws.yml
@@ -241,6 +241,21 @@ channels:
             - x
             - y
             - reason
+      PlayerMoney:
+        description: Money a player currently has
+        payload:
+          type: object
+          additionalProperties: false
+          properties:
+            $id:
+              type: string
+              format: messageId
+            playerMoney:
+              type: integer
+          required:
+            - $id
+            - playerMoney
+
 
 operations:
   requestConnectionToken:
@@ -322,6 +337,13 @@ operations:
       $ref: "#/channels/match"
     messages:
       - $ref: "#/channels/match/messages/TowerPlaced"
+  playerMoney:
+    title: PlayerMoney
+    action: receive
+    channel:
+      $ref: "#/channels/match"
+    messages:
+      - $ref: "#/channels/match/messages/PlayerMoney"