Design Pattern For Game Settings

The 4th is not stated well at all. It’s possible to apply a lot of things to a lot of situations. That doesn’t necessarily make it helpful, or reduce workload. Sure, I could implement a factory pattern when I have a car that can be 2 different colors in my game. Is that more efficient? Not really, but it’s possible.

Again, the observer pattern is overkill. Each setting only effects one value. So, while all the setting values are in one place, each would still, in essence, have to have its own separate listener. The observer pattern is to notify several objects in a certain event, not just one at a time. You may as well make each setting a separate subject and register one observer each.

This is all if we are talking about game configuration settings like volume, resolution, full screen, and the like. Now, if it’s something in game that cause objects to do something as a reaction (think an RTS where, for example, you set the mode to “attack” and each unit needs to receive that message), than sure. But, that’s not the case. These configurations don’t really even need to tell anyone that they’ve changed. Whenever you play an audio file, it plays at the volume that was last set, which you just poll your properties file for. You don’t need to tell the audio player that the volume changed, unless you just want to set a local volume variable. No one else cares about that value, though. When you turn on AA, you don’t really even need to tell anyone then, either. Since that should be some persistent state, than all graphics will automatically be drawn with AA on the next frame. Maybe you tell the rendering context, but again, you are effecting a single object due to a single value change.

There’s nothing complicated about game settings, and it doesn’t get complicated regardless how many you add. You add the value to a file, then you access the file to get the value. You add a new value, you instantly have a new setting. Simple as that. That’s already completely decoupled, and you didn’t even need a pattern. Though, I would still argue that a Singleton would be a good idea here for easier access to the settings file.

At this point, I guess it’s just opinion. I’ve detailed my point as I failed to do in my first point. If you really want to believe an observer pattern would be more useful here that no pattern at all, than I guess I don’t technically have a right to judge that as wrong.

And @Roquen, for anyone that plans to get a degree in Computer Science (like I did), a class in Design Patterns generally isn’t an option. Even if it is, there will be a Software Engineering course you have to take that will throw patterns into the mix. To say you shouldn’t take the class though is something I disagree with. Ever taken one? You learn patterns a lot better than just browsing the Internet. And a lot more. Sure, you can look at a list of 50 patterns online, but that doesn’t mean you understand how to use them.

And to add to your “protip” if you want a language that will really test your logic skills, dabble in Prolog. Logic, to me, is the most important skill a programmer needs to refine. This language does that for you better than any other.

You guys are getting too carried away arguing about trivialities.

Make something that barely works. Once You have a little experience You’ll see the shortcomings, then You’ll make it better. After a few iterations it’s a good solution. With some more experience and time You start using the good solution everywhere. After a few years, You’ll notice that it has a name. It’s called “whatever-pattern-it-was-that-You-were-using”

The problem with teaching patterns and having the lecture is that they mold Your brain into thinking everything patterns. And then You are “the pattern guy” who only talks and writes code in patterns. No… just don’t.

And, for the love of code, don’t argue about it. Just write code and name the pattern later.

My suggestion for the OP: Don’t ask about patterns ( Look at this thread ).

That’s the thing about a class; they beat into your head not to use patterns where they aren’t needed. My teacher’s analogy:

“If you give a 5 year old a hammer, suddenly everything needs to be hammered. Don’t be a 5 year old with a new hammer”.

It’s not just taking a class…there’s a mindset that “design patterns” are interesting. They aren’t. They’re nothing more than some skeleton code-snippets that might be interesting in a given case in a given language for a given problem and design need. Reread the first post and say yourself “Why on earth are we talking about design patterns”? Because we shouldn’t be. It’s taking a trivial problem and making it difficult. I could go on-and-on about design patterns, but I’ve done that enough on this forum.

Ok, here is the most simple solution I could think of.
Everything is hard coded. Not recommended for large projects, but for simple games it should be sufficient.
This example program can be compiled and run as it is.

import java.io.*;
import java.awt.Dimension;
import java.util.Scanner;

class GameFrame {
    Dimension resolution = new Dimension(800,600);
    boolean isFullScreen = false;
    double fps = 60;
    
    Dimension getResolution() {
        return resolution;
    }
    void setResolution(Dimension resolution) {
        this.resolution = resolution;
    }
    boolean isFullScreen() {
        return isFullScreen;
    }
    void setFullScreen(boolean isFullScreen) {
        this.isFullScreen = isFullScreen;
    }
    double getFPS() {
        return fps;
    }
    void setFPS(double fps) {
        this.fps = fps;
    }
}

