In the previous postings (part I and part II) I've created an EMF model to hold the definition of an adventure game. I've used the EMF-generated editors to create an adventure and wrote a little bit of code to run the game.
In this posting I'm going to look at how we can use the EMF built-in serialization as a very easy way to load and save the game state. At the end I will also show how to run the program from the command line outside of Eclipse.
To implement Load and Save, I'm adding two new actions to every location in the game (a player can load and save in every game location): SaveAction and LoadAction.
The SaveAction creates a new SaveGame instance and stores the current location and the current inventory in it.
Looking at the model again, you can see that the SaveGame contains an Inventory object but only has a relation to the Location. This has an impact wrt to the content of the file that we're saving to. The contained items are actually inlined in the file where the referenced items are stored as hrefs.
A sample adventure.save file would look like this. The format of this XML file is called XMI, but as you can see, it reads as very typical XML. You can see that the inventory is actually contained in the file but the location is pointed to. When we save the file, it is in the same Resource Set as the My.adventure resource, so that's how they know about each other. The href URL also explains why we need an ID attribute. It is used in there. The ID attribute value cannot have a space in it so that's why it's generally handy to use a separate attribute for it.
<adv:savegame version="2.0" xmi="http://www.omg.org/XMI" adv="http://adventure">
<inventory>
<items name="Spade" id="spade"/ >
</inventory>
<location
href="file:/C:/EMF_Adventure/bin/My.adventure#desert"/>
</adv:savegame>
The adventure.save file is created using the following code:
SaveGame sg =
AdventureFactory.eINSTANCE.createSaveGame();
sg.setLocation(Status.location);
sg.setInventory(Status.inventory);
String fileName = System.getProperty("java.io.tmpdir") +
"adventure.save";
Resource resource = Status.resourceSet.createResource(
URI.createFileURI(fileName));
resource.getContents().add(sg);
try {
resource.save(Collections.emptyMap());
System.out.println("Game saved to " + fileName);
return true;
} catch (IOException e) {
e.printStackTrace();
} finally {
Status.resourceSet.getResources().remove(resource);
}
- First we create and fill the SaveGame object.
- Then we add a new Resource, stored in a file called adventure.save in the temp dir, to the ResourceSet used by the game.
- Add the SaveGame object to the new Resource.
- call resource.save()
- Finally remove the new resource from the game's ResourceSet again as we don't want it to change after this.
Loading data from an XMI file is very similar. The LoadAction does the following:
String fileName = System.getProperty("java.io.tmpdir") +
"adventure.save";
Resource resource = Status.resourceSet.getResource(
URI.createFileURI(fileName), true);
try {
SaveGame sg = (SaveGame) EcoreUtil.getObjectByType(
resource.getContents(),
AdventurePackage.eINSTANCE.getSaveGame());
if (sg != null) {
Status.location = sg.getLocation();
Status.inventory = sg.getInventory();
System.out.println("Game loaded from " + fileName);
return true;
}
} finally {
Status.resourceSet.getResources().remove(resource);
}
- Create and add a resource that points to the adventure.save file. In this case we use ResourceSet.getResource() instead of ResourceSet.createResource().
- Then we look up all of the contents of the file and find the SaveGame object in it. Since the adventure.save file really should only contain a single SaveGame object, resource.getContents().iterator().next() would also do the job.
- Once we have our SaveGame object, simply change the current location of the game to be the one loaded and change the inventory to be the one from the savegame.
- Finally, remove the adventure.save resource from the resourcset and were done.
Oh, and I'm adding the following 2 lines to Status.initializeActions() so that my load and save actions are available from every location in the game:
actions.add(new SaveAction());
actions.add(new LoadAction());
Running the program from the command line
We're not writing an Eclipse application here, we're only using Eclipse and EMF as a tool to build our app. So we just want to run everything from the commandline. To do this, add to the following jars to your CLASSPATH or copy them into your project. For EMF that comes with Eclipse 3.3.1 the needed libraries are the following, but the actual number could be slightly different if you are using a different version of Eclipse (see the EMF FAQ for more info):
- org.eclipse.emf.common_2.3.0.v200709252135.jar
- org.eclipse.emf.ecore_2.3.1.v200709252135.jar
- org.eclipse.emf.ecore.change_2.3.0.v200709252135.jar
- org.eclipse.emf.ecore.xmi_2.3.1.v200709252135.jar
set CLASSPATH=.;
org.eclipse.emf.common_2.3.0.v200709252135.jar;
org.eclipse.emf.ecore_2.3.1.v200709252135.jar;
org.eclipse.emf.ecore.change_2.3.0.v200709252135.jar;
org.eclipse.emf.ecore.xmi_2.3.1.v200709252135.jar
java game.Main
Welcome to Adventure Island. This is the start of the adventure game. You are at the house of a Viking.
Hints: (q)uit, (i)nventory, (w)est, (e)ast, save, load, you see a Spade
Please enter command:
All the code above is available in the Subversions repository at http://coderthoughts.googlecode.com/svn/trunk/emf_adventure for more info on how to check it out look here. Want all the projects including the generated code, get it here: http://coderthoughts.googlecode.com/svn/trunk/emf_adventure_full