Tetris 2.0

Developed by: Martin Mach(mmach18), Pavel Novotný (panov18), Adam Frémund (adfre18)

Introduction

Tetris is game where bricks made out of squares (from one up to four) are falling down the game board and from the bottom to the top are stacked one on another. When player manages to fill one line horizontally, the line disappears, player gets extra points and all the stacked bricks above this line fall one line lower to fill the gap. Bricks are falling with “game gravity” by them selves, player can just rotate these bricks or move them to sides while they are falling. The game ends when the game board is full and player has no chance to lay down any more bricks. First Tetris game was introduced in 1984 by Alexey Patjinov in Russia. However since then there are lots and lots of Tetris game variations developed all around the world. [1]

 

Our project is also a variation of a classic Tetris game. Our involvement is mainly in the control part of the game as in our app the movement of bricks is controlled through the device’s accelerometer. Users then can control the game simply by shaking their devices. 

Goals

      1. entertainment
      2. logic and cognitive skills improvement

Problems

      1. game logic
      2. scoring
      3. core data
      4. accelerometer data
      5. audio

Methods

We started with basic brainstorming method when we came with the basic idea of movement controlled Tetris game.

When we first started the prototyping, we used simple sketches to evaluate with potential users what would be the most user friendly design. That’s how we got our layout prototype.

For evaluation we asked some potential users of our game – students.

Then our approach was systematic. We started with first digital navigational prototype. Second step was to split the work between developers and then we kept adding features to the first prototype with rapid prototyping process.

