Codementor Events

Building a Real Time iOS multiplayer game with Swift and WebSockets (2)

Published Jul 03, 2018Last updated Dec 29, 2018
Building a Real Time iOS multiplayer game with Swift and WebSockets (2)

Please note: this is part 2 of a 4-part tutorial. For part 1, look here.


Setup the shared library project

The shared library project will define three objects:

  • A Tile type that contains the three valid states a tile can have: X, O or none.
  • A Player object that represents a player in the game. A Player object will only define an id field and comparison methods.
  • A Message object that is used for communication between the server and client regarding game state.

The Message, Player and Tile objects all conform to the Codable protocol, making it easy to encode to and decode from JSON.

The Tile Type

Code for the Tile type is really simple. We also define the strings to use when serializing to JSON or when decoding the JSON back into a Tile type.

import Foundation

public enum Tile: String, Codable {
    case none = "-"
    case x = "X"
    case o = "O"
}

The Player Class

The Player class is slightly more complicated. It contains 2 initializers:

  • An initializer to generate a new player with a random id.
  • An initializer to generate a player from JSON data.

For generating a random identifier we use a UUID.

We want to be able to use a Player object as key in a dictionary for our server project. To support this, we implement the Hashable protocol.

Additionally we will assume a Player to be equal to another Player provided the identifiers are the same, so the comparison function is overridden as well. Two Player objects are assumed to be equal if the identifiers are the same.

The resulting Player class will then look as such:

import Foundation

public enum PlayerError: Error {
    case creationFailed
}

public class Player: Hashable, Codable {
    public let id: String
    
    public init() {
        self.id = NSUUID().uuidString
    }
        
    public init(json: [String: Any]) throws {
        guard let id = json["id"] as? String else {
            throw PlayerError.creationFailed
        }
        
        self.id = id
    }
    
    // MARK: - Hashable
    
    public var hashValue: Int {
        return self.id.hashValue
    }
    
    public static func == (lhs: Player, rhs: Player) -> Bool {
        return lhs.id == rhs.id
    }
}

The Message Class

Finally we implement the Message class.

We define the following communcation messages: join, turn, finish, and stop. A message might need additional data, for example a player object or the board state. In order to always create proper messages, we add a few factory methods:

  • Whenever the game is stopped (e.g. a player left the game) we don't need to provide a board state or a player. The game will be aborted on the client.
  • Whevener the game is finished we both need to know the current board state on the client and the winning player or none if the game is a draw. This way we can show an appropriate status message on the client.
  • Whenever it's a player's turn to play, we need to provide both the current board state as well as the active player. The client can then allow the active player to play and prevent the other player from making a move.
  • Whenever a player is joining the server, we need to provide the player, so the server can add this player to it's own internal list of players.

The resulting Message class looks as follows:

import Foundation

public enum MessageType: String, Codable {
    case join = "join"
    case turn = "turn"
    case finish = "finish"
    case stop = "stop"
}

public class Message: Codable {
    public let type: MessageType
    public let board: [Tile]?
    public let player: Player?
    
    private init(type: MessageType, board: [Tile]?, player: Player? = nil) {
        self.type = type
        self.board = board
        self.player = player
    }
    
    public static func join(player: Player) -> Message {
        return Message(type: .join, board: nil, player: player)
    }
    
    public static func stop() -> Message {
        return Message(type: .stop, board: nil)
    }
    
    public static func turn(board: [Tile], player: Player) -> Message {
        return Message(type: .turn, board: board, player: player)
    }
    
    public static func finish(board: [Tile], winningPlayer: Player?) -> Message {
        return Message(type: .finish, board: board, player: winningPlayer)
    }
}

Continue to part 3 of the tutorial.


Discover and read more posts from Wolfgang Schreurs
get started
post comments1Reply
Hamish Lawson
6 years ago

Hey, great share…

I really liked this post as it is quite informative and useful. Thanks for sharing this post. Keep sharing more…

Cheers!!