class AudioSystem {
    boolean isSoundOn = true;
    double volume = 100;
    
    boolean isSoundOn() {
        return isSoundOn;
    }
    void setSoundOn(boolean isSoundOn) {
        this.isSoundOn = isSoundOn;
    }
    double getVolume() {
        return volume;
    }
    void setVolume(double volume) {
        this.volume = volume;
    }
}

class Game {
    GameFrame gameFrame = new GameFrame();
    AudioSystem audioSystem = new AudioSystem();
    
    GameFrame getGameFrame() {
        return gameFrame;
    }
    AudioSystem getAudioSystem() {
        return audioSystem;
    }
    
    public static void main(String[] args) {
        Game game = new Game();
        Tools.writeSettings("config.txt", game);
        game.getGameFrame().setFPS(100);
        game.getAudioSystem().setVolume(50);
        System.out.println(game.getGameFrame().getFPS());
        System.out.println(game.getAudioSystem().getVolume());
        Tools.readSettings("config.txt", game);
        System.out.println(game.getGameFrame().getFPS());
        System.out.println(game.getAudioSystem().getVolume());
    }
}

class Tools {
    static void writeSettings(String fileName, Game game) {
        PrintWriter out = null;
        try {
            out = new PrintWriter(fileName);
            GameFrame gameFrame = game.getGameFrame();
            AudioSystem audioSystem = game.getAudioSystem();
            Dimension resolution = gameFrame.getResolution();
            out.println("resolution: " + resolution.width + ", " + resolution.height);
            out.println("isFullScreen: " + gameFrame.isFullScreen());
            out.println("fps: " + gameFrame.getFPS());
            out.println("isSoundOn: " + audioSystem.isSoundOn());
            out.println("volume: " + audioSystem.getVolume());
            out.flush();
            out.close();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            if(out != null) out.close();
        }
    }
    static void readSettings(String fileName, Game game) {
        Scanner in = null;
        try {
            in = new Scanner(new File(fileName)).useDelimiter("\\s+|\\s*:\\s*|\\s*,\\s*");
            GameFrame gameFrame = game.getGameFrame();
            AudioSystem audioSystem = game.getAudioSystem();
            while(in.hasNext()) {
                String str = in.next();
                if(str.length() == 0) continue;
                if(str.equals("resolution")) gameFrame.setResolution(
                    new Dimension(Integer.parseInt(in.next()), Integer.parseInt(in.next())));
                else if(str.equals("isFullScreen")) gameFrame.setFullScreen(
                    Boolean.parseBoolean(in.next()));
                else if(str.equals("fps")) gameFrame.setFPS(
                    Double.parseDouble(in.next()));
                else if(str.equals("isSoundOn")) audioSystem.setSoundOn(
                    Boolean.parseBoolean(in.next()));
                else if(str.equals("volume")) audioSystem.setVolume(
                    Double.parseDouble(in.next()));
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            if(in != null) in.close();
        }
    }
}

Now here is another example. This time it uses a completely different strategy. It stores all settings in a HashMap. Also it uses the Observer pattern.

import java.io.*;
import java.util.*;
import java.awt.Dimension;

abstract class SettingListener<T> {
    String name;
    
    SettingListener(String name) {
        this.name = name;
    }
    
    String getName() {
        return name;
    }
    
    abstract void reactToChange(T oldValue, T newValue);
}

class GameFrame {
    Game game;
    GameFrame(Game game) {
        this.game = game;
        game.changeSetting("resolutionX", 800.0);
        game.changeSetting("resolutionY", 600.0);
        game.changeSetting("isFullScreen", false);
        game.changeSetting("fps", 60.0);
    }
}

class AudioSystem {
    Game game;
    AudioSystem(Game game) {
        this.game = game;
        game.changeSetting("isSoundOn", true);
        game.changeSetting("volume", 100.0);
        game.addSettingListener(new SettingListener<Double>("volume") {
            void reactToChange(Double oldValue, Double newValue) {
                System.out.println("volume was changed from " + oldValue + " to " + newValue);
            }
        });
    }
}

class Game {
    Map<String, Object> settings = new HashMap<>();
    List<SettingListener> settingListeners = new ArrayList<>();
    
    void addSettingListener(SettingListener listener) {
        settingListeners.add(listener);
    }
    
    Object getSetting(String name) {
        return settings.get(name);
    }
    