(https://usabilitygeek.com/wp-content/uploads/2014/10/effective-rapid-prototyping-diagram.jpg)

Implementation

    1. Frameworks:
      1. SpriteKit
      2. AVFoundation
      3. CoreMotion
      4. CoreData
    2. During implementation we followed the basic MVC scheme.
    3. As an evaluation of our implementation we did significant amount of the game play and user interface testing.

Architecture

We managed to separate the architecture of our app according to the MVC pattern. View itself is being handled by a storyboard (Main), from which we control complete workflow and all user stories.

The interaction with a user is handled by 4 controllers – one for each of the functional screens (ie. except How to play). In the controllers we have only the UI and user-interaction related code, everything else is in Model. Every controller creates its model classes, ie. Model doesn’t know about the existence of controllers. That way, Model can stay independent and potentially reusable. In one case, we needed to make asynchronous calls from Model to Controller – to update the state of a gameplay to the game screen. We handed this by creating a protocol (basically interface) and implementing the Observer design pattern. This way, the relation between the classes is not required and layers are kept separate.

protocol GameStatus {
    func gameOver()
    func lineClear()
    func score(change: Int)
    func next(item: GameItem)
}

Some of the Model classes – namely UserSettings, AudioClass etc. – should be created only once. To ensure this, these classes are implemented via the Singleton pattern.

Tetris logic is handled by GameConductor. It makes sure that game pieces are being generated (via a factory), moved down the board, all rules are being followed etc. Rendering itself is done through GameScene, which GameConductor controls and tells it what to do. GameScene doesn’t know anything about the logic or game state, it simply renders boxes.

Game render

Rendering of the game is handled in GameScene and controlled by GameConductor, as mentioned above. We use SpriteKit to render individual boxes into the scene. During the development of our app, we tried several approaches. One of the first was to create one node per game item and move it down the board. With this approach, we were facing several challenges – it was problematic to control the movement by the accelerometer and also hard to control whether a line is already completed. So we moved to another solution – we simply pre-fill the gameboard with CxR boxes which we colour by an item it should contain. Even though we have to, for example, handle collisions by ourselves, this approach seems to be more straightforward – at least for Tetris. It’s also much easier to break down individual items when a line is being completed and remove some of their parts.

Movement control

We control the movement by the accelerometer in the MovementControl class. We read the X-axis value on each game refresh (which is being controlled by the SpriteKit Scene, roughly every 10 ms) and if it exceeds a certain threshold (0.2), we move an active item left/right, according to the sign of the value. To allow better control, we would ignore the next values/changes for a few hundred milliseconds – the final value can be controlled by the user via the sensitivity setting in App settings.

During the development, we faced one major issue – none of us owned a Mac computer, virtual MacOS doesn’t work with USB devices and the simulator doesn’t support control of the accelerometer. To be able to do at least basic debugging, we also implemented MovementControlMock which generates random numbers and based on them moves the item left/right/down. Both of them follow for comparison.

import Foundation
import CoreMotion

class MovementControl : RefreshGameProtocol {
    private static let threshold = 0.2
    
    private let motionManager = CMMotionManager()
    private let conductor: GameConductor
    private var initialized = false
    private let sensitivity: Double
    private var lastMovement: Date = Date()
    
    init(conductor: GameConductor, sensitivity: Double) {
        self.conductor = conductor
        self.sensitivity = sensitivity
        if self.motionManager.isAccelerometerAvailable {
            self.motionManager.startAccelerometerUpdates()
        }
    }
    
    public func pause() {
        self.motionManager.stopAccelerometerUpdates()
    }
    
    public func resume() {
        self.motionManager.startAccelerometerUpdates()
    }
    
    func refreshScene() {
        self.initialized = true
    }
    
    func refreshGame() {
        if self.shouldUpdate() && self.initialized, let data = self.motionManager.accelerometerData {
            self.lastMovement = Date()
            
            if data.acceleration.x < -MovementControl.threshold {
                self.conductor.moveLeft()
            } else if data.acceleration.x > MovementControl.threshold {
                self.conductor.moveRight()
            }
        }
    }
    
    private func shouldUpdate() -> Bool {
        let interval = Calendar.current.dateComponents([.nanosecond], from: self.lastMovement, to: Date()).nanosecond! / 1000000
        let target = (2.5 - self.sensitivity * 2) * 100
        
        return Double(interval) > target
    }
}
import Foundation

class MovementControlMock : RefreshGameProtocol {
    private let conductor: GameConductor
    
    init(conductor: GameConductor) {
        self.conductor = conductor
    }
    
    func refreshScene() {
        // empty
    }
    
    func refreshGame() {
            let movement = Int(arc4random_uniform(3)) - 1
            if movement < 0 {
                self.conductor.moveLeft()
            } else if movement > 0 {
                self.conductor.moveRight()
            }
    }
}

 

 

Results

Basic design evaluation

We drafted first prototype as a sketch, prototype scheme: 

with 6 screens and rotation during game play was handled by a button.
Based on our potential user evaluation  we changed the prototype to this scheme:

with 5 screens. We merged the menu and welcome screen into one screen for easier and faster navigation. And also on potential users feedback we got rid of the rotate button and the whole gameplay are is touch sensitive for brick rotation.

Our main contribution to this classic Tetris game is the control by motion game play. We solved this using the CoreMotion Swift library. We get the raw data from accelerometer through CMMotionManager().

Game play

 Scoring in our version has a simple scheme, player gets 10 points for every brick that still has room to appear on the game board and starts to fall down. When player manages to make a full line, then the player receives 100 extra points. Game ends with the classic rule of filling the game board.  After the game ends, players result is compared with the highest ten scores so far and if it fits between the top ten scores, it gets to the high score table. 

We also supported the game play experience with music and FX sounds in the game. We downloaded royalty free sounds for this feature.

Design

We used simple graphic to keep the game simple and clean. Example of the main screen and game screen. 

                                      

 

App workflow

Class diagram

 

General

We managed to meet our goals set at the beginning. Our game is fully functional, stable and contains all wanted features. Based on users feedback we set the game speed to the level they suggested as the best regarding the movement control.

 

Video

In the video is shown the navigation between screens, content of all screens and basic gameplay with all features such as sounds, completing a line, pausing the game and game over dialog.

Link to video here

Source Code

http://bitbucket.org/iosprogrammingsdu/tetris20

Discussion

During development we managed to meet all our goals which we wanted to achieve. We developed a Tetris game with movement controlled by movement of the device. We also implemented other support features such as how to play screen, high score table, audio support, user controlled settings for music, fx sounds and sensitivity. We used CoreMotion framework,  CoreData, AVFoundation framework and SpriteKit framework. So with support of these frameworks we met all the criteria set by first evaluation with potential users.

Our game app is fully functional for users with all it’s features. For future there can be an improvement in the graphics of our game and there could also more game modes implemented in the future. There is also an idea of shared high score table for all the users so they could compete against each other. Last feature that would be nice to add in the future is a possibility to quick drop a brick when falling down.

References

[1] Cs.wikipedia.org. (2018). Tetris. [online] Available at: https://cs.wikipedia.org/wiki/Tetris [Accessed 29 Nov. 2018].

[2] Developer.apple.com. (2018). Apple Developer Documentation. [online] Available at: https://developer.apple.com/documentation [Accessed 29 Nov. 2018].

 

 

Leave a Reply