The world in my strategy game is comprised of a number of towers. At the start of the game, only one tower is generated, and when the player discovers new towers they are created and added to the array of towers that the Game controls. Towers after the first tower start with different settings. For example, they do not start with workers and animals, and different floors are revealed and configured.
Previously, I had all of this code in the initialization of the Tower class. That class has too many responsibilities and has grown to over 1200 lines, so I have been looking for ways to refactor it to remove as much code as possible so that it is easier to understand. To achieve this goal, I have created a factory class to create a tower. The Game calls the class method of the factory class, and gets a fully configured tower in return. This has reduced the Tower class by almost 200 lines.
This is the first time that I have created a factory class, and I think that my implementation could definitely use some feedback. I decided to go with a class method so that I did not need an actual instance of this factory class to create the towers. The result of this is that I needed to use C functions in the factory class. Other options would have been to create a class like normal, and create an instance of the factory class at the start of the Game. The other option would be to create a singleton to get similar syntax to that of a class method while also having the ability to have instance methods.
I should note that the reason that worldSize is passed around everywhere is to make sure that the game is compatible with different screen resolutions.
I am open to criticism about all aspects of the code, but the most important thing is whether this is a good implementation of a factory class.
The header only contains the class method, so I will omit that.
DTTowerFactory.m
#import "DTAnimal.h"
#import "DTDwarf.h"
#import "DTTower.h"
#import "DTTowerFactory.h"
#import "DTTowerFloor.h"
@implementation DTTowerFactory
+(DTTower *) towerWithWorldSize:(CGSize)worldSize isFirstTower:(BOOL)isFirstTower {
DTTower *tower = [[DTTower alloc]initWithWorldSize:worldSize];
if (isFirstTower) {
createTowerFloors(tower, worldSize);
setBackgroundTypes(tower);
setupInitialTowerFloors(tower);
createTowerDwarves(tower, worldSize);
createTowerAnimals(tower, worldSize);
} else {
createTowerFloors(tower, worldSize);
setBackgroundTypes(tower);
setupTowerFloors(tower);
}
return tower;
}
#pragma mark - Tower Defaults
void createTowerFloors(DTTower* tower, CGSize worldSize) {
int topFloor = 10; // later this will be influenced by a gravity setting
//a random number of floors underground, not less than 90
int randomBottomFloor = arc4random_uniform(31) + 90;
int bottomFloor = -randomBottomFloor;
//create the floors and set them all to dirt to start
for (int i = bottomFloor; i <= topFloor; i++) {
NSNumber *floorNumber = [NSNumber numberWithInteger:i];
DTTowerFloor *floor = [[DTTowerFloor alloc]initWithType:Dirt andFloor:i andWorldSize:worldSize];
[tower.towerDict setObject:floor forKey:floorNumber];
}
//the top floors do not have ground or blocks for mining
for (int i = topFloor; i >= 0; i--) {
NSNumber *floorNumber = [NSNumber numberWithInt:i];
DTTowerFloor *floor = [tower.towerDict objectForKey:floorNumber];
[floor.groundBlocks removeAllObjects];
floor.groundType = NoGround;
}
//randomly determine where the different ground types start
int randomStartingDirtRockFloor = (arc4random_uniform(15) + 15);
int randomStartingRockFloor = (arc4random_uniform(30) + randomStartingDirtRockFloor);
int randomStartingRockLavaFloor = (arc4random_uniform(30) + randomStartingRockFloor);
//have to make them all negative inside these loops
for (int i = -randomStartingDirtRockFloor; i > -randomStartingRockFloor; i--) {
NSNumber *floorNumber = [NSNumber numberWithInt:i];
DTTowerFloor *floor = [tower.towerDict objectForKey:floorNumber];
floor.groundType = DirtRock;
}
for (int i = -randomStartingRockFloor; i > -randomStartingRockLavaFloor; i--) {
NSNumber *floorNumber = [NSNumber numberWithInt:i];
DTTowerFloor *floor = [tower.towerDict objectForKey:floorNumber];
floor.groundType = Rock;
}
for (int i = -randomStartingRockLavaFloor; i > bottomFloor; i--) {
NSNumber *floorNumber = [NSNumber numberWithInt:i];
DTTowerFloor *floor = [tower.towerDict objectForKey:floorNumber];
floor.groundType = RockLava;
}
}
void setBackgroundTypes(DTTower* tower) {
for (id key in tower.towerDict) {
DTTowerFloor *floor = [tower.towerDict objectForKey:key];
//could use this later to have a weighted average
//int floorNumber = floor.floorNumber;
//int backgroundType;
//but for now it will be a simple range
int randomNumber = arc4random_uniform(80);
//int randomNumber = skRand(0, 80);
if (randomNumber < 10) {
floor.backgroundType = 0;
} else if (randomNumber >= 10 && randomNumber < 20) {
floor.backgroundType = 1;
} else if (randomNumber >= 20 && randomNumber < 30) {
floor.backgroundType = 2;
} else if (randomNumber >= 30 && randomNumber < 40) {
floor.backgroundType = 3;
} else if (randomNumber >= 40 && randomNumber < 50) {
floor.backgroundType = 4;
} else if (randomNumber >= 50 && randomNumber < 60) {
floor.backgroundType = 5;
} else if (randomNumber >= 60 && randomNumber < 70) {
floor.backgroundType = 6;
} else if (randomNumber >= 70 && randomNumber < 80) {
floor.backgroundType = 7;
}
}
}
#pragma mark - First Tower
void setupInitialTowerFloors(DTTower* tower) {
for (int i = 0; i > -4; i--) {
NSNumber *floorNumber = [NSNumber numberWithInt:i];
DTTowerFloor *floor = [tower.towerDict objectForKey:floorNumber];
floor.isRevealed = YES;
//adds ladders here for convenience
floor.floorBuildState |= FloorHasLadder;
}
//have to do three floors manually for the top and bottom and floor 0
NSNumber *floorNumber0 = [NSNumber numberWithInt:0];
DTTowerFloor *tempFloor0 = [tower.towerDict objectForKey:floorNumber0];
tempFloor0.floorBuildState |= FloorHasWalls | FloorHasBottom | FloorHasRoom;
NSNumber *floorNumber1 = [NSNumber numberWithInt:1];
DTTowerFloor *tempFloor1 = [tower.towerDict objectForKey:floorNumber1];
tempFloor1.isRevealed = YES;
tempFloor1.floorBuildState |= FloorHasBottom;
NSNumber *floorNumber2 = [NSNumber numberWithInt:-4];
DTTowerFloor *tempFloor2 = [tower.towerDict objectForKey:floorNumber2];
tempFloor2.isRevealed = YES;
}
void createTowerDwarves(DTTower* tower, CGSize worldSize) {
int numStartingDwarves = 5;
for (int i = 0; i < numStartingDwarves; i++){
DTDwarf *newDwarf = startingDwarf(worldSize);
[tower.towerDwarves addObject:newDwarf];
[tower.dwarfListForRender addObject:newDwarf];
}
}
DTDwarf* startingDwarf(CGSize worldSize) {
DTDwarf *dwarf = [[DTDwarf alloc]initWithWorldSize:worldSize];
dwarf.dwarfMovement.currentFloor = -1;
dwarf.dwarfMovement.destinationFloor = dwarf.dwarfMovement.currentFloor;
dwarf.dwarfMovement.currentPosition = CGPointMake(0, dwarf.dwarfMovement.currentFloor * worldSize.height/6 - worldSize.height/6/3);
return dwarf;
}
void createTowerAnimals(DTTower* tower, CGSize worldSize) {
DTTowerFloor *floor = [tower.towerDict objectForKey:[NSNumber numberWithInt:0]];
int numStartingAnimals = 3;
for (int i = 0; i < numStartingAnimals; i++) {
[floor acceptAnimal:startingAnimal(worldSize)];
}
}
DTAnimal* startingAnimal(CGSize worldSize) {
int randomNumber = arc4random_uniform(3);
DTAnimal *animal = [[DTAnimal alloc]initWithAnimalType:randomNumber andWorldSize:worldSize];
return animal;
}
#pragma mark - Additional Towers
void setupTowerFloors(DTTower* tower) {
int startingFloorOffset = -51;
int randomNumber = arc4random_uniform(25);
int startingFloorInt = startingFloorOffset + randomNumber;
int oneFloorAboveInt = startingFloorInt + 1;
int oneFloorBelowInt = startingFloorInt - 1;
[tower assignStartingFloorForNewTower:startingFloorInt];
NSNumber *startFloorNum = [NSNumber numberWithInt:startingFloorInt];
DTTowerFloor *startFloor = [tower.towerDict objectForKey:startFloorNum];
[startFloor.groundBlocks removeAllObjects];
startFloor.isRevealed = YES;
startFloor.floorBuildState |= FloorHasLadder | FloorHasWalls | FloorHasBottom | FloorHasRoom | FloorHasRoomUpgrade;
[startFloor buildRoomUpgrade:RoomTypeGatewayExit];
NSNumber *oneFloorAboveNum = [NSNumber numberWithInt:oneFloorAboveInt];
DTTowerFloor *oneFloorAbove = [tower.towerDict objectForKey:oneFloorAboveNum];
oneFloorAbove.isRevealed = YES;
NSNumber *oneFloorBelowNum = [NSNumber numberWithInt:oneFloorBelowInt];
DTTowerFloor *oneFloorBelow = [tower.towerDict objectForKey:oneFloorBelowNum];
oneFloorBelow.isRevealed = YES;
}
@end
Here is the initialization of the Tower, which I will include since this method calls it to begin creating the Tower. There are both instance variables and properties included here. I could have initialized the properties in the factory class, but I left them in here for simplicity. I am not sure if this is the right choice.
DTTower.m
-(id) initWithWorldSize:(CGSize)worldSize {
self = [super init];
if (self){
_worldSize = worldSize;
_towerDict = [[NSMutableDictionary alloc]init];
_balanceSettings = [[DTGameBalance alloc]init];
_completedJobs = [[NSMutableArray alloc]init];
_resourcesForCollection = [[NSMutableArray alloc]init];
_dwarvesForGamePickup = [[NSMutableArray alloc]init];
_startingFloorForNewTower = 0;
_towerEnemies = [[NSMutableArray alloc]init];
_towerDwarves = [[NSMutableArray alloc]init];
_towerAnimals = [[NSMutableArray alloc]init];
_deadDwarfStatsForPickup = [[NSMutableArray alloc]init];
_dwarfListForRender = [[NSMutableArray alloc]init];
}
return self;
}