I would also go for the ID solution. You should give every object in your whole world (not just on the current map) a unique ID. A 32 Bit int should be sufficient. Further every object can store a state value, also a 32 Bit int value. You can squeeze a lot of information into 32 Bit, e.g. you can make 32 flags out of it to store 32 bool values. Or you can use the higher bits for flags and the lower bits to hold a numeric value. For something as simple as a treasure, you only need to store a bool, where a value of 1 means opened and a value of 0 means closed. The you have a game save object with two methods:
- (void)storeState:(uint32_t)state forObject:(uint32_t)objectID;
- (uint32_t)loadStateForObject:(uint32_t)objectID;
If you call store and there is no state stored for this object ID, the object ID is added to a list among with its state. If there already is a stored state, it is updated to the new state. The load method searches the list for the object ID (for fast access, consider a hashtable, so you don't have to perform a linear search or a sorted array, so you can perform a binary search) and returns the stored state or 0, in case no state has been stored (0 should always represent the default state for all objects).
When you now open a map screen, you initialize all the objects on that screen. The objects all perform a loadState()
call to the current game save object to find out, what their initial state should be. For treasures, if it is 0, they display as closed and if it is 1, they display as opened. What is inside the treasure is usually hardcoded in the level data, unless you want to make it random, but if it is random, you don't have to store that in the state either, it will be randomly chosen when the treasure is opened. You may also make it partially random (e.g. the treasure must contain rubies, bombs, etc. and the amount must be at least between X and at most Y). Depends on how (un)predicable you want the game to be. I think the original TLOZ is rather predictable.
If the user now opens a treasure, the treasure updates its state in the game save object, indicating that it has already been opened. So next time the user enters the map screen, the treasure displays opened.
Whenever the game is stored to disk, the game save objects simply writes all known objectIDs and their states in the list to a file. This can be a simple list of two consecutive 32 Bit values like
<id1><state1><id2><state2>...
If a saved game is loaded, the game save object reads this list again and initializes its internal structure with it (the hashtable or sorted array). To indicate the length of the list, you may either store an additional int value in front, that tells the code how the long the list will be or you can terminate the list, e.g. by adding a final entry of objectID 0
to it, where 0
is reserved value (no real object in your game has this ID) and thus indicates the end of the list.
Of course not every aspect of your game may be savable that way, so a save game file may contain more information before or after the object state list. E.g. you may want to store how much rubies/bombs your character currently has, which other special item he posses, and so on. On the other hand, you could store all this to the list as well, using special reserved objectIDs. E.g. real objects start with objectIDs from 1000 upwards. If 0 is reserved, this gives 999 special object IDs. objectID 1 could store the amount of rubies, objectID 2 the amount of bombs, objectID 3 the kind of sword the player owns (in that case different values will indicate different swords; IIRC you could only have one sword at a time in TLOZ).
I admit that this is a very simply approach, but simple is usually good (think of the KISS principle). It should also be no problem to make this whole think very performant. Choosing the right data structure, adding to the list, searching the list and reading/writing the list from/to disk should be very fast, even on the slowest iPhone it should happen in no time. E.g. you can store everything in a NSMutableDictionary
, keys are of type NSNumber
with the objectIDs and values are NSNumber
with the state information. Such a dictionary cannot be stored to disk directly (only dictionaries whose keys are NSString
can be stored to disk with a couple of simple function calls), but iterating over a NSDictionary
is really trivial. Just add all those 32 Bit values to a NSMutableData
and this one can be saved to disk with a single method call. Loading is the other way round, read the whole file into a NSData
(single method call again), iterate over the list and keep adding the keys and values to a NSMutableDictionary
. Should be piece of cake.
I guess the original save format was somewhat similar to such a list, yet as the NES only had a 8-Bit CPU, I guess the values where only 8 Bit (that's why you could not have more than 255 rubies) and the objectIDs probably 16 Bit (16 Bit doesn't fit into the registers of a 8 Bit CPU, but just like 32 Bit CPUs can handle 64 Bit values, 8 Bit CPUs can handle 16 Bit values, it's just slower as multiple operations are required for everything that only requires a single operation for 8 Bit values).
UPDATE
Since I think this is a cool project, I wrote some sample code to give you starting point, in case you decide to go that route. This would go into SaveGame.h
:
#import <Foundation/Foundation.h>
@interface SaveGame : NSObject {
@private
NSString * _filePath;
NSMutableDictionary * _objectStates;
}
@property (readonly) NSString * filePath;
- (id)initWithFilePath:(NSString *)aPath;
- (uint32_t)stateForObjectID:(uint32_t)objID;
- (void)setState:(uint32_t)state forObjectID:(uint32_t)objID;
- (BOOL)saveToFile;
- (BOOL)loadFromFile;
@end
// Special Object IDs
enum {
HeartContainerCountObjectID = 1,
// Value is the number of heart containers
EnergyLevelObjectID = 2,
// Value is the current energy level as "half heart count", so 1 is half a
// heart, 2 is one full heart, 3 is one and a half heart, ec.
RubyCountObjectID = 3,
// Value is the number of rubies
BombCountObjectID = 4,
// Value is the number of bombs
KeyCountObjectID = 5,
// Value is the number of keys
SwordObjectID = 6,
// Values defined below
ShieldObjectID = 7,
// Values defined below
BoomerangObjectID = 7,
// Values defined below
RaftObjectID = 8,
// Boolean value, 1 player has raft, 0 player hasn't
StepladderObjcetID = 9,
// Boolean value, 1 player has stepladder, 0 player hasn't
// ...and so on...
};
// Values for SwordObjectID
enum {
WoodenSwordObjectState = 0, // Default
WhiteSwordObjectState = 1,
MagicalSwordObjectState = 2
};
// Values for ShieldObjectID
enum {
WoodenShieldObjectState = 0, // Default
MagicalShieldObjectState = 1
};
// Values for BoomerangObjectID
enum {
NoBoomerangObjectState = 0, // Default
WoodenBoomerangObjectState = 1,
MagicalBoomerangObjectState = 2
};
// ... and so on ...
And this into SaveGame.m
:
#import "SaveGame.h"
@implementation SaveGame
@synthesize filePath = _filePath;
- (void)dealloc
{
[_filePath release];
[_objectStates release];
[super dealloc];
}
- (id)initWithFilePath:(NSString *)aPath
{
if (!aPath) {
[self release];
return nil;
}
self = [super init];
if (!self) return nil;
// aPath might be a NSMutableString, so `retain` is not safe.
// It's better to copy it. If `aPath` is a `NSString`,
// `copy` in fact only retains the object instead copying it.
_filePath = [aPath copy];
if (!_filePath) {
[self release];
return nil;
}
_objectStates = [[NSMutableDictionary alloc] init];
if (!_objectStates) {
[self release];
return nil;
}
return self;
}
- (uint32_t)stateForObjectID:(uint32_t)objectID
{
NSNumber * stateNum;
stateNum = _objectStates[@(objectID)];
return (uint32_t)[stateNum integerValue];
}
- (void)setState:(uint32_t)state forObjectID:(uint32_t)objID
{
// 0 is a reserved ID, must not be used
if (objID == 0) return;
_objectStates[@(objID)] = @(state);
}
- (BOOL)saveToFile
{
NSUInteger capacity;
__block uint32_t * raw;
NSMutableData * saveData;
// `raw` must be `__block` as we want to modify it within the block
// and the modifications should be visible next time the block is called.
// Two 32-Bit ints per state + end of list terminator
capacity = sizeof(uint32_t) * (([_objectStates count] * 2) + 1);
saveData = [NSMutableData dataWithCapacity:capacity];
if (!saveData) return NO;
raw = [saveData mutableBytes];
[_objectStates enumerateKeysAndObjectsUsingBlock:
^(NSNumber * key, NSNumber * value, BOOL * stop) {
*(raw++) = (uint32_t)[key integerValue];
*(raw++) = (uint32_t)[value integerValue];
}
];
// Terminate list
*raw = 0;
return [saveData writeToFile:_filePath atomically:YES];
}
- (BOOL)loadFromFile
{
NSUInteger i;
NSUInteger count;
NSData * saveData;
const uint32_t * raw;
saveData = [NSData dataWithContentsOfFile:_filePath];
if (!saveData) return NO;
// Even thouh the list is terminated, make sure we never read
// beyond the end of the `NSData` memory! Don't rely on the
// termination, it may be missing, the file may got corrupted, etc.
// Also the file might have been truncated to 0 bytes, so be careful.
raw = [saveData bytes];
count = [saveData length] / 4;
for (i = 0; i < count; i++) {
uint32_t state;
uint32_t objectID;
objectID = *(raw++);
// Stop `for` loop if end of list terminator is found
if (objectID == 0) break;
state = *(raw++);
[self setState:state forObjectID:objectID];
}
// The list terminator was found way too early.
// The save file is probably corrupt.
if (i < count - 1) {
[_objectStates removeAllObjects];
return NO;
}
// No list terminator was found at all, the `for` loop was
// terminated by the counter condition.
// The save file is probably corrupt.
if (i == count) {
[_objectStates removeAllObjects];
return NO;
}
return YES;
}
@end
The code is untested, but I verified that it does compile. It doesn't assume you are using ARC (automatic reference counting), but in case you do, the code will become somewhat simpler (all retain
/release
calls can be removed and dealloc
must not call [super dealloc]
in that case; copy
calls need to persist also with ARC).
Of course, if all the "objects" like the player's sword are also represented by real objects in Obj-C, I would not place the values for the object states (...ObjectState
enum values) into the SaveGame
header. There is no need why the "whole app" needs to know about them, it's enough if the objects know how to interpret the state numbers. In that case I would place them into the implementation files of these objects, since this knowledge should then be private (encapsulated by the object).
I found a pretty useful web page, that lists all the objects from TLOZ.
Storing the Triforce State in object IDs is also easy. Each piece is represented by a flag (you have 32 flags per int, the Triforce doesn't even consist out of that many pieces).
Storing the dungeon maps seems a bit harder, but that isn't really the case. You know, those overview maps:

You only need to know which rooms the player has already visited and if he has the compass for this dungeon or not. Easy. Each room of of each dungeon has an objectID with a boolean state, that is 1 if the player has visited the room, 0 otherwise. Same goes for the compass, the compass of each dungeon has a boolean state. Same goes for doors in the dungeons (which may have been opened or not) and "crack-walls" (which may have been bombed or not), each one has a unique ID and a boolean state. All information of this game can be stored in this one simple list... it's pretty amazing, don't you think :-D
Here is a very cool web page that has detailed maps of all dungeons of TLOZ, as well as an overview map of the whole overwold. Including information where specific items can be found (including secrets). Also check out the "Interactive Overwold Map", which allows you to navigate a 3 by 3 screens map and show/hide secrets and tips, including the insides of all secret caves. Basically this page contains all the information necessary to make an exact copy of the game, pixel for pixel you like.
The game logic is rather simple and the whole game will tun out to need much less code than one might expect. I guess the most time consuming task is setting up all the maps of the game, defining all the object IDs and so. Doing all this "in code" is probably a rather bad idea. I would think of a format that your code can parse, e.g. XML or JSON or maybe something as simple as structured ASCII text. Each map has a fixed number of "fields", so you could store each map as X by X values, where each value defines the content of the field: a tree, water, a coast line and which coast line, rocks and which rocks, a wall and which wall, an enemy and which enemy and into what direction it is looking (most enemies cannot look at a direction, thus you could treat the looking ones simply as different enemies, there are only 4 directions they can look at after all). Values below a certain value represent static objects, those are always the same when you enter the screen, all other values represents dynamic objects (those that store their state) and the number is split into two parts, one part is directly the object ID of the object, the other part the type of object. E.g. dynamic objects have the highest bit set, the next 7 bits define the object type (allows 127 types, e.g. 1 is a door) and the last 24 bits are directly the object ID (allows up to 16.7 Mio objects, you won't need that many), however keep in mind that values below 1000 are reserved as special values.
Since creating levels is often the most time consuming task, you may want to speed the process up and instead of starting by writing the game code, you start by writing a level editor for your PC/Mac. This seems like a waste of time, since it will take a lot of time, but trust me, it DOES pay off. Most professional games started by writing at least a simple level editor. It makes creating levels a very easy task (drag'n drop an object where you want it to be, give it an object ID if required, done). So not only it speed up the level creation process dramatically in the end, you cannot test your game code unless you have level data for testing it, since untested code is worthless code. And if you only created dummy level in the beginning, an editor allows you to quickly create them and also quickly change them to test certain aspects of your code.
Now go out and have fun young jedi. I wish plenty of luck with this project.
Update 2
If you'd like to use all the original artworks in your project, I found them online. Have a look at this page. It contains all the sprites used in the game. Even the original font. Just thought this might be useful for you.