Compare commits

...

3 Commits

Author SHA1 Message Date
Martin Prokoph
f881427a67 feat: save highscores per difficulty 2025-09-09 15:03:24 +02:00
Martin Prokoph
db537f84ae feat: less drastic difficulty scaling 2025-09-09 10:27:56 +02:00
Martin Prokoph
5490ff8b5d docs: update README 2025-09-09 10:25:23 +02:00
8 changed files with 69 additions and 31 deletions

View File

@@ -4,14 +4,15 @@ Erstellt für das Modul "Anwendungsorientierte Programmierung" im Studiengang Me
Dieser Code wurde vollständig von mir und ohne die Verwendung von LLMs geschrieben.
An einigen Stellen wurde auf Wissen von StackOverflow und Baeldung zugegriffen dies ist immer in den JavaDocs gekennzeichnet.
Die Commit-Historie ist [auf meinem Gitea](https://git.midnightdust.eu/Motschen/TetrisClone/commits/branch/main) einsehbar.
Gradle ist als Build-Tool eingerichtet, um elegant Jar-Dateien zu bauen, dabei aber völlig optional.
Können Sie meinen Highscore schlagen?
![Screenshot des Tetris-Spiels](assets/ingame.png)
## Im Falle einer IllegalArgumentException
Eclipse ist unfähig Java-Konventionen zu befolgen und fügt den resources-Ordner nicht automatisch zum classpath hinzu.
Um das zu beheben, gehen Sie in die Projekteinstellungen -> Java Build Path -> Source und fügen Sie den resources-Ordner manuell hinzu.
Eclipse ist unfähig, Java-Konventionen zu befolgen und fügt den 'resources'-Ordner nicht automatisch zum classpath hinzu.
Um das zu beheben, gehen Sie in die Projekteinstellungen -> Java Build Path -> Source und fügen Sie den Ordner `src/main/resources` manuell hinzu.
## Rechtliche Hinweise
»Tetris« ist eine eingetragene Marke von The Tetris Company, Inc.

View File

@@ -1,13 +1,22 @@
package eu.midnightdust.yaytris;
import eu.midnightdust.yaytris.util.Difficulty;
import eu.midnightdust.yaytris.util.json.Comment;
import eu.midnightdust.yaytris.util.json.NightJson;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unused") // Comments are unused in code, but are added to the JSON file for readability
public class HighScores {
private static final NightJson json = new NightJson(HighScores.class, "tetris_scores.json5");
public static Map<String, Integer> scores = new HashMap<>();
public static Comment c1 = new Comment("Highscores for each difficulty");
public static Map<String, Integer> Noob = new HashMap<>();
public static Map<String, Integer> Easy = new HashMap<>();
public static Map<String, Integer> Normal = new HashMap<>();
public static Map<String, Integer> Hard = new HashMap<>();
public static Map<String, Integer> Extreme = new HashMap<>();
public static Map<String, Integer> WTF = new HashMap<>();
/**
* Saves a new high score for the specified player
@@ -16,10 +25,21 @@ public class HighScores {
* @param score the score to save
*/
public static void addScore(String playerName, int score) {
scores.put(playerName, score);
diffToMap(Settings.difficulty).put(playerName, score);
write();
}
public static Map<String, Integer> diffToMap(Difficulty difficulty) {
switch (difficulty) {
case EASY: return Easy;
case NORMAL: return Normal;
case HARD: return Hard;
case EXTREME: return Extreme;
case WTF: return WTF;
default: return Noob;
}
}
public static void load() {
json.readJson();
}

View File

@@ -97,7 +97,7 @@ public class Tetris {
timer.purge();
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).gameOver();
ui.transferFocus();
if (HighScores.scores.values().stream().noneMatch(hs -> hs > space.getScore())) ui.showHighscoreDialog(space.getScore());
if (HighScores.diffToMap(Settings.difficulty).values().stream().noneMatch(hs -> hs > space.getScore())) ui.showHighscoreDialog(space.getScore());
}
/**
@@ -125,12 +125,12 @@ public class Tetris {
* @see ScoreMenu
*/
public static void updateLevel(int score) {
int newLevel = Math.max(0, (int) (score / 1000f));
int newLevel = Math.max(0, (int) (score / 1400f));
if (newLevel != space.level) {
if (gravityTask != null && Settings.shouldScaleSpeed) {
gravityTask.cancel();
gravityTask = new GravityTimerTask();
timer.scheduleAtFixedRate(gravityTask, 0, Math.max(10, Settings.difficulty.getTimerPeriod() - (Settings.difficulty.getTimerPeriod() / 8) * newLevel));
timer.scheduleAtFixedRate(gravityTask, 0, Math.max(10, Settings.difficulty.getTimerPeriod() - (Settings.difficulty.getTimerPeriod() / 16) * newLevel));
}
space.level = newLevel;
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateLevel(newLevel);

View File

@@ -1,6 +1,7 @@
package eu.midnightdust.yaytris.ui;
import eu.midnightdust.yaytris.HighScores;
import eu.midnightdust.yaytris.Settings;
import eu.midnightdust.yaytris.util.CatppuccinColor;
import javax.swing.*;
@@ -8,6 +9,7 @@ import javax.swing.border.LineBorder;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static eu.midnightdust.yaytris.Translation.t;
import static eu.midnightdust.yaytris.ui.TetrisUI.scale;
@@ -20,11 +22,12 @@ public class HighScoreMenu extends JPanel {
this.setBounds(x, y, width, height);
this.setLayout(null);
this.add(new JLabel(t("highscores.title")));
this.add(new JLabel(t("highscores.title", Settings.difficulty)));
List<String> highscores = new ArrayList<>();
for (String key : HighScores.scores.keySet()) {
highscores.add(String.format("%s %s", HighScores.scores.get(key), key));
Map<String, Integer> scores = HighScores.diffToMap(Settings.difficulty);
for (String key : scores.keySet()) {
highscores.add(String.format("%s %s", scores.get(key), key));
}
highscores.sort((s1, s2) -> Integer.compare(Integer.parseInt(s2.split("")[0].replace(" ", "")), Integer.parseInt(s1.split("")[0].replace(" ", ""))));
JList<String> highscoreList = new JList<>(highscores.toArray(String[]::new));

View File

@@ -17,7 +17,7 @@ import java.util.regex.Pattern;
* Concept inspired by GSON
*/
public class NightJson {
private static final String KEY_PATTERN = "\"(-?[A-Za-z-_.]*)\":";
private static final String KEY_PATTERN = "\"(.*)\":";
Class<?> jsonClass;
Field jsonMap;
String fileLocation;
@@ -175,21 +175,8 @@ public class NightJson {
private Object getValue(String s, String key, Function<String, Class<?>> keyToType, Iterator<String> pairIterator) {
String val = s.split(KEY_PATTERN, 2)[1];
if (s.contains("{")) { // Handle maps recursively
StringBuilder submapString = new StringBuilder();
int level = charAmount(s, '{');
submapString.append(val);
if (pairIterator.hasNext()) submapString.append(",");
while (pairIterator.hasNext()) {
String next = pairIterator.next();
submapString.append(next);
level += charAmount(next, '{');
level -= charAmount(next, '}');
if (level <= 0) break;
if (pairIterator.hasNext()) submapString.append(",");
}
Optional<Field> field = getField(key);
return jsonToMap(String.valueOf(submapString), k -> field.isPresent() ? getTypeArgument(field.get(), 1) : String.class);
if (s.contains("{")) {
return readJsonMap(key, pairIterator, val);
}
else {
while (val.startsWith(" ")) val = val.substring(1);
@@ -200,6 +187,27 @@ public class NightJson {
}
}
/**
* Handle maps recursively
*/
private Map<String, Object> readJsonMap(String key, Iterator<String> pairIterator, String val) {
StringBuilder submapString = new StringBuilder();
String next = val;
int level = 0;
while (next != null) {
submapString.append(next);
level += charAmount(next, '{');
level -= charAmount(next, '}');
if (level <= 0) break;
if (!pairIterator.hasNext()) {
submapString.append(",");
next = pairIterator.next();
} else next = null;
}
Optional<Field> field = getField(key);
return jsonToMap(String.valueOf(submapString), k -> field.isPresent() ? getTypeArgument(field.get(), 1) : String.class);
}
/**
* Count the amount of appearances of a char in a string.
*

View File

@@ -5,7 +5,7 @@
"game.over": "Game over :(",
"game.score": "Score: %s",
"game.time": "Zeit: %s",
"highscores.title": "Highscores:",
"highscores.title": "Highscores (%s):",
"menu.exit": "Spiel verlassen",
"menu.highscores": "Highscores",
"menu.settings": "Einstellungen",

View File

@@ -5,7 +5,7 @@
"game.over": "Game over :(",
"game.score": "Score: %s",
"game.time": "Time: %s",
"highscores.title": "Highscores:",
"highscores.title": "Highscores (%s):",
"menu.exit": "Exit Game",
"menu.highscores": "Highscores",
"menu.settings": "Settings",

View File

@@ -1,5 +1,11 @@
{
"scores": {
"MartinProkoph": 8040
}
// Highscores for each difficulty
"Noob": {},
"Easy": {},
"Normal": {
"Martin": 10593
},
"Hard": {},
"Extreme": {},
"WTF": {}
}