From 01c585124c79f29e916f23147c40fd6ba901610b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 13:57:24 +0000 Subject: [PATCH 1/5] Initial plan From 5cb45db9c6ea7453c5780eb55be115016f87059e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:03:32 +0000 Subject: [PATCH 2/5] Add Java conversion of C++ Same Game project Co-authored-by: codervaruns <169798268+codervaruns@users.noreply.github.com> --- .gitignore | 6 + BUILD_JAVA.md | 225 ++++++++++++ pom.xml | 72 ++++ src/main/java/com/samegame/Node.java | 21 ++ src/main/java/com/samegame/SameGame.java | 361 ++++++++++++++++++++ src/main/java/com/samegame/SameGameGUI.java | 343 +++++++++++++++++++ src/main/java/com/samegame/TestGraph.java | 157 +++++++++ 7 files changed, 1185 insertions(+) create mode 100644 BUILD_JAVA.md create mode 100644 pom.xml create mode 100644 src/main/java/com/samegame/Node.java create mode 100644 src/main/java/com/samegame/SameGame.java create mode 100644 src/main/java/com/samegame/SameGameGUI.java create mode 100644 src/main/java/com/samegame/TestGraph.java diff --git a/.gitignore b/.gitignore index 3317412..84e3573 100644 --- a/.gitignore +++ b/.gitignore @@ -9,11 +9,17 @@ test_graph # Build directories build/ output/ +target/ # Object files *.o *.obj +# Java compiled files +*.class +*.jar +!pom.xml + # Debug files *.dSYM/ *.su diff --git a/BUILD_JAVA.md b/BUILD_JAVA.md new file mode 100644 index 0000000..be43284 --- /dev/null +++ b/BUILD_JAVA.md @@ -0,0 +1,225 @@ +# Same Game - Java Version Build Instructions + +## Prerequisites + +### Required Software +1. **Java Development Kit (JDK) 11 or higher** + - Download from: https://adoptium.net/ or https://www.oracle.com/java/technologies/downloads/ + - Verify installation: `java -version` and `javac -version` + +2. **Maven 3.6 or higher** (for building with Maven) + - Download from: https://maven.apache.org/download.cgi + - Verify installation: `mvn -version` + +## Project Structure + +``` +Same-Game/ +├── src/ +│ └── main/ +│ └── java/ +│ └── com/ +│ └── samegame/ +│ ├── Node.java # Node structure for graph +│ ├── SameGame.java # Game logic class +│ ├── SameGameGUI.java # Swing GUI implementation +│ └── TestGraph.java # Test suite +├── pom.xml # Maven build configuration +└── BUILD_JAVA.md # This file +``` + +## Building the Project + +### Option 1: Using Maven (Recommended) + +#### Compile the project: +```bash +mvn clean compile +``` + +#### Create executable JAR: +```bash +mvn clean package +``` + +This will create two JAR files in the `target/` directory: +- `same-game-1.0.0.jar` - Regular JAR +- `same-game-1.0.0-shaded.jar` - Fat JAR with all dependencies (not needed for this project) + +#### Run the game: +```bash +java -jar target/same-game-1.0.0.jar +``` + +Or: +```bash +mvn exec:java -Dexec.mainClass="com.samegame.SameGameGUI" +``` + +### Option 2: Using javac (Manual Compilation) + +#### Compile all Java files: +```bash +cd src/main/java +javac com/samegame/*.java +``` + +#### Run the GUI: +```bash +java com.samegame.SameGameGUI +``` + +#### Run the tests: +```bash +java -ea com.samegame.TestGraph +``` + +Note: The `-ea` flag enables assertions for the test suite. + +### Option 3: Using an IDE + +#### IntelliJ IDEA: +1. Open the project folder in IntelliJ IDEA +2. The IDE will automatically detect the Maven project +3. Right-click on `SameGameGUI.java` and select "Run 'SameGameGUI.main()'" + +#### Eclipse: +1. Import as "Existing Maven Project" +2. Right-click on `SameGameGUI.java` and select "Run As > Java Application" + +#### VS Code: +1. Install "Extension Pack for Java" +2. Open the project folder +3. Press F5 or click "Run" on `SameGameGUI.java` + +## Running the Application + +### Run the GUI Game: +```bash +# Using Maven +mvn exec:java -Dexec.mainClass="com.samegame.SameGameGUI" + +# Or run the JAR +java -jar target/same-game-1.0.0.jar + +# Or directly with java +cd src/main/java +java com.samegame.SameGameGUI +``` + +### Run the Test Suite: +```bash +# Using Maven +mvn exec:java -Dexec.mainClass="com.samegame.TestGraph" + +# Or directly with java (with assertions enabled) +cd src/main/java +java -ea com.samegame.TestGraph +``` + +## Controls + +- **Mouse Hover**: Highlight clusters of same-colored tiles +- **Left Click**: Remove a cluster (minimum 2 tiles of same color) +- **R Key**: Restart game with a new random grid +- **ESC Key**: Quit the game + +## Game Rules + +1. The game starts with a 6x8 grid of colored tiles (5 colors: Green, White, Red, Blue, Yellow) +2. Player and computer take turns +3. Click on a cluster of 2 or more adjacent tiles of the same color to remove them +4. Score is calculated as: `(cluster_size - 2)²` +5. After removing tiles: + - Remaining tiles fall down (vertical gravity) + - Empty columns shift left (horizontal gravity) +6. Game ends when no more valid moves are available +7. Winner is determined by highest score + +## Features + +### Core Game Logic (SameGame.java) +- Graph-based tile representation +- BFS cluster detection algorithm +- Gravity system (vertical and horizontal) +- Turn-based gameplay (User vs Computer) +- Greedy AI for computer moves + +### GUI (SameGameGUI.java) +- Java Swing-based graphical interface +- Mouse hover highlights for clusters +- Visual tile colors and borders +- Score display for both players +- Turn indicator +- Game over detection and restart + +### Testing (TestGraph.java) +- Comprehensive test suite +- Tests initialization, cluster detection, gravity, and game logic +- Validates User vs Computer gameplay + +## Differences from C++ Version + +### Technology Changes: +- **C++ → Java**: Language conversion +- **SDL2 → Java Swing**: GUI framework change +- **Manual memory management → Garbage collection**: Memory handling +- **Headers (.h/.cpp) → Single files (.java)**: File structure + +### Code Equivalents: +- `vector` → `List` / `ArrayList` +- `unordered_map` → `HashMap` +- `unordered_set` → `HashSet` +- `queue` → `Queue` / `LinkedList` +- `pair` → Custom `Pair` class +- `tuple` → Custom `ClusterInfo` class +- Pointers (`*`) → Object references + +### GUI Differences: +- SDL2 rendering → Java Swing components +- SDL2 events → Java AWT event listeners +- SDL_Rect → java.awt.Rectangle +- SDL_Color → java.awt.Color +- TTF fonts → Java Font class + +## Troubleshooting + +### Issue: "java: package does not exist" +**Solution**: Make sure you're in the correct directory or use Maven to handle compilation. + +### Issue: Maven not found +**Solution**: Install Maven and add it to your system PATH. + +### Issue: Java version mismatch +**Solution**: Ensure JDK 11 or higher is installed and set as JAVA_HOME. + +### Issue: Assertions not working in tests +**Solution**: Run with `-ea` flag to enable assertions: `java -ea com.samegame.TestGraph` + +### Issue: GUI not displaying correctly +**Solution**: Make sure you have a display environment. On Linux, you may need to set DISPLAY variable. + +## Performance Notes + +- The Java version uses similar algorithms as the C++ version +- Java Swing provides smooth 60 FPS rendering via Timer +- Garbage collection handles memory automatically +- Performance is comparable for this game's scale (6x8 grid) + +## Development + +### Adding New Features: +1. Modify the appropriate Java class +2. Recompile: `mvn clean compile` +3. Test your changes +4. Rebuild: `mvn clean package` + +### Code Style: +- Follows Java naming conventions (camelCase for methods/variables) +- Classes start with uppercase letters +- Package name: `com.samegame` +- Proper encapsulation with private/public modifiers + +## License + +This is a direct conversion from the original C++ implementation, maintaining the same game logic and structure. diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..18e050f --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.samegame + same-game + 1.0.0 + jar + + Same Game + Same Game implementation in Java with Swing GUI + + + UTF-8 + 11 + 11 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 11 + 11 + + + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + com.samegame.SameGameGUI + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + + package + + shade + + + + + com.samegame.SameGameGUI + + + + + + + + + diff --git a/src/main/java/com/samegame/Node.java b/src/main/java/com/samegame/Node.java new file mode 100644 index 0000000..09c4e39 --- /dev/null +++ b/src/main/java/com/samegame/Node.java @@ -0,0 +1,21 @@ +package com.samegame; + +import java.util.ArrayList; +import java.util.List; + +// Node structure for graph representation +public class Node { + public int row; + public int col; + public char color; + public boolean active; + public List neighbors; // indices of adjacent nodes + + public Node(int r, int c, char clr) { + this.row = r; + this.col = c; + this.color = clr; + this.active = true; + this.neighbors = new ArrayList<>(); + } +} diff --git a/src/main/java/com/samegame/SameGame.java b/src/main/java/com/samegame/SameGame.java new file mode 100644 index 0000000..3842ec4 --- /dev/null +++ b/src/main/java/com/samegame/SameGame.java @@ -0,0 +1,361 @@ +package com.samegame; + +import java.util.*; + +public class SameGame { + private List nodes; // Graph nodes + private Map> posToNodeIndex; // maps (row, col) to node index + private int rows; + private int cols; + private int score; + private int moves; + + // User vs Computer mode + private boolean isUserTurn; // true = user's turn, false = computer's turn + private int userScore; + private int computerScore; + + // Constructor + public SameGame(char[][] initialGrid) { + this.nodes = new ArrayList<>(); + this.posToNodeIndex = new HashMap<>(); + reset(initialGrid); + } + + // Game state queries + public int getRows() { return rows; } + public int getCols() { return cols; } + public int getScore() { return score; } + public int getMoves() { return moves; } + + public char getTile(int row, int col) { + int nodeIdx = getNodeIndex(row, col); + if (nodeIdx != -1) { + return nodes.get(nodeIdx).color; + } + return '\0'; + } + + public boolean isTileActive(int row, int col) { + int nodeIdx = getNodeIndex(row, col); + if (nodeIdx != -1) { + return nodes.get(nodeIdx).active; + } + return false; + } + + public boolean getUserTurn() { return isUserTurn; } + public int getUserScore() { return userScore; } + public int getComputerScore() { return computerScore; } + + // Game logic + public List> getCluster(int row, int col) { + return detectClusterBFS(row, col); + } + + public int getClusterSize(int row, int col) { + return detectClusterBFS(row, col).size(); + } + + public boolean removeCluster(int row, int col) { + List> cluster = detectClusterBFS(row, col); + + // Only remove clusters of size 2 or more + if (cluster.size() < 2) { + return false; + } + + // Remove tiles in cluster + for (Pair tile : cluster) { + int nodeIdx = getNodeIndex(tile.getFirst(), tile.getSecond()); + if (nodeIdx != -1) { + nodes.get(nodeIdx).active = false; + } + } + + // Calculate score: (size - 2)^2 + int clusterSize = cluster.size(); + int points = (clusterSize - 2) * (clusterSize - 2); + score += points; + + // Add to appropriate player's score + if (isUserTurn) { + userScore += points; + } else { + computerScore += points; + } + + moves++; + + // Switch turns after successful move + switchTurn(); + + // Apply gravity + applyGravity(); + + return true; + } + + public boolean hasMovesLeft() { + for (Node node : nodes) { + if (node.active) { + int clusterSize = getClusterSize(node.row, node.col); + if (clusterSize >= 2) { + return true; + } + } + } + return false; + } + + public void reset(char[][] initialGrid) { + buildGraph(initialGrid); + score = 0; + moves = 0; + isUserTurn = true; // User goes first + userScore = 0; + computerScore = 0; + } + + public void switchTurn() { isUserTurn = !isUserTurn; } + + // Get all available clusters + public List getAllClusters() { + List clusters = new ArrayList<>(); + Set visited = new HashSet<>(); + + for (int nodeIdx = 0; nodeIdx < nodes.size(); nodeIdx++) { + Node node = nodes.get(nodeIdx); + if (node.active && !visited.contains(nodeIdx)) { + List> cluster = detectClusterBFS(node.row, node.col); + + // Mark all tiles in cluster as visited + for (Pair tile : cluster) { + int idx = getNodeIndex(tile.getFirst(), tile.getSecond()); + if (idx != -1) { + visited.add(idx); + } + } + + // Only include clusters of size >= 2 + if (cluster.size() >= 2) { + clusters.add(new ClusterInfo(cluster.size(), node.color, node.row, node.col)); + } + } + } + + return clusters; + } + + // Computer AI - greedy algorithm + public Pair getBestMove() { + // Get all available clusters + List clusters = getAllClusters(); + + if (clusters.isEmpty()) { + return new Pair<>(-1, -1); // No moves available + } + + // Sort by size (descending) - greedy strategy picks largest cluster + clusters.sort((a, b) -> Integer.compare(b.size, a.size)); + + // Return the position of the largest cluster + ClusterInfo best = clusters.get(0); + return new Pair<>(best.row, best.col); + } + + // Helper functions + private void buildGraph(char[][] initialGrid) { + nodes.clear(); + posToNodeIndex.clear(); + + rows = initialGrid.length; + cols = rows > 0 ? initialGrid[0].length : 0; + + // Create nodes for each tile + int index = 0; + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + nodes.add(new Node(i, j, initialGrid[i][j])); + if (!posToNodeIndex.containsKey(i)) { + posToNodeIndex.put(i, new HashMap<>()); + } + posToNodeIndex.get(i).put(j, index); + index++; + } + } + + // Build adjacency relationships + updateNeighbors(); + } + + private void updateNeighbors() { + // Clear all neighbor lists + for (Node node : nodes) { + node.neighbors.clear(); + } + + // Rebuild neighbors based on current positions + int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + for (Node node : nodes) { + if (!node.active) continue; + + int row = node.row; + int col = node.col; + + for (int[] dir : directions) { + int newRow = row + dir[0]; + int newCol = col + dir[1]; + + if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { + int neighborIdx = getNodeIndex(newRow, newCol); + if (neighborIdx != -1 && nodes.get(neighborIdx).active) { + node.neighbors.add(neighborIdx); + } + } + } + } + } + + private int getNodeIndex(int row, int col) { + Map rowMap = posToNodeIndex.get(row); + if (rowMap != null) { + Integer colIdx = rowMap.get(col); + if (colIdx != null) { + return colIdx; + } + } + return -1; + } + + private List> detectClusterBFS(int startRow, int startCol) { + List> cluster = new ArrayList<>(); + + int startNodeIdx = getNodeIndex(startRow, startCol); + if (startNodeIdx == -1 || !nodes.get(startNodeIdx).active) { + return cluster; + } + + char color = nodes.get(startNodeIdx).color; + Set visited = new HashSet<>(); + Queue q = new LinkedList<>(); + + q.add(startNodeIdx); + visited.add(startNodeIdx); + cluster.add(new Pair<>(nodes.get(startNodeIdx).row, nodes.get(startNodeIdx).col)); + + int[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}}; + + while (!q.isEmpty()) { + int currentIdx = q.poll(); + Node currentNode = nodes.get(currentIdx); + int x = currentNode.row; + int y = currentNode.col; + + for (int[] dir : directions) { + int newX = x + dir[0]; + int newY = y + dir[1]; + + int neighborIdx = getNodeIndex(newX, newY); + if (neighborIdx != -1 && !visited.contains(neighborIdx) && + nodes.get(neighborIdx).active && nodes.get(neighborIdx).color == color) { + + visited.add(neighborIdx); + q.add(neighborIdx); + cluster.add(new Pair<>(nodes.get(neighborIdx).row, nodes.get(neighborIdx).col)); + } + } + } + + return cluster; + } + + private void applyGravity() { + // Vertical gravity: tiles fall down + for (int j = 0; j < cols; j++) { + int pos = rows - 1; + for (int i = rows - 1; i >= 0; i--) { + int nodeIdx = getNodeIndex(i, j); + if (nodeIdx != -1 && nodes.get(nodeIdx).active) { + if (i != pos) { + // Move node to new position + Node node = nodes.get(nodeIdx); + + // Update position mapping + posToNodeIndex.get(i).remove(j); + posToNodeIndex.get(pos).put(j, nodeIdx); + + // Update node position + node.row = pos; + node.col = j; + } + pos--; + } + } + } + + // Horizontal gravity: empty columns shift left + int col = 0; + for (int j = 0; j < cols; j++) { + boolean hasTiles = false; + for (int i = 0; i < rows; i++) { + int nodeIdx = getNodeIndex(i, j); + if (nodeIdx != -1 && nodes.get(nodeIdx).active) { + hasTiles = true; + break; + } + } + + if (hasTiles) { + if (j != col) { + for (int i = 0; i < rows; i++) { + int nodeIdx = getNodeIndex(i, j); + if (nodeIdx != -1) { + Node node = nodes.get(nodeIdx); + + // Update position mapping + posToNodeIndex.get(i).remove(j); + posToNodeIndex.get(i).put(col, nodeIdx); + + // Update node position + node.col = col; + } + } + } + col++; + } + } + + // Update neighbor relationships after gravity + updateNeighbors(); + } + + // Helper classes + public static class Pair { + private final F first; + private final S second; + + public Pair(F first, S second) { + this.first = first; + this.second = second; + } + + public F getFirst() { return first; } + public S getSecond() { return second; } + } + + public static class ClusterInfo { + public int size; + public char color; + public int row; + public int col; + + public ClusterInfo(int size, char color, int row, int col) { + this.size = size; + this.color = color; + this.row = row; + this.col = col; + } + } +} diff --git a/src/main/java/com/samegame/SameGameGUI.java b/src/main/java/com/samegame/SameGameGUI.java new file mode 100644 index 0000000..d354df4 --- /dev/null +++ b/src/main/java/com/samegame/SameGameGUI.java @@ -0,0 +1,343 @@ +package com.samegame; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.List; +import java.util.Random; + +public class SameGameGUI extends JFrame { + private static final int WINDOW_WIDTH = 750; + private static final int WINDOW_HEIGHT = 650; + private static final int TILE_SIZE = 80; + private static final int GRID_OFFSET_X = 80; + private static final int GRID_OFFSET_Y = 140; + private static final int UI_HEIGHT = 120; + + private SameGame game; + private GamePanel gamePanel; + + // Mouse state + private int hoveredRow = -1; + private int hoveredCol = -1; + private List> hoveredCluster = null; + + // Game state + private boolean gameOver = false; + private boolean gameWon = false; + + // Computer AI timing + private long lastComputerMoveTime = 0; + private static final long COMPUTER_MOVE_DELAY = 3000; // 3 second delay for computer move + private Timer gameTimer; + + public SameGameGUI(SameGame game) { + this.game = game; + + setTitle("Same Game"); + setSize(WINDOW_WIDTH, WINDOW_HEIGHT); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setResizable(false); + + gamePanel = new GamePanel(); + add(gamePanel); + + // Setup key listeners + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_R) { + handleRestart(); + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + System.exit(0); + } + } + }); + + // Setup game timer for computer moves + gameTimer = new Timer(16, e -> { + // Execute computer move if it's computer's turn and enough time has passed + if (!gameOver && !game.getUserTurn()) { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastComputerMoveTime >= COMPUTER_MOVE_DELAY) { + executeComputerMove(); + lastComputerMoveTime = currentTime; + } + } + gamePanel.repaint(); + }); + gameTimer.start(); + + lastComputerMoveTime = System.currentTimeMillis(); + } + + private Color getColorForTile(char tile) { + switch(tile) { + case 'G': return new Color(0, 200, 0); // Green + case 'W': return new Color(255, 255, 255); // White + case 'R': return new Color(200, 0, 0); // Red + case 'B': return new Color(0, 0, 200); // Blue + case 'Y': return new Color(200, 200, 0); // Yellow + default: return new Color(128, 128, 128); // Gray + } + } + + private void handleMouseMove(int mouseX, int mouseY) { + int col = (mouseX - GRID_OFFSET_X) / TILE_SIZE; + int row = (mouseY - GRID_OFFSET_Y) / TILE_SIZE; + + if (row >= 0 && row < game.getRows() && col >= 0 && col < game.getCols()) { + if (row != hoveredRow || col != hoveredCol) { + hoveredRow = row; + hoveredCol = col; + + if (row >= 0 && col >= 0 && game.isTileActive(row, col)) { + hoveredCluster = game.getCluster(row, col); + } else { + hoveredCluster = null; + } + } + } else { + hoveredRow = -1; + hoveredCol = -1; + hoveredCluster = null; + } + } + + private void handleMouseClick(int mouseX, int mouseY) { + if (gameOver || !game.getUserTurn()) return; // Only allow clicks on user's turn + + int col = (mouseX - GRID_OFFSET_X) / TILE_SIZE; + int row = (mouseY - GRID_OFFSET_Y) / TILE_SIZE; + + if (row >= 0 && row < game.getRows() && col >= 0 && col < game.getCols()) { + if (game.removeCluster(row, col)) { + // Clear hovered state after move + hoveredCluster = null; + + // Check if game is over + if (!game.hasMovesLeft()) { + gameOver = true; + // Check if won (no tiles left or very few) + int activeTiles = 0; + for (int i = 0; i < game.getRows(); i++) { + for (int j = 0; j < game.getCols(); j++) { + if (game.isTileActive(i, j)) activeTiles++; + } + } + gameWon = (activeTiles == 0); + } + } + } + } + + private void handleRestart() { + char[][] initialGrid = generateRandomGrid(6, 8); + game.reset(initialGrid); + gameOver = false; + gameWon = false; + hoveredCluster = null; + lastComputerMoveTime = System.currentTimeMillis(); + } + + private void executeComputerMove() { + if (gameOver || game.getUserTurn()) return; + + // Get the best move using greedy algorithm + SameGame.Pair bestMove = game.getBestMove(); + + if (bestMove.getFirst() != -1 && bestMove.getSecond() != -1) { + // Execute the move + game.removeCluster(bestMove.getFirst(), bestMove.getSecond()); + + // Check if game is over + if (!game.hasMovesLeft()) { + gameOver = true; + int activeTiles = 0; + for (int i = 0; i < game.getRows(); i++) { + for (int j = 0; j < game.getCols(); j++) { + if (game.isTileActive(i, j)) activeTiles++; + } + } + gameWon = (activeTiles == 0); + } + } + } + + private static char[][] generateRandomGrid(int rows, int cols) { + char[][] grid = new char[rows][cols]; + char[] colors = {'G', 'W', 'R', 'B', 'Y'}; // Green, White, Red, Blue, Yellow + + Random rand = new Random(); + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + grid[i][j] = colors[rand.nextInt(colors.length)]; + } + } + + return grid; + } + + class GamePanel extends JPanel { + public GamePanel() { + setBackground(new Color(20, 20, 20)); + + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + handleMouseMove(e.getX(), e.getY()); + repaint(); + } + }); + + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + handleMouseClick(e.getX(), e.getY()); + repaint(); + } + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + drawGrid(g2d); + drawUI(g2d); + } + + private void drawGrid(Graphics2D g) { + // Draw background + g.setColor(new Color(40, 40, 40)); + g.fillRect(GRID_OFFSET_X - 5, GRID_OFFSET_Y - 5, + game.getCols() * TILE_SIZE + 10, + game.getRows() * TILE_SIZE + 10); + + // Draw tiles + for (int i = 0; i < game.getRows(); i++) { + for (int j = 0; j < game.getCols(); j++) { + boolean highlight = false; + + // Check if this tile is in the hovered cluster + if (hoveredCluster != null) { + for (SameGame.Pair tile : hoveredCluster) { + if (tile.getFirst() == i && tile.getSecond() == j) { + highlight = true; + break; + } + } + } + + drawTile(g, i, j, highlight); + } + } + } + + private void drawTile(Graphics2D g, int row, int col, boolean highlight) { + if (!game.isTileActive(row, col)) return; + + int x = GRID_OFFSET_X + col * TILE_SIZE; + int y = GRID_OFFSET_Y + row * TILE_SIZE; + + Color color = getColorForTile(game.getTile(row, col)); + + // Draw tile background + g.setColor(color); + g.fillRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4); + + // Draw highlight border if needed + if (highlight) { + g.setColor(new Color(255, 255, 0)); + for (int i = 0; i < 4; i++) { + g.drawRect(x + i, y + i, TILE_SIZE - 2*i, TILE_SIZE - 2*i); + } + } else { + // Draw normal border + g.setColor(new Color(64, 64, 64)); + g.drawRect(x, y, TILE_SIZE, TILE_SIZE); + } + } + + private void drawUI(Graphics2D g) { + g.setFont(new Font("Arial", Font.BOLD, 24)); + + // Draw turn indicator + String turnText = game.getUserTurn() ? "YOUR TURN" : "COMPUTER'S TURN"; + Color turnColor = game.getUserTurn() ? new Color(100, 255, 100) : new Color(255, 100, 100); + g.setColor(turnColor); + g.drawString(turnText, 20, 40); + + // Draw user score + g.setColor(new Color(100, 200, 255)); + g.drawString("User Score: " + game.getUserScore(), 20, 70); + + // Draw computer score + g.setColor(new Color(255, 150, 100)); + g.drawString("Computer Score: " + game.getComputerScore(), 20, 100); + + // Draw total moves + g.setColor(new Color(200, 200, 200)); + g.drawString("Total Moves: " + game.getMoves(), 300, 70); + + // Draw cluster size if hovering + if (hoveredCluster != null && hoveredCluster.size() >= 2) { + int clusterSize = hoveredCluster.size(); + int points = (clusterSize - 2) * (clusterSize - 2); + String clusterText = "Cluster: " + clusterSize + " tiles (" + points + " pts)"; + g.setColor(new Color(255, 255, 0)); + g.drawString(clusterText, 250, 40); + } + + // Show "Thinking..." when computer is about to move + if (!gameOver && !game.getUserTurn()) { + g.setColor(new Color(255, 200, 100)); + g.drawString("Thinking...", 250, 70); + } + + // Draw game over message + if (gameOver) { + String message; + if (gameWon) { + message = "All tiles cleared!"; + } else if (game.getUserScore() > game.getComputerScore()) { + message = "USER WINS! " + game.getUserScore() + " - " + game.getComputerScore(); + } else if (game.getComputerScore() > game.getUserScore()) { + message = "COMPUTER WINS! " + game.getComputerScore() + " - " + game.getUserScore(); + } else { + message = "TIE GAME! " + game.getUserScore() + " - " + game.getComputerScore(); + } + + g.setFont(new Font("Arial", Font.BOLD, 28)); + g.setColor(new Color(255, 100, 100)); + FontMetrics fm = g.getFontMetrics(); + int msgWidth = fm.stringWidth(message); + g.drawString(message, WINDOW_WIDTH / 2 - msgWidth / 2, WINDOW_HEIGHT - 70); + + String restartText = "Press R to Restart"; + g.setFont(new Font("Arial", Font.PLAIN, 20)); + g.setColor(new Color(200, 200, 200)); + fm = g.getFontMetrics(); + int restartWidth = fm.stringWidth(restartText); + g.drawString(restartText, WINDOW_WIDTH / 2 - restartWidth / 2, WINDOW_HEIGHT - 40); + } + } + } + + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + // Initialize game with random grid + char[][] initialGrid = generateRandomGrid(6, 8); + + SameGame game = new SameGame(initialGrid); + SameGameGUI gui = new SameGameGUI(game); + + gui.setVisible(true); + }); + } +} diff --git a/src/main/java/com/samegame/TestGraph.java b/src/main/java/com/samegame/TestGraph.java new file mode 100644 index 0000000..2456813 --- /dev/null +++ b/src/main/java/com/samegame/TestGraph.java @@ -0,0 +1,157 @@ +package com.samegame; + +public class TestGraph { + + private static void printGrid(SameGame game) { + System.out.println("Current Grid:"); + for (int i = 0; i < game.getRows(); i++) { + for (int j = 0; j < game.getCols(); j++) { + if (game.isTileActive(i, j)) { + System.out.print(game.getTile(i, j) + " "); + } else { + System.out.print(". "); + } + } + System.out.println(); + } + System.out.println(); + } + + public static void main(String[] args) { + System.out.println("Testing Graph-based SameGame implementation...\n"); + + // Test 1: Basic initialization + System.out.println("Test 1: Basic initialization"); + char[][] grid1 = { + {'G', 'G', 'W', 'R'}, + {'G', 'W', 'W', 'R'}, + {'W', 'W', 'W', 'R'} + }; + + SameGame game = new SameGame(grid1); + assert game.getRows() == 3; + assert game.getCols() == 4; + assert game.getScore() == 0; + assert game.getMoves() == 0; + + printGrid(game); + System.out.println("✓ Initialization test passed\n"); + + // Test 2: getTile and isTileActive + System.out.println("Test 2: getTile and isTileActive"); + assert game.getTile(0, 0) == 'G'; + assert game.getTile(0, 2) == 'W'; + assert game.getTile(2, 3) == 'R'; + assert game.isTileActive(0, 0) == true; + assert game.isTileActive(1, 1) == true; + System.out.println("✓ getTile and isTileActive tests passed\n"); + + // Test 3: Cluster detection + System.out.println("Test 3: Cluster detection"); + int clusterSize_0_0 = game.getClusterSize(0, 0); + System.out.println("Cluster size at (0,0) [Green]: " + clusterSize_0_0); + assert clusterSize_0_0 == 3; // 3 Green tiles connected + + int clusterSize_1_2 = game.getClusterSize(1, 2); + System.out.println("Cluster size at (1,2) [White]: " + clusterSize_1_2); + assert clusterSize_1_2 == 6; // 6 White tiles connected + + int clusterSize_0_3 = game.getClusterSize(0, 3); + System.out.println("Cluster size at (0,3) [Red]: " + clusterSize_0_3); + assert clusterSize_0_3 == 3; // 3 Red tiles connected + System.out.println("✓ Cluster detection tests passed\n"); + + // Test 4: Remove cluster and gravity + System.out.println("Test 4: Remove cluster and gravity"); + int scoreBeforeRemove = game.getScore(); + boolean removed = game.removeCluster(1, 2); // Remove white cluster (size 6) + assert removed == true; + + int expectedPoints = (6 - 2) * (6 - 2); // (size - 2)^2 = 16 + assert game.getScore() == scoreBeforeRemove + expectedPoints; + assert game.getMoves() == 1; + + printGrid(game); + System.out.println("Score: " + game.getScore() + ", Moves: " + game.getMoves()); + System.out.println("✓ Remove cluster test passed\n"); + + // Test 5: Check tiles after gravity + System.out.println("Test 5: Check tiles after gravity"); + // After removing white cluster, tiles should fall and shift left + // Green tiles should be at bottom left + assert game.isTileActive(2, 0) == true; + assert game.getTile(2, 0) == 'G'; + System.out.println("✓ Gravity test passed\n"); + + // Test 6: getAllClusters + System.out.println("Test 6: getAllClusters"); + java.util.List clusters = game.getAllClusters(); + System.out.println("Found " + clusters.size() + " clusters:"); + for (SameGame.ClusterInfo cluster : clusters) { + System.out.println(" Size: " + cluster.size + ", Color: " + cluster.color + + ", Position: (" + cluster.row + ", " + cluster.col + ")"); + } + assert clusters.size() >= 2; // Should have at least Green and Red clusters + System.out.println("✓ getAllClusters test passed\n"); + + // Test 7: hasMovesLeft + System.out.println("Test 7: hasMovesLeft"); + boolean hasMove = game.hasMovesLeft(); + System.out.println("Has moves left: " + (hasMove ? "Yes" : "No")); + assert hasMove == true; + System.out.println("✓ hasMovesLeft test passed\n"); + + // Test 8: Reset game + System.out.println("Test 8: Reset game"); + game.reset(grid1); + assert game.getScore() == 0; + assert game.getMoves() == 0; + assert game.getTile(0, 0) == 'G'; + assert game.isTileActive(1, 2) == true; + printGrid(game); + System.out.println("✓ Reset test passed\n"); + + // Test 9: Remove single tile (should fail) + System.out.println("Test 9: Attempt to remove single tile"); + char[][] grid2 = { + {'G', 'W', 'R'}, + {'W', 'R', 'G'}, + {'R', 'G', 'W'} + }; + game.reset(grid2); + printGrid(game); + boolean removedSingle = game.removeCluster(0, 0); // Try to remove single Green + assert removedSingle == false; + assert game.getScore() == 0; + assert game.getMoves() == 0; + System.out.println("✓ Single tile removal correctly rejected\n"); + + // Test 10: User vs Computer mode + System.out.println("Test 10: User vs Computer mode"); + game.reset(grid1); + assert game.getUserTurn() == true; + assert game.getUserScore() == 0; + assert game.getComputerScore() == 0; + + // User makes a move + game.removeCluster(0, 0); // Remove green cluster + assert game.getUserTurn() == false; // Should switch to computer + assert game.getUserScore() > 0; + + // Computer makes a move + SameGame.Pair bestMove = game.getBestMove(); + assert bestMove.getFirst() != -1; // Should find a move + game.removeCluster(bestMove.getFirst(), bestMove.getSecond()); + assert game.getUserTurn() == true; // Should switch back to user + assert game.getComputerScore() > 0; + + System.out.println("User score: " + game.getUserScore()); + System.out.println("Computer score: " + game.getComputerScore()); + System.out.println("✓ User vs Computer mode test passed\n"); + + System.out.println("=================================="); + System.out.println("All tests passed! ✓"); + System.out.println("Graph-based implementation working correctly!"); + System.out.println("=================================="); + } +} From 05de79b52607f7582311960b5e5e06412656443e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:05:38 +0000 Subject: [PATCH 3/5] Add documentation, scripts, and README files for Java version Co-authored-by: codervaruns <169798268+codervaruns@users.noreply.github.com> --- README.md | 256 +++++++++++++++++++++++++++++++++++++++++++++++++ README_JAVA.md | 216 +++++++++++++++++++++++++++++++++++++++++ run_java.bat | 47 +++++++++ run_java.sh | 43 +++++++++ 4 files changed, 562 insertions(+) create mode 100644 README.md create mode 100644 README_JAVA.md create mode 100644 run_java.bat create mode 100755 run_java.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..5106304 --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +# Same Game + +A tile-matching puzzle game with both **C++** and **Java** implementations. The game features a graph-based approach for tile management and includes a computer AI opponent using a greedy algorithm. + +## 🎮 Game Overview + +Same Game is a puzzle game where you remove clusters of adjacent tiles of the same color. The game ends when no more moves are possible, and the player with the highest score wins! + +### Game Rules + +1. Click on a cluster of 2+ adjacent tiles of the same color to remove them +2. Score is calculated as: `(cluster_size - 2)²` +3. After removing tiles: + - Remaining tiles fall down (vertical gravity) + - Empty columns shift left (horizontal gravity) +4. Player and computer take turns +5. Game ends when no valid moves remain +6. Highest score wins! + +### Colors +- 🟢 Green +- ⚪ White +- 🔴 Red +- 🔵 Blue +- 🟡 Yellow + +## 📁 Two Complete Implementations + +This repository contains **two complete, equivalent implementations**: + +### 1. C++ Version (Original) +- **GUI**: SDL2 with SDL_ttf +- **Files**: `SameGame.h`, `SameGame.cpp`, `main.cpp`, `test_graph.cpp` +- **Build**: MinGW/g++ with SDL2 libraries +- **Documentation**: `BUILD.md` + +### 2. Java Version (Converted) +- **GUI**: Java Swing/AWT +- **Files**: `src/main/java/com/samegame/*.java` +- **Build**: Maven or javac (no external dependencies!) +- **Documentation**: `BUILD_JAVA.md`, `README_JAVA.md` + +## 🚀 Quick Start + +### Run C++ Version + +```bash +# Install SDL2 and SDL2_ttf first (see BUILD.md) +g++ -std=c++17 -I. SameGame.cpp main.cpp -o SameGame.exe -lmingw32 -lSDL2main -lSDL2 -lSDL2_ttf +./SameGame.exe +``` + +### Run Java Version + +```bash +# Option 1: Using the run script (Unix/Linux/Mac) +./run_java.sh + +# Option 2: Using the run script (Windows) +run_java.bat + +# Option 3: Using Maven +mvn exec:java -Dexec.mainClass="com.samegame.SameGameGUI" + +# Option 4: Manual compilation +cd src/main/java +javac com/samegame/*.java +java com.samegame.SameGameGUI +``` + +## 🎯 Features + +Both versions include: + +- ✅ **Graph-based tile system** - Efficient adjacency tracking +- ✅ **BFS cluster detection** - Find connected tiles of same color +- ✅ **Gravity system** - Vertical drop + horizontal collapse +- ✅ **Turn-based gameplay** - User vs Computer +- ✅ **Greedy AI** - Computer opponent picks largest clusters +- ✅ **Visual highlighting** - See clusters before clicking +- ✅ **Real-time scoring** - Track both players' scores +- ✅ **Game over detection** - Automatic win/loss determination +- ✅ **Restart functionality** - Press R for new game + +## 🎮 Controls + +Both versions use identical controls: + +| Input | Action | +|-------|--------| +| **Mouse Hover** | Highlight cluster | +| **Left Click** | Remove cluster | +| **R Key** | Restart game | +| **ESC Key** | Quit | + +## 📚 Documentation + +- **[BUILD.md](BUILD.md)** - C++ build instructions with SDL2 setup +- **[BUILD_JAVA.md](BUILD_JAVA.md)** - Comprehensive Java build guide +- **[README_JAVA.md](README_JAVA.md)** - Java version overview and comparison +- **[GRAPH_STRUCTURE.md](GRAPH_STRUCTURE.md)** - Algorithm and data structure details + +## 🔄 C++ to Java Conversion + +The Java version is a **direct, faithful conversion** of the C++ code: + +### Technology Mappings + +| Aspect | C++ | Java | +|--------|-----|------| +| **GUI** | SDL2 + SDL_ttf | Java Swing + AWT | +| **Containers** | `vector`, `unordered_map` | `ArrayList`, `HashMap` | +| **Memory** | Manual (pointers) | Automatic (GC) | +| **Build** | g++/MinGW | Maven/javac | +| **Dependencies** | SDL2, SDL2_ttf | None (Swing is built-in) | +| **Platform** | Requires recompilation | Write once, run anywhere | + +### Why Two Versions? + +1. **Educational**: Shows how to convert C++ to Java +2. **Accessibility**: Java version requires no external libraries +3. **Cross-platform**: Java runs everywhere without recompilation +4. **Comparison**: Learn differences between C++ and Java +5. **Choice**: Use whichever technology stack you prefer + +## 🧪 Testing + +Both versions include comprehensive test suites: + +```bash +# C++ tests +g++ -std=c++17 SameGame.cpp test_graph.cpp -o test_graph +./test_graph + +# Java tests +cd src/main/java +javac com/samegame/*.java +java -ea com.samegame.TestGraph +``` + +All tests validate: +- Grid initialization +- Tile operations +- Cluster detection +- Gravity mechanics +- Score calculation +- Turn-based gameplay +- AI functionality + +## 📂 Project Structure + +``` +Same-Game/ +├── C++ Implementation +│ ├── SameGame.h # Game logic header +│ ├── SameGame.cpp # Game logic implementation +│ ├── main.cpp # SDL2 GUI +│ ├── test_graph.cpp # Test suite +│ ├── greedy.cpp # Console prototype +│ ├── build.bat # Windows build script +│ └── build.sh # Unix build script +│ +├── Java Implementation +│ ├── src/main/java/com/samegame/ +│ │ ├── Node.java # Graph node +│ │ ├── SameGame.java # Game logic +│ │ ├── SameGameGUI.java # Swing GUI +│ │ └── TestGraph.java # Test suite +│ ├── pom.xml # Maven configuration +│ ├── run_java.sh # Unix run script +│ └── run_java.bat # Windows run script +│ +├── Documentation +│ ├── README.md # This file +│ ├── README_JAVA.md # Java-specific readme +│ ├── BUILD.md # C++ build guide +│ ├── BUILD_JAVA.md # Java build guide +│ └── GRAPH_STRUCTURE.md # Algorithm documentation +│ +└── Configuration + ├── .gitignore # Git ignore rules + └── build_output.txt # Build logs +``` + +## 🛠️ Development + +### Prerequisites + +**For C++ version:** +- C++17 compiler (g++/MinGW) +- SDL2 development libraries +- SDL2_ttf library + +**For Java version:** +- JDK 11 or higher +- Maven (optional) + +### Building + +See language-specific build documentation: +- C++: [BUILD.md](BUILD.md) +- Java: [BUILD_JAVA.md](BUILD_JAVA.md) + +## 🎯 Algorithm Overview + +Both implementations use identical algorithms: + +1. **Graph Representation**: Each tile is a node with edges to adjacent tiles +2. **Cluster Detection**: BFS traversal finds connected same-color tiles +3. **Gravity**: Two-phase system + - Phase 1: Vertical - tiles fall down + - Phase 2: Horizontal - empty columns shift left +4. **AI Strategy**: Greedy algorithm always picks largest available cluster + +For detailed algorithm explanation, see [GRAPH_STRUCTURE.md](GRAPH_STRUCTURE.md) + +## 📈 Performance + +Both versions provide excellent performance: +- **C++**: Hardware-accelerated SDL2 rendering +- **Java**: Smooth Swing double-buffered rendering +- Both: ~60 FPS, instant cluster detection +- Grid size: 6×8 (48 tiles) + +## 🤝 Contributing + +Contributions are welcome! When adding features: +1. Maintain consistency between C++ and Java versions +2. Ensure all tests pass +3. Follow existing code style +4. Update relevant documentation + +## 📄 License + +Open source project. See individual files for licensing details. + +## 🎓 Learning Resources + +This project demonstrates: +- Graph-based game design +- BFS algorithm implementation +- GUI programming (SDL2 and Swing) +- Cross-language code conversion +- Turn-based game logic +- AI opponent implementation + +## 🙏 Acknowledgments + +- Original C++ implementation with SDL2 +- Direct Java conversion maintaining all features +- Graph-based tile system design +- Greedy AI algorithm + +--- + +**Choose your preferred version and enjoy the game! 🎮** diff --git a/README_JAVA.md b/README_JAVA.md new file mode 100644 index 0000000..a4f1376 --- /dev/null +++ b/README_JAVA.md @@ -0,0 +1,216 @@ +# Same Game - Java Version + +A complete Java conversion of the C++ Same Game implementation, featuring a Java Swing GUI instead of SDL2. + +## Overview + +This is a direct conversion from C++ to Java, maintaining the same game logic, structure, and algorithms. The project demonstrates how to translate C++ code to Java while preserving functionality. + +## What's Converted + +### Files Converted: +1. **SameGame.h/cpp** → **SameGame.java** + **Node.java** + - Graph-based game logic + - BFS cluster detection + - Gravity system + - Turn-based gameplay + - Greedy AI algorithm + +2. **main.cpp (SDL2 GUI)** → **SameGameGUI.java (Swing GUI)** + - Complete GUI implementation + - Mouse interaction + - Visual rendering + - Game loop + - Event handling + +3. **test_graph.cpp** → **TestGraph.java** + - Comprehensive test suite + - All original tests maintained + +## Technology Stack + +- **Language**: Java 11+ +- **GUI Framework**: Java Swing/AWT (instead of SDL2) +- **Build Tool**: Maven +- **No external dependencies** (Swing is part of JDK) + +## Quick Start + +### Prerequisites +- Java JDK 11 or higher +- Maven (optional, for building) + +### Compile and Run + +#### Using Maven: +```bash +# Compile +mvn clean compile + +# Run the game +mvn exec:java -Dexec.mainClass="com.samegame.SameGameGUI" + +# Run tests +mvn exec:java -Dexec.mainClass="com.samegame.TestGraph" +``` + +#### Using javac directly: +```bash +# Compile +cd src/main/java +javac com/samegame/*.java + +# Run the game +java com.samegame.SameGameGUI + +# Run tests (with assertions) +java -ea com.samegame.TestGraph +``` + +## Game Controls + +- **Mouse Hover**: Highlight clusters of same-colored tiles +- **Left Click**: Remove a cluster (minimum 2 tiles) +- **R Key**: Restart game with new random grid +- **ESC Key**: Quit game + +## Key Conversion Details + +### Language Mappings + +| C++ | Java | +|-----|------| +| `vector` | `ArrayList` / `List` | +| `unordered_map` | `HashMap` | +| `unordered_set` | `HashSet` | +| `queue` | `Queue` / `LinkedList` | +| `pair` | Custom `Pair` class | +| `tuple` | Custom `ClusterInfo` class | +| Pointers (`*`) | Object references | +| Header files (`.h`) | Single `.java` files | + +### GUI Framework Mappings + +| SDL2 (C++) | Java Swing | +|------------|------------| +| `SDL_Window` | `JFrame` | +| `SDL_Renderer` | `Graphics2D` | +| `SDL_Color` | `java.awt.Color` | +| `SDL_Rect` | `java.awt.Rectangle` | +| `SDL_Event` | AWT Event Listeners | +| `TTF_Font` | `java.awt.Font` | +| `SDL_RenderFillRect()` | `fillRect()` | +| `SDL_RenderDrawRect()` | `drawRect()` | +| Manual game loop | `javax.swing.Timer` | + +## Project Structure + +``` +src/main/java/com/samegame/ +├── Node.java # Graph node structure (from SameGame.h) +├── SameGame.java # Game logic (from SameGame.cpp) +├── SameGameGUI.java # Swing GUI (from main.cpp) +└── TestGraph.java # Test suite (from test_graph.cpp) +``` + +## Features Preserved + +All features from the C++ version are maintained: + +✅ Graph-based tile representation +✅ BFS cluster detection algorithm +✅ Vertical and horizontal gravity +✅ Turn-based gameplay (User vs Computer) +✅ Greedy AI for computer moves +✅ Score calculation: `(cluster_size - 2)²` +✅ Visual tile highlighting +✅ Real-time score display +✅ Game over detection +✅ Restart functionality + +## Advantages of Java Version + +1. **No external dependencies**: Swing is built into JDK +2. **Cross-platform**: Runs on Windows, macOS, Linux without recompilation +3. **Memory management**: Automatic garbage collection +4. **Easier distribution**: Single JAR file +5. **IDE support**: Better tooling and debugging +6. **No manual library installation**: SDL2/TTF not needed + +## Documentation + +- **BUILD_JAVA.md**: Comprehensive build and usage instructions +- **BUILD.md**: Original C++ build instructions (still available) +- **GRAPH_STRUCTURE.md**: Original algorithm documentation (applies to both) + +## Testing + +Run the comprehensive test suite: + +```bash +# With Maven +mvn exec:java -Dexec.mainClass="com.samegame.TestGraph" + +# Or directly +cd src/main/java +java -ea com.samegame.TestGraph +``` + +All tests validate: +- Initialization +- Tile operations +- Cluster detection +- Gravity mechanics +- Turn-based gameplay +- AI functionality + +## Building a JAR + +Create an executable JAR file: + +```bash +mvn clean package +java -jar target/same-game-1.0.0.jar +``` + +## Comparison with C++ Version + +### Similarities: +- Identical game logic and algorithms +- Same scoring system +- Same AI strategy (greedy) +- Same visual appearance +- Same controls and gameplay + +### Differences: +- **Language**: Java instead of C++ +- **GUI**: Swing instead of SDL2 +- **Build**: Maven instead of g++/MinGW +- **Dependencies**: None (vs SDL2, SDL2_ttf) +- **Memory**: GC managed (vs manual) + +## Performance + +The Java version provides comparable performance to the C++ version for this game's scale (6x8 grid): +- Smooth 60 FPS rendering +- Instant cluster detection +- Real-time user interaction +- Negligible overhead for game logic + +## Contributing + +When making changes: +1. Ensure tests pass: `java -ea com.samegame.TestGraph` +2. Maintain code structure consistency with C++ version +3. Follow Java naming conventions +4. Document any algorithm changes + +## License + +This is a direct conversion of the C++ implementation, maintaining the original structure and logic. + +## See Also + +- Original C++ version (in repository root) +- BUILD_JAVA.md for detailed build instructions +- GRAPH_STRUCTURE.md for algorithm details diff --git a/run_java.bat b/run_java.bat new file mode 100644 index 0000000..9b50db3 --- /dev/null +++ b/run_java.bat @@ -0,0 +1,47 @@ +@echo off +REM Build and run script for Same Game Java version (Windows) + +echo ========================================== +echo Same Game - Java Version +echo ========================================== +echo. + +REM Check if Java is installed +javac -version >nul 2>&1 +if errorlevel 1 ( + echo Error: Java compiler (javac) not found! + echo Please install Java JDK 11 or higher. + exit /b 1 +) + +echo Java version detected: +javac -version +echo. + +REM Compile the Java files +echo Compiling Java files... +cd src\main\java +javac com\samegame\*.java + +if errorlevel 1 ( + echo Compilation failed! + exit /b 1 +) + +REM Check what to run +if "%1"=="test" ( + echo Running test suite... + java -ea com.samegame.TestGraph +) else if "%1"=="clean" ( + echo Cleaning compiled files... + del /Q com\samegame\*.class 2>nul + echo Clean complete! +) else ( + echo Starting Same Game GUI... + echo Controls: + echo - Mouse: Hover to highlight, Click to remove clusters + echo - R: Restart game + echo - ESC: Quit + echo. + java com.samegame.SameGameGUI +) diff --git a/run_java.sh b/run_java.sh new file mode 100755 index 0000000..d5c0e71 --- /dev/null +++ b/run_java.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Build and run script for Same Game Java version + +set -e + +echo "==========================================" +echo "Same Game - Java Version" +echo "==========================================" +echo "" + +# Check if Java is installed +if ! command -v javac &> /dev/null; then + echo "Error: Java compiler (javac) not found!" + echo "Please install Java JDK 11 or higher." + exit 1 +fi + +echo "Java version detected:" +javac -version +echo "" + +# Compile the Java files +echo "Compiling Java files..." +cd src/main/java +javac com/samegame/*.java + +# Check what to run +if [ "$1" == "test" ]; then + echo "Running test suite..." + java -ea com.samegame.TestGraph +elif [ "$1" == "clean" ]; then + echo "Cleaning compiled files..." + rm -f com/samegame/*.class + echo "Clean complete!" +else + echo "Starting Same Game GUI..." + echo "Controls:" + echo " - Mouse: Hover to highlight, Click to remove clusters" + echo " - R: Restart game" + echo " - ESC: Quit" + echo "" + java com.samegame.SameGameGUI +fi From 0b88db1b16a2d6aff47b3c18b42468178fe85138 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:07:41 +0000 Subject: [PATCH 4/5] Add comprehensive C++ to Java conversion guide Co-authored-by: codervaruns <169798268+codervaruns@users.noreply.github.com> --- CONVERSION_GUIDE.md | 473 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 CONVERSION_GUIDE.md diff --git a/CONVERSION_GUIDE.md b/CONVERSION_GUIDE.md new file mode 100644 index 0000000..b8516ea --- /dev/null +++ b/CONVERSION_GUIDE.md @@ -0,0 +1,473 @@ +# C++ to Java Conversion Reference + +This document provides a detailed mapping of how the C++ Same Game project was converted to Java. + +## File Mappings + +| C++ File | Java File | Purpose | +|----------|-----------|---------| +| `SameGame.h` | `Node.java` + `SameGame.java` | Header split into separate class files | +| `SameGame.cpp` | `SameGame.java` | Game logic implementation | +| `main.cpp` | `SameGameGUI.java` | GUI implementation (SDL2 → Swing) | +| `test_graph.cpp` | `TestGraph.java` | Test suite | +| `greedy.cpp` | *(Integrated in SameGame.java)* | AI logic integrated into main class | +| `build.bat` / `build.sh` | `run_java.bat` / `run_java.sh` | Build/run scripts | +| *(none)* | `pom.xml` | Maven build configuration (new) | + +## Class/Struct Mappings + +### Node Structure + +**C++ (SameGame.h):** +```cpp +struct Node { + int row; + int col; + char color; + bool active; + vector neighbors; + + Node(int r, int c, char clr); +}; +``` + +**Java (Node.java):** +```java +public class Node { + public int row; + public int col; + public char color; + public boolean active; + public List neighbors; + + public Node(int r, int c, char clr); +} +``` + +### SameGame Class + +**C++ (SameGame.h):** +```cpp +class SameGame { +private: + vector nodes; + unordered_map> posToNodeIndex; + int rows, cols, score, moves; + bool isUserTurn; + int userScore, computerScore; + +public: + SameGame(const vector>& initialGrid); + // ... methods +}; +``` + +**Java (SameGame.java):** +```java +public class SameGame { + private List nodes; + private Map> posToNodeIndex; + private int rows, cols, score, moves; + private boolean isUserTurn; + private int userScore, computerScore; + + public SameGame(char[][] initialGrid) { } + // ... methods +} +``` + +## Data Structure Mappings + +| C++ Type | Java Type | Notes | +|----------|-----------|-------| +| `vector` | `ArrayList` or `List` | Dynamic array | +| `unordered_map` | `HashMap` or `Map` | Hash table | +| `unordered_set` | `HashSet` or `Set` | Hash set | +| `queue` | `Queue` or `LinkedList` | FIFO queue | +| `pair` | Custom `Pair` class | Key-value pair | +| `tuple` | Custom `ClusterInfo` class | Multiple values | +| `bool` | `boolean` | Boolean type | +| `char` | `char` | Character type | +| `int` | `int` | Integer type | +| Raw pointers (`*`) | Object references | Memory management | +| `const` reference (`const T&`) | Regular reference | Immutability | +| `nullptr` | `null` | Null pointer/reference | + +## Algorithm Mappings + +### BFS Cluster Detection + +**C++ (SameGame.cpp):** +```cpp +vector> SameGame::detectClusterBFS(int startRow, int startCol) { + vector> cluster; + unordered_set visited; + queue q; + + q.push(startNodeIdx); + visited.insert(startNodeIdx); + + while (!q.empty()) { + int currentIdx = q.front(); + q.pop(); + // ... BFS logic + } + return cluster; +} +``` + +**Java (SameGame.java):** +```java +private List> detectClusterBFS(int startRow, int startCol) { + List> cluster = new ArrayList<>(); + Set visited = new HashSet<>(); + Queue q = new LinkedList<>(); + + q.add(startNodeIdx); + visited.add(startNodeIdx); + + while (!q.isEmpty()) { + int currentIdx = q.poll(); + // ... BFS logic + } + return cluster; +} +``` + +### Gravity System + +Both implementations use identical two-phase gravity: + +1. **Vertical Gravity** - Tiles fall down +2. **Horizontal Gravity** - Empty columns shift left + +The logic is nearly identical, with only syntax differences. + +## GUI Framework Mappings + +### Window Creation + +**C++ (main.cpp - SDL2):** +```cpp +SDL_Init(SDL_INIT_VIDEO); +TTF_Init(); +window = SDL_CreateWindow("Same Game", + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + WINDOW_WIDTH, WINDOW_HEIGHT, + SDL_WINDOW_SHOWN); +renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); +``` + +**Java (SameGameGUI.java - Swing):** +```java +setTitle("Same Game"); +setSize(WINDOW_WIDTH, WINDOW_HEIGHT); +setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); +setResizable(false); +setVisible(true); +``` + +### Rendering + +**C++ (main.cpp - SDL2):** +```cpp +void drawTile(int row, int col, bool highlight) { + SDL_Color color = getColorForTile(game->getTile(row, col)); + SDL_Rect tileRect = {x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4}; + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer, &tileRect); +} +``` + +**Java (SameGameGUI.java - Swing):** +```java +private void drawTile(Graphics2D g, int row, int col, boolean highlight) { + Color color = getColorForTile(game.getTile(row, col)); + g.setColor(color); + g.fillRect(x + 2, y + 2, TILE_SIZE - 4, TILE_SIZE - 4); +} +``` + +### Event Handling + +**C++ (main.cpp - SDL2):** +```cpp +SDL_Event e; +while (SDL_PollEvent(&e) != 0) { + if (e.type == SDL_QUIT) { + quit = true; + } else if (e.type == SDL_MOUSEMOTION) { + handleMouseMove(e.motion.x, e.motion.y); + } else if (e.type == SDL_MOUSEBUTTONDOWN) { + handleMouseClick(e.button.x, e.button.y); + } +} +``` + +**Java (SameGameGUI.java - Swing):** +```java +addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + handleMouseMove(e.getX(), e.getY()); + } +}); + +addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + handleMouseClick(e.getX(), e.getY()); + } +}); +``` + +### Game Loop + +**C++ (main.cpp - SDL2):** +```cpp +void run() { + bool quit = false; + while (!quit) { + // Event handling + while (SDL_PollEvent(&e) != 0) { ... } + + // Computer move logic + if (!gameOver && !game->getUserTurn()) { ... } + + // Render + render(); + SDL_Delay(16); // ~60 FPS + } +} +``` + +**Java (SameGameGUI.java - Swing):** +```java +Timer gameTimer = new Timer(16, e -> { + // Computer move logic + if (!gameOver && !game.getUserTurn()) { ... } + + // Repaint triggers paintComponent() + gamePanel.repaint(); +}); +gameTimer.start(); +``` + +### Color Definitions + +**C++ (main.cpp - SDL2):** +```cpp +SDL_Color getColorForTile(char tile) { + switch(tile) { + case 'G': return {0, 200, 0, 255}; // Green + case 'W': return {255, 255, 255, 255}; // White + // ... + } +} +``` + +**Java (SameGameGUI.java - Swing):** +```java +private Color getColorForTile(char tile) { + switch(tile) { + case 'G': return new Color(0, 200, 0); // Green + case 'W': return new Color(255, 255, 255); // White + // ... + } +} +``` + +## Build System Mappings + +### C++ Build + +**Using g++/MinGW:** +```bash +g++ -std=c++17 -I. SameGame.cpp main.cpp -o SameGame.exe \ + -lmingw32 -lSDL2main -lSDL2 -lSDL2_ttf +``` + +**Dependencies:** +- SDL2 library +- SDL2_ttf library +- MinGW/g++ compiler + +### Java Build + +**Using Maven:** +```bash +mvn clean compile +mvn exec:java -Dexec.mainClass="com.samegame.SameGameGUI" +``` + +**Using javac:** +```bash +cd src/main/java +javac com/samegame/*.java +java com.samegame.SameGameGUI +``` + +**Dependencies:** +- None! (Swing is part of JDK) + +## Testing + +### C++ Test Suite + +**File:** `test_graph.cpp` + +```cpp +#include "SameGame.h" +#include +#include + +int main() { + // Tests using assert() + assert(game.getRows() == 3); + // ... + cout << "All tests passed! ✓" << endl; + return 0; +} +``` + +**Run:** +```bash +g++ -std=c++17 SameGame.cpp test_graph.cpp -o test_graph +./test_graph +``` + +### Java Test Suite + +**File:** `TestGraph.java` + +```java +package com.samegame; + +public class TestGraph { + public static void main(String[] args) { + // Tests using assert + assert game.getRows() == 3; + // ... + System.out.println("All tests passed! ✓"); + } +} +``` + +**Run:** +```bash +cd src/main/java +javac com/samegame/*.java +java -ea com.samegame.TestGraph # -ea enables assertions +``` + +## Key Differences + +### Memory Management + +**C++:** +- Manual memory management with pointers +- Need to explicitly manage SDL resources +- `delete`, destructors, RAII + +**Java:** +- Automatic garbage collection +- No manual memory management +- Finalize methods (rarely needed) + +### Header Files + +**C++:** +- Separate `.h` header and `.cpp` implementation files +- Need include guards or `#pragma once` +- Forward declarations + +**Java:** +- Single `.java` file per class +- No header files +- Automatic class discovery + +### Packages and Namespaces + +**C++:** +```cpp +using namespace std; +// Global scope or namespace blocks +``` + +**Java:** +```java +package com.samegame; +// All classes in a package +``` + +### Access Modifiers + +**C++:** +```cpp +class SameGame { +private: + int rows; +public: + int getRows() const; +}; +``` + +**Java:** +```java +public class SameGame { + private int rows; + public int getRows() { return rows; } +} +``` + +### Const Correctness + +**C++:** +```cpp +int getRows() const; // Method doesn't modify object +const vector& getColors() const; // Return const reference +``` + +**Java:** +```java +public int getRows() { } // No const keyword +public List getColors() { } // Returns reference (can't enforce const) +``` + +## Performance Considerations + +### C++ Advantages: +- Direct hardware access via SDL2 +- No garbage collection overhead +- Better control over memory layout +- Potentially faster for very large grids + +### Java Advantages: +- JIT compilation optimizes hot paths +- Modern GC is very efficient +- Swing double-buffering is optimized +- Negligible difference for this game's scale + +## Portability + +### C++: +- Requires recompilation for each platform +- Need to install SDL2 libraries on each platform +- Different build commands for Windows/Linux/Mac + +### Java: +- Write once, run anywhere +- No external dependencies (Swing is built-in) +- Same JAR file runs on all platforms with JVM + +## Conclusion + +The Java conversion maintains 100% feature parity with the C++ version while providing: +- ✅ Simpler build process +- ✅ No external dependencies +- ✅ Better cross-platform support +- ✅ Automatic memory management +- ✅ Easier distribution (single JAR file) + +Both versions use identical algorithms and provide the same gameplay experience. From e92f666437c2b606e4ae91cb1149882ea70003de Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:10:23 +0000 Subject: [PATCH 5/5] Fix code review feedback: KeyListener focus and wildcard imports Co-authored-by: codervaruns <169798268+codervaruns@users.noreply.github.com> --- src/main/java/com/samegame/SameGame.java | 9 ++++++++- src/main/java/com/samegame/SameGameGUI.java | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/samegame/SameGame.java b/src/main/java/com/samegame/SameGame.java index 3842ec4..2b7d3b6 100644 --- a/src/main/java/com/samegame/SameGame.java +++ b/src/main/java/com/samegame/SameGame.java @@ -1,6 +1,13 @@ package com.samegame; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; public class SameGame { private List nodes; // Graph nodes diff --git a/src/main/java/com/samegame/SameGameGUI.java b/src/main/java/com/samegame/SameGameGUI.java index d354df4..8184af7 100644 --- a/src/main/java/com/samegame/SameGameGUI.java +++ b/src/main/java/com/samegame/SameGameGUI.java @@ -42,8 +42,9 @@ public SameGameGUI(SameGame game) { gamePanel = new GamePanel(); add(gamePanel); - // Setup key listeners - addKeyListener(new KeyAdapter() { + // Setup key listeners on the panel (which can receive focus) + gamePanel.setFocusable(true); + gamePanel.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_R) { @@ -53,6 +54,7 @@ public void keyPressed(KeyEvent e) { } } }); + gamePanel.requestFocusInWindow(); // Setup game timer for computer moves gameTimer = new Timer(16, e -> {