    @SuppressWarnings("unchecked")
    void changeSetting(String name, Object value) {
        Object oldValue = settings.get(name);
        settings.put(name, value);
        for(SettingListener listener: settingListeners) {
            if(listener.getName().equals(name))
                listener.reactToChange(oldValue, value);
        }
    }
    
    GameFrame gameFrame = new GameFrame(this);
    AudioSystem audioSystem = new AudioSystem(this);
    
    GameFrame getGameFrame() {
        return gameFrame;
    }
    AudioSystem getAudioSystem() {
        return audioSystem;
    }
    
    public static void main(String[] args) {
        Game game = new Game();
        Tools.writeSettings("config.txt", game);
        game.changeSetting("fps", 100.0);
        game.changeSetting("volume", 50.0);
        System.out.println(game.getSetting("fps"));
        System.out.println(game.getSetting("volume"));
        Tools.readSettings("config.txt", game);
        System.out.println(game.getSetting("fps"));
        System.out.println(game.getSetting("volume"));
    }
}

class Tools {
    static void writeSettings(String fileName, Game game) {
        PrintWriter out = null;
        try {
            out = new PrintWriter(fileName);
            for(String name: game.settings.keySet()) {
                Object value = game.settings.get(name);
                out.println(name + ": " + value);
            }
            out.flush();
            out.close();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            if(out != null) out.close();
        }
    }
    static void readSettings(String fileName, Game game) {
        Scanner in = null;
        try {
            in = new Scanner(new File(fileName)).useDelimiter("\\s+|\\s*:\\s*");
            while(in.hasNext()) {
                String name = in.next();
                if(name.length() == 0) continue;
                String value = in.next();
                Double doubleValue = null;
                try {
                    doubleValue = Double.valueOf(value);
                } catch(NumberFormatException e) {
                }
                if(doubleValue != null) game.changeSetting(name, doubleValue);
                else game.changeSetting(name, value);
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            if(in != null) in.close();
        }
    }
}

You’re right when you say this has essentially boiled down to opinion.

You don’t ask yourself “do I need a design pattern NOW?” No, because now you don’t. If all you’re only concerned about is the now, you mid as well make everything concrete, throw away your OO design principals and forget about patterns all together.

I assume you are implying to include the future in the “do I need a design pattern?” I am just highlighting that point.

If you’re trying to present a case where it isn’t efficient, that case doesn’t illustrate at all what the abstract factory pattern or factory method pattern aims to solve. If you’ve coupled your object creation code with another class that could otherwise have been closed for change, you can’t say that this class is closed for changes and you now have a maintenance issue whenever you’ve got a new colour. Is it a problem? It depends, if your building very simple code, probably not at all. If you’re building a framework for other developers and there may be more car colours, YES definitely. They have to hack crap into your instantiation code every time they want a new colour.

You should ask yourself will this module need to be expanded in the future and will expanding the function of this module become an issue (does your class violate the open\closed principal?) If it does, and there is a violation of this principal (or various other common design principals) chances are that yes, you will have trouble expanding on to the module later.

Those violations are the problem, and the solutions to those problems may have already been solved & proven. You can develop your own solution of course, but it doesn’t hurt to have a library of them in your head.

By not using an observer pattern for configurations, the OPs code cannot be ‘closed for changes and open for expansion.’ Is that a problem? Will it need to be expanded later? That’s his call not ours.

As I said, this being like the third time, for this specific scenario (game settings), yes, you can implement them without a pattern and still be closed to modification. The “subject” is nothing more than a list of values. You pop in a new value, you have a new setting. You don’t have to change any code, only expand to utilize that new value. Seriously, there’s nothing about that where the observer pattern makes it any more de-coupled.

Anyway. I guess I’m done here. You realize that if the OP didn’t explicitly ask for a pattern, that one probably would have never even been suggested? I applaud you for simply trying to give him what he wants. I just strongly believe he doesn’t quite understand what he’s asking for. Roquen is right. This problem is easy enough to solve without a pattern, and it’s not something that will get more complex as the aspect evolves. There’s no need to even be talking about them (besides the fact that it’s the exact topic of this thread).

Well I’m done too, with a last note:

The observer pattern doesn’t solve the coupling in the storing of the configuration (there isn’t any there, I agree), but coupling in the distribution of the modifications to those configurations from the subject to the subsystems in which such changes must be reflected in real-time. You’re point about it not helping (in terms of coupling) targets the wrong aspect of the design.

It probably wouldn’t have been suggested since the thread wouldn’t exist? Since the question is in the thread’s title.