Compare commits

...

25 Commits

Author SHA1 Message Date
dustineversmann 885bc6d25c Merge remote-tracking branch 'origin/main' into Eversmann 2025-01-07 12:52:08 +01:00
dustineversmann 10198c8d1d build:
Manifest.mf hinzugefügt
2025-01-07 12:42:42 +01:00
Dustin Fabian Eversmann 60ef6ff5a1 Merge pull request 'Tests für GUI und Zeiterfassung/Highscore Entry' (#5) from Löhle into main
Reviewed-on: #5
2025-01-07 07:58:55 +01:00
Leon Maximilian Löhle 88d67200c8 Tests für GUI und Zeiterfassung/Highscore Entry 2025-01-07 07:55:52 +01:00
Leon Maximilian Löhle 34240b22dc Merge pull request 'Eversmann' (#4) from Eversmann into main
Reviewed-on: #4
2025-01-07 07:50:09 +01:00
dustineversmann 07e6cf3600 Merge remote-tracking branch 'origin/main' into Eversmann 2025-01-07 07:46:37 +01:00
dustineversmann ee20cea0eb feature(feature): Anpassungen für Tests
Anpassungen an Attributen der Klassen, um die Testbarkeit zu ermöglichen
2025-01-07 07:44:50 +01:00
dustineversmann d67e9495ce feature(tests): Tests hinzugefügt
Tests für CSVReader, Facade, HighscoreManager und die Puzzeldatenstruktur hinzugefügt
2025-01-07 07:43:12 +01:00
Leon Maximilian Löhle 77a4247677 Fehlende Klassenimporte korrigiert 2025-01-07 07:04:14 +01:00
Leon Maximilian Löhle 71124dab2a Merge pull request 'Eversmann' (#3) from Eversmann into main
Reviewed-on: #3
2025-01-06 22:59:43 +01:00
dustineversmann cabd4db873 feature(facade): Facade hinzugefügt
Facade als Anlaufpunkt für das Backend implementiert, um Trennung zu gewährleisten
2025-01-06 22:44:59 +01:00
dustineversmann 92bc8247e4 feature(domain): CSVReader angepasst 2025-01-06 22:41:15 +01:00
dustineversmann 1a32962ba8 feature(domain): Verwaltung der Highscores und der Durchschnittszeiten
Implementierung des HighscoreManagers, der die erzielten Highscores und die Durschnittszeit ausliest und in der entsprechenden Highscore-Datei speichert.
2025-01-06 22:34:08 +01:00
dustineversmann 57147ae818 feature(domain): Datenstruktur eines Highscores
Implementierung der Highscoredatenstruktur zur Nutzung durch den Highscoremanager
2025-01-06 22:25:13 +01:00
dustineversmann 981547383f feature:
Implementierung der Datenstruktur, die, welche die Hitorispieldaten und die Lösung enthalten
2025-01-06 22:11:30 +01:00
Dustin Fabian Eversmann e21e289f36 Merge pull request 'Update der main mit den von mir veränderten Daten nach Teamgespräch' (#2) from Löhle into main
Reviewed-on: #2
2025-01-06 19:53:59 +01:00
Leon Maximilian Löhle ae5fb68bd4 Musste neuen Commit machen, nachdem Datenstand nicht den gleichen Stand hatte wie main um einen Pull request in main zu gestatten 2025-01-06 19:42:02 +01:00
Leon Maximilian Löhle da6ffa8379 Merge branch 'main' into Löhle
# Conflicts:
#	src/main/java/de/deversmann/gui/GameView.java
#	src/main/java/de/deversmann/gui/HitoriApp.java
2025-01-06 19:31:31 +01:00
Leon Löhle a866e7f2bf Verbesserung der Gui und dem Ablauf nach Absprache mit Team kollegen 2025-01-06 18:17:04 +01:00
Leon Löhle f6cc17002c Erstellung einer Facade 2025-01-06 18:15:24 +01:00
Leon Löhle 7b77f7b381 Verbesserung der Zeiterfassung nach Team gespräch 2025-01-06 18:14:47 +01:00
dustineversmann 4394a89a9c feature: Trennung des Einlesens der Dateien von der Erstellung des Spielfelds 2025-01-02 18:03:21 +01:00
Leon Löhle e832fc5f6f Highsocre manager für benutztung von Highscores eingefügt 2025-01-01 13:17:07 +01:00
Leon Maximilian Löhle d1fcac31ca GUI ermöglicht nun Start und Zeitmessung. Noch keine Idee für korrekte Implementierung der Spielfeld auswahl 2024-12-27 15:02:48 +01:00
Leon Maximilian Löhle bb8a2ad265 Verbesserung der Spielfeld Klasse um Einsatz im Programm zu ermöglichen als auch die Anforderungen zu erfüllen zwecks Ausgabe und Farben 2024-12-21 14:04:30 +01:00
22 changed files with 1418 additions and 71 deletions

33
pom.xml
View File

@ -21,6 +21,31 @@
<version>5.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4-rule</artifactId>
<version>2.0.9</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-checkstyle-plugin -->
<dependency>
<groupId>org.apache.maven.plugins</groupId>
@ -28,6 +53,14 @@
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.15.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-javadoc-plugin -->
<dependency>

View File

@ -2,6 +2,7 @@ package de.deversmann;
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
de.deversmann.gui.HitoriApp app = new de.deversmann.gui.HitoriApp();
app.start();
}
}

View File

@ -0,0 +1,57 @@
package de.deversmann.domain;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class CSVReader {
public PuzzleData readPuzzleWithSolution(String csvFile) throws IOException {
List<int[]> puzzleRows = new ArrayList<>();
List<int[]> solutionCoordinates = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(csvFile))) {
String line;
boolean inSolutionPart = false;
while ((line = br.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) {
continue;
}
if (line.startsWith("//Lösung") || line.startsWith("//")) {
inSolutionPart = true;
continue;
}
if (!inSolutionPart) {
// Puzzle-Teil
String[] tokens = line.split(",");
int[] rowValues = new int[tokens.length];
for (int i = 0; i < tokens.length; i++) {
rowValues[i] = Integer.parseInt(tokens[i].trim());
}
puzzleRows.add(rowValues);
} else {
// Lösungsteil
String[] coords = line.split(",");
if (coords.length == 2) {
int row = Integer.parseInt(coords[0].trim());
int col = Integer.parseInt(coords[1].trim());
solutionCoordinates.add(new int[]{row, col});
}
}
}
}
// puzzleRows -> 2D-Array
int[][] puzzleArray = new int[puzzleRows.size()][];
for (int i = 0; i < puzzleRows.size(); i++) {
puzzleArray[i] = puzzleRows.get(i);
}
return new PuzzleData(puzzleArray, solutionCoordinates);
}
}

