Time Remaining: 48 Hours
In my post where I was oh so smug about how abstract my game objects were, I watched in horror as my code filled out with stuff like this:
if let itemName = itemNode.name {
if itemName.containsString("BrewHead") {
itemNode.accepts = .Top
itemNode.dispenses.append(.Espresso)
itemNode.requires = .CoffeeGround
if itemName.containsString("1") {
if let holder = self.childNodeWithName("//HolderBottom1") as? Zone {
itemNode.linkedZone = holder
}
} else if itemName.containsString("2") {
if let holder = self.childNodeWithName("//HolderBottom2") as? Zone {
itemNode.linkedZone = holder
}
}
} else if itemName.containsString("Grinder") {
itemNode.accepts = .Top
itemNode.dispenses.append(.CoffeeGround)
} else if itemName.containsString("Holder") {
itemNode.dispenses.append(.Espresso)
} else if itemName.containsString("Milk") {
itemNode.dispenses.append(.Steam)
itemNode.dispenses.append(.HotWater)
}
}
Lots of conditional statements that search strings is definitely a way to have super maintainable and adaptable code. Future me is going to be so happy to work with this!
I am now wishing I had gone with the Component Entity system as I had originally planned. I am now realising that there is enough difference in how the entities act that it makes sense to keep the abilities of those entities separate. The most glaring example was that all Actors can contain something (milk, coffee grounds, etc) but there was one non-Actor that can also contain something—the bean hopper can contain beans.
Having a component that was Containable
would have made sense here—something that didn’t care what it was attached to, whose only job was to hold stuff for whoever needed stuff holding.
Anyway, I’m in the process of doing a little bit of refactoring to clean all of this up a bit. It’s too late to go back now (time’s a-wasting), so I’ve started to use the old sledgehammer of inheritance as it’s unlikely to get too unwieldily in this simple game (famous last words) and removes a lot of the need for string searching.
I’ve also noticed that my aggressive desire to abstract everything has made some of the more complicated parts of the logic that little bit more inscrutable. Example:
func addIngredientFromNode(node : SKNode) {
// If the node has a parent and that parent is a zone
if let parent = node.parent as? Zone {
for ingredient in parent.dispenses {
// Do we need to care about a totally unrelated zone?
if let linkedZone = parent.linkedZone {
if let parentActor = parent.currentActor where parentActor.container.containsIngredient(parent.requires) {
let quantity = parentActor.container.ingredientQuantity(parent.requires)
if let actor = linkedZone.currentActor where actor.container.addIngredient(ingredient, withQuantity: quantity) {
print("Adding ingredient \(ingredient) to \(actor.name!) with quantity \(quantity)")
parentActor.container.removeIngredient(parent.requires)
} else {
print("Couldn't add ingredient \(ingredient)")
}
} else {
print("Something's missing")
}
} else {
if let parentName = parent.name where parentName.containsString("Grinder") {
if let hopper = self.childNodeWithName("//*Hopper") as? BeanHopper {
if hopper.container.ingredientQuantity(.CoffeeBeans) > 0 {
if let actor = parent.currentActor where actor.container.addIngredient(ingredient, withQuantity: 1) {
print("Adding ingredient \(ingredient) to \(actor.name!) with quantity 1")
hopper.container.removeIngredient(.CoffeeBeans, quantity: BaristaLimits.limit(.BeansPerGrind))
} else {
print("Couldn't add ingredient \(ingredient)")
}
} else {
print("No more grounds!")
}
}
} else {
if let actor = parent.currentActor where actor.container.addIngredient(ingredient, withQuantity: 1) {
print("Adding ingredient \(ingredient) to \(actor.name!) with quantity 1")
} else {
print("Couldn't add ingredient \(ingredient)")
}
if let isJug = parent.currentActor as? Jug where ingredient == .Steam {
isJug.isSteamed = true
}
}
}
}
}
}
Yeah, I know. I’m embarrassed too1.
The reason for this horrorshow was that I realised that the brew heads had to first make sure there were grinds available in one zone, then make sure that there was a container that could accept espresso shots in a completely unrelated zone.
Before, this code was relatively elegant—just get the Actor sitting in the Zone and try to add an ingredient to its container. Since then, edge cases have appeared (like the shot delivery) that has resulted in this mess.
Probably ten minutes of really thinking through all aspects of this problem would have thrown up this little peccadillo.
What can I say? I code from the hip.
It does work, though, and I do intend to refactor this if I have time but sometimes quality has to be sacrificed in the name of expediency. One thing I have learned from my studying of game creation is that continual iteration is crucial and in order to do that, I first need to get to a playable state as quickly as possible. It’s much more important that I get a playable game as soon as possible so that I can make refinements to the gameplay.
Here’s where we are at now:
There’s now a second class, called GameManager, that handles all of the scoring, the creating of the drinks orders, the timers and verifying that submitted drinks are all correct. The bean hopper and the milk jug can also now be refilled. Currently, new drinks are added every 2 seconds but I want to introduce some randomness into this. Also, the game needs to gradually get harder over time.
There will be no levels, but there will be bonus “mistakes”. Your boss is a bit of an asshole who runs a three strikes policy, but if you do continual good work he’ll soften a little over time.
Balancing it getting harder in a challenging way without it getting impossible in an unfair way is going to be tricky, which is why I’d like to start playing it as soon as possible.
If I design it right, then tweaking it should just be a case of adjusting numbers to find a nice balance but, given what I’ve ended up with above, then “design it right” might be too high a bar!
-
Coding is like writing—it should go though multiple iterations to sculpt it from a crude idea into an elegant finished piece. As my skill increases, I would expect to start writing more elegant code from the get go but I think it’s important, especially for beginning programmers, to see that ugly, inelegant code does exist and is simply part of the process.↩