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
ornone
. - A
Player
object that represents a player in the game. APlayer
object will only define anid
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.
Hey, great share…
I really liked this post as it is quite informative and useful. Thanks for sharing this post. Keep sharing more…
Cheers!!