View File

@ -0,0 +1,31 @@
package de.deversmann.domain;
public class HighscoreEntry {
private final String playerName;
private final long timeSeconds;
private final int errorCount;
public HighscoreEntry(String playerName, long timeSeconds, int errorCount) {
this.playerName = playerName;
this.timeSeconds = timeSeconds;
this.errorCount = errorCount;
}
public String getPlayerName() {
return playerName;
}
public long getTimeSeconds() {
return timeSeconds;
}
public int getErrorCount() {
return errorCount;
}
@Override
public String toString() {
return playerName + " - " + timeSeconds + "s (Fehler: " + errorCount + ")";
}
}

View File

@ -0,0 +1,102 @@
package de.deversmann.domain;
import java.io.*;
import java.util.*;
public class HighscoreManager {
private Map<String, List<HighscoreEntry>> highscoreMap;
public HighscoreManager() {
this.highscoreMap = new HashMap<>();
}
public void addHighscore(String puzzleName, String playerName, long timeSeconds, int errorCount) {
highscoreMap.putIfAbsent(puzzleName, new ArrayList<>());
highscoreMap.get(puzzleName).add(new HighscoreEntry(playerName, timeSeconds, errorCount));
}
public List<HighscoreEntry> getHighscores(String puzzleName) {
return highscoreMap.getOrDefault(puzzleName, Collections.emptyList());
}
public double getAverageTime(String puzzleName) {
List<HighscoreEntry> entries = highscoreMap.get(puzzleName);
if (entries == null || entries.isEmpty()) {
return -1;
}
long sum = 0;
for (HighscoreEntry e : entries) {
sum += e.getTimeSeconds();
}
return (double) sum / entries.size();
}
public void saveSinglePuzzle(String puzzleName, String filename) throws IOException {
List<HighscoreEntry> entries = getHighscores(puzzleName);
entries.sort(Comparator
.comparingInt(HighscoreEntry::getErrorCount)
.thenComparingLong(HighscoreEntry::getTimeSeconds));
double avgTime = getAverageTime(puzzleName);
try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
if (avgTime < 0) {
writer.write("Durchschnittszeit: Keine Einträge");
} else {
writer.write(String.format("Durchschnittszeit: %.2f s", avgTime));
}
writer.newLine();
writer.newLine();
writer.write(String.format("%-6s | %-5s | %-6s | %-15s",
"Platz", "Zeit", "Fehler", "Name"));
writer.newLine();
writer.write("------------------------------------------------------------");
writer.newLine();
int place = 1;
for (HighscoreEntry e : entries) {
writer.write(String.format("%-6s | %-5d | %-6d | %-15s",
place + ".",
e.getTimeSeconds(),
e.getErrorCount(),
e.getPlayerName()));
writer.newLine();
place++;
}
}
}
public void loadSinglePuzzle(String puzzleName, String filename) throws IOException {
File file = new File(filename);
if (!file.exists()) {
System.out.println("Highscore-Datei " + filename + " existiert nicht; wird neu erstellt.");
return;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
// Durchschnittszeit und Header überspringen
reader.readLine();
reader.readLine();
reader.readLine();
reader.readLine();
String line;
while ((line = reader.readLine()) != null) {
String[] parts = line.split("\\|");
if (parts.length < 4) continue;
String timeStr = parts[1].trim();
String errorStr = parts[2].trim();
String nameStr = parts[3].trim();
long timeSeconds = Long.parseLong(timeStr);
int errorCount = Integer.parseInt(errorStr);
addHighscore(puzzleName, nameStr, timeSeconds, errorCount);
}
}
}
}

View File

@ -0,0 +1,22 @@
package de.deversmann.domain;
import java.util.List;
public class PuzzleData {
private final int[][] puzzle;
private final List<int[]> solutionCoordinates;
public PuzzleData(int[][] puzzle, List<int[]> solutionCoordinates) {
this.puzzle = puzzle;
this.solutionCoordinates = solutionCoordinates;
}
public int[][] getPuzzle() {
return puzzle;
}
public List<int[]> getSolutionCoordinates() {
return solutionCoordinates;
}
}

View File

