Skip to content

Commit 4c08e10

Browse files
authored
Merge pull request #7 from Zwazel-Teaching-Projects/feat/debug-view
Feat/debug view
2 parents 67acb3b + fe529c8 commit 4c08e10

File tree

7 files changed

+278
-0
lines changed

7 files changed

+278
-0
lines changed

src/main/java/dev/zwazel/GameWorld.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import dev.zwazel.internal.InternalGameWorld;
66
import dev.zwazel.internal.PublicGameWorld;
77
import dev.zwazel.internal.connection.ConnectionManager;
8+
import dev.zwazel.internal.debug.MapVisualiser;
89
import dev.zwazel.internal.game.state.ClientState;
910
import dev.zwazel.internal.game.tank.Tank;
1011
import dev.zwazel.internal.game.tank.TankFactory;
@@ -13,6 +14,7 @@
1314
import dev.zwazel.internal.message.data.GameState;
1415
import dev.zwazel.internal.message.data.StartGameConfig;
1516

17+
import javax.swing.*;
1618
import java.io.DataOutputStream;
1719
import java.io.IOException;
1820
import java.util.List;
@@ -197,6 +199,11 @@ public List<MessageContainer> getIncomingMessages() {
197199
return List.copyOf(incomingMessages);
198200
}
199201

