Skip to main content

Create a Tetromino Puzzle Game Using Swift - Drawing Objects

By Rico Zuñiga



Free JavaScript Book!

Write powerful, clean and maintainable JavaScript.

RRP $11.95

The GameScene

Now it’s time to draw something on the screen. A GameScene class that extends SKScene was already declared for us by default in GameScene.swift. Update its definition with the following code:

class GameScene: SKScene {
    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        let block = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 50, height: 50))
        block.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))


    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        /* Called when a touch begins */

        for touch: AnyObject in touches {

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */

Let’s focus our discussion on the only non-empty method so far in this class, the didMoveToView method which we overrode from SKScene. As described in the comment, this is where we should setup our scene.

All we need at this time is to display a square in the middle of the screen. We accomplish this by using a SKSpriteNode object initialized with a color and size value. We formally call this square object a sprite. Sprites are the most basic visual elements of a game.

let block = SKSpriteNode(color: SKColor.orangeColor(), size: CGSize(width: 50, height: 50))

We set the block’s position to the center of the screen by reusing code from the default spaceship example that was generated for us by Xcode.

block.position = CGPoint(x:CGRectGetMidX(self.frame), y:CGRectGetMidY(self.frame))

To show the block on the screen, we need to add it as a child of the scene. Our block sprite becomes part of the scene’s tree of node objects and is rendered along with the other nodes, Although at this point, the square is the only node object in the scene.


Run (⌘ + R) our game to see the lone orange block on the center of the screen.


In the next section, we will describe and draw the seven tetromino shapes.

Drawing Tetrominoes

According to Wikipedia, a tetromino is a geometric shape composed of four squares, connected orthogonally. There are a few different types of tetrominoes and among these, we will be using the one-sided tetromino which has seven distinct shapes:








Each tetromino can be represented by a letter that abstractly resembles its shape. We use the letters I, O, T, J, L, S and Z to represent each of these tetrominoes. We also use colors to further differentiate between the shapes and of course to add some eye candy.

The Tetromino Class

Let’s create a new class to represent our tetrominoes.

  1. Press ⌘ + N to open the file template selection window and choose Swift File from the iOS Source group. Click Next to save the file.

    New file

  2. Name the file Tetromino and click Create.

    New file

  3. Add the following enumerations to the Tetromino.swift file after the import statement:

    enum Direction {
        case Left, Right, Down, None
    enum Rotation {
        case Counterclockwise, Clockwise
    enum Shape {
        case I, O, T, J, L, S, Z
        static func randomShape() -> Shape {
            let shapes: [Shape] = [.I, .O, .T, .J, .L, .S, .Z]
            let count = UInt32(shapes.count)
            let randShape = Int(arc4random_uniform(count))
            return shapes[randShape]

    These three enums describe values that are intrinsic to our tetrominoes. Direction, as the name suggests, enumerates the possible directions a tetromino can move to in our game. That’s either to the left, right, or down. It also includes a value for remaining stationary but no value for the up direction for obvious reasons.

    The Rotation enum defines either a counterclockwise or clockwise value.

    The Shape enum defines the seven distinct one-sided tetrominoes we will be using in our game. One advantage of enumerations in Swift is the capability to define their own methods which include both type (defined with the static keyword) and instance methods.

    Looking at the gameplay animation above, we can easily deduce that tetrominoes are being generated randomly. So for convenience, let’s implement this consistent behavior as a type method named randomShape. We use the built in arc4random_uniform method by passing in the total number of shapes to generate a random number. We then use this number to access an element from the shapes array.

  4. Add the bitmap data representing the shapes. The bitmaps data is quite complex but don’t be intimidated. It is just a dictionary type which uses a Shape as key to a three-dimensional array value. These arrays represent all of the shape’s rotational configurations.

    let bitmaps: [Shape: [[[Int]]]] = [
        .I: [[[0, 1], [0, 1], [0, 1], [0, 1]], [[0, 0, 0, 0], [1, 1, 1, 1]]],
        .O: [[[2, 2], [2, 2]]],
        .T: [[[3, 3, 3], [0, 3, 0], [0, 0, 0]], [[3, 0], [3, 3], [3, 0]], [[0, 0, 0], [0, 3, 0], [3, 3, 3]], [[0, 0, 3], [0, 3, 3], [0, 0, 3]]],
        .J: [[[0, 4, 4], [0, 4, 0], [0, 4, 0]], [[4, 0, 0], [4, 4, 4], [0, 0, 0]], [[0, 4, 0], [0, 4, 0], [4, 4, 0]], [[0, 0, 0], [4, 4, 4], [0, 0, 4]]],
        .L: [[[0, 5, 0], [0, 5, 0], [0, 5, 5]], [[0, 0, 5], [5, 5, 5], [0, 0, 0]], [[5, 5, 0], [0, 5, 0], [0, 5, 0]], [[0, 0, 0], [5, 5, 5], [5, 0, 0]]],
        .S: [[[0, 6, 0], [0, 6, 6], [0, 0, 6]], [[0, 0, 0], [0, 6, 6], [6, 6, 0]], [[0, 6, 0], [0, 6, 6], [0, 0, 6]], [[0, 0, 0], [0, 6, 6], [6, 6, 0]]],
        .Z: [[[0, 7, 0], [7, 7, 0], [7, 0, 0]], [[0, 0, 0], [7, 7, 0], [0, 7, 7]], [[0, 7, 0], [7, 7, 0], [7, 0, 0]], [[0, 0, 0], [7, 7, 0], [0, 7, 7]]]

    We can certainly implement matrix rotation algorithms here to dynamically rotate the shape bitmap but that would be too computationally expensive for our simple needs. There aren’t a lot of configurations anyway so “hardcoding” them is preferable. Also, we can use SpriteKit’s SKAction to rotate the sprites at a specified angle but that’s not how our game works data structure-wise.

    We arranged the values horizontally to avoid adding unnecessary length to this section but feel free to format it in your own code to make the shapes stand out. The zeroes represent empty spaces while the non-zero integers represent an index value in an array of colors (to be added later). Here’s the T shape in a more readable format. You can clearly see the different configurations:

    .T: [[
            [3, 3, 3],
            [0, 3, 0],
            [0, 0, 0]],
            [3, 0],
            [3, 3],
            [3, 0]],
            [0, 0, 0],
            [0, 3, 0],
            [3, 3, 3]],
            [0, 0, 3],
            [0, 3, 3],
            [0, 0, 3]

    Arrays and Dictionaries

    Our bitmap data is a good example of Swift’s collection type. It is a dictionary containing a collection of arrays. Arrays and dictionaries store ordered and unordered collections of values respectively. Array and dictionary elements are accessed (and set) using subscript notation. It uses square brackets ([]) which may contain an index value starting with zero for arrays, or a key, a Shape key in our case, for dictionaries.

    For example, to access an element from the bitmaps dictionary, you may write it as:



    Dictionaries return an optional value. Optional means that the key used to access an element of the dictionary may not exist requiring the dictionary to return nil which represents the absence of a value. Optionals are used to determine whether a value exists or not and to aid in handling such cases.

  5. Let’s define the Tetromino class.

    class Tetromino {
        let shape = Shape.randomShape()
        var bitmap: [[Int]] {
            let bitmapSet = bitmaps[shape]!
            return bitmapSet[rotationalState]
        var position = (x: 0, y: 0)
        private var rotationalState: Int
        init() {
            let bitmapSet = bitmaps[shape]!
            let count = UInt32(bitmapSet.count)
            rotationalState = Int(arc4random_uniform(count))
        func moveTo(direction: Direction) {
            switch direction {
            case .Left:
            case .Right:
            case .Down:
            case .None:
        func rotate(rotation: Rotation = .Counterclockwise) {
            switch rotation {
            case .Counterclockwise:
                let bitmapSet = bitmaps[shape]!
                if rotationalState + 1 == bitmapSet.count {
                    rotationalState = 0
                } else {
            case .Clockwise:
                let bitmapSet = bitmaps[shape]!
                if rotationalState == 0 {
                    rotationalState = bitmapSet.count - 1
                } else {

    Property Initialization

    Class instance properties in Swift must be either defined with a default value like the immutable shape property or be given a value during initialization as with the case of rotationalState. This means we need to implement an initializer to set the value of rotationalState using the init keyword.

    Again, we encounter Optionals in the statement let bitmapSet = bitmaps[shape]!. Accessing an element of a dictionary returns an optional type so we need a way to access the value contained inside it, if there is one. We use an exclamation mark to unwrap the optional and expose the underlying value.

    Finally, we use arc4random_uniform to assign a random initial rotational state for the tetromino.

    Computed Properties

    The bitmap property of our class is defined as a read-only computed property. This type of property does not store a value. Instead, it acts like a function to come up with the correct value every time it is accessed. Computed properties can also optionally receive values that it uses to set other properties in the class.


    A tuple is a compound value composed of zero or more values of different types. Tuples in swift are written as comma delimited values with optional names contained within a set of parentheses. Here we use the position property, a tuple of type (Int, Int), to keep track of this tetromino’s x and y location on the game board.

    Conditional Statements

    Our class defines two methods that control the movement and rotation of the tetromino. One is the moveTo method which takes a parameter of type Direction, an enumeration we defined earlier. This method uses a switch statement to check all possible direction values to be able to adjust the tetromino’s position accordingly.

    Unlike switch statements in most other languages, Swift does not allow falling through the next case when a break is not encountered. This adds a layer of protection from unintentionally triggering wrong handlers because of missing breaks. It is also required to handle all cases. This means you will have to include a default case for all other values if you do not have a finite set of cases like in our enums.

    The rotate method takes in a parameter of type Rotation but sets a default value – .Counterclockwise. It also utilizes a switch statement to check for both rotational directions. Traditionally, the player can rotate the bricks in both directions. But in our game, we will only allow the player to rotate in one to keep things simple. Our purpose for handling both is to be able to reset the tetromino to its previous state when a collision has occurred. We will discuss collision detection in more detail later.

    Aside from the switch statement, rotate also utilizes if statements to check for array out of bounds conditions. The if statement takes in a condition and executes a set of statements contained within braces if the condition is true. Unlike in other languages, the conditions in Swift’s if statement do not need to be enclosed in parentheses. However, the pair of braces are required even if it’s empty or only has one statement to execute.

The GameScene Class

Let’s now update our GameScene class to display random tetrominoes with the following code:

import SpriteKit

let colors: [SKColor] = [

let blockSize: CGFloat = 18.0

class GameScene: SKScene {

    override func didMoveToView(view: SKView) {
        /* Setup your scene here */
        self.anchorPoint = CGPoint(x: 0, y: 1.0)

    override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
        /* Called when a touch begins */
        for touch: AnyObject in touches {

    override func update(currentTime: CFTimeInterval) {
        /* Called before each frame is rendered */

    func drawTetrominoAtPoint(location: CGPoint) {
        let t = Tetromino()
        for row in 0..<t.bitmap.count {
            for col in 0..<t.bitmap[row].count {
                if t.bitmap[row][col] > 0 {
                    let block = t.bitmap[row][col]
                    let square = SKSpriteNode(color: colors[block], size: CGSize(width: blockSize, height: blockSize))
                    square.anchorPoint = CGPoint(x: 1.0, y: 0)
                    square.position = CGPoint(x: col * Int(blockSize) + col, y: -row * Int(blockSize) + -row)
                    square.position.x += location.x
                    square.position.y += location.y

After our import statement, we created an immutable array of color objects that will be used to give life to our otherwise boring shapes. Conveniently, we have SKColor‘s (just a wrapper for UIColor) preset values that we can reuse. It’s as if they were created for this kind of game as they closely match the standard colors of the official game.

We set the size of each block then proceed with updating the GameScene class. We replace the previous contents of didMoveToView method with a single line of code that moves the anchor point from the default lower-left corner of the scene all the way to the upper-left corner. This essentially moves the origin (0, 0) to that particular corner.

In touchesBegan, we have to loop through all the possible touch points owing to the fact that we are running on a multitouch device. But since we disabled multitouch capability for this game, only the first touch will register. We then call the drawTetrominoAtPoint method passing in the location of the touch.

Anchor Points and the Origin

In the drawTetrominoAtPoint method, we start by creating a tetromino with a random shape and rotational state. SpriteKit’s coordinate system uses the traditional Cartesian system with an origin (0, 0) that is anchored on a particular location which is referred to as the anchorPoint. This differs from other screen coordinate systems where the y value increases from top to bottom and with the origin located on the upper left corner by default.

This runs counter to how our game’s arrays are defined. Bitmap array indexes start from 0, practically representing the top row then increments by one as you go down one row at a time. So to be able to conveniently work around this situation, we moved the view’s anchor point from its default location as described earlier. This means we are starting from the origin y value of zero and moving down along the negative y axis. We’ll have to compensate by negating our row index values using the unary - (minus) operator, to be able to correctly position the block along the y axis:

square.position = CGPoint(x: col * Int(blockSize) + col, y: -row * Int(blockSize) + -row)

For Loops

The shape of tetrominoes in our game is expressed as a bitmap for convenience. Bitmaps have row and column (x and y) values that represent pixels of 2d images. We extract each pixel data by looping through all the rows and columns of the bitmap. Swift also has its own for in statement like in many other languages. In our drawTetrominoAtPoint method we have a nested implementation of the for in loop to access each element of the bitmap.

We need access to each bitmap index so we define this as a range of integers from 0 up to but not including the total number of rows or columns. We use the half-open range operator .. to express this. If we want to include the total count we’ll have to use the closed range operator ... to define the range. The current value of the range is stored in whatever variable you put after the for keyword.

An element of the shape bitmap can either have a zero or non-zero value. We exclude the pixels with a zero value from being displayed using the conditional if statement. We proceed with creating each block sprite using the same process we used in the previous section on drawing a block. The color index is by design, mapped to the value of the block producing a consistent color scheme for the entire tetromino set.

Each block’s position is calculated based on the length of its side and its index in the array. To achieve the “space between blocks” effect, we add a gap with a single point width using the current value of the row or column. We also want the tetromino to be drawn near the area we touched so we adjust the position by adding the location value we received from the parameter. Setting the anchor point of the block sprites to the lower right corner also helped center the tetromino.

Run (⌘ + R) our game and touch anywhere on the simulator’s screen to draw random tetrominoes and create your own artwork like this:


In the next instalment we move on to the fun part, the gameplay mechanics!

17+ years in the software industry. Experienced CTO in blockchain and cryptocurrency. Community leader, developer advocate, mentor, entrepreneur, and lifelong learner.

New books out now!

Learn how Git works, and how to use it to streamline your workflow!

Google, Netflix and ILM are Python users. Maybe you should too?