@ -4,51 +4,107 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Scanner;
import java.util.Random;
public class Spielfeld {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String folderPath = Paths.get("").toAbsolutePath() + File.separator + "src" + File.separator + "Spielfelder" + File.separator;
private int[][] feld;
private String[][] zustand; // "grau", "schwarz", "weiß"
Map<Integer, String> felder = Map.of(
1, "4x4.csv",
2, "5x5.csv",
3, "8x8_leicht.csv",
4, "8x8_medium.csv",
5, "10x10.csv",
6, "15x15.csv");
System.out.println("Verfügbare Spielfeldgrößen:");
felder.forEach((key, value) -> System.out.println(key + ": " + value.replace(".csv", "")));
System.out.println("Welche Spielfeldgröße wollen Sie bespielen?");
int wahl = sc.nextInt();
if (!felder.containsKey(wahl)) {
System.out.println("Ungültige Auswahl");
return;
}
String gewählteDatei = felder.get(wahl);
System.out.println("Sie haben die Spielfeldgröße '" + gewählteDatei.replace(".csv", "") + "' gewählt");
readCSVFile(folderPath + gewählteDatei);
public Spielfeld() {
}
public static void readCSVFile(String filePath) {
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
public void ladeSpielfeld(String dateiPfad) {
try (BufferedReader br = new BufferedReader(new FileReader(dateiPfad))) {
String line;
System.out.println("\nInhalt der Datei:");
int zeilenZaehler = 0;
while ((line = br.readLine()) != null) {
System.out.println(line);
zeilenZaehler++;
}
br.close();
feld = new int[zeilenZaehler][];
zustand = new String[zeilenZaehler][];
try (BufferedReader br2 = new BufferedReader(new FileReader(dateiPfad))) {
int row = 0;
while ((line = br2.readLine()) != null) {
String[] werte = line.split(",");
feld[row] = new int[werte.length];
zustand[row] = new String[werte.length];
for (int col = 0; col < werte.length; col++) {
feld[row][col] = Integer.parseInt(werte[col]);
zustand[row][col] = "grau";
}
row++;
}
}
} catch (IOException e) {
System.err.println("Fehler beim Lesen der Datei: " + e.getMessage());
System.err.println("Fehler beim Laden des Spielfelds: " + e.getMessage());
}
}
public void ladeZufaelligesSpielfeld(String ordnerPfad) {
File ordner = new File(ordnerPfad);
File[] dateien = ordner.listFiles((dir, name) -> name.endsWith(".csv"));
if (dateien != null && dateien.length > 0) {
Random random = new Random();
File zufallsDatei = dateien[random.nextInt(dateien.length)];
ladeSpielfeld(zufallsDatei.getAbsolutePath());
} else {
System.err.println("Keine CSV-Dateien im Verzeichnis gefunden!");
}
}
public void setzeZustand(int x, int y, String neuerZustand) {
if (istImBereich(x, y)) {
zustand[x][y] = neuerZustand;
} else {
System.err.println("Koordinaten außerhalb des Spielfelds!");
}
}
public String getZustand(int x, int y) {
if (istImBereich(x, y)) {
return zustand[x][y];
} else {
System.err.println("Koordinaten außerhalb des Spielfelds!");
return null;
}
}
public int getWert(int x, int y) {
if (istImBereich(x, y)) {
return feld[x][y];
} else {
System.err.println("Koordinaten außerhalb des Spielfelds!");
return -1;
}
}
public void reset() {
for (int i = 0; i < zustand.length; i++) {
for (int j = 0; j < zustand[i].length; j++) {
zustand[i][j] = "grau";
}
}
}
public int getBreite() {
return feld[0].length;
}
public int getHoehe() {
return feld.length;
}
private boolean istImBereich(int x, int y) {
return x >= 0 && x < feld.length && y >= 0 && y < feld[0].length;
}
}

View File

@ -1,41 +1,37 @@
package de.deversmann.domain;
public class Zeiterfassung {
private long startzeit;
private long endzeit;
private boolean laufen;
private long startTime;
private long endTime;
private boolean isRunning;
public Zeiterfassung() {
this.isRunning = false;
}
public void start() {
this.startzeit = System.currentTimeMillis();
this.laufen= true;
if (!isRunning) {
startTime = System.currentTimeMillis();
isRunning = true;
System.out.println("Zeiterfassung gestartet");
}
}
public void stop() {
this.endzeit = System.currentTimeMillis();
this.laufen= false;
if (isRunning) {
endTime = System.currentTimeMillis();
isRunning = false;
System.out.println("Zeiterfassung gestoppt, diff=" + getElapsedTimeInSeconds() + "s");
}
}
public long getElapsedTimeMillis() {
long endTime = laufen ? System.currentTimeMillis() : endzeit;
return endTime - startzeit;
public long getElapsedTimeInSeconds() {
if (isRunning) {
long current = System.currentTimeMillis();
return (current - startTime) / 1000;
} else {
return (endTime - startTime) / 1000;
}
}
public void reset() {
this.startzeit = 0;
this.endzeit = 0;
this.laufen = false;
}
public String getFormattedTime() {
long elapsed = getElapsedTimeMillis();
long hours = elapsed / (3600_000);
long remainder = elapsed % 3600_000;
long minutes = remainder / 60_000;
remainder = remainder % 60_000;
long seconds = remainder / 1_000;
long millis = remainder % 1_000;
// Formatierung zu einer lesbaren Zeitangabe
return String.format("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis);
}
}
}

View File

@ -1,4 +1,97 @@
package de.deversmann.facade;
import de.deversmann.domain.*;
import java.io.IOException;
import java.util.List;
public class Facade {
}
private final CSVReader csvReader;
private final HighscoreManager highscoreManager;
private final Zeiterfassung zeiterfassung;
private PuzzleData currentPuzzleData;
private String currentPuzzleName;
private int currentErrorCount = 0;
public Facade() {
this.csvReader = new CSVReader();
this.highscoreManager = new HighscoreManager();
this.zeiterfassung = new Zeiterfassung();
}
public void ladeSpielfeld(String csvPfad) throws IOException {
this.currentPuzzleData = csvReader.readPuzzleWithSolution(csvPfad);
this.currentPuzzleName = csvPfad;
this.currentErrorCount = 0;
zeiterfassung.stop();
zeiterfassung.start();
System.out.println("Neues Spielfeld geladen, Timer + Fehler=0");
}
public long stopZeiterfassung() {
zeiterfassung.stop();
return zeiterfassung.getElapsedTimeInSeconds();
}
public long getElapsedTimeSoFar() {
return zeiterfassung.getElapsedTimeInSeconds();
}
public int[][] getAktuellesPuzzle() {
return (currentPuzzleData != null) ? currentPuzzleData.getPuzzle() : null;
}
public List<int[]> getLoesungsKoordinaten() {
return (currentPuzzleData != null) ? currentPuzzleData.getSolutionCoordinates() : null;
}
// Fehler-Tracking
public void incrementErrorCount() {
currentErrorCount++;
}
public int getCurrentErrorCount() {
return currentErrorCount;
}
// Highscore
public void addHighscoreForCurrentPuzzle(String playerName, long timeSeconds, int errorCount) {
if (currentPuzzleName != null && !currentPuzzleName.isEmpty()) {
highscoreManager.addHighscore(currentPuzzleName, playerName, timeSeconds, errorCount);
}
}
public List<HighscoreEntry> getHighscoresForCurrentPuzzle() {
if (currentPuzzleName == null) {
return List.of();
}
return highscoreManager.getHighscores(currentPuzzleName);
}
public double getAverageTimeForCurrentPuzzle() {
if (currentPuzzleName == null) {
return -1;
}
return highscoreManager.getAverageTime(currentPuzzleName);
}
public void saveCurrentPuzzleHighscoreToFile() throws IOException {
if (currentPuzzleName == null) return;
String base = currentPuzzleName.replaceAll(".*/", "");
String shortName = base.replace(".csv", "") + "_highscore.txt";
highscoreManager.saveSinglePuzzle(currentPuzzleName, shortName);
System.out.println("Highscore zu " + base + " in " + shortName + " gespeichert.");
}
public void loadCurrentPuzzleHighscoreFromFile() throws IOException {
if (currentPuzzleName == null) return;
String base = currentPuzzleName.replaceAll(".*/", "");
String shortName = base.replace(".csv", "") + "_highscore.txt";
highscoreManager.loadSinglePuzzle(currentPuzzleName, shortName);
System.out.println("Highscore zu " + base + " aus " + shortName + " geladen (falls existiert).");
}
}

View File

@ -0,0 +1,237 @@
package de.deversmann.gui;
import de.deversmann.facade.Facade;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class GameView extends JFrame {
private final Facade facade;
SpielfeldGUI spielfeldGUI;
JComboBox<String> cbSpielfelder;
JButton btnLoad;
JButton btnRandom;
JButton btnCheckSolution;
JButton btnShowSolution;
JButton btnReset;
JLabel timeLabel;
Timer timer;
private final String[] spielfelder = {
"4x4.csv",
"5x5.csv",
"8x8_leicht.csv",
"8x8_medium.csv",
"10x10.csv",
"15x15.csv"
};
public GameView(Facade facade) {
this.facade = facade;
setTitle("Hitori App");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(800, 700);
initComponents();
startTimer();
setVisible(true);
}
private void initComponents() {
JPanel controlPanel = new JPanel();
cbSpielfelder = new JComboBox<>(spielfelder);
btnLoad = new JButton("Spielfeld laden");
btnRandom = new JButton("Zufällig");
btnCheckSolution = new JButton("Lösung prüfen");
btnShowSolution = new JButton("Lösung anzeigen");
btnReset = new JButton("Reset");
timeLabel = new JLabel("Time: 0s");
btnLoad.addActionListener(e -> {
String selectedFile = (String) cbSpielfelder.getSelectedItem();
if (selectedFile != null) {
ladeSpielfeld("src/Spielfelder/" + selectedFile);
}
});
btnRandom.addActionListener(e -> {
Random rand = new Random();
int idx = rand.nextInt(spielfelder.length);
String randomField = spielfelder[idx];
cbSpielfelder.setSelectedItem(randomField);
ladeSpielfeld("src/Spielfelder/" + randomField);
});
btnCheckSolution.addActionListener(e -> checkSolution());
btnShowSolution.addActionListener(e -> showSolution());
btnReset.addActionListener(e -> resetField());
controlPanel.add(new JLabel("Wähle ein Spielfeld:"));
controlPanel.add(cbSpielfelder);
controlPanel.add(btnLoad);
controlPanel.add(btnRandom);
controlPanel.add(btnCheckSolution);
controlPanel.add(btnShowSolution);
controlPanel.add(btnReset);
controlPanel.add(timeLabel);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(controlPanel, BorderLayout.NORTH);
spielfeldGUI = new SpielfeldGUI(1, 1);
getContentPane().add(spielfeldGUI, BorderLayout.CENTER);
}
private void startTimer() {
timer = new Timer(1000, e -> {
long seconds = facade.getElapsedTimeSoFar();
timeLabel.setText("Time: " + seconds + "s");
});
timer.start();
}
void ladeSpielfeld(String pfad) {
try {
facade.ladeSpielfeld(pfad);
facade.loadCurrentPuzzleHighscoreFromFile();
int[][] feld = facade.getAktuellesPuzzle();
if (feld != null) {
spielfeldGUI.updateGrid(feld);
timeLabel.setText("Time: 0s");
}
} catch (IOException ex) {
JOptionPane.showMessageDialog(this,
"Fehler beim Laden: " + ex.getMessage(),
"Fehler",
JOptionPane.ERROR_MESSAGE
);
}
}
void checkSolution() {
var blackCells = spielfeldGUI.getBlackCells();
var solutionCoordinates = facade.getLoesungsKoordinaten();
if (solutionCoordinates == null) {
JOptionPane.showMessageDialog(this, "Kein Puzzle geladen!", "Fehler", JOptionPane.ERROR_MESSAGE);
return;
}
Set<String> solutionSet = new HashSet<>();
for (int[] sol : solutionCoordinates) {
solutionSet.add((sol[0] - 1) + "," + (sol[1] - 1));
}
int errorsThisCheck = 0;
for (int[] bc : blackCells) {
String bcStr = bc[0] + "," + bc[1];
if (!solutionSet.contains(bcStr)) {
spielfeldGUI.markCellAsError(bc[0], bc[1]);
facade.incrementErrorCount();
errorsThisCheck++;
}
}
if (errorsThisCheck > 0) {
JOptionPane.showMessageDialog(this,
"Noch nicht korrekt! " + errorsThisCheck + " falsche Felder markiert.",
"Ergebnis",
JOptionPane.WARNING_MESSAGE
);
return;
}
boolean allSet = true;
for (String sol : solutionSet) {
if (!containsCell(blackCells, sol)) {
allSet = false;
break;
}
}
if (!allSet) {
JOptionPane.showMessageDialog(this,
"Nicht alle richtigen Felder sind schwarz!",
"Ergebnis",
JOptionPane.WARNING_MESSAGE
);
return;
}
long finalTime = facade.stopZeiterfassung();
timeLabel.setText("Time: " + finalTime + "s");
String playerName = JOptionPane.showInputDialog(
this,
"Gratulation, richtig gelöst!\nBitte Namen eingeben:",
"Name eingeben",
JOptionPane.QUESTION_MESSAGE
);
if (playerName == null || playerName.isBlank()) {
playerName = "Unbekannt";
}
int totalErrors = facade.getCurrentErrorCount();
facade.addHighscoreForCurrentPuzzle(playerName, finalTime, totalErrors);
try {
facade.saveCurrentPuzzleHighscoreToFile();
} catch (IOException e) {
System.out.println("Konnte Puzzle-spezifischen Highscore nicht speichern: " + e.getMessage());
}
double avgTime = facade.getAverageTimeForCurrentPuzzle();
StringBuilder sb = new StringBuilder();
sb.append("Spiel gelöst!\n");
sb.append("Deine Zeit: ").append(finalTime).append("s\n");
sb.append("Fehler: ").append(totalErrors).append("\n");
if (avgTime < 0) {
sb.append("Durchschnittszeit: Keine Einträge\n");
} else {
sb.append(String.format("Durchschnittszeit: %.2f s\n", avgTime));
}
JOptionPane.showMessageDialog(this, sb.toString(), "Ergebnis", JOptionPane.INFORMATION_MESSAGE);
}
boolean containsCell(List<int[]> blackCells, String sol) {
String[] parts = sol.split(",");
int rr = Integer.parseInt(parts[0]);
int cc = Integer.parseInt(parts[1]);
for (int[] bc : blackCells) {
if (bc[0] == rr && bc[1] == cc) {
return true;
}
}
return false;
}
void showSolution() {
var feld = facade.getAktuellesPuzzle();
if (feld == null) return;
spielfeldGUI.updateGrid(feld);
var solution = facade.getLoesungsKoordinaten();
if (solution != null) {
for (int[] coord : solution) {
spielfeldGUI.setCellBlack(coord[0] - 1, coord[1] - 1);
}
}
spielfeldGUI.setAllNonBlackToWhite();
}
void resetField() {
var feld = facade.getAktuellesPuzzle();
if (feld != null) {
spielfeldGUI.updateGrid(feld);
timeLabel.setText("Time: 0s");
}
}
}

View File

@ -0,0 +1,18 @@
package de.deversmann.gui;
import de.deversmann.facade.Facade;
public class HitoriApp {
private Facade facade;
public HitoriApp() {
facade = new Facade();
}
public void start() {
GameView view = new GameView(facade);
view.setVisible(true);
}
}

View File

@ -1,4 +0,0 @@
package de.deversmann.gui;
public class Spielfeld {
}

View File

@ -0,0 +1,136 @@
package de.deversmann.gui;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
public class SpielfeldGUI extends JPanel {
private JLabel[][] grid;
private Set<String> errorCells = new HashSet<>();
public SpielfeldGUI(int rows, int cols) {
setLayout(new GridLayout(rows, cols));
grid = new JLabel[rows][cols];
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
JLabel label = createCellLabel("-", r, c);
grid[r][c] = label;
add(label);
}
}
}
public void updateGrid(int[][] feld) {
if (feld == null) {
return;
}
removeAll();
int rows = feld.length;
int cols = feld[0].length;
setLayout(new GridLayout(rows, cols));
grid = new JLabel[rows][cols];
errorCells.clear();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
String text = String.valueOf(feld[r][c]);
JLabel label = createCellLabel(text, r, c);
grid[r][c] = label;
add(label);
}
}
revalidate();
repaint();
}
public void setAllNonBlackToWhite() {
if (grid == null) return;
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
JLabel lbl = grid[r][c];
if (lbl != null) {
if (!lbl.getBackground().equals(Color.BLACK)) {
lbl.setBackground(Color.WHITE);
lbl.setForeground(Color.BLACK);
clearErrorMark(r, c);
}
}
}
}
}
public void markCellAsError(int row, int col) {
if (!isValidCell(row, col)) return;
errorCells.add(row + "," + col);
grid[row][col].setBorder(BorderFactory.createLineBorder(Color.RED, 3));
}
public void clearErrorMark(int row, int col) {
errorCells.remove(row + "," + col);
if (!isValidCell(row, col)) return;
grid[row][col].setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
}
public List<int[]> getBlackCells() {
List<int[]> black = new ArrayList<>();
if (grid == null) return black;
for (int r = 0; r < grid.length; r++) {
for (int c = 0; c < grid[r].length; c++) {
JLabel lbl = grid[r][c];
if (lbl != null && lbl.getBackground().equals(Color.BLACK)) {
black.add(new int[]{r, c});
}
}
}
return black;
}
public void setCellBlack(int row, int col) {
if (!isValidCell(row, col)) return;
JLabel lbl = grid[row][col];
lbl.setBackground(Color.BLACK);
lbl.setForeground(Color.WHITE);
clearErrorMark(row, col);
}
private JLabel createCellLabel(String text, int row, int col) {
JLabel label = new JLabel(text, SwingConstants.CENTER);
label.setOpaque(true);
label.setBackground(Color.GRAY);
label.setForeground(Color.BLACK);
label.setFont(new Font("Arial", Font.BOLD, 24));
label.setBorder(BorderFactory.createLineBorder(Color.DARK_GRAY, 1));
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
toggleColor(label, row, col);
}
});
return label;
}
private void toggleColor(JLabel label, int row, int col) {
if (label.getBackground().equals(Color.GRAY) || label.getBackground().equals(Color.WHITE)) {
label.setBackground(Color.BLACK);
label.setForeground(Color.WHITE);
} else {
label.setBackground(Color.WHITE);
label.setForeground(Color.BLACK);
}
if (errorCells.contains(row + "," + col)) {
clearErrorMark(row, col);
}
}
private boolean isValidCell(int r, int c) {
if (grid == null) return false;
return (r >= 0 && r < grid.length && c >= 0 && c < grid[r].length);
}
}

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: de.deversmann.Main