202+
@Override
203+
public void registerVisualiser(JPanel panel) {
204+
this.simulationThread.setMapVisualiser(panel);
205+
}
206+
200207
@Override
201208
public void stop() {
202209
running = false;

src/main/java/dev/zwazel/internal/GameSimulationThread.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
import dev.zwazel.internal.message.data.GameConfig;
77
import dev.zwazel.internal.message.data.GameState;
88
import lombok.RequiredArgsConstructor;
9+
import lombok.Setter;
910

11+
import javax.swing.*;
1012
import java.io.DataOutputStream;
1113
import java.io.IOException;
1214
import java.nio.charset.StandardCharsets;
@@ -20,6 +22,8 @@ public class GameSimulationThread implements Runnable {
2022
private final DataOutputStream output;
2123
private final ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
2224
private boolean ranSetup = false;
25+
@Setter
26+
private JPanel mapVisualiser;
2327

2428
@Override
2529
public void run() {
@@ -43,6 +47,10 @@ public void run() {
4347
if (state != null && state.tick() > currentTickToProcess) {
4448
// Process the next tick
4549
currentTickToProcess = state.tick();
50+
// Update the map visualiser
51+
if (mapVisualiser != null) {
52+
mapVisualiser.repaint();
53+
}
4654

4755
// Remove any message from the queue that is older than the current tick
4856
long finalCurrentTickToProcess = currentTickToProcess;

src/main/java/dev/zwazel/internal/PublicGameWorld.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import dev.zwazel.internal.message.data.GameConfig;
1111
import dev.zwazel.internal.message.data.GameState;
1212

13+
import javax.swing.*;
1314
import java.util.List;
1415
import java.util.Optional;
1516

@@ -147,4 +148,6 @@ default List<MessageContainer> getIncomingMessages(Class<? extends MessageData>
147148
default Optional<TankConfig> getTankConfig(TankType tankType) {
148149
return getGameConfig().getTankConfig(tankType);
149150
}
151+
152+
void registerVisualiser(JPanel panel);
150153
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package dev.zwazel.internal.debug;
2+
3+
import dev.zwazel.internal.PublicGameWorld;
4+
import dev.zwazel.internal.game.map.MapDefinition;
5+
import dev.zwazel.internal.game.transform.Vec3;
6+
import dev.zwazel.internal.game.utils.Node;
7+
import lombok.Data;
8+
import lombok.EqualsAndHashCode;
9+
10+
import javax.swing.*;
11+
import java.awt.*;
12+
import java.awt.event.KeyAdapter;
13+
import java.awt.event.KeyEvent;
14+
import java.util.LinkedList;
15+
16+
@EqualsAndHashCode(callSuper = true)
17+
@Data
18+
public class MapVisualiser extends JPanel {
19+
private final PublicGameWorld world;
20+
private final int CELL_SIZE = 50;
21+
private DrawingMode drawingMode = DrawingMode.HEIGHT;
22+
private LinkedList<Node> path = new LinkedList<>();
23+
24+
public void showMap() {
25+
int width = ((int) world.getGameConfig().mapDefinition().width() + 1) * CELL_SIZE;
26+
int height = ((int) world.getGameConfig().mapDefinition().depth() + 1) * CELL_SIZE;
27+
28+
JFrame frame = new JFrame("Map Visualiser");
29+
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
30+
frame.setSize(width, height);
31+
frame.getContentPane().add(this);
32+
frame.setLocationRelativeTo(null);
33+
frame.setVisible(true);
34+
35+
// Add key listener to switch drawing modes
36+
frame.addKeyListener(new KeyAdapter() {
37+
@Override
38+
public void keyPressed(KeyEvent e) {
39+
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
40+
toggleDrawingMode();
41+
}
42+
}
43+
});
44+
}
45+
46+
// Drawing the map
47+
@Override
48+
public void paintComponent(Graphics g) {
49+
super.paintComponent(g);
50+
Graphics2D g2d = (Graphics2D) g;
51+
MapDefinition mapDefinition = world.getGameConfig().mapDefinition();
52+
53+
switch (drawingMode) {
54+
case HEIGHT -> drawHeightMap(g2d, mapDefinition);
55+
case PATH -> drawPath(g2d, mapDefinition);
56+
}
57+
58+
// Draw cell borders
59+
drawCellBorders(g2d, mapDefinition);
60+
}
61+
62+
private void drawHeightMap(Graphics2D g2d, MapDefinition mapDefinition) {
63+
// Determine min and max heights to normalize cell colors
64+
float min = Float.MAX_VALUE;
65+
float max = Float.MIN_VALUE;
66+
for (int x = 0; x < mapDefinition.width(); x++) {
67+
for (int y = 0; y < mapDefinition.depth(); y++) {
68+
float tileHeight = mapDefinition.tiles()[y][x];
69+
if (tileHeight < min) {
70+
min = tileHeight;
71+
}
72+
if (tileHeight > max) {
73+
max = tileHeight;
74+
}
75+
}
76+
}
77+
78+
// First Pass: Draw each cell with a color gradient based on its height.
79+
for (int x = 0; x < mapDefinition.width(); x++) {
80+
for (int y = 0; y < mapDefinition.depth(); y++) {
81+
float tileHeight = mapDefinition.tiles()[y][x];
82+
float normalizedHeight = (tileHeight - min) / (max - min);
83+
// Draw cell
84+
Color cellColor = new Color(normalizedHeight, 1 - normalizedHeight, 0);
85+
g2d.setColor(cellColor);
86+
g2d.fillRect(
87+
x * CELL_SIZE,
88+
y * CELL_SIZE,
89+
CELL_SIZE,
90+
CELL_SIZE
91+
);
92+
}
93+
}
94+
95+
// Second Pass: Draw tank
96+
drawTank(g2d, world);
97+
98+
// Third Pass: Draw heights
99+
for (int x = 0; x < mapDefinition.width(); x++) {
100+
for (int y = 0; y < mapDefinition.depth(); y++) {
101+
float tileHeight = mapDefinition.tiles()[y][x];
102+
g2d.setColor(Color.BLACK);
103+
g2d.drawString(
104+
String.format("%.2f", tileHeight),
105+
x * CELL_SIZE + 5,
106+
y * CELL_SIZE + 15
107+
);
108+
}
109+
}
110+
}
111+
112+
private void drawPath(Graphics2D g2d, MapDefinition mapDefinition) {
113+
// First pass: Draw Tank
114+
drawTank(g2d, world);
115+
116+
// Second pass: Draw start and end cells
117+
if (!path.isEmpty()) {
118+
Node startNode = path.getFirst();
119+
Node endNode = path.getLast();
120+
121+
// Draw start cell in green
122+
g2d.setColor(Color.GREEN);
123+
g2d.fillRect(
124+
startNode.getX() * CELL_SIZE,
125+
startNode.getY() * CELL_SIZE,
126+
CELL_SIZE,
127+
CELL_SIZE
128+
);
129+
130+
// Draw end cell in red
131+
g2d.setColor(Color.RED);
132+
g2d.fillRect(
133+
endNode.getX() * CELL_SIZE,
134+
endNode.getY() * CELL_SIZE,
135+
CELL_SIZE,
136+
CELL_SIZE
137+
);
138+
}
139+
140+
// Third pass: Draw Path
141+
g2d.setColor(Color.RED);
142+
for (int i = 0; i < path.size() - 1; i++) {
143+
Node node1 = path.get(i);
144+
Node node2 = path.get(i + 1);
145+
g2d.drawLine(
146+
node1.getX() * CELL_SIZE + CELL_SIZE / 2,
147+
node1.getY() * CELL_SIZE + CELL_SIZE / 2,
148+
node2.getX() * CELL_SIZE + CELL_SIZE / 2,
149+
node2.getY() * CELL_SIZE + CELL_SIZE / 2
150+
);
151+
}
152+
153+
// Fourth pass: Draw costs
154+
for (Node node : path) {
155+
g2d.setColor(Color.BLACK);
156+
g2d.drawString(
157+
String.format("%.2f", node.getCost()),
158+
node.getX() * CELL_SIZE + 5,
159+
node.getY() * CELL_SIZE + 15
160+
);
161+
}
162+
}
163+
164+
private void drawCellBorders(Graphics2D g2d, MapDefinition mapDefinition) {
165+
for (int x = 0; x < mapDefinition.width(); x++) {
166+
for (int y = 0; y < mapDefinition.depth(); y++) {
167+
// Draw cell border
168+
g2d.setColor(Color.BLACK);
169+
g2d.drawRect(
170+
x * CELL_SIZE,
171+
y * CELL_SIZE,
172+
CELL_SIZE,
173+
CELL_SIZE
174+
);
175+
}
176+
}
177+
}
178+
179+
private void drawTank(Graphics2D g2d, PublicGameWorld world) {
180+
// Draw location of my tank (drawing the cell blue, and the actual position as a point)
181+
Vec3 myPosition = world.getMyState().transformBody().getTranslation();
182+
Vec3 closestTile = world.getGameConfig().mapDefinition().getClosestTileFromWorld(myPosition);
183+
184+
g2d.setColor(Color.BLUE);
185+
g2d.fillRect(
186+
(int) closestTile.getX() * CELL_SIZE,
187+
(int) closestTile.getZ() * CELL_SIZE,
188+
CELL_SIZE,
189+
CELL_SIZE
190+
);
191+
192+
// Turn the position from float to int, so it can be drawn. from units to pixels
193+
int x = (int) (myPosition.getX() * CELL_SIZE);
194+
int y = (int) (myPosition.getZ() * CELL_SIZE);
195+
196+
g2d.setColor(Color.ORANGE);
197+
g2d.fillOval(
198+
x,
199+
y,
200+
15,
201+
15
202+
);
203+
}
204+
205+
private void toggleDrawingMode() {
206+
DrawingMode[] modes = DrawingMode.values();
207+
int currentIndex = drawingMode.ordinal();
208+
int nextIndex = (currentIndex + 1) % modes.length;
209+
drawingMode = modes[nextIndex];
210+
repaint();
211+
}
212+
213+
// Enum for switching drawing modes
214+
public enum DrawingMode {
215+
HEIGHT,
216+
PATH
217+
}
218+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
package dev.zwazel.internal.game.misc;
22

33
public record SimplifiedRGB(float r, float g, float b) {
4+
public java.awt.Color toColor() {
5+
return new java.awt.Color(r, g, b);
6+
}
47
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package dev.zwazel.internal.game.utils;
2+
3+
import lombok.Getter;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.Setter;
6+
7+
import java.util.LinkedList;
8+
9+
@Getter
10+
@Setter
11+
@RequiredArgsConstructor
12+
public class Node {
13+
private final float height;
14+
private final int x;
15+
private final int y;
16+
private final LinkedList<Node> neighbours = new LinkedList<>();
17+
private Node parent;
18+
private double cost = Double.MAX_VALUE;
19+
20+
@Override
21+
public String toString() {
22+
return "Node{" +
23+
"height=" + height +
24+
", x=" + x +
25+
", y=" + y +
26+
", cost=" + cost +
27+
'}';
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.zwazel.internal.game.utils;
2+
3+
import java.util.Comparator;
4+
5+
public class NodeComparator implements Comparator<Node> {
6+
@Override
7+
public int compare(Node o1, Node o2) {
8+
return Double.compare(o1.getCost(), o2.getCost());
9+
}
10+
}

0 commit comments

Comments
 (0)