/Snake-Game

Snake Game Built with Java Swing

Primary LanguageJava

Snake-Game

Final Project for OOP

Name: Riski Ilyas
NRP : 5025211189

Reference: https://www.youtube.com/watch?v=bI6e6qjJ8JQ&t=1532s

Description

This project is a simple GUI game called Snake Game. This game has 10 levels that needs to be completed and each level has different diffculties and obstacles. There is also Casual Mode to play the game with no obstacles. There is also a Highscore that records your top score in each Casual Game. Besides the gameplay, it also has Settings with multiple options such as Changing Player Name, Turn On/Off Music, Turn On/Off SFX, & Reset Game. All the settings data will be stored in an external file so that the game still remember the data even when the Game has Closed.

Screenshots

To-do

  • Casting/Conversion (Casting GameMusic)
    GameMusic gameMusic = (GameMusic) sounds.get(GAME_MUSIC);
  • Constructor (Constructors for each screen)
    public GameScreen(ScreenRouter screenRouter, SoundManager soundManager, DataStore dataStore) {
    public LevelScreen(ScreenRouter screenRouter, SoundManager soundManager, DataStore dataStore) {
    public MenuScreen(ScreenRouter screenRouter, SoundManager soundManager, DataStore dataStore) {
    public SettingsScreen(ScreenRouter screenRouter, SoundManager soundManager, DataStore dataStore) {
  • Overloading (Overloading at screenRouter to route to gamescreen)
    public void route(ScreenState state) {
    screenState.setValue(state);
    }
    public void route(LevelState levelState) {
    this.levelState = levelState;
    screenState.setValue(ScreenState.GAME);
    }
  • Encapsulation (Observable encapsulation on DataStore)
    private final Observable<Boolean> isMusicOn = new Observable<>(true);
    private final Observable<Boolean> isSfxOn = new Observable<>(true);
    private final Observable<String> playerName = new Observable<>("Player 1");
    private final Observable<Integer> currentLevel = new Observable<>(1);
    private final Observable<Integer> highscores = new Observable<>(0);
    public Observable<Boolean> getMusicObservable() {
    return isMusicOn;
    }
    public Observable<Boolean> getSfxObservable() {
    return isSfxOn;
    }
    public Observable<String> getPlayerNameObservable() {
    return playerName;
    }
    public Observable<Integer> getCurrentLevelObservable() {
    return currentLevel;
    }
  • Inheritance (Inheritance on Buttons)
    public class Button extends ImageIcon {
    public class BackButton extends Button {
    public class MenuButton extends Button {
  • Polymorphism (Level polymorphism on GameScreen)
    private Level playingLevel;
    private void getLevel(LevelState levelState) {
    switch (levelState) {
    case L1 -> playingLevel = new Level1();
    case L2 -> playingLevel = new Level2();
    case L3 -> playingLevel = new Level3();
    case L4 -> playingLevel = new Level4();
    case L5 -> playingLevel = new Level5();
    case L6 -> playingLevel = new Level6();
    case L7 -> playingLevel = new Level7();
    case L8 -> playingLevel = new Level8();
    case L9 -> playingLevel = new Level9();
    case L10 -> playingLevel = new Level10();
    case CASUAL -> playingLevel = null;
    }
    }
  • ArrayList (Arraylist for Linsteners on Observable)
    private final ArrayList<OnValueChangedListener<V>> listeners;
  • Exception Handling (Exception Handling on DataStore)
    private void getData() {
    try {
    Scanner dataScanner = new Scanner(data);
    while (dataScanner.hasNextLine()) {
    String[] keyValue = dataScanner.nextLine().split(":");
    switch (keyValue[0]) {
    case IS_MUSIC_ON -> isMusicOn.setValue((Integer.parseInt(keyValue[1]) == 1));
    case IS_SFX_ON -> isSfxOn.setValue((Integer.parseInt(keyValue[1]) == 1));
    case PLAYER_NAME -> playerName.setValue(keyValue[1]);
    case CURRENT_LEVEL -> currentLevel.setValue(Integer.valueOf(keyValue[1]));
    case HIGHSCORE -> highscores.setValue(Integer.valueOf(keyValue[1]));
    }
    }
    dataScanner.close();
    } catch (Exception ignored) {}
    }
  • GUI (Java Swing)
    public class GameFrame extends JFrame {
    public GameFrame() {
    DataStore dataStore = new DataStore();
    ScreenRouter screenRouter = new ScreenRouter(this, dataStore);
    this.setTitle("Snake");
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    this.setResizable(false);
    this.pack();
    this.setLocationRelativeTo(null);
    this.setVisible(true);
    dataStore.getPlayerNameObservable().observe((name) -> {
    this.setTitle("Snake Game " + ((name.isBlank()) ? "" : ("(" + name + ")")));
    });
    }
    }
  • Interface (Interface for callback on Observable)
    public interface OnValueChangedListener<V> {
    void onValueChanged(V value);
    }
  • Abstract Class (Abstract class for Screen and Level)
    public abstract class Screen extends JPanel {
    protected ScreenRouter screenRouter;
    protected SoundManager soundManager;
    public Screen(ScreenRouter screenRouter, SoundManager soundManager) {
    this.screenRouter = screenRouter;
    this.soundManager = soundManager;
    }
    public abstract void stopScreen();
    public abstract void startScreen();
    }
  • Generics (Pair Class)
    public class Pair<F, S> {
    public F first;
    public S second;
    public Pair(F first, S second) {
    this.first = first;
    this.second = second;
    }
    public boolean compareWith(Pair<F,S> anotherPair) {
    return first == anotherPair.first && second == anotherPair.second;
    }
    public Pair<F, S> copy() {
    return new Pair<>(first, second);
    }
    }
  • Collection (Hashmap on SounManager)
    private final HashMap<SoundState, Sound> sounds = new HashMap<>();
  • Input Output (I/O for the dataStore)
    private void getData() {
    try {
    Scanner dataScanner = new Scanner(data);
    while (dataScanner.hasNextLine()) {
    String[] keyValue = dataScanner.nextLine().split(":");
    switch (keyValue[0]) {
    case IS_MUSIC_ON -> isMusicOn.setValue((Integer.parseInt(keyValue[1]) == 1));
    case IS_SFX_ON -> isSfxOn.setValue((Integer.parseInt(keyValue[1]) == 1));
    case PLAYER_NAME -> playerName.setValue(keyValue[1]);
    case CURRENT_LEVEL -> currentLevel.setValue(Integer.valueOf(keyValue[1]));
    case HIGHSCORE -> highscores.setValue(Integer.valueOf(keyValue[1]));
    }
    }
    dataScanner.close();
    } catch (Exception ignored) {}
    }
    // Write data using new Thread
    private void writeData() {
    new Thread(() -> {
    try {
    FileWriter fileWriter = new FileWriter(data);
    PrintWriter printWriter = new PrintWriter(fileWriter, false);
    printWriter.println(IS_MUSIC_ON + ":" + (isMusicOn.getValue() ? 1 : 0));
    printWriter.println(IS_SFX_ON + ":" + (isSfxOn.getValue() ? 1 : 0));
    printWriter.println(PLAYER_NAME + ":" + playerName.getValue());
    printWriter.println(CURRENT_LEVEL + ":" + currentLevel.getValue());
    printWriter.println(HIGHSCORE + ":" + highscores.getValue());
    printWriter.close();
    } catch (IOException ignored) {
    }
    }).start();
    }