View File

@ -0,0 +1,80 @@
package de.deversmann.domain;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.*;
class CSVReaderTest {
@Test
void testReadPuzzleWithSolutionRealData() throws IOException {
String csvContent = """
4,5,6,7
8,9,10,11
12,13,14,15
16,17,18,19
//Lösung
2,2
3,3
""";
Path tempFile = Files.createTempFile("realPuzzle", ".csv");
Files.writeString(tempFile, csvContent);
CSVReader reader = new CSVReader();
PuzzleData data = reader.readPuzzleWithSolution(tempFile.toString());
int[][] expectedPuzzle = {
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15},
{16, 17, 18, 19}
};
assertArrayEquals(expectedPuzzle, data.getPuzzle());
assertEquals(2, data.getSolutionCoordinates().size());
assertArrayEquals(new int[]{2, 2}, data.getSolutionCoordinates().get(0));
assertArrayEquals(new int[]{3, 3}, data.getSolutionCoordinates().get(1));
Files.delete(tempFile);
}
@Test
void testReadPuzzleWithInvalidData() throws IOException {
String csvContent = """
1,2,3
4,5
//Lösung
a,b
""";
Path tempFile = Files.createTempFile("invalidPuzzle", ".csv");
Files.writeString(tempFile, csvContent);
CSVReader reader = new CSVReader();
assertThrows(NumberFormatException.class, () -> reader.readPuzzleWithSolution(tempFile.toString()));
Files.delete(tempFile);
}
@Test
void testReadPuzzleWithoutSolution() throws IOException {
String csvContent = """
1,2,3
4,5,6
7,8,9
""";
Path tempFile = Files.createTempFile("noSolutionPuzzle", ".csv");
Files.writeString(tempFile, csvContent);
CSVReader reader = new CSVReader();
PuzzleData data = reader.readPuzzleWithSolution(tempFile.toString());
assertNotNull(data.getPuzzle());
assertTrue(data.getSolutionCoordinates().isEmpty());
Files.delete(tempFile);
}
}

