package fr.monkhanny.dorfromantik.game; import fr.monkhanny.dorfromantik.listeners.GameZoomListener; import fr.monkhanny.dorfromantik.listeners.GameArrowKeyListener; import fr.monkhanny.dorfromantik.listeners.GameSpaceKeyListener; import fr.monkhanny.dorfromantik.listeners.GameMouseClickListener; import fr.monkhanny.dorfromantik.listeners.GameMouseWheelListener; import fr.monkhanny.dorfromantik.Options; import fr.monkhanny.dorfromantik.enums.Biome; import fr.monkhanny.dorfromantik.enums.Fonts; import fr.monkhanny.dorfromantik.enums.TileOrientation; import fr.monkhanny.dorfromantik.controller.GameModeController; import fr.monkhanny.dorfromantik.utils.Database; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.awt.Graphics; import javax.swing.JPanel; import javax.swing.JFrame; import java.awt.Point; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Font; // TEMPORAIRE : import java.awt.event.MouseMotionAdapter; // Import pour MouseMotionAdapter import java.awt.AlphaComposite; import java.util.HashMap; import java.util.Map; /** * Représente le plateau de jeu. */ public class Board extends JPanel{ private List tiles; private List availablePositions; private Random random; private Game game; private JFrame gameFrame; private Tile centralTile; private double zoomFactor = 1.0; // Facteur de zoom initial private int offsetX = 0; // Décalage horizontal du plateau private int offsetY = 0; // Décalage vertical du plateau private Tile nextTile; private Map biomeGroups; private Point mousePosition; private ScoreManager scoreManager; private int currentScore; private Database database; // Constructeur avec seed public Board(JFrame gameFrame, long seed) { this.gameFrame = gameFrame; this.tiles = new ArrayList<>(); this.biomeGroups = new HashMap<>(); this.availablePositions = new ArrayList<>(); this.random = new Random(seed); this.game = new Game(seed); this.scoreManager = new ScoreManager(biomeGroups); for (Biome biome : Biome.values()) { biomeGroups.put(biome, new BiomeGroup()); } // Placer une tuile centrale au démarrage initializeCentralTile(); // Ajouter un écouteur de molette de souris pour gérer le zoom gameFrame.addMouseWheelListener(new GameZoomListener(this)); // Ajouter un écouteur de clavier pour déplacer le plateau gameFrame.addKeyListener(new GameArrowKeyListener(this)); gameFrame.setFocusable(true); this.addMouseWheelListener(new GameMouseWheelListener(this)); gameFrame.addKeyListener(new GameSpaceKeyListener(this)); this.addMouseListener(new GameMouseClickListener(this)); this.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(java.awt.event.MouseEvent e) { handleMouseMove(e); } }); } public void handleMouseMove(java.awt.event.MouseEvent e) { // Récupérer les coordonnées du curseur Point cursorPoint = e.getPoint(); // Ajuster la position de la souris en fonction du zoom et des offsets int adjustedX = (int)((cursorPoint.x - offsetX) / zoomFactor); int adjustedY = (int)((cursorPoint.y - offsetY) / zoomFactor); // Vérifier si la souris est proche d'une des positions disponibles for (Point position : availablePositions) { if (new Point(adjustedX, adjustedY).distance(position) < 20) { mousePosition = position; repaint(); // Redessiner le plateau avec la tuile transparente return; } } // Si la souris n'est pas proche d'une position valide, ne rien faire mousePosition = null; repaint(); // Redessiner sans la tuile transparente } private void initializeNextTile() { int offsetX = 50; // Décalage pour la position en haut à gauche int offsetY = 50; // Décalage pour la position en haut à gauche this.nextTile = new Tile(this, offsetX, offsetY, 50); // Création de la nouvelle tuile } public Tile getNextTile() { return nextTile; } public void handleSpaceKeyPress() { // Calculer les dimensions totales du plateau (largeur et hauteur des tuiles) int totalWidth = 0; int totalHeight = 0; // Calculer la largeur et la hauteur totale de toutes les tuiles for (Tile tile : tiles) { totalWidth = Math.max(totalWidth, tile.getXCoord() + tile.getRadius()); totalHeight = Math.max(totalHeight, tile.getYCoord() + tile.getRadius()); } // Ajouter une petite marge pour les bords totalWidth += 50; // Marge pour éviter que les tuiles ne soient collées au bord totalHeight += 50; // Marge pour éviter que les tuiles ne soient collées au bord // Calculer le facteur de zoom pour que toutes les tuiles tiennent sur l'écran double horizontalZoom = (double) getWidth() / totalWidth; double verticalZoom = (double) getHeight() / totalHeight; // Choisir le zoom le plus petit pour éviter que les tuiles ne sortent de l'écran zoomFactor = Math.min(horizontalZoom, verticalZoom); // Ajuster les offsets pour centrer les tuiles après le dézoom adjustOffsets(totalWidth, totalHeight); // Recalculer les positions disponibles et redessiner le plateau repaint(); } private void adjustOffsets(int totalWidth, int totalHeight) { // Calculer les décalages nécessaires pour centrer le plateau int targetOffsetX = (int) ((getWidth() - totalWidth * zoomFactor) / 2); int targetOffsetY = (int) ((getHeight() - totalHeight * zoomFactor) / 2); // Appliquer les nouveaux offsets setOffsetX(targetOffsetX); setOffsetY(targetOffsetY); } public void handleMouseClick(java.awt.event.MouseEvent e) { // Récupérer les coordonnées du clic Point clickedPoint = e.getPoint(); // Ajuster les coordonnées du clic en tenant compte du zoom et des déplacements // Annuler l'effet du zoom et du déplacement int adjustedX = (int)((clickedPoint.x - offsetX) / zoomFactor); int adjustedY = (int)((clickedPoint.y - offsetY) / zoomFactor); // Vérifiez si la position ajustée est dans la liste des positions disponibles et si la distance est suffisante for (Point position : availablePositions) { // Vérifiez la distance entre le clic ajusté et la position if (new Point(adjustedX, adjustedY).distance(position) < 20) { placeTileAtPosition(position); // Place une tuile à cette position break; // Si une tuile est ajoutée, on peut sortir de la boucle } } } private void initializeCentralTile() { int centerX = gameFrame.getWidth() / 2; int centerY = gameFrame.getHeight() / 2; this.centralTile = new Tile(this, centerX, centerY, 50); addTile(centralTile); // Calculer les positions disponibles autour de la tuile centrale calculateAvailablePositions(centralTile); initializeNextTile(); } public void addTile(Tile tile) { tiles.add(tile); updatePockets(tile); calculateCurrentScore(); } private void updatePockets(Tile newTile) { for (TileOrientation orientation : TileOrientation.values()) { Biome biome = newTile.getBiome(orientation); BiomeGroup biomeGroup = biomeGroups.get(biome); // Trouver toutes les poches connectées à la tuile List connectedPockets = new ArrayList<>(); for (Pocket pocket : biomeGroup.getPockets()) { // Vérifier si la nouvelle tuile est adjacente à une tuile de cette poche if (isConnectedToPocket(newTile, pocket)) { connectedPockets.add(pocket); } } if (connectedPockets.isEmpty()) { // Si aucune poche n'est connectée, créer une nouvelle poche Pocket newPocket = new Pocket(biome); newPocket.addTile(newTile); biomeGroup.addPocket(newPocket); } else { // Fusionner toutes les poches connectées Pocket mergedPocket = connectedPockets.get(0); // Première poche mergedPocket.addTile(newTile); // Ajouter les tuiles des autres poches connectées for (int i = 1; i < connectedPockets.size(); i++) { Pocket pocketToMerge = connectedPockets.get(i); // Ajouter uniquement les tuiles adjacentes for (Tile tile : pocketToMerge.getTiles()) { if (areAdjacent(newTile, tile)) { mergedPocket.addTile(tile); } } // Supprimer les poches fusionnées biomeGroup.getPockets().remove(pocketToMerge); } } } } // Méthode pour vérifier si une tuile est connectée à une poche existante private boolean isConnectedToPocket(Tile newTile, Pocket pocket) { // Vérifier si la nouvelle tuile est adjacente à l'une des tuiles déjà dans la poche for (Tile existingTile : pocket.getTiles()) { if (areAdjacent(newTile, existingTile)) { return true; } } return false; } /** * Vérifie si deux tuiles sont adjacentes (en partageant un bord ou un coin). */ private boolean areAdjacent(Tile tile1, Tile tile2) { // Vérification si les tuiles partagent un bord (ici, simplification, vous devez adapter à votre modèle de tuiles) return tile1.isAdjacentTo(tile2); // Méthode qui vérifie l'adjacence, à adapter selon votre structure de tuile } // Vérifie si deux tuiles sont connectées par un même biome private boolean areTilesConnected(Tile tile1, Tile tile2) { if (!tile1.isAdjacentTo(tile2)) { return false; // Pas adjacentes } // Vérifier si les biomes adjacents correspondent for (TileOrientation orientation : TileOrientation.values()) { TileOrientation oppositeOrientation = orientation.oppositeOrientation(); if (tile1.getBiome(orientation).equals(tile2.getBiome(oppositeOrientation))) { return true; } } return false; } private void calculateCurrentScore() { scoreManager.updateScore(); // Met à jour le score currentScore = scoreManager.getCurrentScore(); // Récupère le score actuel } public int getCurrentScore() { return currentScore; } public Random getRandom() { return random; } public Game getGame() { return game; } /** * Calcule les positions disponibles autour de la tuile donnée. * Les points rouges seront générés autour de cette tuile. * * @param tile La tuile pour laquelle on calcule les positions disponibles */ private void calculateAvailablePositions(Tile tile) { int tileX = tile.getXCoord(); int tileY = tile.getYCoord(); int radius = (int) (tile.getRadius() * 1.72); // Utiliser un rayon uniforme pour toutes les directions // Définir les directions possibles autour de la tuile (6 directions pour une tuile hexagonale) Point[] directions = { new Point(0, -radius), // Nord new Point((int)(radius * Math.sqrt(3) / 2), -radius / 2), // Nord-Est (ajuster horizontalement) new Point((int)(radius * Math.sqrt(3) / 2), radius / 2), // Sud-Est (ajuster horizontalement) new Point(0, radius), // Sud new Point(-(int)(radius * Math.sqrt(3) / 2), radius / 2), // Sud-Ouest (ajuster horizontalement) new Point(-(int)(radius * Math.sqrt(3) / 2), -radius / 2) // Nord-Ouest (ajuster horizontalement) }; // Calculer les positions disponibles autour de la tuile for (Point direction : directions) { Point newPoint = new Point(tileX + direction.x, tileY + direction.y); if (!isTileAtPosition(newPoint)) { availablePositions.add(newPoint); // Ajouter la position si une tuile n'est pas déjà là } } } /** * Vérifie si une tuile existe déjà à la position donnée. * * @param position La position à vérifier * @return true si une tuile est présente à cette position, false sinon */ private boolean isTileAtPosition(Point position) { for (Tile t : tiles) { if (t.getXCoord() == position.x && t.getYCoord() == position.y) { return true; } } return false; } /** * Lorsqu'un utilisateur clique, ajoute une nouvelle tuile à la position sélectionnée. * * @param position La position où ajouter la tuile */ public void placeTileAtPosition(Point position) { // Vérifie si la position est disponible et que la tuile n'est pas déjà placée à cet endroit if (availablePositions.contains(position) && !isTileAtPosition(position)) { if (tiles.size() < Options.MAX_TILE_NUMBER) { // Vérifiez si la nextTile existe, sinon on ignore if (nextTile != null) { // Place la nextTile à la position choisie nextTile.setPosition(position.x, position.y); addTile(nextTile); // Ajoute la nextTile au tableau des tuiles calculateAvailablePositions(nextTile); // Calcule de nouvelles positions disponibles repaint(); // Redessine le plateau autoReFocus(nextTile); // Initialiser une nouvelle nextTile pour le prochain tour initializeNextTile(); } } else { try { this.database = new Database(); } catch (Exception e) { } GameOver gameOverPanel = new GameOver(gameFrame, currentScore, database,Options.mainMenu); gameFrame.getContentPane().removeAll(); // Supprime l'ancien contenu gameFrame.getContentPane().add(gameOverPanel); // Ajoute le GameOver gameFrame.revalidate(); // Revalidate pour mettre à jour la fenêtre gameFrame.repaint(); // Repaint pour afficher les modifications } } } public void autoReFocus(Tile newlyPlacedTile) { if (Options.AUTO_FOCUS) { // Récupérer les coordonnées de la nouvelle tuile int newlyPlacedTileX = newlyPlacedTile.getXCoord(); int newlyPlacedTileY = newlyPlacedTile.getYCoord(); // Calculer les décalages nécessaires pour centrer la tuile // Nous utilisons la largeur et la hauteur du panneau de jeu (getWidth et getHeight) // Divisé par 2 pour centrer la nouvelle tuile dans la fenêtre. int targetOffsetX = (int) ((getWidth() - newlyPlacedTile.getRadius() * 2) / 2 - newlyPlacedTileX); int targetOffsetY = (int) ((getHeight() - newlyPlacedTile.getRadius() * 2) / 2 - newlyPlacedTileY); TilePanningTransition panningTransition = new TilePanningTransition(this, targetOffsetX, targetOffsetY, 15); panningTransition.start(); } } public double getZoomFactor() { return zoomFactor;} public void setZoomFactor(double zoomFactor) { this.zoomFactor = zoomFactor; } public int getOffsetX() { return offsetX; } public void setOffsetX(int offsetX) { this.offsetX = offsetX; } public int getOffsetY() { return offsetY; } public void setOffsetY(int offsetY) { this.offsetY = offsetY; } public void zoomIn() { zoomFactor *= 1.1; // Augmenter le facteur de zoom repaint(); } public void zoomOut() { zoomFactor /= 1.1; // Diminuer le facteur de zoom repaint(); } public void moveBoard(int dx, int dy) { offsetX += dx; offsetY += dy; repaint(); } /** * Afficher les points rouges pour indiquer les positions disponibles. * * @param g Le contexte graphique */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; // Appliquer l'échelle de zoom et le déplacement g2d.scale(zoomFactor, zoomFactor); // Appliquer le zoom g2d.translate(offsetX / zoomFactor, offsetY / zoomFactor); // Appliquer le déplacement (en tenant compte du zoom) // Dessiner les points rouges pour les positions disponibles for (Point position : availablePositions) { g.setColor(Color.RED); g.fillOval(position.x - 5, position.y - 5, 10, 10); // Dessiner un point rouge } // Dessiner les tuiles existantes for (Tile tile : tiles) { int tileX = tile.getXCoord(); int tileY = tile.getYCoord(); tile.drawTileAt(g,tileX-50,tileY-50,1f); } // Vérifier si la position de la souris est valide et ne pas dessiner si elle est occupée if (mousePosition != null && nextTile != null && !isTileAtPosition(mousePosition)) { int nextTileX = mousePosition.x; int nextTileY = mousePosition.y; g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); // Rendre la tuile transparente nextTile.drawTileAt(g, nextTileX - 50, nextTileY - 50, 1f); g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f)); // Rétablir l'opacité } // Obtenez la police SCORE avec une taille ajustée (par exemple, 30 points) Font scoreFont = Fonts.SCORE.getFont(30f); // Vous pouvez ajuster la taille ici g.setFont(scoreFont); // Calculer la position du score (avec zoom et décalage) g.setColor(Color.BLACK); int scoreX = (int) ((getWidth() / (2 * zoomFactor) - offsetX / zoomFactor) - 20); int scoreY = (int) (40 / zoomFactor - offsetY / zoomFactor); // Dessiner le texte du score g.drawString("Score : " + currentScore, scoreX, scoreY); if (nextTile != null) { // Calculer la position correcte de la nextTile (en tenant compte du zoom et des décalages) int nextTileX = 0; // Position x dans l'espace global int nextTileY = 0; // Position y dans l'espace global // Appliquer la transformation inverse (ne pas zoomer ni déplacer la nextTile) g2d.scale(1 / zoomFactor, 1 / zoomFactor); // Inverser le zoom g2d.translate(-offsetX, -offsetY); // Inverser le décalage (revenir à l'espace global) // Dessiner la nextTile à sa position d'origine (0,0) nextTile.drawTileAt(g, nextTileX, nextTileY, 1f); // Rétablir les transformations pour les autres éléments (tuiles existantes, etc.) g2d.translate(offsetX / zoomFactor, offsetY / zoomFactor); // Re-appliquer le décalage g2d.scale(zoomFactor, zoomFactor); // Re-appliquer le zoom } } }