This post is of the Swiftris series so if you have no idea what I’m talking about, read this.
If you’ve played a lot of tetris in your life you understand that not all is over when one ‘tetrino’ touches the settled rubble at the bottom of the pit – when that happens you still have 1 ‘tick’ to move it sideways or even spin the piece if it fits! Let’s call that the ‘last move rule’. Swiftris ADDS code to prevent you from doing that – it prematurely settles the tetrino.
Somewhere in your Swiftris.swift file you’ll have something like:
[sourcecode language=”javascript” title=”Swiftris.swift excerpt”]
func letShapeFall() {
if let shape = fallingShape {
shape.lowerShapeByOneRow()
if detectIllegalPlacement() {
shape.raiseShapeByOneRow()
if detectIllegalPlacement() {
endGame()
} else {
settleShape()
}
} else {
delegate?.gameShapeDidMove(self)
if detectTouch() {
settleShape()
}
}
}
}
[/sourcecode]
‘letShapeFall()’ is the one method executed over and over when a shape is falling and ‘detectIllegalPlacement()’ is the method that verifies either the board contains an illegal placement.
Additionally, it introduces the ‘detectTouch()’ which simply duplicates the functionality of ‘detectIllegalPlacement()’ but implemented separately.. for no apparent reason.
‘detectTouch()’ is only used to ‘settle’ a tetrino when it touches the rubble at the bottom of the pit. Basically, only used to ‘ruin’ the ‘last move’ rule. Suppose we force ‘detectTouch()’ to always return false, that way, the ‘life’ of that tetrino will be extended by one ‘tick’ cycle only because as soon as ‘letShapeFall()’ is called again at the next cycle ‘detectIllegalPlacement()’ will return true and effectively ‘settleShape()’. You see, the current implementation offers two different paths for the ‘settleShape()’ calls where logically they should happen under the same circumstances. By any standards that stinks a bit. So, let’s just get rid of it. Try changing your code to this:
[sourcecode language=”javascript” title=”Swiftris.swift excerpt”]
func letShapeFall() {
if let shape = fallingShape {
shape.lowerShapeByOneRow()
if detectIllegalPlacement() {
shape.raiseShapeByOneRow()
if detectIllegalPlacement() {
endGame()
} else {
settleShape()
}
} else {
delegate?.gameShapeDidMove(self)
// Comment out this.
// if detectTouch() {
// settleShape()
// }
}
}
}
[/sourcecode]
Try it out.. everything still works fine and now the life of the Tetrinos at their last hopeless moment of fall is a tiny bit extended! Yay!
Now, if you look around you’ll see that there’s a LOT of code that is there for nothing.. detectTouch() is never used, and so isn’t ‘bottomBlocksForOrientations’ or ‘bottomBlocks’ in Shape.swift and all it’s child (JShape, LineShape, LShape, SquareShape, SShape, TShape, ZShape). You can safely remove all that and it’s about 80 lines.. that serve no purpose but be buggy and confuse ppl 🙂
For example, some shape implementations become really simple and short. TShape for example:
[sourcecode language=”javascript” title=”TShape.swift”]
class TShape:Shape {
/* … */
override var blockRowColumnPositions: [Orientation: Array<(cDiff: Int, rDiff: Int)>] {
return [
Orientation.Zero: [(1, 0), (0, 1), (1, 1), (2, 1)],
Orientation.Ninety: [(2, 1), (1, 0), (1, 1), (1, 2)],
Orientation.OneEighty: [(1, 2), (0, 1), (1, 1), (2, 1)],
Orientation.TwoSeventy: [(0, 1), (1, 0), (1, 1), (1, 2)]
]
}
}
[/sourcecode]
Try out the game after the changes.. it all works fine.. I hope!
Another problem with Swiftris is that whenever you complete a line it treats all blocks above that line as independently free falling – that’s not how original Tetris works and it makes the game a heck easier than the original. In the original, the fallen blocks all fall as a single connected mass leaving whatever small holes you had below it untouched. Plus, the logic for the fallen blocks, something intrinsic to the game is written inside Swiftris.swift but it’s called from the GameViewController.swift.. a pretty awkward implementation of any Model-View-Controller or Model-View-ViewModel pattern they got in there – it works and all but.. My next post will get a bit into that.