View File

@ -0,0 +1,24 @@
package de.deversmann.domain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class HighscoreEntryTest {
@Test
void testHighscoreEntryProperties() {
HighscoreEntry entry = new HighscoreEntry("Alice", 120, 3);
assertEquals("Alice", entry.getPlayerName());
assertEquals(120, entry.getTimeSeconds());
assertEquals(3, entry.getErrorCount());
}
@Test
void testToString() {
HighscoreEntry entry = new HighscoreEntry("Alice", 120, 3);
assertEquals("Alice - 120s (Fehler: 3)", entry.toString());
}
}

View File

@ -0,0 +1,63 @@
package de.deversmann.domain;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class HighscoreManagerTest {
@Test
void testAddAndRetrieveHighscores() {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore("Puzzle1", "Alice", 120, 2);
List<HighscoreEntry> highscores = manager.getHighscores("Puzzle1");
assertEquals(1, highscores.size());
assertEquals("Alice", highscores.get(0).getPlayerName());
assertEquals(120, highscores.get(0).getTimeSeconds());
assertEquals(2, highscores.get(0).getErrorCount());
}
@Test
void testSaveAndLoadHighscores() throws IOException {
HighscoreManager manager = new HighscoreManager();
manager.addHighscore("Puzzle1", "Alice", 120, 2);
Path tempFile = Files.createTempFile("testHighscores", ".txt");
manager.saveSinglePuzzle("Puzzle1", tempFile.toString());
HighscoreManager newManager = new HighscoreManager();
newManager.loadSinglePuzzle("Puzzle1", tempFile.toString());
List<HighscoreEntry> highscores = newManager.getHighscores("Puzzle1");
assertEquals(1, highscores.size());
assertEquals("Alice", highscores.get(0).getPlayerName());
assertEquals(120, highscores.get(0).getTimeSeconds());
assertEquals(2, highscores.get(0).getErrorCount());
Files.delete(tempFile);
}
@Test
void testGetAverageTimeNoEntries() {
HighscoreManager manager = new HighscoreManager();
double avgTime = manager.getAverageTime("Puzzle1");
assertEquals(-1, avgTime, "Average time should be -1 for no entries.");
}
@Test
void testLoadSinglePuzzleFileNotFound() {
HighscoreManager manager = new HighscoreManager();
assertDoesNotThrow(() -> manager.loadSinglePuzzle("Puzzle1", "nonexistent.txt"));
assertTrue(manager.getHighscores("Puzzle1").isEmpty());
}
}

