I wanted to create a save game system for my city building game that did not require the player to input a name for the city. It was also important to allow infinite save games, so I could not just use save slots to solve the problem. With a bit of help, I devised a system that would allow the player to optionally enter a name, and if they didn't the game would determine a default name based on the existing save names.
At first I was just counting all of the existing worlds and then adding that number to "world"
to get the name. However, I was concerned about the edge case where someone would, for example, make 5 worlds, then delete "world4"
, and then the game would suggest the name "world5"
again, and if the player wasn't paying attention, their save would be overwritten.
When the user chooses to start a new game, a TextField appears that is populated by the correct string, which would be "world"
followed by the next unused number.
String startName = this.getFirstUnusuedWorldName(this.getNumWorlds());
this.worldNameField = new TextField(startName, this.skin);
Here is the getNumWorlds()
method:
private int getNumWorlds() {
FileHandle[] files = Gdx.files.local("worlds/").list();
int numDirectories = 0;
for (FileHandle handle : files) {
if (handle.isDirectory()) {
numDirectories++;
}
}
return numDirectories;
}
And here is the getFirstUnusuedWorldName
method:
private String getFirstUnusuedWorldName(int numWorlds) {
FileHandle[] files = Gdx.files.local("worlds/").list();
if (files.length == 0) {
return "world1";
}
ArrayList<String> possibleNames = new ArrayList<String>();
for (int i = 1; i <= numWorlds + 1; i++) { //world names start with 1, need to search 1 past length
possibleNames.add("world" + String.valueOf(i));
}
for (String possibleName : possibleNames) {
boolean containsName = false;
for (FileHandle file : files) {
if (file.name().equals(possibleName)) {
containsName = true;
}
}
if (!containsName) {
return possibleName;
}
}
return "world1";
}
I'm hoping there's a better way to do this.
2 Answers 2
You manage to do the right thing, but the way that you do it is a bit... what shall we say...? twisted? backwards? non-optimal!
Your approach:
- Get list of all files in directory
- Loop over a range and add the possible names to a list
- Loop through the possible names and check if it matches an existing file
My comments to that:
- Using the correct data structures would help greatly here. Adding the existing file names to a
Set<String>
will make the lookup time \$O(1)\$. - Adding the names to a list is just completely unnecessary as you loop through that list directly afterwards.
- Looping through a specific range is also unnecessary. Start loop from 1 and just don't stop until you have found a free spot.
- This will also make the special-case
files.length == 0
unnecessary.
Here's what we can end up with:
private String getFirstUnusuedWorldName() {
FileHandle[] files = Gdx.files.local("worlds/").list();
Set<String> fileNames = new HashSet<String>();
for (FileHandle handle : files) {
fileNames.add(handle.getName());
}
for (int i = 1; ; i++) {
String name = "world" + i;
if (!fileNames.contains(name)) {
return name;
}
}
}
Note the lack of a stopping condition in the for
-loop (You don't need one as sooner or later, there will be an empty spot).
Also note that you no longer need the int numWorlds
parameter.
Edited thanks to @Quill.
Assuming that the player doesn't care about the name allocated to the file, browsing the filetree and making exceptions for possible user interactions seems like a lot of work.
You appear to be searching for a unique string to add to the base world name. One is readily available in the form of a timestamp, for example world20150709132530
based on the base world name, year, month, hour, minute and second. I'm sorry not to be able to provide a code example as I'm not familiar with the language, but this should be readily available in the reference documents.
Possible dangers associated with this alternate method include moving to a different time zone, summer time or clock resyncs. In all cases, a problem occurs if the user saves in the 'same second' in the new timeframe as they did in the previous timeframe. Using UTC eliminates the first two errors, but adds a layer of confusion for the user if they look at the filenames from a country not currently in the UTC timezone.
-
\$\begingroup\$ The name is used to populate a TextField that is visible to the player, so it is better in my case not to use a strange number. Interesting approach though! \$\endgroup\$bazola– bazola2015年07月09日 15:14:14 +00:00Commented Jul 9, 2015 at 15:14
-
\$\begingroup\$ is
world-2015-Jul-09-T13:25:30
better? using e.g.String filename = new SimpleDateFormat("'world'-yyyy-MMM-dd-'T'HH:mm:ss").format(new Date());
\$\endgroup\$pbeentje– pbeentje2015年07月10日 08:04:29 +00:00Commented Jul 10, 2015 at 8:04 -
1\$\begingroup\$ @pbeentje I don't think there's a OS in the world that allows
:
in a filename. \$\endgroup\$Simon Forsberg– Simon Forsberg2015年07月13日 16:45:08 +00:00Commented Jul 13, 2015 at 16:45 -
1\$\begingroup\$ @SimonAndréForsberg Any OS following the filename rules of The Single Unix Specification allows any character except NUL and forward slash. This includes both OS X and Linux, which are the basis for iOS and Android respectively. See the Open Group Base Specifications. \$\endgroup\$jacwah– jacwah2015年09月02日 15:03:43 +00:00Commented Sep 2, 2015 at 15:03