parent
70d4fcd33e
commit
8770d1f423
@ -0,0 +1,53 @@
|
||||
import Model
|
||||
|
||||
extension Board: Codable {
|
||||
enum CodingKeys: CodingKey {
|
||||
case width, height, content
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let width = try container.decode(Int.self, forKey: .width)
|
||||
let height = try container.decode(Int.self, forKey: .height)
|
||||
let pieces = try container.decode([Piece?].self, forKey: .content)
|
||||
|
||||
if pieces.count != width * height {
|
||||
throw DeError.InvalidData
|
||||
}
|
||||
|
||||
guard var zelf = Self.init(columns: width, rows: height) else {
|
||||
throw DeError.InitializationFailed
|
||||
}
|
||||
|
||||
zelf.coords.forEach { coord in
|
||||
zelf[coord] = pieces[coord.toIndex(width: width)]
|
||||
}
|
||||
|
||||
self = zelf
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(self.columns, forKey: .width)
|
||||
try container.encode(self.rows, forKey: .height)
|
||||
// Array is flattened because Board inner cell data structure is private and only accessible via a sequence
|
||||
try container.encode(Array(self.cells), forKey: .content)
|
||||
}
|
||||
}
|
||||
|
||||
extension Coords: Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
|
||||
self.init(try container.decode(Int.self), try container.decode(Int.self))
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.unkeyedContainer()
|
||||
|
||||
try container.encode(self.col)
|
||||
try container.encode(self.row)
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
internal struct CodableEnum<E: CaseIterable & CodingKey>: Codable {
|
||||
public let variant: E
|
||||
|
||||
init(of variant: E) {
|
||||
self.variant = variant
|
||||
}
|
||||
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let name = try container.decode(String.self)
|
||||
|
||||
guard let variant = E.allCases.first(where: { variant in
|
||||
variant.stringValue == name
|
||||
}) else {
|
||||
throw DeError.InvalidVariant
|
||||
}
|
||||
|
||||
self.variant = variant
|
||||
}
|
||||
|
||||
func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(self.variant.stringValue)
|
||||
}
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
import Model
|
||||
|
||||
/// This struct is a workaround as it seems codeable required initializer to be required, which is not possible from an extension
|
||||
public struct CodableGameWrapper: Codable {
|
||||
public let game: Game
|
||||
|
||||
public init(of game: Game) {
|
||||
self.game = game
|
||||
}
|
||||
|
||||
enum CodingKeys: CodingKey {
|
||||
case players, rules, board, state
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
guard let context = decoder.userInfo[.gameDecodingContext] as? CodableContext
|
||||
else { throw DeError.CodableContext }
|
||||
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
context.players = (try container.decode([CodablePlayerWrapper].self, forKey: .players)).map { $0.player }
|
||||
|
||||
context.board = try container.decode(Board.self, forKey: .board)
|
||||
|
||||
let rules = (try container.decode(CodableRulesWrapper.self, forKey: .rules)).rules
|
||||
|
||||
let state = try container.decode(GameState.self, forKey: .state)
|
||||
|
||||
guard let game = Game.init(players: context.players!, rules: rules, board: context.board!, state: state)
|
||||
else { throw DeError.InitializationFailed }
|
||||
|
||||
self.game = game
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
guard let context = encoder.userInfo[.gameDecodingContext] as? CodableContext
|
||||
else { throw DeError.CodableContext }
|
||||
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
context.players = self.game.players
|
||||
context.board = self.game.board
|
||||
|
||||
try container.encode(self.game.players.map { CodablePlayerWrapper(of: $0) }, forKey: .players)
|
||||
|
||||
try container.encode(self.game.board, forKey: .board)
|
||||
|
||||
try container.encode(CodableRulesWrapper(of: self.game.rules), forKey: .rules)
|
||||
|
||||
try container.encode(self.game.state, forKey: .state)
|
||||
}
|
||||
}
|
||||
|
||||
extension GameState: Codable {
|
||||
enum VariantCodingKeys: CaseIterable, CodingKey {
|
||||
case Playing, Win, Draw, Invalid
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
|
||||
self = switch (try container.decode(CodableEnum<VariantCodingKeys>.self, forKey: .type)).variant {
|
||||
case .Playing:
|
||||
.Playing(turn: (try CodablePlayerWrapper(from: decoder)).player)
|
||||
case .Win:
|
||||
try Self.decodeWin(from: decoder)
|
||||
case .Draw:
|
||||
.Draw
|
||||
case .Invalid:
|
||||
.Invalid
|
||||
}
|
||||
}
|
||||
|
||||
private enum ExtraCodingKeys: CodingKey {
|
||||
case player, winner, cells
|
||||
}
|
||||
|
||||
private static func decodeWin(from decoder: any Decoder) throws -> Self {
|
||||
guard let context = decoder.userInfo[.gameDecodingContext] as? CodableContext
|
||||
else { throw DeError.InvalidVariant }
|
||||
|
||||
let container = try decoder.container(keyedBy: ExtraCodingKeys.self)
|
||||
|
||||
let winner = (try container.decode(CodablePlayerReferenceWrapper.self, forKey: .winner)).player
|
||||
|
||||
let cells = try container.decode([Coords].self, forKey: .cells)
|
||||
|
||||
return .Win(winner: winner, board: context.board!, cells: cells)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
var container2 = encoder.container(keyedBy: ExtraCodingKeys.self)
|
||||
|
||||
switch self {
|
||||
case .Playing(let turn):
|
||||
try container.encode(CodableEnum(of: VariantCodingKeys.Playing), forKey: .type)
|
||||
try container2.encode(CodablePlayerReferenceWrapper(of: turn), forKey: .player)
|
||||
case .Win(let winner, _, let cells):
|
||||
try container.encode(CodableEnum(of: VariantCodingKeys.Win), forKey: .type)
|
||||
try container2.encode(CodablePlayerReferenceWrapper(of: winner), forKey: .winner)
|
||||
try container2.encode(cells, forKey: .cells)
|
||||
case .Draw:
|
||||
try container.encode(CodableEnum(of: VariantCodingKeys.Draw), forKey: .type)
|
||||
case .Invalid:
|
||||
try container.encode(CodableEnum(of: VariantCodingKeys.Invalid), forKey: .type)
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +1,27 @@
|
||||
import Foundation
|
||||
import Model
|
||||
|
||||
public enum DeError: Error {
|
||||
case CodableContext
|
||||
case InvalidVariant
|
||||
case InvalidData
|
||||
case InitializationFailed
|
||||
}
|
||||
|
||||
extension CodingUserInfoKey {
|
||||
public static let gameDecodingContext = CodingUserInfoKey(rawValue: "gameDecodingContext")!
|
||||
}
|
||||
|
||||
public class CodableContext {
|
||||
internal var callbackFactory: ((_ name: String, _ type: PieceType) -> MoveCallback)?
|
||||
internal var players: [Player]? = nil
|
||||
internal var board: Board? = nil
|
||||
|
||||
public init(creatingCallbacks callbackFactory: ((_ name: String, _ type: PieceType) -> MoveCallback)? = nil) {
|
||||
self.callbackFactory = callbackFactory
|
||||
}
|
||||
}
|
||||
|
||||
enum EnumCommonCodingKeys: CodingKey {
|
||||
case type
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
import Model
|
||||
|
||||
extension Piece: Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let player = (try CodablePlayerReferenceWrapper.init(from: decoder)).player
|
||||
self = Self.init(owner: player)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
try CodablePlayerReferenceWrapper(of: self.owner).encode(to: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension PieceType: CodingKey { }
|
@ -0,0 +1,131 @@
|
||||
import Model
|
||||
|
||||
public struct CodablePlayerWrapper: Codable {
|
||||
public let player: Player
|
||||
|
||||
public init(of player: Player) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
|
||||
self.player = try (try container.decode(PlayerType.self, forKey: .type)).decodePlayer(from: decoder)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
let type = self.player.type
|
||||
try container.encode(type, forKey: .type)
|
||||
switch type {
|
||||
case .Human:
|
||||
try CodableHumanPlayerWrapper(of: self.player as! HumanPlayer).encode(to: encoder)
|
||||
case .AIRandom:
|
||||
try CodableRandomAIPlayerWrapper(of: self.player as! RandomPlayer).encode(to: encoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PlayerType: CodingKey, Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
self = (try CodableEnum<Self>(from: decoder)).variant
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
try CodableEnum(of: self).encode(to: encoder)
|
||||
}
|
||||
|
||||
public func decodePlayer(from decoder: any Decoder) throws -> Player {
|
||||
switch self {
|
||||
case .Human: (try CodableHumanPlayerWrapper(from: decoder)).player
|
||||
case .AIRandom: (try CodableRandomAIPlayerWrapper(from: decoder)).player
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum CommonPlayerCodingKeys: CodingKey {
|
||||
case name, pieces
|
||||
}
|
||||
|
||||
private struct CodableHumanPlayerWrapper: Codable {
|
||||
let player: HumanPlayer
|
||||
|
||||
init(of player: HumanPlayer) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
init(from decoder: any Decoder) throws {
|
||||
guard let context = decoder.userInfo[.gameDecodingContext] as? CodableContext
|
||||
else { throw DeError.CodableContext }
|
||||
|
||||
let container = try decoder.container(keyedBy: CommonPlayerCodingKeys.self)
|
||||
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
let pieces = try container.decode(CodableEnum<PieceType>.self, forKey: .pieces)
|
||||
|
||||
self.init(of: HumanPlayer(name: name, piece_type: pieces.variant, callback: context.callbackFactory!(name, pieces.variant)))
|
||||
}
|
||||
|
||||
func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CommonPlayerCodingKeys.self)
|
||||
|
||||
try container.encode(self.player.name, forKey: .name)
|
||||
try container.encode(CodableEnum(of: self.player.piece_type), forKey: .pieces)
|
||||
}
|
||||
}
|
||||
|
||||
private struct CodableRandomAIPlayerWrapper: Codable {
|
||||
let player: RandomPlayer
|
||||
|
||||
init(of player: RandomPlayer) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CommonPlayerCodingKeys.self)
|
||||
|
||||
let name = try container.decode(String.self, forKey: .name)
|
||||
let pieces = try container.decode(CodableEnum<PieceType>.self, forKey: .pieces)
|
||||
|
||||
self.init(of: RandomPlayer(name: name, piece_type: pieces.variant))
|
||||
}
|
||||
|
||||
func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CommonPlayerCodingKeys.self)
|
||||
|
||||
try container.encode(self.player.name, forKey: .name)
|
||||
try container.encode(CodableEnum(of: self.player.piece_type), forKey: .pieces)
|
||||
}
|
||||
}
|
||||
|
||||
public struct CodablePlayerReferenceWrapper: Codable {
|
||||
public let player: Player
|
||||
|
||||
public init(of player: Player) {
|
||||
self.player = player
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
guard
|
||||
let context = decoder.userInfo[.gameDecodingContext] as? CodableContext,
|
||||
let players = context.players
|
||||
else { throw DeError.CodableContext }
|
||||
|
||||
let container = try decoder.singleValueContainer()
|
||||
|
||||
let id = try container.decode(Int.self)
|
||||
guard id < players.count else { throw DeError.InvalidData }
|
||||
|
||||
self.player = players[id]
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
guard
|
||||
let context = encoder.userInfo[.gameDecodingContext] as? CodableContext,
|
||||
let players = context.players
|
||||
else { throw DeError.CodableContext }
|
||||
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(players.firstIndex(where: { self.player === $0 })!)
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
import Model
|
||||
|
||||
public struct CodableRulesWrapper: Codable {
|
||||
public let rules: Rules
|
||||
|
||||
public init(of rules: Rules) {
|
||||
self.rules = rules
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
|
||||
self.rules = try (try container.decode(RulesTypes.self, forKey: .type)).decodeRules(from: decoder)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: EnumCommonCodingKeys.self)
|
||||
let type = self.rules.type
|
||||
try container.encode(type, forKey: .type)
|
||||
try (self.rules as! Encodable).encode(to: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension RulesTypes: CodingKey, Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
self = (try CodableEnum<Self>(from: decoder)).variant
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
try CodableEnum(of: self).encode(to: encoder)
|
||||
}
|
||||
|
||||
public func decodeRules(from decoder: any Decoder) throws -> Rules {
|
||||
switch self {
|
||||
case .FourInARow: try FourInARowRules(from: decoder)
|
||||
case .TicTacToe: try TicTacToeRules(from: decoder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum CommonRulesCodingKeys: CodingKey {
|
||||
case minAligned
|
||||
}
|
||||
|
||||
extension FourInARowRules: Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
guard let context = decoder.userInfo[.gameDecodingContext] as? CodableContext else {
|
||||
throw DeError.CodableContext
|
||||
}
|
||||
|
||||
let container = try decoder.container(keyedBy: CommonRulesCodingKeys.self)
|
||||
|
||||
guard let zelf = Self.init(
|
||||
columns: context.board!.columns,
|
||||
rows: context.board!.rows,
|
||||
minAligned: try container.decode(Int.self, forKey: .minAligned),
|
||||
players: context.players!
|
||||
) else {
|
||||
throw DeError.InitializationFailed
|
||||
}
|
||||
|
||||
self = zelf
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CommonRulesCodingKeys.self)
|
||||
try container.encode(self.minAligned, forKey: .minAligned)
|
||||
}
|
||||
}
|
||||
|
||||
extension TicTacToeRules: Codable {
|
||||
public init(from decoder: any Decoder) throws {
|
||||
guard let context = decoder.userInfo[.gameDecodingContext] as? CodableContext else {
|
||||
throw DeError.CodableContext
|
||||
}
|
||||
|
||||
let container = try decoder.container(keyedBy: CommonRulesCodingKeys.self)
|
||||
|
||||
guard let zelf = Self.init(
|
||||
columns: context.board!.columns,
|
||||
rows: context.board!.rows,
|
||||
minAligned: try container.decode(Int.self, forKey: .minAligned),
|
||||
players: context.players!
|
||||
) else {
|
||||
throw DeError.InitializationFailed
|
||||
}
|
||||
|
||||
self = zelf
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CommonRulesCodingKeys.self)
|
||||
try container.encode(self.minAligned, forKey: .minAligned)
|
||||
}
|
||||
}
|
Loading…
Reference in new issue