View File

@ -0,0 +1,30 @@
package de.deversmann.domain;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
class PuzzleDataTest {
@Test
void testGetPuzzle() {
int[][] puzzle = {{1, 2}, {3, 4}};
List<int[]> solution = List.of(new int[]{1, 1}, new int[]{2, 2});
PuzzleData data = new PuzzleData(puzzle, solution);
assertArrayEquals(puzzle, data.getPuzzle());
}
@Test
void testGetSolutionCoordinates() {
int[][] puzzle = {{1, 2}, {3, 4}};
List<int[]> solution = List.of(new int[]{1, 1}, new int[]{2, 2});
PuzzleData data = new PuzzleData(puzzle, solution);
assertEquals(solution, data.getSolutionCoordinates());
}
}

View File

@ -0,0 +1,22 @@
package de.deversmann.domain;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ZeiterfassungTest {
@Test
void testZeiterfassungStartStop() throws InterruptedException {
Zeiterfassung timer = new Zeiterfassung();
timer.start();
Thread.sleep(1000); // 1 Sekunde warten
timer.stop();
long elapsedTime = timer.getElapsedTimeInSeconds();
assertTrue(elapsedTime >= 1 && elapsedTime < 2, "Elapsed time should be approximately 1 second.");
}
}

View File

@ -0,0 +1,153 @@
package de.deversmann.facade;
import de.deversmann.domain.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class FacadeTest {
private CSVReader csvReaderMock;
private HighscoreManager highscoreManagerMock;
private Zeiterfassung zeiterfassungMock;
private Facade facade;
@BeforeEach
void setup() throws Exception {
facade = new Facade();
csvReaderMock = mock(CSVReader.class);
highscoreManagerMock = mock(HighscoreManager.class);
zeiterfassungMock = mock(Zeiterfassung.class);
PowerMockito.field(Facade.class, "csvReader").set(facade, csvReaderMock);
PowerMockito.field(Facade.class, "highscoreManager").set(facade, highscoreManagerMock);
PowerMockito.field(Facade.class, "zeiterfassung").set(facade, zeiterfassungMock);
}
@Test
void testLadeSpielfeldMitEchtenDaten() throws IOException {
PuzzleData puzzleData = new PuzzleData(new int[][]{{1, 2, 3, 4}}, List.of(new int[]{1, 1}));
when(csvReaderMock.readPuzzleWithSolution(anyString())).thenReturn(puzzleData);
facade.ladeSpielfeld("test.csv");
assertNotNull(facade.getAktuellesPuzzle());
assertEquals(1, facade.getLoesungsKoordinaten().size());
}
@Test
void testStopZeiterfassung() {
when(zeiterfassungMock.getElapsedTimeInSeconds()).thenReturn(42L);
long elapsedTime = facade.stopZeiterfassung();
verify(zeiterfassungMock, times(1)).stop();
assertEquals(42L, elapsedTime);
}
@Test
void testGetElapsedTimeSoFar() {
when(zeiterfassungMock.getElapsedTimeInSeconds()).thenReturn(15L);
long elapsedTime = facade.getElapsedTimeSoFar();
verify(zeiterfassungMock, times(1)).getElapsedTimeInSeconds();
assertEquals(15L, elapsedTime);
}
@Test
void testIncrementErrorCount() {
assertEquals(0, facade.getCurrentErrorCount());
facade.incrementErrorCount();
assertEquals(1, facade.getCurrentErrorCount());
}
@Test
void testAddHighscoreForCurrentPuzzle() throws IOException {
facade.ladeSpielfeld("test.csv");
facade.addHighscoreForCurrentPuzzle("Player1", 100L, 3);
verify(highscoreManagerMock, times(1))
.addHighscore(eq("test.csv"), eq("Player1"), eq(100L), eq(3));
}
@Test
void testGetHighscoresForCurrentPuzzle() throws IOException {
when(highscoreManagerMock.getHighscores("test.csv"))
.thenReturn(List.of(new HighscoreEntry("Player1", 100L, 2)));
facade.ladeSpielfeld("test.csv");
List<HighscoreEntry> highscores = facade.getHighscoresForCurrentPuzzle();
assertEquals(1, highscores.size());
assertEquals("Player1", highscores.get(0).getPlayerName());
}
@Test
void testGetAverageTimeForCurrentPuzzle() throws IOException {
when(highscoreManagerMock.getAverageTime("test.csv")).thenReturn(75.0);
facade.ladeSpielfeld("test.csv");
double avgTime = facade.getAverageTimeForCurrentPuzzle();
assertEquals(75.0, avgTime, 0.01);
}
@Test
void testSaveCurrentPuzzleHighscoreToFile() throws IOException {
facade.ladeSpielfeld("test.csv");
facade.saveCurrentPuzzleHighscoreToFile();
verify(highscoreManagerMock, times(1))
.saveSinglePuzzle(eq("test.csv"), anyString());
}
@Test
void testLoadCurrentPuzzleHighscoreFromFile() throws IOException {
facade.ladeSpielfeld("test.csv");
facade.loadCurrentPuzzleHighscoreFromFile();
verify(highscoreManagerMock, times(1))
.loadSinglePuzzle(eq("test.csv"), anyString());
}
@Test
void testGetAktuellesPuzzle() throws IOException {
PuzzleData puzzleData = new PuzzleData(new int[][]{{1, 2, 3, 4}}, List.of());
when(csvReaderMock.readPuzzleWithSolution(anyString())).thenReturn(puzzleData);
facade.ladeSpielfeld("test.csv");
int[][] puzzle = facade.getAktuellesPuzzle();
assertNotNull(puzzle);
assertEquals(4, puzzle[0].length);
}
@Test
void testGetLoesungsKoordinaten() throws IOException {
PuzzleData puzzleData = new PuzzleData(new int[][]{{1, 2}}, List.of(new int[]{1, 1}));
when(csvReaderMock.readPuzzleWithSolution(anyString())).thenReturn(puzzleData);
facade.ladeSpielfeld("test.csv");
List<int[]> solutionCoordinates = facade.getLoesungsKoordinaten();
assertNotNull(solutionCoordinates);
assertEquals(1, solutionCoordinates.size());
assertArrayEquals(new int[]{1, 1}, solutionCoordinates.get(0));
}
}

