Compare commits
5 Commits
4d175761a5
...
d10313ea76
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d10313ea76 | ||
|
|
93c0ee5f95 | ||
|
|
8037f9a323 | ||
|
|
6cc7b95852 | ||
|
|
a228e9ab1a |
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@@ -4,7 +4,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<file type="web" url="file://$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
24
src/main/java/eu/midnightdust/yaytris/HighScores.java
Normal file
24
src/main/java/eu/midnightdust/yaytris/HighScores.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package eu.midnightdust.yaytris;
|
||||
|
||||
import eu.midnightdust.yaytris.util.NightJson;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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 void addScore(String playerName, int score) {
|
||||
scores.put(playerName, score);
|
||||
write();
|
||||
}
|
||||
|
||||
public static void load() {
|
||||
json.readJson();
|
||||
}
|
||||
|
||||
public static void write() {
|
||||
json.writeJson();
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ public class Settings {
|
||||
public static int musicVolume = 100;
|
||||
public static int soundVolume = 100;
|
||||
public static float guiScale = 3.f;
|
||||
public static boolean shouldScaleSpeed = true;
|
||||
public static Difficulty difficulty = Difficulty.NORMAL;
|
||||
|
||||
public static void load() {
|
||||
|
||||
@@ -25,6 +25,7 @@ public class Tetris {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Exception | Error e) { System.out.printf("%s: %s\n", "Error setting system look and feel", e); }
|
||||
Settings.load();
|
||||
HighScores.load();
|
||||
timer = new Timer("Tetris falling pieces");
|
||||
space = new Space();
|
||||
ui = new TetrisUI();
|
||||
@@ -55,14 +56,28 @@ 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());
|
||||
}
|
||||
|
||||
public static void updateScore(int score) {
|
||||
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateScore(score);
|
||||
updateLevel(score);
|
||||
}
|
||||
public static void updateTime() {
|
||||
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateTime(startTime);
|
||||
}
|
||||
public static void updateLevel(int score) {
|
||||
int newLevel = Math.max(0, (int) (score / 1000f));
|
||||
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));
|
||||
}
|
||||
space.level = newLevel;
|
||||
if (ui.getMenuPanel() instanceof ScoreMenu) ((ScoreMenu) ui.getMenuPanel()).updateLevel(newLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public static class GravityTimerTask extends TimerTask {
|
||||
@Override
|
||||
|
||||
@@ -13,6 +13,7 @@ public class Space {
|
||||
private TetrominoShape nextShape;
|
||||
private Tetromino currentTetromino;
|
||||
private int score;
|
||||
public int level;
|
||||
|
||||
public Space() {
|
||||
gameMap = new Color[14][8];
|
||||
@@ -64,6 +65,10 @@ public class Space {
|
||||
return gameMap;
|
||||
}
|
||||
|
||||
public int getScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
public void onLinesChanged(Tetromino tetromino, int... lines) {
|
||||
int combo = 0;
|
||||
Set<Integer> completedLines = new TreeSet<>();
|
||||
@@ -79,7 +84,7 @@ public class Space {
|
||||
gameMap[line][i] = newBlobs[i];
|
||||
}
|
||||
if (Arrays.stream(gameMap[line]).noneMatch(Objects::isNull)) { // Line completed
|
||||
combo += 10;
|
||||
combo += 40;
|
||||
completedLines.add(line);
|
||||
combo *= completedLines.size();
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ import static eu.midnightdust.yaytris.ui.TetrisUI.setFontScale;
|
||||
public class AbstractMenu extends JPanel {
|
||||
@Override
|
||||
public Component add(Component comp) {
|
||||
comp.setBounds(scale(60), scale(20+23*this.getComponentCount()), scale(100), scale(20));
|
||||
comp.setBounds(scale(60), scale(20+getSpacing()*this.getComponentCount()), scale(100), scale(20));
|
||||
if (comp instanceof JComponent) setFontScale((JComponent) comp);
|
||||
return super.add(comp);
|
||||
}
|
||||
|
||||
public int getSpacing() {
|
||||
return 23;
|
||||
}
|
||||
}
|
||||
|
||||
45
src/main/java/eu/midnightdust/yaytris/ui/HighScoreMenu.java
Normal file
45
src/main/java/eu/midnightdust/yaytris/ui/HighScoreMenu.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package eu.midnightdust.yaytris.ui;
|
||||
|
||||
import eu.midnightdust.yaytris.HighScores;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static eu.midnightdust.yaytris.ui.TetrisUI.scale;
|
||||
|
||||
public class HighScoreMenu extends JPanel {
|
||||
final TetrisUI ui;
|
||||
|
||||
HighScoreMenu(int x, int y, int width, int height, TetrisUI ui) {
|
||||
this.ui = ui;
|
||||
this.setBounds(x, y, width, height);
|
||||
this.setLayout(null);
|
||||
|
||||
this.add(new JLabel("Highscores:"));
|
||||
|
||||
List<String> highscores = new ArrayList<>();
|
||||
for (String key : HighScores.scores.keySet()) {
|
||||
highscores.add(String.format("%s – %s", HighScores.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));
|
||||
JScrollPane highscoreScrollPane = new JScrollPane(highscoreList);
|
||||
this.add(highscoreScrollPane);
|
||||
highscoreScrollPane.setBounds(scale(60), scale(43), scale(100), scale(80));
|
||||
|
||||
JButton backButton = new JButton("Zurück");
|
||||
backButton.addActionListener(ui::openMainMenu);
|
||||
backButton.setBounds(scale(60), scale(140), scale(100), scale(20));
|
||||
this.add(backButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component add(Component comp) {
|
||||
if (comp instanceof JLabel) {
|
||||
comp.setBounds(scale(60), scale(30), scale(100), scale(7));
|
||||
}
|
||||
return super.add(comp);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,10 @@ public class MainMenu extends AbstractMenu {
|
||||
settingsButton.addActionListener(ui::openSettings);
|
||||
this.add(settingsButton);
|
||||
|
||||
JButton highscoreButton = new JButton("Highscores");
|
||||
highscoreButton.addActionListener(ui::openHighscores);
|
||||
this.add(highscoreButton);
|
||||
|
||||
JButton leaveButton = new JButton("Spiel verlassen");
|
||||
leaveButton.addActionListener(e -> System.exit(0));
|
||||
this.add(leaveButton);
|
||||
|
||||
@@ -15,6 +15,7 @@ public class ScoreMenu extends AbstractMenu {
|
||||
final PreviewCanvas previewCanvas;
|
||||
final JLabel gameOverLabel;
|
||||
final JLabel currentScoreLabel;
|
||||
final JLabel currentLevelLabel;
|
||||
final JLabel currentTimeLabel;
|
||||
|
||||
ScoreMenu(int x, int y, int width, int height, TetrisUI ui) {
|
||||
@@ -34,6 +35,9 @@ public class ScoreMenu extends AbstractMenu {
|
||||
this.currentScoreLabel = new JLabel("Score: 0");
|
||||
this.add(currentScoreLabel);
|
||||
|
||||
this.currentLevelLabel = new JLabel("Level: 0");
|
||||
this.add(currentLevelLabel);
|
||||
|
||||
this.currentTimeLabel = new JLabel("Zeit: 00:00");
|
||||
this.add(currentTimeLabel);
|
||||
|
||||
@@ -43,7 +47,7 @@ public class ScoreMenu extends AbstractMenu {
|
||||
}
|
||||
|
||||
public void updateScore(int score) {
|
||||
this.currentScoreLabel.setText("Score: %s".formatted(score));
|
||||
this.currentScoreLabel.setText(String.format("Score: %s", score));
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
@@ -51,11 +55,21 @@ public class ScoreMenu extends AbstractMenu {
|
||||
LocalTime timeElapsed = LocalTime.ofNanoOfDay(LocalTime.now().toNanoOfDay() - startingTime.toNanoOfDay());
|
||||
|
||||
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern(timeElapsed.getHour() > 0 ? "HH:mm:ss" : "mm:ss");
|
||||
this.currentTimeLabel.setText("Zeit: %s".formatted(timeFormatter.format(timeElapsed)));
|
||||
this.currentTimeLabel.setText(String.format("Zeit: %s", timeFormatter.format(timeElapsed)));
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void gameOver() {
|
||||
this.gameOverLabel.setText("Game over :(");
|
||||
}
|
||||
|
||||
public void updateLevel(int level) {
|
||||
this.currentLevelLabel.setText(String.format("Level: %s", level));
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSpacing() {
|
||||
return 17;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,14 @@ public class SettingsMenu extends JPanel {
|
||||
});
|
||||
this.add(scaleSlider);
|
||||
|
||||
this.add(new JLabel("Geschwindigkeitszunahme:"));
|
||||
JCheckBox speedCheckbox = new JCheckBox("Aktiviert", Settings.shouldScaleSpeed);
|
||||
speedCheckbox.addChangeListener(change -> {
|
||||
Settings.shouldScaleSpeed = speedCheckbox.isSelected();
|
||||
Settings.write();
|
||||
});
|
||||
this.add(speedCheckbox);
|
||||
|
||||
this.add(new JLabel("Schwierigkeit:"));
|
||||
JComboBox<Difficulty> difficultySelector = new JComboBox<>(Difficulty.values());
|
||||
difficultySelector.setSelectedItem(Settings.difficulty);
|
||||
@@ -62,7 +70,7 @@ public class SettingsMenu extends JPanel {
|
||||
public Component add(Component comp) {
|
||||
comp.setBounds(scale(60), scale(20+23*this.getComponentCount()-labelAmount*10), scale(100), scale(20));
|
||||
if (comp instanceof JLabel) {
|
||||
comp.setBounds(scale(60), scale(50+23*(this.getComponentCount()-1)-labelAmount*10), scale(100), scale(5));
|
||||
comp.setBounds(scale(60), scale(50+23*(this.getComponentCount()-1)-labelAmount*10), scale(100), scale(7));
|
||||
labelAmount++;
|
||||
}
|
||||
if (comp instanceof JComponent) setFontScale((JComponent) comp);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.midnightdust.yaytris.ui;
|
||||
|
||||
import eu.midnightdust.yaytris.HighScores;
|
||||
import eu.midnightdust.yaytris.Tetris;
|
||||
import eu.midnightdust.yaytris.util.CatppuccinColor;
|
||||
|
||||
@@ -114,6 +115,20 @@ public class TetrisUI extends JFrame implements KeyListener {
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void openHighscores(ActionEvent actionEvent) {
|
||||
if (this.menuPanel != null) this.remove(menuPanel);
|
||||
menuPanel = new HighScoreMenu(scale(170), scale(40), scale(220), scale(226), this);
|
||||
menuPanel.setBackground(CatppuccinColor.BASE.getColor());
|
||||
menuPanel.setBorder(new LineBorder(CatppuccinColor.SURFACE0.getColor(), scale(2)));
|
||||
this.add(menuPanel);
|
||||
this.repaint();
|
||||
}
|
||||
|
||||
public void showHighscoreDialog(int score) {
|
||||
String playerName = JOptionPane.showInputDialog(null, "Gib deinen Namen ein:", "Neuer Highscore!", JOptionPane.PLAIN_MESSAGE);
|
||||
HighScores.addScore(playerName, score);
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/a/19746437
|
||||
private void setWindowPosition(JFrame window, int screen) {
|
||||
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@@ -17,15 +18,8 @@ public class NightJson {
|
||||
Class<?> jsonClass;
|
||||
String fileLocation;
|
||||
|
||||
public NightJson(Class<?> jsonClass) {
|
||||
this.jsonClass = jsonClass;
|
||||
}
|
||||
public NightJson(Class<?> jsonClass, String fileLocation) {
|
||||
this(jsonClass);
|
||||
this.fileLocation = fileLocation;
|
||||
}
|
||||
|
||||
public void setFileLocation(String fileLocation) {
|
||||
this.jsonClass = jsonClass;
|
||||
this.fileLocation = fileLocation;
|
||||
}
|
||||
|
||||
@@ -40,10 +34,11 @@ public class NightJson {
|
||||
Field field = it.next();
|
||||
jsonFile.write("\t");
|
||||
if (field.getType() == Comment.class) {
|
||||
jsonFile.write("// %s\n".formatted(((Comment) field.get(null)).commentString));
|
||||
jsonFile.write(String.format("// %s\n", ((Comment) field.get(null)).commentString));
|
||||
continue;
|
||||
}
|
||||
jsonFile.write((field.getType() == String.class || field.getType().isEnum() ? "\"%s\": \"%s\"" : "\"%s\": %s").formatted(field.getName(), field.get(null)));
|
||||
jsonFile.write(String.format("\"%s\": ", field.getName()));
|
||||
jsonFile.write(objToString(field.get(null), field.getType()));
|
||||
jsonFile.write(it.hasNext() ? ",\n" : "\n");
|
||||
}
|
||||
jsonFile.write("}");
|
||||
@@ -72,9 +67,13 @@ public class NightJson {
|
||||
lastKey.set(s.replaceAll("([\":])", ""));
|
||||
jsonKeyValuePairs.put(lastKey.get(), "");
|
||||
}
|
||||
else jsonKeyValuePairs.put(lastKey.get(), (jsonKeyValuePairs.get(
|
||||
lastKey.get()).isEmpty() ? "" : jsonKeyValuePairs.get(lastKey.get()) + " "
|
||||
) + s.replaceAll("([\",])", ""));
|
||||
else {
|
||||
String val = s.replaceAll("(\")", "");
|
||||
if (val.endsWith(",")) val = val.substring(0, val.length()-1);
|
||||
jsonKeyValuePairs.put(lastKey.get(), (jsonKeyValuePairs.get(
|
||||
lastKey.get()).isEmpty() ? "" : jsonKeyValuePairs.get(lastKey.get()) + " "
|
||||
) + val);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,17 +84,7 @@ public class NightJson {
|
||||
try { field = jsonClass.getField(key);
|
||||
} catch (NoSuchFieldException e) {continue;}
|
||||
|
||||
Object value = switch (field.getType().getName()) {
|
||||
case "byte" -> Byte.parseByte(currentString);
|
||||
case "int" -> Integer.parseInt(currentString);
|
||||
case "long" -> Long.parseLong(currentString);
|
||||
case "float" -> Float.parseFloat(currentString);
|
||||
case "double" -> Double.parseDouble(currentString);
|
||||
default -> currentString;
|
||||
};
|
||||
if (field.getType().isEnum()) value = Arrays.stream(field.getType().getEnumConstants())
|
||||
.filter(enumConstant -> Objects.equals(enumConstant.toString(), currentString)).findFirst().orElseThrow();
|
||||
field.set(field, value);
|
||||
field.set(field, stringToFieldObj(currentString, field));
|
||||
}
|
||||
jsonFile.close();
|
||||
} catch (IOException | IllegalAccessException | NoSuchElementException e) {
|
||||
@@ -104,6 +93,61 @@ public class NightJson {
|
||||
}
|
||||
}
|
||||
|
||||
private String objToString(Object value, Class<?> type) throws IllegalAccessException {
|
||||
if (type == Map.class) {
|
||||
StringBuilder mapPairs = new StringBuilder();
|
||||
Map<?, ?> map = (Map<?, ?>) value;
|
||||
Iterator<?> it = map.keySet().iterator();
|
||||
if (it.hasNext()) mapPairs.append("{");
|
||||
while (it.hasNext()) {
|
||||
Object key = it.next();
|
||||
Object val = map.get(key);
|
||||
mapPairs.append(String.format("%s: %s", objToString(key, key.getClass()), objToString(val, val.getClass())));
|
||||
if (it.hasNext()) mapPairs.append(",");
|
||||
else mapPairs.append("}");
|
||||
}
|
||||
return mapPairs.toString();
|
||||
}
|
||||
return String.format(type == String.class || type.isEnum() ? "\"%s\"" : "%s", value);
|
||||
}
|
||||
|
||||
private Object stringToFieldObj(String currentString, Field field) {
|
||||
if (field.getType() == Map.class) {
|
||||
Map<Object, Object> map = new HashMap<>();
|
||||
Iterator<String> it = Arrays.stream(currentString.substring(1, currentString.length()-1).split(",")).iterator();
|
||||
while (it.hasNext()) {
|
||||
String pair = it.next();
|
||||
if (!pair.contains(":")) break;
|
||||
int semicolonPos = pair.indexOf(":");
|
||||
Class<?> keyType = getPrimitiveType((Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]);
|
||||
Class<?> valType = getPrimitiveType((Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[1]);
|
||||
map.put(stringToObj(pair.substring(0, semicolonPos), keyType),
|
||||
stringToObj(pair.substring(semicolonPos+2), valType));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
return stringToObj(currentString, field.getType());
|
||||
}
|
||||
|
||||
private Object stringToObj(String currentString, Class<?> type) {
|
||||
switch (type.getName()) {
|
||||
case "byte": return Byte.parseByte(currentString);
|
||||
case "int": return Integer.parseInt(currentString);
|
||||
case "long": return Long.parseLong(currentString);
|
||||
case "float": return Float.parseFloat(currentString);
|
||||
case "double": return Double.parseDouble(currentString);
|
||||
case "boolean": return Boolean.parseBoolean(currentString);
|
||||
}
|
||||
if (type.isEnum()) return Arrays.stream(type.getEnumConstants())
|
||||
.filter(enumConstant -> Objects.equals(enumConstant.toString(), currentString)).findFirst().orElseThrow();
|
||||
else return currentString;
|
||||
}
|
||||
|
||||
public static Class<?> getPrimitiveType(Class<?> rawType) {
|
||||
try { return (Class<?>) rawType.getField("TYPE").get(null); // Tries to get primitive types from non-primitives (e.g. Boolean -> boolean)
|
||||
} catch (NoSuchFieldException | IllegalAccessException ignored) { return rawType; }
|
||||
}
|
||||
|
||||
public static class Comment {
|
||||
final String commentString;
|
||||
public Comment(String commentString) {
|
||||
|
||||
3
tetris_scores.json5
Normal file
3
tetris_scores.json5
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"scores": {"Martin": 5703}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"musicVolume": 100,
|
||||
"musicVolume": 19,
|
||||
"soundVolume": 100,
|
||||
"guiScale": 6.0,
|
||||
"shouldScaleSpeed": true,
|
||||
"difficulty": "Normal"
|
||||
}
|
||||
Reference in New Issue
Block a user