diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..23567b34b183d1037608f3eddb27ecafc77a9a8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "java.project.referencedLibraries": [ + "lib/**/*.jar" + ] + } + \ No newline at end of file diff --git a/Struktur b/Struktur new file mode 100644 index 0000000000000000000000000000000000000000..d13f7b14c87e9c7450a8ca32472cb8debb8e12aa --- /dev/null +++ b/Struktur @@ -0,0 +1,37 @@ +src/ +├── main/ +│ └── Main.java # Startpunkt der Anwendung +│ +├── models/ # Datenklassen +│ ├── User.java # Repräsentiert einen Benutzer +│ ├── GameData.java # Speichert Spielfortschritt (z.B. Level, Score) +│ ├── Level.java # Repräsentiert ein Level des Spiels +│ +├── managers/ # Logik und Verwaltung +│ ├── UserManager.java # Verwaltung der Benutzer (Login/Registrierung) +│ ├── SessionManager.java # Verwaltung der "angemeldet bleiben"-Sitzung +│ ├── GameManager.java # Verwaltung des Spielfortschritts +│ +├── utils/ # Hilfsklassen und Tools +│ ├── FileHandler.java # Speichern und Laden von Daten +│ ├── Constants.java # Konstanten für das Spiel +│ +├── gui/ # GUI-Klassen für Screens +│ ├── LoginScreen.java # Login-Bildschirm +│ ├── RegisterScreen.java # Registrierungsbildschirm +│ ├── StartScreen.java # Startbildschirm (Login/Register Buttons) +│ ├── MainScreen.java # Hauptmenü (Start Game, Load, Settings, etc.) +│ ├── GameScreen.java # Der eigentliche Traffic Jam-Spielbildschirm +│ ├── LoadScreen.java # Laden von Spielständen +│ ├── SettingsScreen.java # Einstellungen (optional) +│ ├── HowToPlayScreen.java # Anleitung zum Spiel (optional) +│ ├── CreditsScreen.java # Credits-Bildschirm (optional) +│ +├── game/ # Spiellogik +│ ├── TrafficJamGame.java # Hauptlogik des Spiels +│ ├── Tile.java # Einzelne Spielfelder (z.B. Autos, Hindernisse) +│ +└── data/ # Speicherordner für Benutzerdaten und Spielfortschritt + ├── users.dat # Binäre Datei mit Benutzerdaten + ├── savegame1.dat # Beispiel-Spielstand + ├── savegame2.dat # Beispiel-Spielstand \ No newline at end of file diff --git a/lib/gson-2.10.jar b/lib/gson-2.10.jar new file mode 100644 index 0000000000000000000000000000000000000000..4b57f5bec7733e27ec996f03a76193c32e0a3e86 Binary files /dev/null and b/lib/gson-2.10.jar differ diff --git a/src/data/GameData.json b/src/data/GameData.json new file mode 100644 index 0000000000000000000000000000000000000000..c9f8b2ebdefc2b8de699d24044e4b77872e0b79f --- /dev/null +++ b/src/data/GameData.json @@ -0,0 +1,16 @@ +{ + "1": { + "highestLevel": 2, + "highScores": { + "1": 5, + "2": 7 + } + }, + "2": { + "highestLevel": 2, + "highScores": { + "1": 5, + "2": 7 + } + } +} \ No newline at end of file diff --git a/src/data/Session.json b/src/data/Session.json new file mode 100644 index 0000000000000000000000000000000000000000..be1222065f909c10e17b0d66d36d17d0e3567827 --- /dev/null +++ b/src/data/Session.json @@ -0,0 +1 @@ +{"id":2,"username":"Paul2","passwordHash":"MJ1s2c3HpzPmzZg/s3EO/B/2zliC2Ihs6we4u8jS+ds\u003d","salt":"5xcEbfypxAEJNEXj/Zuo6A\u003d\u003d","highScore":0} \ No newline at end of file diff --git a/src/data/Users.json b/src/data/Users.json new file mode 100644 index 0000000000000000000000000000000000000000..e8886265db970fc676054e65a81fa5a1bc9e1496 --- /dev/null +++ b/src/data/Users.json @@ -0,0 +1 @@ +[{"id":1,"username":"Paul","passwordHash":"4dvqE5OxmjB9OhucOaipFDLMhnh+PMB1bd9Mf2jC0WQ\u003d","salt":"9vtHY/D1k/MKvnxUxhOHlw\u003d\u003d","highScore":0},{"id":2,"username":"Paul2","passwordHash":"MJ1s2c3HpzPmzZg/s3EO/B/2zliC2Ihs6we4u8jS+ds\u003d","salt":"5xcEbfypxAEJNEXj/Zuo6A\u003d\u003d","highScore":0}] \ No newline at end of file diff --git a/src/game/Game.java b/src/game/Game.java new file mode 100644 index 0000000000000000000000000000000000000000..71a1b47fb4005a260ef808308b8698493dee85cf --- /dev/null +++ b/src/game/Game.java @@ -0,0 +1,151 @@ +// File: src/game/Game.java +package game; + +import com.google.gson.Gson; +import com.google.gson.stream.JsonReader; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import managers.GameDataManager; + +public class Game { + private List<Vehicle> vehicles; + private Vehicle redCar; + private int currentLevel; + private int userId; + + public Game() { + currentLevel = 1; + vehicles = new ArrayList<>(); + userId = 1; // Default user ID + currentLevel = GameDataManager.getHighestLevel(userId); // Load highest level + boolean loaded = loadLevel(currentLevel); + if (!loaded) { + System.out.println("Kein Level gefunden!"); + } + } + + public Game(int level) { + currentLevel = level; + vehicles = new ArrayList<>(); + userId = 1; // Default user ID + boolean loaded = loadLevel(currentLevel); + if (!loaded) { + System.out.println("Kein Level gefunden!"); + } + } + + public Game(int userId, int level) { + this.userId = userId; + currentLevel = level; + vehicles = new ArrayList<>(); + boolean loaded = loadLevel(currentLevel); + if (!loaded) { + System.out.println("Kein Level gefunden!"); + } + } + + /** + * Lädt das Level aus der Datei src/maps/Level_<levelNumber>.json. + */ + public boolean loadLevel(int levelNumber) { + String filePath = "src/maps/Level_" + levelNumber + ".json"; + try { + if (!Files.exists(Paths.get(filePath))) { + return false; + } + Gson gson = new Gson(); + JsonReader reader = new JsonReader(new FileReader(filePath)); + LevelData levelData = gson.fromJson(reader, LevelData.class); + reader.close(); + + // Bestehende Fahrzeuge löschen + vehicles.clear(); + redCar = null; + + // Roter Wagen startet immer an der linken Seite in der mittleren Zeile + redCar = new Vehicle(0, 2, 2, true, true); + vehicles.add(redCar); + + for (VehicleData vData : levelData.vehicles) { + // Verhindere, dass blaue Wagen in den Pfad des roten Wagens spawnen + if (vData.isRed || (vData.x == 0 && vData.y == 2)) continue; + Vehicle v = new Vehicle(vData.x, vData.y, vData.length, vData.isHorizontal, vData.isRed); + vehicles.add(v); + } + + currentLevel = levelNumber; + if (userId != 0) { + GameDataManager.updateHighestLevel(userId, currentLevel); // Ensure the current level is updated + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Versucht, das nächste Level zu laden. Liefert true, wenn ein neues Level gefunden wurde. + */ + public boolean nextLevel() { + int nextLevel = currentLevel + 1; + boolean loaded = loadLevel(nextLevel); + if (loaded && userId != 0) { + GameDataManager.updateHighestLevel(userId, nextLevel); + } + return loaded; + } + + public List<Vehicle> getVehicles() { + return vehicles; + } + + public Vehicle getRedCar() { + return redCar; + } + + public int getCurrentLevel() { + return currentLevel; + } + + /** + * Gewinnbedingung: Wenn der rote Wagen das rechte Spielfeldende erreicht. + */ + public boolean checkWin() { + if (redCar == null) return false; + // Bei einem 6x6-Feld: x + Länge == 6 + return redCar.getX() + redCar.getLength() == 6; + } + + // Überprüfung von Kollisionen + public boolean checkCollision() { + for (Vehicle v : vehicles) { + if (v != redCar && redCar.collidesWith(v)) { + return true; + } + } + return false; + } + + // Setzt das Level zurück + public void resetLevel() { + loadLevel(currentLevel); + } + + // Innere Klassen, um die Level-Daten aus JSON zu repräsentieren + public static class LevelData { + public List<VehicleData> vehicles; + } + + public static class VehicleData { + public int x; + public int y; + public int length; + public boolean isHorizontal; + public boolean isRed; + } +} diff --git a/src/game/Vehicle.java b/src/game/Vehicle.java new file mode 100644 index 0000000000000000000000000000000000000000..91935e8d2ce4948534e0de315c70aa7f86a2cc2c --- /dev/null +++ b/src/game/Vehicle.java @@ -0,0 +1,85 @@ +// File: src/game/Vehicle.java +package game; + +import java.awt.*; +import java.util.List; + +public class Vehicle { + private int x, y, length; + private boolean isHorizontal, isRed; + + public Vehicle(int x, int y, int length, boolean isHorizontal, boolean isRed) { + this.x = x; + this.y = y; + this.length = length; + this.isHorizontal = isHorizontal; + this.isRed = isRed; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + + public int getLength() { + return length; + } + + public boolean isHorizontal() { + return isHorizontal; + } + + public boolean isRed() { + return isRed; + } + + public boolean canMove(int dx, int dy, List<Vehicle> vehicles) { + int newX = x + dx; + int newY = y + dy; + + // Check grid boundaries + if (isHorizontal) { + if (newX < 0 || newX + length > 6) return false; + } else { + if (newY < 0 || newY + length > 6) return false; + } + + // Check for collisions with other vehicles + Rectangle newBounds = new Rectangle(newX * 100, newY * 100, + isHorizontal ? length * 100 : 100, + isHorizontal ? 100 : length * 100); + for (Vehicle v : vehicles) { + if (v != this && newBounds.intersects(v.getBounds())) { + return false; + } + } + return true; + } + + public boolean move(int dx, int dy, List<Vehicle> vehicles) { + if (canMove(dx, dy, vehicles)) { + x += dx; + y += dy; + return true; + } + return false; + } + + public void move(int dx, int dy) { + x += dx; + y += dy; + } + + public Rectangle getBounds() { + int width = isHorizontal ? length * 100 : 100; + int height = isHorizontal ? 100 : length * 100; + return new Rectangle(x * 100, y * 100, width, height); + } + + public boolean collidesWith(Vehicle other) { + return this.getBounds().intersects(other.getBounds()); + } +} diff --git a/src/gui/GameScreen.java b/src/gui/GameScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..0676205e805989e232543bbfaf92a7d01345d4ad --- /dev/null +++ b/src/gui/GameScreen.java @@ -0,0 +1,267 @@ +// File: src/gui/GameScreen.java +package gui; + +import game.Game; +import game.Vehicle; +import managers.GameDataManager; +import models.User; +import gui.MainScreen; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.List; + +public class GameScreen extends JFrame { + private Game game; + private GamePanel gamePanel; + private JLabel levelLabel; + private JLabel moveCounterLabel; + private JLabel highScoreLabel; + private int moveCounter; + private int highScore; + + public GameScreen(User user, int level) { + setTitle("Traffic Jam Game"); + setSize(1280, 720); // Fenstergröße auf 1280x720 Pixel + setMinimumSize(new Dimension(1280, 720)); + setMaximumSize(new Dimension(1280, 720)); + setResizable(false); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + getContentPane().setBackground(Color.WHITE); + + game = new Game(user.getId(), level); // Pass the correct user ID + moveCounter = 0; + highScore = GameDataManager.getHighScore(user.getId(), level); + + // ----------------------------- + // Header-Panel: Enthält nur das Level-Label + // ----------------------------- + JPanel headerPanel = new JPanel(); + headerPanel.setLayout(null); // Absolute Positionierung + headerPanel.setPreferredSize(new Dimension(1280, 50)); + headerPanel.setBackground(Color.WHITE); + + // Level-Label in der Mitte des Header-Panels + levelLabel = new JLabel("Level: " + game.getCurrentLevel(), SwingConstants.CENTER); + levelLabel.setFont(new Font("Arial", Font.BOLD, 16)); + levelLabel.setOpaque(true); + levelLabel.setBackground(Color.WHITE); + int levelLabelWidth = 120; + int levelLabelX = (1280 - levelLabelWidth) / 2; + levelLabel.setBounds(levelLabelX, 10, levelLabelWidth, 30); + headerPanel.add(levelLabel); + add(headerPanel, BorderLayout.NORTH); + + // ----------------------------- + // GamePanel: Zeichnet das Spielfeld und die Fahrzeuge + // ----------------------------- + gamePanel = new GamePanel(user); + add(gamePanel, BorderLayout.CENTER); + + // ----------------------------- + // "Züge"-Label als freies Element (nicht in einem Panel) + // ----------------------------- + moveCounterLabel = new JLabel("Züge: " + moveCounter, SwingConstants.LEFT); + moveCounterLabel.setFont(new Font("Arial", Font.BOLD, 16)); + moveCounterLabel.setOpaque(true); + moveCounterLabel.setBackground(Color.WHITE); + // Feste Positionierung, z. B. x = 940, y = 56 + moveCounterLabel.setBounds(940, 56, 120, 30); + getLayeredPane().add(moveCounterLabel, JLayeredPane.MODAL_LAYER); + + highScoreLabel = new JLabel("Highscore: " + (highScore == Integer.MAX_VALUE ? "N/A" : highScore), SwingConstants.LEFT); + highScoreLabel.setFont(new Font("Arial", Font.BOLD, 16)); + highScoreLabel.setOpaque(true); + highScoreLabel.setBackground(Color.WHITE); + highScoreLabel.setBounds(940, 86, 120, 30); + getLayeredPane().add(highScoreLabel, JLayeredPane.MODAL_LAYER); + + // Add ESC key listener to the GameScreen + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + showPauseDialog(user); + } + } + }); + + setVisible(true); + } + + private void showPauseDialog(User user) { + int response = JOptionPane.showOptionDialog( + GameScreen.this, + "Was möchten Sie tun?", + "Pause", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + new String[]{"Nochmal spielen", "Hauptmenü"}, + "Nochmal spielen"); + + if (response == JOptionPane.YES_OPTION) { + game.resetLevel(); + moveCounter = 0; + moveCounterLabel.setText("Züge: " + moveCounter); + levelLabel.setText("Level: " + game.getCurrentLevel()); + highScore = GameDataManager.getHighScore(user.getId(), game.getCurrentLevel()); + highScoreLabel.setText("Highscore: " + (highScore == Integer.MAX_VALUE ? "N/A" : highScore)); + repaint(); + } else if (response == JOptionPane.NO_OPTION) { + new MainScreen(user); // Return to MainScreen + dispose(); // Close the current window + } + } + + /** + * Das Panel, in dem das Spielfeld und die Fahrzeuge gezeichnet werden. + */ + private class GamePanel extends JPanel { + private Vehicle selectedVehicle = null; + private User user; + + public GamePanel(User user) { + this.user = user; + setPreferredSize(new Dimension(1280, 720)); + setBackground(Color.WHITE); + setFocusable(true); + requestFocusInWindow(); + setLayout(new GridBagLayout()); // Damit das Spielfeld zentriert wird + + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + int mouseX = e.getX(); + int mouseY = e.getY(); + int gridSize = 600; // 6x6 Raster, je 100px pro Zelle + int xOffset = (getWidth() - gridSize) / 2; + int yOffset = (getHeight() - gridSize) / 2; + + // Passe die Mauskoordinaten an den Raster-Offset an + mouseX -= xOffset; + mouseY -= yOffset; + + // Finde das Fahrzeug, das angeklickt wurde + for (Vehicle v : game.getVehicles()) { + if (v.getBounds().contains(mouseX, mouseY)) { + selectedVehicle = v; + break; + } + } + } + }); + + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + showPauseDialog(user); + } + + if (selectedVehicle != null) { + int key = e.getKeyCode(); + boolean moved = false; + if (selectedVehicle.isHorizontal()) { + if (key == KeyEvent.VK_LEFT) { + moved = selectedVehicle.move(-1, 0, game.getVehicles()); + } else if (key == KeyEvent.VK_RIGHT) { + moved = selectedVehicle.move(1, 0, game.getVehicles()); + } + } else { + if (key == KeyEvent.VK_UP) { + moved = selectedVehicle.move(0, -1, game.getVehicles()); + } else if (key == KeyEvent.VK_DOWN) { + moved = selectedVehicle.move(0, 1, game.getVehicles()); + } + } + if (moved) { + moveCounter++; + moveCounterLabel.setText("Züge: " + moveCounter); + } + repaint(); + + if (game.checkWin()) { + // Vergleiche den aktuellen Zugzähler mit dem gespeicherten Highscore + boolean isNewHighScore = moveCounter < highScore; + if (isNewHighScore) { + highScore = moveCounter; + GameDataManager.updateHighScore(user.getId(), game.getCurrentLevel(), highScore); + } + // Nach dem Gewinn den Highscore immer neu auslesen, um Aktualität zu gewährleisten + highScore = GameDataManager.getHighScore(user.getId(), game.getCurrentLevel()); + highScoreLabel.setText("Highscore: " + (highScore == Integer.MAX_VALUE ? "N/A" : highScore)); + + String message = isNewHighScore + ? "NEW HIGHSCORE: Versuche " + moveCounter + : "Gewonnen: Versuche " + moveCounter; + + int response = JOptionPane.showOptionDialog( + GameScreen.this, + message + "\nWas möchten Sie tun?", + "Gewonnen", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + new String[]{"Nochmal spielen", "Nächstes Level", "Hauptmenü"}, + "Nächstes Level"); + + if (response == JOptionPane.YES_OPTION) { + game.resetLevel(); + moveCounter = 0; + moveCounterLabel.setText("Züge: " + moveCounter); + levelLabel.setText("Level: " + game.getCurrentLevel()); + highScore = GameDataManager.getHighScore(user.getId(), game.getCurrentLevel()); + highScoreLabel.setText("Highscore: " + (highScore == Integer.MAX_VALUE ? "N/A" : highScore)); + repaint(); + } else if (response == JOptionPane.NO_OPTION) { + boolean nextLoaded = game.nextLevel(); + if (nextLoaded) { + moveCounter = 0; + moveCounterLabel.setText("Züge: " + moveCounter); + levelLabel.setText("Level: " + game.getCurrentLevel()); + highScore = GameDataManager.getHighScore(user.getId(), game.getCurrentLevel()); + highScoreLabel.setText("Highscore: " + (highScore == Integer.MAX_VALUE ? "N/A" : highScore)); + repaint(); + } else { + JOptionPane.showMessageDialog(GameScreen.this, "Alle Levels gespielt!"); + new MainScreen(user); // Return to MainScreen + dispose(); // Close the current window + } + } else { + new MainScreen(user); // Übergibt den Benutzer an MainScreen + dispose(); // Schließt das aktuelle Fenster + } + } + } + } + }); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2d = (Graphics2D) g; + int gridSize = 600; // Rastergröße + int xOffset = (getWidth() - gridSize) / 2; + int yOffset = (getHeight() - gridSize) / 2; + + // Zeichne das 6x6 Raster (100px pro Zelle) + g2d.setColor(Color.LIGHT_GRAY); + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 6; j++) { + g2d.drawRect(xOffset + i * 100, yOffset + j * 100, 100, 100); + } + } + // Zeichne die Fahrzeuge + List<Vehicle> vehicles = game.getVehicles(); + for (Vehicle v : vehicles) { + g2d.setColor(v.isRed() ? Color.RED : Color.BLUE); + Rectangle bounds = v.getBounds(); + g2d.fillRect(xOffset + bounds.x, yOffset + bounds.y, bounds.width, bounds.height); + } + } + } +} diff --git a/src/gui/LoginScreen.java b/src/gui/LoginScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..af62ab0a01482b92dbde0e62312382905f3f2331 --- /dev/null +++ b/src/gui/LoginScreen.java @@ -0,0 +1,82 @@ +package gui; + +import managers.UserManager; +import managers.SessionManager; +import models.User; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class LoginScreen extends JFrame { + private UserManager userManager; + + public LoginScreen() { + userManager = new UserManager(); + + setTitle("Login"); + setSize(400, 300); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(5, 5, 5, 5); + + JLabel usernameLabel = new JLabel("Username:"); + gbc.gridx = 0; + gbc.gridy = 0; + add(usernameLabel, gbc); + + JTextField usernameField = new JTextField(20); + gbc.gridx = 1; + gbc.gridy = 0; + add(usernameField, gbc); + + JLabel passwordLabel = new JLabel("Password:"); + gbc.gridx = 0; + gbc.gridy = 1; + add(passwordLabel, gbc); + + JPasswordField passwordField = new JPasswordField(20); + gbc.gridx = 1; + gbc.gridy = 1; + add(passwordField, gbc); + + JCheckBox rememberMeCheckBox = new JCheckBox("Remember Me"); + gbc.gridx = 1; + gbc.gridy = 2; + add(rememberMeCheckBox, gbc); + + JButton loginButton = new JButton("Login"); + gbc.gridx = 1; + gbc.gridy = 3; + add(loginButton, gbc); + + loginButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + User user = userManager.login(username, password); + if (user != null) { + if (rememberMeCheckBox.isSelected()) { + SessionManager.saveSession(user); + } + JOptionPane.showMessageDialog(LoginScreen.this, "Login erfolgreich!"); + dispose(); // Close the login screen + new MainScreen(user); // Navigate to the main screen + } else { + JOptionPane.showMessageDialog(LoginScreen.this, "Ungültiger Benutzername oder Passwort."); + } + } + }); + + setVisible(true); + } + + public static void main(String[] args) { + new LoginScreen(); + } +} diff --git a/src/gui/MainScreen.java b/src/gui/MainScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..33809e6df1422a1272fa06121655e1a73137de1f --- /dev/null +++ b/src/gui/MainScreen.java @@ -0,0 +1,154 @@ +// File: src/gui/MainScreen.java +package gui; + +import managers.SessionManager; +import models.User; +import managers.GameDataManager; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; + +public class MainScreen extends JFrame { + + public MainScreen(User user) { + System.out.println("Initializing MainScreen for user: " + user.getUsername()); + + setTitle("Main Menu"); + setSize(1280, 720); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + // Hintergrund der ContentPane auf weiß setzen + getContentPane().setBackground(Color.WHITE); + setLayout(new BorderLayout()); + + // --------------------------- + // Linkes Panel mit Buttons + // --------------------------- + JPanel leftPanel = new JPanel(); + leftPanel.setLayout(new BoxLayout(leftPanel, BoxLayout.Y_AXIS)); + leftPanel.setPreferredSize(new Dimension(200, 0)); // feste Breite, Höhe passt sich an + leftPanel.setBackground(Color.WHITE); + + // Buttons erstellen + JButton newGameButton = new JButton("New Game"); + JButton playLevelButton = new JButton("Play Level " + getHighestLevel(user)); + JButton howToPlayButton = new JButton("How To Play"); + JButton creditsButton = new JButton("Credits"); + JButton logoutButton = new JButton("Logout"); + + // Buttons horizontal zentrieren + newGameButton.setAlignmentX(Component.CENTER_ALIGNMENT); + playLevelButton.setAlignmentX(Component.CENTER_ALIGNMENT); + howToPlayButton.setAlignmentX(Component.CENTER_ALIGNMENT); + creditsButton.setAlignmentX(Component.CENTER_ALIGNMENT); + logoutButton.setAlignmentX(Component.CENTER_ALIGNMENT); + + // Mit Glue-Elementen werden die Buttons gleichmäßig über die Höhe verteilt + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(newGameButton); + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(playLevelButton); + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(howToPlayButton); + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(creditsButton); + leftPanel.add(Box.createVerticalGlue()); + leftPanel.add(logoutButton); + leftPanel.add(Box.createVerticalGlue()); + + add(leftPanel, BorderLayout.WEST); + + // --------------------------- + // Oberes Panel: zentrierter Titel & Profilname rechts + // --------------------------- + JPanel topPanel = new JPanel(new BorderLayout()); + topPanel.setBackground(Color.WHITE); + topPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + + // West-Platzhalter: Gleiche Breite wie das Profil-Label, damit der Titel mittig liegt + JPanel westPlaceholder = new JPanel(); + westPlaceholder.setPreferredSize(new Dimension(200, 30)); + westPlaceholder.setBackground(Color.WHITE); + topPanel.add(westPlaceholder, BorderLayout.WEST); + + // East: Profilname (klickbar) + JLabel userNameLabel = new JLabel(user.getUsername(), SwingConstants.RIGHT); + userNameLabel.setFont(new Font("Arial", Font.BOLD, 16)); + userNameLabel.setOpaque(true); + userNameLabel.setBackground(Color.WHITE); + userNameLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + userNameLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); + topPanel.add(userNameLabel, BorderLayout.EAST); + + add(topPanel, BorderLayout.NORTH); + + // --------------------------- + // ActionListener für die Buttons + // --------------------------- + newGameButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new GameScreen(user, 1); // Neues Spiel, Level 1 + dispose(); + } + }); + + playLevelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new GameScreen(user, getHighestLevel(user)); // Höchstes freigeschaltetes Level + dispose(); + } + }); + + howToPlayButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(MainScreen.this, "How to play instructions..."); + } + }); + + creditsButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + JOptionPane.showMessageDialog(MainScreen.this, "Credits..."); + } + }); + + logoutButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleLogout(); + } + }); + + userNameLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent evt) { + new ProfileScreen(user); // Profilansicht öffnen + } + }); + + setVisible(true); + } + + private void handleLogout() { + SessionManager.clearSession(); + dispose(); + new StartScreen().setVisible(true); + } + + private int getHighestLevel(User user) { + return GameDataManager.getHighestLevel(user.getId()); + } + + public static void main(String[] args) { + User user = SessionManager.loadSession(); + if (user != null) { + new MainScreen(user); + } else { + new LoginScreen().setVisible(true); + } + } +} diff --git a/src/gui/ProfileScreen.java b/src/gui/ProfileScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..d1d739bc268d7ae32323c13c4a143d361695d22a --- /dev/null +++ b/src/gui/ProfileScreen.java @@ -0,0 +1,61 @@ +package gui; + +import managers.SessionManager; +import managers.UserManager; +import models.User; + +import javax.swing.*; +import java.awt.*; + +public class ProfileScreen extends JFrame { + public ProfileScreen(User user) { + setTitle("Profile"); + setSize(400, 300); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + setLocationRelativeTo(null); + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(5, 5, 5, 5); + + JLabel usernameLabel = new JLabel("Username:"); + gbc.gridx = 0; + gbc.gridy = 0; + add(usernameLabel, gbc); + + JLabel usernameValue = new JLabel(user.getUsername()); + gbc.gridx = 1; + gbc.gridy = 0; + add(usernameValue, gbc); + + // Button zum Löschen des Accounts + JButton deleteAccountButton = new JButton("Account löschen"); + gbc.gridx = 0; + gbc.gridy = 1; + gbc.gridwidth = 2; + add(deleteAccountButton, gbc); + + deleteAccountButton.addActionListener(e -> { + int confirmation = JOptionPane.showConfirmDialog( + this, + "Sind Sie sicher, dass Sie Ihren Account löschen möchten?", + "Account löschen", + JOptionPane.YES_NO_OPTION + ); + + if (confirmation == JOptionPane.YES_OPTION) { + UserManager userManager = new UserManager(); + if (userManager.deleteUser(user.getUsername())) { + SessionManager.clearSession(); + JOptionPane.showMessageDialog(this, "Account erfolgreich gelöscht."); + dispose(); + new StartScreen(); // Zurück zum Startbildschirm + } else { + JOptionPane.showMessageDialog(this, "Fehler beim Löschen des Accounts."); + } + } + }); + + setVisible(true); + } +} diff --git a/src/gui/RegisterScreen.java b/src/gui/RegisterScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..b93cd1184ad49a443e7bd4c1890474e05a2d365c --- /dev/null +++ b/src/gui/RegisterScreen.java @@ -0,0 +1,79 @@ +package gui; + +import managers.UserManager; +import managers.SessionManager; +import models.User; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class RegisterScreen extends JFrame { + private UserManager userManager; + + public RegisterScreen() { + userManager = new UserManager(); + + setTitle("Register"); + setSize(400, 300); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + setLayout(new GridBagLayout()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(5, 5, 5, 5); + + JLabel usernameLabel = new JLabel("Username:"); + gbc.gridx = 0; + gbc.gridy = 0; + add(usernameLabel, gbc); + + JTextField usernameField = new JTextField(20); + gbc.gridx = 1; + gbc.gridy = 0; + add(usernameField, gbc); + + JLabel passwordLabel = new JLabel("Password:"); + gbc.gridx = 0; + gbc.gridy = 1; + add(passwordLabel, gbc); + + JPasswordField passwordField = new JPasswordField(20); + gbc.gridx = 1; + gbc.gridy = 1; + add(passwordField, gbc); + + JButton registerButton = new JButton("Register"); + gbc.gridx = 1; + gbc.gridy = 2; + add(registerButton, gbc); + + registerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String username = usernameField.getText(); + String password = new String(passwordField.getPassword()); + boolean success = userManager.register(username, password); + if (success) { + // Direkt einloggen: Hier holen wir den User über den Login-Vorgang (da der Hash bereits erzeugt wurde) + User user = userManager.login(username, password); + if (user != null) { + SessionManager.saveSession(user); + JOptionPane.showMessageDialog(RegisterScreen.this, "Registrierung erfolgreich und du bist eingeloggt!"); + new MainScreen(user); // Navigate to the main screen + dispose(); // Close the register screen + } + } else { + JOptionPane.showMessageDialog(RegisterScreen.this, "Username existiert bereits."); + } + } + }); + + setVisible(true); + } + + public static void main(String[] args) { + new RegisterScreen(); + } +} diff --git a/src/gui/StartScreen.java b/src/gui/StartScreen.java new file mode 100644 index 0000000000000000000000000000000000000000..3dd365b82d5578e5bbab67235113d8d522bb436b --- /dev/null +++ b/src/gui/StartScreen.java @@ -0,0 +1,77 @@ +package gui; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class StartScreen extends JFrame { + public StartScreen() { + System.out.println("Initializing StartScreen..."); + + setTitle("Traffic Jam"); + setSize(1280, 720); + setDefaultCloseOperation(EXIT_ON_CLOSE); + setLocationRelativeTo(null); + setLayout(new BorderLayout()); + + JPanel mainPanel = new JPanel(new GridBagLayout()); + add(mainPanel, BorderLayout.CENTER); + + JPanel containerPanel = new JPanel(); + containerPanel.setLayout(null); + containerPanel.setPreferredSize(new Dimension(600, 300)); + + JLabel titleLabel = new JLabel("Traffic Jam", SwingConstants.CENTER); + titleLabel.setFont(new Font("Arial", Font.BOLD, 48)); + titleLabel.setBounds(0, 20, 600, 60); + containerPanel.add(titleLabel); + + JButton loginButton = new JButton("Login"); + loginButton.setBounds(130, 120, 120, 40); + containerPanel.add(loginButton); + + JButton registerButton = new JButton("Register"); + registerButton.setBounds(350, 120, 120, 40); + containerPanel.add(registerButton); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.weightx = 1.0; + gbc.weighty = 1.0; + gbc.anchor = GridBagConstraints.CENTER; + mainPanel.add(containerPanel, gbc); + + JLabel versionLabel = new JLabel("Version 1.0", SwingConstants.RIGHT); + versionLabel.setFont(new Font("Arial", Font.ITALIC, 12)); + JPanel versionPanel = new JPanel(new BorderLayout()); + versionPanel.add(versionLabel, BorderLayout.EAST); + add(versionPanel, BorderLayout.SOUTH); + + loginButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Login button clicked"); + dispose(); // Close the start screen + new LoginScreen(); // Open the login screen + } + }); + + registerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + System.out.println("Register button clicked"); + dispose(); // Close the start screen + new RegisterScreen(); // Open the register screen + } + }); + + setVisible(true); + System.out.println("StartScreen is now visible"); + } + + public static void main(String[] args) { + new StartScreen(); + } +} \ No newline at end of file diff --git a/src/main/Main.java b/src/main/Main.java new file mode 100644 index 0000000000000000000000000000000000000000..414c616f020f3fff8ce77481fd9dbe708139a57a --- /dev/null +++ b/src/main/Main.java @@ -0,0 +1,27 @@ +package main; + +import managers.SessionManager; +import models.User; + +import javax.swing.*; + +import gui.StartScreen; +import gui.MainScreen; + +public class Main { + public static void main(String[] args) { + System.out.println("Starting application..."); + + // Prüfe, ob eine Session existiert + User sessionUser = SessionManager.loadSession(); + if (sessionUser != null) { + System.out.println("Session found for user: " + sessionUser.getUsername()); + JOptionPane.showMessageDialog(null, "Willkommen zurück, " + sessionUser.getUsername() + "!"); + // Starte den Hauptbildschirm bzw. das Spiel und übergebe den Benutzer + new MainScreen(sessionUser); + } else { + System.out.println("No session found, starting StartScreen..."); + new StartScreen(); + } + } +} diff --git a/src/managers/GameDataManager.java b/src/managers/GameDataManager.java new file mode 100644 index 0000000000000000000000000000000000000000..9510b200856ab53a9cb0c467fc33f8c517572fba --- /dev/null +++ b/src/managers/GameDataManager.java @@ -0,0 +1,110 @@ +// File: src/managers/GameDataManager.java +package managers; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +public class GameDataManager { + private static final String FILE_PATH = "src/data/GameData.json"; + private static Map<Integer, UserData> gameData = new HashMap<>(); + + static { + loadGameData(); + } + + /** + * Gibt den Highscore für den angegebenen User und Level zurück. + * Falls noch kein Highscore existiert, wird Integer.MAX_VALUE zurückgegeben. + */ + public static int getHighScore(int userId, int level) { + UserData userData = gameData.get(userId); + if (userData != null && userData.highScores.containsKey(level)) { + int score = userData.highScores.get(level); + // Falls der gespeicherte Score 0 ist, betrachten wir ihn als nicht gesetzt. + return score == 0 ? Integer.MAX_VALUE : score; + } + return Integer.MAX_VALUE; + } + + /** + * Gibt das höchste freigeschaltete Level für den angegebenen User zurück. + * Falls keine Daten vorhanden sind, wird 1 zurückgegeben. + */ + public static int getHighestLevel(int userId) { + UserData userData = gameData.get(userId); + if (userData != null) { + return userData.highestLevel; + } + return 1; + } + + /** + * Aktualisiert den Highscore für den gegebenen User und Level, falls der neue Score niedriger ist. + */ + public static void updateHighScore(int userId, int level, int score) { + UserData userData = gameData.computeIfAbsent(userId, k -> new UserData()); + int current = userData.highScores.getOrDefault(level, Integer.MAX_VALUE); + if (current == 0) { + current = Integer.MAX_VALUE; + } + if (score < current) { + userData.highScores.put(level, score); + saveGameData(); + } + } + + /** + * Aktualisiert das höchste freigeschaltete Level für den gegebenen User, sofern ein höheres Level erreicht wurde. + */ + public static void updateHighestLevel(int userId, int level) { + UserData userData = gameData.computeIfAbsent(userId, k -> new UserData()); + userData.highestLevel = level; + saveGameData(); // Ensure the data is saved after updating + } + + /** + * Lädt die Spieldaten aus der JSON-Datei. + */ + private static void loadGameData() { + File file = new File(FILE_PATH); + if (file.exists()) { + try (Reader reader = new FileReader(file)) { + Gson gson = new Gson(); + Type type = new TypeToken<Map<Integer, UserData>>() {}.getType(); + Map<Integer, UserData> data = gson.fromJson(reader, type); + if (data != null) { + gameData = data; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Speichert die aktuellen Spieldaten in die JSON-Datei. + */ + private static void saveGameData() { + try (Writer writer = new FileWriter(FILE_PATH)) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + gson.toJson(gameData, writer); + System.out.println("Game data saved to " + FILE_PATH); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Interne Klasse zur Repräsentation der Daten eines Users. + */ + private static class UserData { + int highestLevel = 1; + Map<Integer, Integer> highScores = new HashMap<>(); + } +} diff --git a/src/managers/SessionManager.java b/src/managers/SessionManager.java new file mode 100644 index 0000000000000000000000000000000000000000..02f26da98836ed2a9e9d82a21e9c9620f815d2c5 --- /dev/null +++ b/src/managers/SessionManager.java @@ -0,0 +1,42 @@ +package managers; + +import com.google.gson.Gson; +import models.User; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class SessionManager { + private static final String SESSION_FILE = "src/data/Session.json"; + private static final Gson gson = new Gson(); + + // Speichert den aktuellen User in der Session-Datei + public static void saveSession(User user) { + try (Writer writer = new FileWriter(SESSION_FILE)) { + gson.toJson(user, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Lädt den User aus der Session-Datei (falls vorhanden) + public static User loadSession() { + if (new File(SESSION_FILE).exists()) { + try (Reader reader = new FileReader(SESSION_FILE)) { + return gson.fromJson(reader, User.class); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + // Löscht die Session (z. B. beim Logout) + public static void clearSession() { + try { + Files.deleteIfExists(Paths.get(SESSION_FILE)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/managers/UserManager.java b/src/managers/UserManager.java new file mode 100644 index 0000000000000000000000000000000000000000..e27f15ae946077794418a1f7b6b470c92b97fc90 --- /dev/null +++ b/src/managers/UserManager.java @@ -0,0 +1,87 @@ +package managers; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import models.User; +import utils.PasswordUtils; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class UserManager { + private static final String FILE_PATH = "src/data/Users.json"; + private List<User> users; + private int nextId; + + public UserManager() { + users = loadUsers(); + nextId = users.size() + 1; + } + + public boolean register(String username, String password) { + if (getUserByUsername(username) != null) { + return false; // User already exists + } + users.add(new User(nextId++, username, password)); + saveUsers(); + return true; + } + + public User login(String username, String password) { + User user = getUserByUsername(username); + if (user != null) { + // Hash des eingegebenen Passworts mit dem beim Registrieren gespeicherten Salt + String hashedInput = PasswordUtils.hashPassword(password, user.getSalt()); + if (hashedInput.equals(user.getPasswordHash())) { + return user; + } + } + return null; + } + + public boolean deleteUser(String username) { + User user = getUserByUsername(username); + if (user != null) { + users.remove(user); + saveUsers(); + return true; + } + return false; + } + + private User getUserByUsername(String username) { + for (User user : users) { + if (user.getUsername().equals(username)) { + return user; + } + } + return null; + } + + private void saveUsers() { + try (Writer writer = new FileWriter(FILE_PATH)) { + Gson gson = new Gson(); + gson.toJson(users, writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private List<User> loadUsers() { + if (new File(FILE_PATH).exists()) { + try (Reader reader = new FileReader(FILE_PATH)) { + Gson gson = new Gson(); + Type userListType = new TypeToken<ArrayList<User>>() {}.getType(); + List<User> loadedUsers = gson.fromJson(reader, userListType); + if (loadedUsers != null) { + return loadedUsers; + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return new ArrayList<>(); + } +} diff --git a/src/maps/Level_1.json b/src/maps/Level_1.json new file mode 100644 index 0000000000000000000000000000000000000000..10d3d613e7ca3cf60122bbb52308129721936da3 --- /dev/null +++ b/src/maps/Level_1.json @@ -0,0 +1,10 @@ +{ + "vehicles": [ + { "x": 0, "y": 0, "length": 2, "isHorizontal": false, "isRed": false }, + { "x": 2, "y": 0, "length": 2, "isHorizontal": true, "isRed": false }, + { "x": 4, "y": 1, "length": 2, "isHorizontal": false, "isRed": false }, + { "x": 1, "y": 4, "length": 2, "isHorizontal": false, "isRed": false }, + { "x": 2, "y": 3, "length": 2, "isHorizontal": true, "isRed": true }, + { "x": 5, "y": 4, "length": 2, "isHorizontal": false, "isRed": false } + ] +} diff --git a/src/maps/level_2.json b/src/maps/level_2.json new file mode 100644 index 0000000000000000000000000000000000000000..58c7130cac56e5ff5e9a880033748347094ec4d7 --- /dev/null +++ b/src/maps/level_2.json @@ -0,0 +1,10 @@ +{ + "vehicles": [ + { "x": 0, "y": 0, "length": 2, "isHorizontal": false, "isRed": false }, + { "x": 4, "y": 2, "length": 2, "isHorizontal": false, "isRed": false }, + { "x": 4, "y": 4, "length": 2, "isHorizontal": true, "isRed": false }, + { "x": 3, "y": 1, "length": 2, "isHorizontal": true, "isRed": false }, + { "x": 0, "y": 2, "length": 2, "isHorizontal": true, "isRed": true } + ] + } + \ No newline at end of file diff --git a/src/models/User.java b/src/models/User.java new file mode 100644 index 0000000000000000000000000000000000000000..ac0e4f332c3716c3d70e3677d937592313ed63dd --- /dev/null +++ b/src/models/User.java @@ -0,0 +1,36 @@ +// File: src/models/User.java +package models; + +import java.io.Serializable; +import utils.PasswordUtils; + +public class User implements Serializable { + private int id; + private String username; + private String passwordHash; + private String salt; + + public User(int id, String username, String password) { + this.id = id; + this.username = username; + // Generiere einen Salt und erstelle den Passwort-Hash + this.salt = PasswordUtils.generateSalt(); + this.passwordHash = PasswordUtils.hashPassword(password, salt); + } + + public int getId() { + return id; + } + + public String getUsername() { + return username; + } + + public String getPasswordHash() { + return passwordHash; + } + + public String getSalt() { + return salt; + } +} diff --git a/src/utils/PasswordUtils.java b/src/utils/PasswordUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..ccf34904bc57b4a387f7b70a85adc96b95f48559 --- /dev/null +++ b/src/utils/PasswordUtils.java @@ -0,0 +1,29 @@ +package utils; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public class PasswordUtils { + + // Generiert einen zufälligen Salt (Base64-kodiert) + public static String generateSalt() { + SecureRandom sr = new SecureRandom(); + byte[] salt = new byte[16]; // 128 Bit Salt + sr.nextBytes(salt); + return Base64.getEncoder().encodeToString(salt); + } + + // Hashing des Passworts unter Verwendung von SHA-256 und dem Salt + public static String hashPassword(String password, String salt) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(salt.getBytes()); // Salt hinzufügen + byte[] hashedPassword = md.digest(password.getBytes()); + return Base64.getEncoder().encodeToString(hashedPassword); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("SHA-256 Algorithmus nicht verfügbar", e); + } + } +}