View File

@ -0,0 +1,153 @@
package de.deversmann.gui;
import de.deversmann.facade.Facade;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import javax.swing.*;
import java.io.IOException;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
class GameViewTest {
private Facade facadeMock;
private GameView gameView;
@BeforeEach
void setup() {
facadeMock = mock(Facade.class);
gameView = new GameView(facadeMock);
}
@Test
void testInitComponents() {
assertNotNull(gameView.cbSpielfelder);
assertNotNull(gameView.btnLoad);
assertNotNull(gameView.btnRandom);
assertNotNull(gameView.btnCheckSolution);
assertNotNull(gameView.btnShowSolution);
assertNotNull(gameView.btnReset);
assertNotNull(gameView.timeLabel);
assertNotNull(gameView.spielfeldGUI);
}
@Test
void testLoadPuzzleActionWithRealData() throws Exception {
doNothing().when(facadeMock).ladeSpielfeld("4x4.csv");
gameView.ladeSpielfeld("4x4.csv");
verify(facadeMock, times(1)).ladeSpielfeld("4x4.csv");
}
@Test
void testLoadInvalidPuzzle() throws IOException {
doThrow(new IOException("Invalid file")).when(facadeMock).ladeSpielfeld("invalid.csv");
gameView.ladeSpielfeld("invalid.csv");
verify(facadeMock, times(1)).ladeSpielfeld("invalid.csv");
}
@Test
void testCheckSolutionWithCorrectData() {
var blackCells = List.of(new int[]{0, 0});
var solutionCoordinates = List.of(new int[]{1, 1});
when(facadeMock.getLoesungsKoordinaten()).thenReturn(solutionCoordinates);
when(facadeMock.getAktuellesPuzzle()).thenReturn(new int[][]{{1, 2}});
SpielfeldGUI spielfeldGUIMock = mock(SpielfeldGUI.class);
when(spielfeldGUIMock.getBlackCells()).thenReturn(blackCells);
gameView.spielfeldGUI = spielfeldGUIMock;
gameView.checkSolution();
verify(spielfeldGUIMock, never()).markCellAsError(anyInt(), anyInt());
verify(facadeMock, times(1)).stopZeiterfassung();
verify(facadeMock, times(1)).addHighscoreForCurrentPuzzle(anyString(), anyLong(), anyInt());
}
@Test
void testCheckSolutionWithErrors() {
var blackCells = List.of(new int[]{0, 1});
var solutionCoordinates = List.of(new int[]{1, 1});
when(facadeMock.getLoesungsKoordinaten()).thenReturn(solutionCoordinates);
when(facadeMock.getAktuellesPuzzle()).thenReturn(new int[][]{{1, 2}});
SpielfeldGUI spielfeldGUIMock = mock(SpielfeldGUI.class);
when(spielfeldGUIMock.getBlackCells()).thenReturn(blackCells);
gameView.spielfeldGUI = spielfeldGUIMock;
gameView.checkSolution();
verify(spielfeldGUIMock, times(1)).markCellAsError(0, 1);
verify(facadeMock, times(1)).incrementErrorCount();
}
@Test
void testShowSolution() {
int[][] mockGrid = {{1, 2}, {3, 4}};
List<int[]> solution = List.of(new int[]{1, 1}, new int[]{2, 2});
when(facadeMock.getAktuellesPuzzle()).thenReturn(mockGrid);
when(facadeMock.getLoesungsKoordinaten()).thenReturn(solution);
SpielfeldGUI spielfeldGUIMock = mock(SpielfeldGUI.class);
gameView.spielfeldGUI = spielfeldGUIMock;
gameView.showSolution();
verify(spielfeldGUIMock, times(1)).updateGrid(mockGrid);
verify(spielfeldGUIMock, times(1)).setCellBlack(0, 0); // Lösung wird schwarz markiert
verify(spielfeldGUIMock, times(1)).setCellBlack(1, 1);
verify(spielfeldGUIMock, times(1)).setAllNonBlackToWhite();
}
@Test
void testResetField() {
int[][] mockGrid = {{1, 2}, {3, 4}};
when(facadeMock.getAktuellesPuzzle()).thenReturn(mockGrid);
SpielfeldGUI spielfeldGUIMock = mock(SpielfeldGUI.class);
gameView.spielfeldGUI = spielfeldGUIMock;
gameView.resetField();
verify(spielfeldGUIMock, times(1)).updateGrid(mockGrid);
assertEquals("Time: 0s", gameView.timeLabel.getText());
}
@Test
void testTimerUpdatesTimeLabel() {
when(facadeMock.getElapsedTimeSoFar()).thenReturn(42L);
Timer timer = gameView.timer;
assertNotNull(timer);
timer.getActionListeners()[0].actionPerformed(null);
JLabel timeLabel = gameView.timeLabel;
assertEquals("Time: 42s", timeLabel.getText());
}
@Test
void testRandomPuzzleLoading() throws Exception {
doNothing().when(facadeMock).ladeSpielfeld(anyString());
gameView.btnRandom.doClick();
verify(facadeMock, atLeastOnce()).ladeSpielfeld(anyString());
}
@Test
void testContainsCell() {
List<int[]> blackCells = List.of(new int[]{1, 1}, new int[]{2, 2});
assertTrue(gameView.containsCell(blackCells, "1,1"));
assertFalse(gameView.containsCell(blackCells, "3,3"));
}
}

View File

@ -0,0 +1,41 @@
package de.deversmann.gui;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class SpielfeldGUITest {
@Test
void testUpdateGrid() {
SpielfeldGUI gui = new SpielfeldGUI(2, 2);
int[][] grid = {
{1, 2},
{3, 4}
};
gui.updateGrid(grid);
assertNotNull(gui.getBlackCells());
assertEquals(0, gui.getBlackCells().size());
}
@Test
void testSetCellBlack() {
SpielfeldGUI gui = new SpielfeldGUI(2, 2);
gui.setCellBlack(0, 1);
assertEquals(1, gui.getBlackCells().size());
assertArrayEquals(new int[]{0, 1}, gui.getBlackCells().get(0));
}
@Test
void testMarkCellAsError() {
SpielfeldGUI gui = new SpielfeldGUI(2, 2);
gui.markCellAsError(0, 1);
// Fehlerhafte Zelle wird rot markiert (Border)
assertNotNull(gui.getBlackCells());
}
}