package com.example.flow_free; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.RectF; import android.preference.PreferenceManager; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class FlowFreeView extends View { private final Puzzle puzzle; private final Paint paint = new Paint(); private final int[][] board; private final Map> paths = new HashMap<>(); private final Map colorMap = new HashMap<>(); private final Set completedColors = new HashSet<>();// liste des chemin fini private boolean win = false; private boolean isAchromate ; private static final int[] DISTINCT_COLORS = { Color.rgb(255, 0, 0), // Rouge vif Color.rgb(0, 0, 255), // Bleu vif Color.rgb(0, 255, 0), // Vert vif Color.rgb(255, 255, 0), // Jaune vif Color.rgb(255, 0, 255), // Fuchsia / Magenta Color.rgb(0, 255, 255), // Cyan Color.rgb(255, 165, 0), // Orange vif Color.rgb(128, 0, 0), // Bordeaux Color.rgb(0, 128, 0), // Vert foncé Color.rgb(0, 0, 128), // Bleu foncé Color.rgb(128, 128, 0), // Olive Color.rgb(128, 0, 128), // Violet foncé Color.rgb(0, 128, 128), // Teal foncé Color.rgb(255, 105, 180), // Rose Color.rgb(139, 69, 19), // Chocolat Color.rgb(255, 20, 147), // Rose vif Color.rgb(173, 255, 47), // Vert citron Color.rgb(75, 0, 130), // Indigo profond }; private static final int[] DISTINCT_GRIS = { Color.rgb(0, 0, 0), // Noir Color.rgb(40, 40, 40), // Gris très foncé Color.rgb(80, 80, 80), // Gris foncé Color.rgb(120, 120, 120), // Gris moyen foncé Color.rgb(160, 160, 160), // Gris moyen Color.rgb(200, 200, 200), // Gris moyen clair Color.rgb(220, 220, 220), // Gris clair Color.rgb(240, 240, 240), // Gris très clair Color.rgb(255, 255, 255), // Blanc Color.rgb(25, 25, 25), Color.rgb(55, 55, 55), Color.rgb(95, 95, 95), Color.rgb(135, 135, 135), Color.rgb(175, 175, 175) }; private int colorId=0; private int selectedColor = 0; private int cellSize; public FlowFreeView(Context context, Puzzle puzzle) { super(context); this.puzzle = puzzle; this.board = new int[puzzle.getSize()][puzzle.getSize()]; // Initialisation du board avec les points du puzzle for (int[] pair : puzzle.getPairs()) { int col1 = pair[0]; int row1 = pair[1]; int col2 = pair[2]; int row2 = pair[3]; // Crée une couleur id unique pour chaque paire (1, 2, 3, ...) colorId = colorId+ 1; board[row1][col1] = colorId; board[row2][col2] = colorId; } } private void printBoardToLog() { StringBuilder sb = new StringBuilder(); for (int[] row : board) { for (int cell : row) { sb.append(String.format("%3d", cell)).append(" "); } sb.append("\n"); } } @Override//test pour calculer cellsize protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (puzzle != null) { cellSize = Math.min(w, h) / puzzle.getSize(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); isAchromate = preferences.getBoolean("achromate_mode", false); paint.setStrokeWidth(4); paint.setStyle(Paint.Style.STROKE); paint.setColor(Color.BLACK); // Affiche la grille for (int i = 0; i <= puzzle.getSize(); i++) { canvas.drawLine(i * cellSize, 0, i * cellSize, puzzle.getSize() * cellSize, paint); canvas.drawLine(0, i * cellSize, puzzle.getSize() * cellSize, i * cellSize, paint); } // Affiche les points de couleur paint.setStyle(Paint.Style.FILL); for (int row = 0; row < puzzle.getSize(); row++) { for (int col = 0; col < puzzle.getSize(); col++) { int colorId = board[row][col]; if (colorId > 0) { // permet de ne pas dessiner des cercle quand je fais un trait paint.setColor(getColorForId(colorId,isAchromate)); float cx = col * cellSize + cellSize / 2f; float cy = row * cellSize + cellSize / 2f; canvas.drawCircle(cx, cy, cellSize / 4f, paint); } } } // Affiche les chemins tracés paint.setStrokeWidth(cellSize / 4f); paint.setStyle(Paint.Style.STROKE); for (Map.Entry> entry : paths.entrySet()) { paint.setColor(getColorForId(Math.abs(entry.getKey()),isAchromate)); List path = entry.getValue(); for (int i = 0; i < path.size() - 1; i++) { int[] p1 = path.get(i); int[] p2 = path.get(i + 1); canvas.drawLine( p1[1] * cellSize + cellSize / 2f, p1[0] * cellSize + cellSize / 2f, p2[1] * cellSize + cellSize / 2f, p2[0] * cellSize + cellSize / 2f, paint ); } } //dessine quand c'est victoir if (win) { String message = "🎉 Bravo !"; paint.setTextSize(80); paint.setTextAlign(Paint.Align.CENTER); // Mesure le texte Paint.FontMetrics fm = paint.getFontMetrics(); float textWidth = paint.measureText(message); float textHeight = fm.bottom - fm.top; float centerX = getWidth() / 2f; float centerY = getHeight() / 2f; // 🎨 Dessine un fond blanc arrondi paint.setStyle(Paint.Style.FILL); paint.setColor(Color.WHITE); float padding = 30; RectF background = new RectF( centerX - textWidth / 2 - padding, centerY + fm.top - padding / 2, centerX + textWidth / 2 + padding, centerY + fm.bottom + padding / 2 ); canvas.drawRoundRect(background, 30, 30, paint); // 🖤 Ombre paint.setColor(Color.BLACK); canvas.drawText(message, centerX + 3, centerY + 3, paint); // 🔴 Texte principal paint.setColor(Color.RED); canvas.drawText(message, centerX, centerY, paint); } } //test private void clearPath(int color) { // Supprime visuellement le chemin for (int r = 0; r < board.length; r++) { for (int c = 0; c < board[r].length; c++) { if (board[r][c] == -color) { board[r][c] = 0; } } } // Supprime le chemin logique paths.remove(color); completedColors.remove(color); invalidate(); // Redessine } //test //verrifie condition victoir private void checkWin() { // 1. Vérifie si le tableau est complètement rempli for (int r = 0; r < board.length; r++) { for (int c = 0; c < board[r].length; c++) { if (board[r][c] == 0) { return; // Case vide : pas encore gagné } } } // 2. Vérifie si toutes les paires sont terminées if (completedColors.size() == puzzle.getPairs().size()) { win = true; invalidate(); // Redessine pour afficher le message } } @Override public boolean onTouchEvent(MotionEvent event) { int col = (int) (event.getX() / cellSize); int row = (int) (event.getY() / cellSize); if (col < 0 || row < 0 || col >= puzzle.getSize() || row >= puzzle.getSize()) return false; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: int colorAtCell = board[row][col]; if (colorAtCell > 0) { clearPath(colorAtCell); // 👈 même fonction de nettoyage selectedColor = colorAtCell; paths.put(selectedColor, new ArrayList<>()); paths.get(selectedColor).add(new int[]{row, col}); if (completedColors.contains(colorAtCell)) {// regarde si le chemin qu'on veut tracer est fini // 🔒 Ne pas toucher à un chemin terminé return true; } selectedColor = colorAtCell; if (!paths.containsKey(selectedColor)) { paths.put(selectedColor, new ArrayList<>()); } // Si on relâche et reprend depuis la dernière case List path = paths.get(selectedColor); if (path.isEmpty() || (path.get(path.size() - 1)[0] != row || path.get(path.size() - 1)[1] != col)) { path.add(new int[]{row, col}); } } break; case MotionEvent.ACTION_MOVE: if (selectedColor != 0) { List path = paths.get(selectedColor); if (path == null || path.isEmpty()) break; int[] last = path.get(path.size() - 1); int dr = Math.abs(last[0] - row); int dc = Math.abs(last[1] - col); if ((dr == 1 && dc == 0) || (dr == 0 && dc == 1)) { int current = board[row][col]; // 1️⃣ Avancer sur une case vide if (current == 0) { board[row][col] = -selectedColor; path.add(new int[]{row, col}); invalidate(); } // 2️⃣ Atteindre l’autre point de la paire else if (current == selectedColor) { // ⚠️ Ne pas revenir au point de départ int[] start = path.get(0); if (row == start[0] && col == start[1]) { break; } path.add(new int[]{row, col}); board[row][col] = selectedColor; completedColors.add(selectedColor); selectedColor = 0; checkWin(); invalidate(); } // 3️⃣ Reculer sur une case du chemin else if (current == -selectedColor && path.size() >= 2) { int[] beforeLast = path.get(path.size() - 2); if (beforeLast[0] == row && beforeLast[1] == col) { // 🔙 On recule d’une case int[] removed = path.remove(path.size() - 1); board[removed[0]][removed[1]] = 0; invalidate(); } } } } break; case MotionEvent.ACTION_UP: selectedColor = 0; // Permet de reprendre à la prochaine touche break; } invalidate(); return true; } // Attribue une couleur vive et contrastée à chaque identifiant de point private int getColorForId(int id,boolean achromate) { if(achromate) { if (!colorMap.containsKey(id)) { int index = colorMap.size() % DISTINCT_GRIS.length; colorMap.put(id, DISTINCT_GRIS[index]); } return colorMap.get(id); } else { if (!colorMap.containsKey(id)) { int index = colorMap.size() % DISTINCT_COLORS.length; colorMap.put(id, DISTINCT_COLORS[index]); } return colorMap.get(id); } } }