Skip to content

Commit

Permalink
feat(upgradeConf): Upgrade the configuration
Browse files Browse the repository at this point in the history
- add argument cli parser
- change configruation to take ArgParser feature.

fix #59
  • Loading branch information
mcgivrer committed Oct 10, 2021
1 parent fd3fd93 commit c004152
Show file tree
Hide file tree
Showing 10 changed files with 583 additions and 54 deletions.
219 changes: 211 additions & 8 deletions src/docs/chapters/09-extract-configuration.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: From a Class to Game
chapter: 09 - Extracting the Configuration
author: Frédéric Delorme
description: A dedicated service to Scenes management.
created: 2021-08-01
title: From a Class to Game
chapter: 09 - Extracting the Configuration
author: Frédéric Delorme
description: A dedicated service to Scenes management.
created: 2021-08-01
tags: gamedev, scene
---

Expand Down Expand Up @@ -183,13 +183,216 @@ public class Configuration {
}
}
```

### Evolution

The Configuration class will integrate progressively more and
The Configuration class will integrate progressively more and
more parameters to be set and loaded from the properties file.

The best way to add new attributes to the configuration will be to add
The best way to add new attributes to the configuration will be to add
some helpers on definition and on parsing vaue, but also on help side.
Requesting from the CLI, you would get a well formated and documented
Requesting from the CLI, you would get a well formated and documented
help bout possible parameters and their values.

You would get some `ArgParser` interface and a `CLIManager` to support those parser.

#### the IArgParser interface

```java
public interface IArgParser<T> {
public boolean validate(String strValue);
public T getValue();
public String getShortKey();
public String getLongKey();
public String getName();
public String getDescription();
public String getErrorMessage(Object[] args);
public T getDefaultValue();
}
```

In this interface, the main intersting thing are name and description,
useful to generate the help on the cli. Name will be the `name` of the parameter, and `description` will help understanding its usage.

An abstract class will provide a default implmentation for all parsers.

```java
public abstract class ArgParser<T> implements IArgParser<T> {

public String name;
public String shortKey;
public String longKey;
public Class<?> type;
public T value;
public T defaultValue;
public String description;
public String errorMessage;

protected ArgParser() {

}

protected ArgParser(String name, String shortKey, String longKey, String description) {
this.name = name;
this.shortKey = shortKey;
this.longKey = longKey;
this.defaultValue = defaultValue;
this.description = description;
}

public abstract T parse(String strValue);

public abstract boolean validate(String strValue);

/**
* @return the longKey
*/
public String getLongKey() {
return longKey;
}

@Override
public T getDefaultValue() {
return defaultValue;
}

@Override
public String getDescription() {
return String.format("[%s/%s] : %s ( default:%s )",shortKey,longKey,description,defaultValue);
}

@Override
public String getErrorMessage(Object[] args) {
return String.format(errorMessage, args);
}

@Override
public String getShortKey() {
return shortKey;
}

@Override
public String getName() {
return name;
}

public T getValue(){
return value;
}
}
```

And the CLIManager will be used to parse command line interface parameters, but also to provide the help text.

```java
public class CliManager {
@SuppressWarnings("unused")
private Game game;
private Map<String, IArgParser<?>> argParsers = new HashMap<>();
private Map<String, Object> values = new HashMap<>();
public CliManager(Game g) {
this.game = g;
}
public void add(IArgParser<?> ap) {
argParsers.put(ap.getName(), ap);
log.debug("add cli parser for " + ap.getDescription());
}
public void parse(String[] args) {
for (String arg : args) {
if (arg.equals("h") || arg.equals("help")) {
System.out.println("\n\nCommand Usage:\n--------------");
for (IArgParser<?> ap : argParsers.values()) {
System.out.println("- " + ap.getDescription());
}
System.exit(0);
} else {
parseArguments(arg);
}
}
}
private void parseArguments(String arg) {
String[] itemValue = arg.split("=");
for (IArgParser<?> ap : argParsers.values()) {
if (ap.getShortKey().equals(itemValue[0]) || ap.getLongKey().equals(itemValue[0])) {
if (ap.validate(itemValue[1])) {
values.put(ap.getName(), ap.getValue());
} else {
log.error(ap.getErrorMessage(null));
}
}
}
}
public Object getValue(String key) throws ArgumentUnknownException {
if (values.containsKey(key)) {
return values.get(key);
} else {
return argParsers.get(key).getDefaultValue();
}
}
public boolean isExists(String key) {
return values.containsKey(key);
}
}
```

#### Implementing a parser

An Integer parameter will have to parse int values.

```java
public class IntArgParser extends ArgParser<Integer>{

public IntArgParser() {
super();
}
public IntArgParser(
String name,
String shortKey,
String longKey,
int defaultValue,
int min,
int max,
String description,
String errorMessage) {
super(name, shortKey, longKey, defaultValue, min, max, description, errorMessage);
}
@Override
public boolean validate(String strValue) {
value = defaultValue;
try {
value = parse(strValue);
if ((min != null && value < min) || (max != null && value > max)) {
errorMessage += String.format(
"value for %s must be between %s and %s. Value has been limited to min/max", name, min, max,
defaultValue);
value = (value < min ? min : (value > max ? max : value));
}
} catch (Exception e) {
value = defaultValue;
errorMessage += String.format("value %s for argument %s is not possible.reset to default Value %s",
strValue, name, defaultValue);
return false;
}
return true;
}
@Override
public Integer parse(String strValue) {6
int value = Integer.parseInt(strValue);
return value;
}
}
```

The same way to implement Boolean, Double and Float will be used.

- `BooleanArgParser` will parse a boolean value parameter,
- `DoubleArgParser` will parse a double value parameter,
- `FloatArgParser` will parse a float value parameter.

And specific one will be

- `IntArrayArgParser` will parse a list of int values parameter.9

And a final pass would provide a way to load/save those parameter to a configuration file.

But this is another story !
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

import java.util.ResourceBundle;

import fr.snapgames.fromclasstogame.core.config.cli.ArgumentUnknownException;
import fr.snapgames.fromclasstogame.core.config.cli.CliManager;
import fr.snapgames.fromclasstogame.core.config.cli.DoubleArgParser;
import fr.snapgames.fromclasstogame.core.config.cli.IntegerArgParser;
import fr.snapgames.fromclasstogame.core.exceptions.cli.UnknownArgumentException;

public class Configuration {

public ResourceBundle defaultConfig;
public CliManager cm;

public String title = "fromClassToGame";

Expand All @@ -25,61 +30,54 @@ public class Configuration {
public int debugLevel;

public Configuration(String configurationPath) {

cm = new CliManager(null);
defaultConfig = ResourceBundle.getBundle(configurationPath);
initializeArgParser();
readValuesFromFile();

}

public void readValuesFromFile() {
private void initializeArgParser() {

cm.add(new IntegerArgParser("debug", "dbg", "debug", "set the value for the debug mode (0 to 5)",
"game.setup.debugLevel", 0));
cm.add(new StringArgParser("title", "t", "title", "Define the game window title", "game.setup.title", "title"));
cm.add(new IntegerArgParser("width", "w", "width", "default width of the game screen", "game.setup.width",
320));
cm.add(new IntegerArgParser("height", "h", "height", "default height of the game screen", "game.setup.height",
200));
cm.add(new DoubleArgParser("scale", "sc", "scale", "default game screen scaling", "game.setup.scale", 2.0));
cm.add(new IntegerArgParser("FPS", "f", "fps", "set the frame per second (25-60)", "game.setup.fps", 60));
cm.add(new DoubleArgParser("gravity", "g", "gravity", "define the default game gravity", "game.setup.gravity",
0.981));
cm.add(new IntegerArgParser("display", "disp", "display", "set the default display to play the game",
"game.setup.screen", 0));
cm.add(new StringArgParser("scenes", "scns", "scenes",
"Define the scene names and classes to initalize the game", "game.setup.scenes", ""));
cm.add(new StringArgParser("scene", "scn", "scene", "Define the defaul scene to start with",
"game.setup.scene.default", ""));

this.debugLevel = Integer.parseInt(defaultConfig.getString("game.setup.debugLevel"));
this.defaultScreen = Integer.parseInt(defaultConfig.getString("game.setup.screen"));
this.width = Integer.parseInt(defaultConfig.getString("game.setup.width"));
this.height = Integer.parseInt(defaultConfig.getString("game.setup.height"));
this.scale = Double.parseDouble(defaultConfig.getString("game.setup.scale"));
this.FPS = Double.parseDouble(defaultConfig.getString("game.setup.fps"));

this.title = defaultConfig.getString("game.setup.title");
this.scenes = defaultConfig.getString("game.setup.scenes");
this.defaultScene = defaultConfig.getString("game.setup.scene.default");
this.gravity = Double.parseDouble(defaultConfig.getString("game.setup.world.gravity"));
}

public Configuration parseArgs(String[] argv) throws UnknownArgumentException {
if (argv != null) {
for (String arg : argv) {
String[] values = arg.split("=");
switch (values[0].toLowerCase()) {
case "debug":
this.debugLevel = Integer.parseInt(values[1]);
break;
case "width":
this.width = Integer.parseInt(values[1]);
break;
case "height":
this.height = Integer.parseInt(values[1]);
break;
case "scale":
this.scale = Double.parseDouble(values[1]);
break;
case "fps":
this.FPS = Double.parseDouble(values[1]);
break;
case "title":
this.title = values[1];
break;
case "scene":
this.defaultScene = values[1];
break;
case "screen":
this.defaultScreen = Integer.parseInt(values[1]);
break;
default:
throw new UnknownArgumentException(String.format("The argument %s is unknown", arg));
}
}
public void readValuesFromFile() {
try {

this.width = (Integer) cm.getValue("width");
this.height = (Integer) cm.getValue("height");
this.scale = (Double) cm.getValue("scale");
this.debugLevel = (Integer) cm.getValue("debug");
this.FPS = (Integer) cm.getValue("FPS");
this.defaultScreen = (Integer) cm.getValue("display");
this.defaultScene = (String) cm.getValue("scene");
this.title = (String) cm.getValue("title");

} catch (ArgumentUnknownException e) {
System.err.println("unable to parse configuration : " + e.getMessage());
}
}

public Configuration parseArgs(String[] argv) throws UnknownArgumentException {
cm.parse(argv);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package fr.snapgames.fromclasstogame.core.config;

import fr.snapgames.fromclasstogame.core.config.cli.AbstractArgParser;

public class StringArgParser extends AbstractArgParser<String> {

public StringArgParser(String name, String description, String shortKey, String longKey, String configKey,String defaultValue) {
super(name, description, shortKey, longKey, configKey);
setDefaultValue(defaultValue);
}

@Override
public void parseFromConfigFile(String line) {
// TODO Auto-generated method stub

}

@Override
public String parse(String strValue) {
return strValue;
}

@Override
public boolean validate(String strValue) {
return true;
}

}
Loading

0 comments on commit c004152

Please sign in to comment.