2025-03-26 00:45:12 +01:00
|
|
|
|
package com.example.flow_free;
|
|
|
|
|
|
|
|
|
|
|
|
import android.content.Context;
|
2025-03-30 21:46:34 +02:00
|
|
|
|
import android.content.SharedPreferences;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
import android.graphics.Canvas;
|
|
|
|
|
|
import android.graphics.Color;
|
|
|
|
|
|
import android.graphics.Paint;
|
|
|
|
|
|
import android.graphics.RectF;
|
2025-03-30 21:46:34 +02:00
|
|
|
|
import android.preference.PreferenceManager;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
import android.view.MotionEvent;
|
|
|
|
|
|
import android.view.View;
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
import java.util.HashMap;
|
2025-03-28 01:42:44 +01:00
|
|
|
|
import java.util.HashSet;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
import java.util.List;
|
|
|
|
|
|
import java.util.Map;
|
2025-03-28 01:42:44 +01:00
|
|
|
|
import java.util.Set;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
|
|
|
|
|
|
public class FlowFreeView extends View {
|
|
|
|
|
|
private final Puzzle puzzle;
|
|
|
|
|
|
private final Paint paint = new Paint();
|
|
|
|
|
|
private final int[][] board;
|
|
|
|
|
|
private final Map<Integer, List<int[]>> paths = new HashMap<>();
|
|
|
|
|
|
|
2025-03-30 21:46:34 +02:00
|
|
|
|
private final Map<Integer, Integer> colorMap = new HashMap<>();
|
2025-03-28 18:53:33 +01:00
|
|
|
|
private final Set<Integer> completedColors = new HashSet<>();// liste des chemin fini
|
2025-03-29 20:23:22 +01:00
|
|
|
|
private boolean win = false;
|
2025-03-30 21:46:34 +02:00
|
|
|
|
private boolean isAchromate ;
|
2025-03-28 18:53:33 +01:00
|
|
|
|
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-30 21:46:34 +02:00
|
|
|
|
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)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
|
|
|
|
|
private int colorId=0;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
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, ...)
|
2025-03-28 01:42:44 +01:00
|
|
|
|
colorId = colorId+ 1;
|
2025-03-26 00:45:12 +01:00
|
|
|
|
board[row1][col1] = colorId;
|
|
|
|
|
|
board[row2][col2] = colorId;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
2025-03-30 21:46:34 +02:00
|
|
|
|
private void printBoardToLog() {
|
2025-03-28 18:53:33 +01:00
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
for (int[] row : board) {
|
|
|
|
|
|
for (int cell : row) {
|
|
|
|
|
|
sb.append(String.format("%3d", cell)).append(" ");
|
|
|
|
|
|
}
|
|
|
|
|
|
sb.append("\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
@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();
|
|
|
|
|
|
}
|
2025-03-30 21:46:34 +02:00
|
|
|
|
}
|
2025-03-26 00:45:12 +01:00
|
|
|
|
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
@Override
|
|
|
|
|
|
protected void onDraw(Canvas canvas) {
|
|
|
|
|
|
super.onDraw(canvas);
|
2025-03-30 21:46:34 +02:00
|
|
|
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
|
|
|
|
|
|
isAchromate = preferences.getBoolean("achromate_mode", false);
|
2025-03-26 00:45:12 +01:00
|
|
|
|
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];
|
2025-03-28 18:53:33 +01:00
|
|
|
|
if (colorId > 0) { // permet de ne pas dessiner des cercle quand je fais un trait
|
2025-03-30 21:46:34 +02:00
|
|
|
|
paint.setColor(getColorForId(colorId,isAchromate));
|
2025-03-26 00:45:12 +01:00
|
|
|
|
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<Integer, List<int[]>> entry : paths.entrySet()) {
|
2025-03-30 21:46:34 +02:00
|
|
|
|
paint.setColor(getColorForId(Math.abs(entry.getKey()),isAchromate));
|
2025-03-26 00:45:12 +01:00
|
|
|
|
List<int[]> 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
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-03-29 20:23:22 +01:00
|
|
|
|
//dessine quand c'est victoir
|
|
|
|
|
|
if (win) {
|
2025-03-30 19:23:38 +02:00
|
|
|
|
String message = "🎉 Bravo !";
|
2025-03-29 20:23:22 +01:00
|
|
|
|
paint.setTextSize(80);
|
|
|
|
|
|
paint.setTextAlign(Paint.Align.CENTER);
|
2025-03-30 19:23:38 +02:00
|
|
|
|
|
|
|
|
|
|
// 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);
|
2025-03-29 20:23:22 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-29 20:23:22 +01:00
|
|
|
|
//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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
|
|
|
|
|
|
@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];
|
2025-03-29 20:23:22 +01:00
|
|
|
|
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});
|
2025-03-28 18:53:33 +01:00
|
|
|
|
if (completedColors.contains(colorAtCell)) {// regarde si le chemin qu'on veut tracer est fini
|
|
|
|
|
|
// 🔒 Ne pas toucher à un chemin terminé
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
2025-03-26 00:45:12 +01:00
|
|
|
|
selectedColor = colorAtCell;
|
|
|
|
|
|
if (!paths.containsKey(selectedColor)) {
|
|
|
|
|
|
paths.put(selectedColor, new ArrayList<>());
|
|
|
|
|
|
}
|
|
|
|
|
|
// Si on relâche et reprend depuis la dernière case
|
|
|
|
|
|
List<int[]> 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;
|
|
|
|
|
|
|
2025-03-28 18:53:33 +01:00
|
|
|
|
case MotionEvent.ACTION_MOVE:
|
|
|
|
|
|
if (selectedColor != 0) {
|
2025-03-30 19:23:38 +02:00
|
|
|
|
List<int[]> path = paths.get(selectedColor);
|
|
|
|
|
|
if (path == null || path.isEmpty()) break;
|
|
|
|
|
|
int[] last = path.get(path.size() - 1);
|
2025-03-30 17:18:44 +02:00
|
|
|
|
|
2025-03-30 19:23:38 +02:00
|
|
|
|
int dr = Math.abs(last[0] - row);
|
|
|
|
|
|
int dc = Math.abs(last[1] - col);
|
2025-03-30 17:18:44 +02:00
|
|
|
|
|
2025-03-30 19:23:38 +02:00
|
|
|
|
if ((dr == 1 && dc == 0) || (dr == 0 && dc == 1)) {
|
|
|
|
|
|
int current = board[row][col];
|
2025-03-30 17:18:44 +02:00
|
|
|
|
|
2025-03-30 19:23:38 +02:00
|
|
|
|
// 1️⃣ Avancer sur une case vide
|
|
|
|
|
|
if (current == 0) {
|
|
|
|
|
|
board[row][col] = -selectedColor;
|
|
|
|
|
|
path.add(new int[]{row, col});
|
|
|
|
|
|
invalidate();
|
2025-03-28 18:53:33 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-30 19:23:38 +02:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
2025-03-28 18:53:33 +01:00
|
|
|
|
|
2025-03-30 19:23:38 +02:00
|
|
|
|
path.add(new int[]{row, col});
|
|
|
|
|
|
board[row][col] = selectedColor;
|
2025-03-28 18:53:33 +01:00
|
|
|
|
completedColors.add(selectedColor);
|
2025-03-30 19:23:38 +02:00
|
|
|
|
selectedColor = 0;
|
2025-03-29 20:23:22 +01:00
|
|
|
|
checkWin();
|
2025-03-30 19:23:38 +02:00
|
|
|
|
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();
|
|
|
|
|
|
}
|
2025-03-28 18:53:33 +01:00
|
|
|
|
}
|
2025-03-26 00:45:12 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
2025-03-30 19:23:38 +02:00
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
case MotionEvent.ACTION_UP:
|
|
|
|
|
|
selectedColor = 0; // Permet de reprendre à la prochaine touche
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
invalidate();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-03-28 01:42:44 +01:00
|
|
|
|
// Attribue une couleur vive et contrastée à chaque identifiant de point
|
2025-03-30 21:46:34 +02:00
|
|
|
|
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);
|
2025-03-28 01:42:44 +01:00
|
|
|
|
}
|
2025-03-26 00:45:12 +01:00
|
|
|
|
}
|
2025-03-28 01:42:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
2025-03-26 00:45:12 +01:00
|
|
|
|
}
|