├── .gitignore ├── LICENSE.md ├── README.md ├── Shared ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── board3.imageset │ │ ├── Contents.json │ │ ├── board3.png │ │ └── board3@2x.png ├── Model │ ├── TTTBoard.swift │ ├── TTTModel.swift │ ├── TTTMove.swift │ └── TTTPlayer.swift ├── Multiplayer │ ├── GameCenterController.swift │ └── MulitpeerController.swift ├── Nodes │ ├── ButtonNode.swift │ ├── GameButton.swift │ ├── GlyphNode.swift │ ├── MenuButton.swift │ └── PositionNode.swift ├── ScenePresentationDelegate.swift ├── Scenes │ ├── GameScene+Input.swift │ ├── GameScene+Private.swift │ ├── GameScene.swift │ └── GameSelectionScene.swift ├── States │ ├── CheckBoardState.swift │ ├── GameOverState.swift │ ├── InPlayStateMachine.swift │ ├── InPlayStateType.swift │ ├── PlayerState.swift │ └── SelectNextPlayerState.swift └── Style │ ├── HexColors.swift │ ├── SharedTypes.swift │ └── Style.swift ├── TicTacToe-iOS ├── AppDelegate.swift ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── GameViewController.swift ├── Info.plist ├── MatchesDataSource.swift ├── MatchesViewController.swift ├── MenuTableViewCell.swift ├── MenuTableViewCell.xib ├── MenuViewController.swift └── RoundCellView.swift ├── TicTacToe-macOS ├── AppDelegate.swift ├── Base.lproj │ └── MainMenu.xib └── Info.plist ├── TicTacToe-tvOS ├── AppDelegate.swift ├── Base.lproj │ └── Main.storyboard ├── GameViewController.swift └── Info.plist └── TicTacToe.xcodeproj └── project.pbxproj /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | *.xcodeproj/xcuserdata/* 4 | *.xcodeproj/*.xcworkspace/* 5 | 6 | *.xcworkspace/xcuserdata/* 7 | *.xcworkspace 8 | 9 | Carthage/* 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2016-2019 Andrew Shepard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TicTacToe 2 | 3 | ![Swift 4.0](https://img.shields.io/badge/swift-4.0-orange.svg) 4 | ![Platform](https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20tvOS-blue.svg) 5 | [![license MIT](https://img.shields.io/badge/license-MIT-lightgray.svg)](http://opensource.org/licenses/MIT) 6 | 7 | Tic-tac-toe for Apple platforms. Written in Swift with SpriteKit and GameplayKit. 8 | 9 | ## Requirements 10 | 11 | * Xcode 10.2 12 | * Swift 5 13 | 14 | ## Setup 15 | 16 | Clone the repo and open the project in Xcode. 17 | 18 | ## Design 19 | 20 | The game is designed to run on iOS, tvOS, and macOS by using a shared codebase for the core logic and SpriteKit for the gameplay and mechanics. The platform specific pieces are written using UIKit and AppKit. 21 | 22 | The game logic centers are three distinct types. A `TTTPiece` is an `enum` with two possible states, X and O. A `TTTPosition` is either empty or contains an associated piece. A `TTTBoard` is composed of 9 position, all initially empty. As the game played, the empty positions on the board are filled with pieces until one player wins or the game ends in draw. 23 | 24 | Building on top of the core types, the game play model is defined using `GameplayKit`. This includes defining a player, what the game model looks like, and how the model changes using gameplay. The `GKGameModelPlayer`, `GKGameModel`, and `GKGameModelUpdate` protocols are all conformed to by various classes in order to model the game. 25 | 26 | There are two state machines there are built using GameplayKit. The scene state machine manages transitions between the menu and gameplay states. The gameplay state machine manages the moves made during the course of the game. This includes making moves for player X and O, checking for wins, and deciding whose move is next. 27 | 28 | ## Screenshot 29 | 30 | ![screenshot](http://i.imgur.com/g62uMtw.gif) 31 | -------------------------------------------------------------------------------- /Shared/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "mac", 5 | "size" : "16x16", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "mac", 10 | "size" : "16x16", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "mac", 15 | "size" : "32x32", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "mac", 20 | "size" : "32x32", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "mac", 25 | "size" : "128x128", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "mac", 30 | "size" : "128x128", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "mac", 35 | "size" : "256x256", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "mac", 40 | "size" : "256x256", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "mac", 45 | "size" : "512x512", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "mac", 50 | "size" : "512x512", 51 | "scale" : "2x" 52 | } 53 | ], 54 | "info" : { 55 | "version" : 1, 56 | "author" : "xcode" 57 | } 58 | } -------------------------------------------------------------------------------- /Shared/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Shared/Assets.xcassets/board3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "board3.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "board3@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Shared/Assets.xcassets/board3.imageset/board3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoascientist/TicTacToe/da41b31e76348b427de3882bdfa7d1126eea97b4/Shared/Assets.xcassets/board3.imageset/board3.png -------------------------------------------------------------------------------- /Shared/Assets.xcassets/board3.imageset/board3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cocoascientist/TicTacToe/da41b31e76348b427de3882bdfa7d1126eea97b4/Shared/Assets.xcassets/board3.imageset/board3@2x.png -------------------------------------------------------------------------------- /Shared/Model/TTTBoard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TTTBoard.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum TTTPiece { 12 | case x 13 | case o 14 | 15 | var glyph: String { 16 | switch self { 17 | case .x: 18 | return "X" 19 | case .o: 20 | return "O" 21 | } 22 | } 23 | 24 | var hexColor: String { 25 | switch self { 26 | case .x: 27 | return "#EF946C" 28 | case .o: 29 | return "#00FCDB" 30 | } 31 | } 32 | 33 | var opposite: TTTPiece { 34 | switch self { 35 | case .x: 36 | return .o 37 | case .o: 38 | return .x 39 | } 40 | } 41 | } 42 | 43 | enum TTTPosition { 44 | case empty 45 | case piece(TTTPiece) 46 | } 47 | 48 | func ==(lhs: TTTPosition, rhs: TTTPosition) -> Bool { 49 | switch (lhs, rhs) { 50 | case (.empty, .empty): 51 | return true 52 | case (let .piece(p1), let .piece(p2)): 53 | return p1 == p2 54 | default: return false 55 | } 56 | } 57 | 58 | typealias PositionMarker = (value: Int, position: TTTPosition) 59 | 60 | private func emptyBoardPositions() -> [PositionMarker] { 61 | // http://mathworld.wolfram.com/MagicSquare.html 62 | let magicSquares = [8, 1, 6, 3, 5, 7, 4, 9, 2] 63 | 64 | let positions = magicSquares.map({ (value) -> PositionMarker in 65 | return (value: value, position: .empty) 66 | }) 67 | 68 | return positions 69 | } 70 | 71 | struct TTTBoard { 72 | let rows = 3 73 | let columns = 3 74 | var positions: [PositionMarker] 75 | 76 | // http://mathworld.wolfram.com/MagicSquare.html 77 | fileprivate let magicSquares = [8, 1, 6, 3, 5, 7, 4, 9, 2] 78 | 79 | init(positions: [PositionMarker] = emptyBoardPositions()) { 80 | self.positions = positions 81 | } 82 | 83 | func afterMaking(_ move: TTTMove) -> TTTBoard { 84 | return afterMakingMove(with: move.piece, at: move.index) 85 | } 86 | 87 | func afterMakingMove(with piece: TTTPiece, at index: Int) -> TTTBoard { 88 | var positions = self.positions 89 | let placemarker = positions[index] 90 | positions[index] = (value: placemarker.value, position: .piece(piece)) 91 | 92 | return TTTBoard(positions: positions) 93 | } 94 | 95 | func hasEmptyPlaces() -> Bool { 96 | let empty = self.positions.filter { (value, position) -> Bool in 97 | return position == TTTPosition.empty 98 | } 99 | 100 | return empty.count > 0 101 | } 102 | 103 | func winningCombo() -> [Int]? { 104 | 105 | var winners: [Int]? = nil 106 | 107 | for combo in winningCombos { 108 | 109 | if isWinCombo(combo, forPiece: .o) || isWinCombo(combo, forPiece: .x) { 110 | winners = combo 111 | break 112 | } 113 | } 114 | 115 | return winners 116 | } 117 | 118 | func isWinCombo(_ combo: [Int], forPiece piece: TTTPiece) -> Bool { 119 | var accumulated: Int = 0 120 | 121 | for index in combo { 122 | let position = self.positions[index].position 123 | if case .piece(let current) = position, current == piece { 124 | accumulated += 1 125 | } 126 | } 127 | 128 | return (accumulated == 3) 129 | } 130 | 131 | fileprivate var winningCombos: [[Int]] { 132 | /* 133 | 0 | 1 | 2 134 | --------- 135 | 3 | 4 | 5 136 | --------- 137 | 6 | 7 | 8 138 | */ 139 | 140 | return [ 141 | [0,1,2],[3,4,5],[6,7,8], /* horizontals */ 142 | [0,3,6],[1,4,7],[2,5,8], /* veritcals */ 143 | [0,4,8],[2,4,6] /* diagonals */ 144 | ] 145 | } 146 | 147 | fileprivate var corners: [Int] { 148 | return [0,2,6,8] 149 | } 150 | 151 | fileprivate var middle: Int { 152 | return 4 153 | } 154 | 155 | func isWin(forPiece piece: TTTPiece) -> Bool { 156 | var didWin = false 157 | 158 | for combo in winningCombos { 159 | var accumulated = 0 160 | 161 | for index in combo { 162 | let position = self.positions[index].position 163 | if case .piece(let current) = position, current == piece { 164 | accumulated += self.positions[index].value 165 | } 166 | } 167 | 168 | didWin = (accumulated == 15) 169 | if didWin { break } 170 | } 171 | 172 | return didWin 173 | } 174 | 175 | func score(forPiece piece: TTTPiece) -> Int { 176 | var score = 0 177 | 178 | // checking for opponant winning combos 179 | for combo in winningCombos { 180 | var matches = 0 181 | for index in combo { 182 | let position = self.positions[index].position 183 | if case .piece(let current) = position, current == piece.opposite { 184 | matches += 1 185 | } 186 | } 187 | 188 | if matches == 3 { 189 | score -= 100 190 | } 191 | } 192 | 193 | if score < 0 { return score } 194 | 195 | // checking for opponant 2-in-a-row with empty place 196 | for combo in winningCombos { 197 | var matches = 0 198 | var empty = 0 199 | for index in combo { 200 | let position = self.positions[index].position 201 | if case .piece(let current) = position, current == piece.opposite { 202 | matches += 1 203 | } else if case .empty = position { 204 | empty += 1 205 | } 206 | } 207 | 208 | if matches == 2 && empty == 1 { 209 | score -= 90 210 | } 211 | } 212 | 213 | if score < 0 { return score } 214 | 215 | // checking winning combos 216 | for combo in winningCombos { 217 | var matches = 0 218 | var empty = 0 219 | 220 | for index in combo { 221 | let position = self.positions[index].position 222 | if case .piece(let current) = position, current == piece.opposite { 223 | matches += 1 224 | } else if case .empty = position { 225 | empty += 1 226 | } 227 | } 228 | 229 | if matches == 3 { 230 | score += 100 231 | } else if matches == 2 && empty == 1 { 232 | score += 50 233 | } 234 | } 235 | 236 | // check the corners 237 | for index in corners { 238 | let position = self.positions[index].position 239 | if case .piece(let current) = position, current == piece { 240 | score += 20 241 | } 242 | } 243 | 244 | // check middle piece 245 | let position = self.positions[middle].position 246 | if case .piece(let current) = position, current == piece { 247 | score += 30 248 | } 249 | 250 | return score 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /Shared/Model/TTTModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TTTModel.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | 11 | 12 | class TTTModel: NSObject { 13 | private(set) var players: [GKGameModelPlayer]? 14 | var activePlayer: GKGameModelPlayer? 15 | 16 | fileprivate(set) var board: TTTBoard 17 | 18 | init(players: [GKGameModelPlayer]?) { 19 | self.players = players 20 | self.board = TTTBoard() 21 | super.init() 22 | } 23 | 24 | func resetGameBoard() { 25 | self.board = TTTBoard() 26 | self.activePlayer = nil 27 | } 28 | } 29 | 30 | extension TTTModel: NSCopying { 31 | func copy(with zone: NSZone? = nil) -> Any { 32 | let model = TTTModel(players: players) 33 | model.setGameModel(self) 34 | return model 35 | } 36 | } 37 | 38 | extension TTTModel { 39 | fileprivate func nextPlayerAfter(_ player: GKGameModelPlayer?) -> GKGameModelPlayer? { 40 | guard let players = players as? [TTTPlayer] else { return nil } 41 | guard let player = player as? TTTPlayer else { return nil } 42 | 43 | assert(players.count == 2) 44 | 45 | let playerX = players.filter { $0.piece == TTTPiece.x }.first! 46 | let playerO = players.filter { $0.piece == TTTPiece.o }.first! 47 | 48 | return (player == playerX) ? playerO : playerX 49 | } 50 | } 51 | 52 | extension TTTModel: GKGameModel { 53 | func setGameModel(_ model: GKGameModel) { 54 | guard let model = model as? TTTModel else { return } 55 | 56 | self.board = model.board 57 | self.activePlayer = model.activePlayer 58 | } 59 | 60 | func gameModelUpdates(for player: GKGameModelPlayer) -> [GKGameModelUpdate]? { 61 | guard let player = player as? TTTPlayer else { return nil } 62 | 63 | let indexed = board.positions.enumerated().map { return (index: $0, marker: $1) } 64 | let empty = indexed.filter { (_, marker) -> Bool in 65 | return (marker.position == .empty) ? true : false 66 | } 67 | 68 | let moves = empty.map { return TTTMove(index: $0.index, piece: player.piece)} 69 | return (moves.count > 0) ? moves: nil 70 | } 71 | 72 | func apply(_ gameModelUpdate: GKGameModelUpdate) { 73 | guard let move = gameModelUpdate as? TTTMove else { return } 74 | 75 | self.board = board.afterMaking(move) 76 | self.activePlayer = nextPlayerAfter(activePlayer) 77 | } 78 | 79 | func score(for player: GKGameModelPlayer) -> Int { 80 | guard let player = player as? TTTPlayer else { return 0 } 81 | let piece = player.piece 82 | 83 | let score = board.score(forPiece: piece) 84 | let opponent = (board.score(forPiece: piece.opposite) - 20) * -1 85 | let adjusted = score + opponent 86 | 87 | return adjusted 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Shared/Model/TTTMove.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TTTMove.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | 11 | class TTTMove: NSObject, GKGameModelUpdate { 12 | 13 | var value: Int = 0 14 | 15 | fileprivate(set) var piece: TTTPiece 16 | fileprivate(set) var index: Int 17 | 18 | required init(index: Int, piece: TTTPiece) { 19 | self.index = index 20 | self.piece = piece 21 | } 22 | } 23 | 24 | extension TTTMove { 25 | override var description: String { 26 | return "index: \(index), value: \(value)" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Shared/Model/TTTPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TTTPlayer.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | 11 | @objc class TTTPlayer: NSObject, GKGameModelPlayer { 12 | 13 | fileprivate(set) var playerId: Int 14 | fileprivate(set) var piece: TTTPiece 15 | 16 | init(playerId: Int, piece: TTTPiece) { 17 | self.playerId = playerId 18 | self.piece = piece 19 | super.init() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Shared/Multiplayer/GameCenterController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameCenterController.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/11/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | // GCHelper.swift (v. 0.3.2) 10 | // 11 | // Copyright (c) 2016 Jack Cook 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in 21 | // all copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 | // THE SOFTWARE. 30 | 31 | import GameKit 32 | 33 | 34 | public protocol GameCenterControllerDelegate: class { 35 | 36 | /// Method called when a match has been initiated. 37 | func matchStarted() 38 | 39 | /// Method called when the device received data about the match from another device in the match. 40 | func match(_ match: GKMatch, didReceiveData: Data, fromPlayer: String) 41 | 42 | /// Method called when the match has ended. 43 | func matchEnded() 44 | } 45 | 46 | open class GameCenterController: NSObject { 47 | 48 | public static let shared = GameCenterController() 49 | 50 | /// The match object provided by GameKit. 51 | fileprivate(set) var match: GKMatch! 52 | 53 | fileprivate weak var delegate: GameCenterControllerDelegate? 54 | fileprivate var invite: GKInvite! 55 | fileprivate var invitedPlayer: GKPlayer! 56 | fileprivate var playersDict = [String: AnyObject]() 57 | fileprivate weak var presentingViewController: UIViewController! 58 | 59 | fileprivate var authenticated = false 60 | fileprivate var matchStarted = false 61 | 62 | override init() { 63 | super.init() 64 | 65 | let name = Notification.Name(rawValue: "GKPlayerAuthenticationDidChangeNotificationName") 66 | let selector = #selector(GameCenterController.authenticationChanged(sender:)) 67 | NotificationCenter.default.addObserver(self, selector: selector, name: name, object: nil) 68 | } 69 | 70 | /** 71 | Authenticates the user with their Game Center account if possible 72 | 73 | */ 74 | public func authenticateLocalUser() { 75 | print("Authenticating local user...") 76 | if GKLocalPlayer.local.isAuthenticated == false { 77 | GKLocalPlayer.local.authenticateHandler = { (view, error) in 78 | guard error == nil else { 79 | print("Authentication error: \(String(describing: error?.localizedDescription))") 80 | return 81 | } 82 | 83 | self.authenticated = true 84 | } 85 | } else { 86 | print("Already authenticated") 87 | } 88 | } 89 | 90 | /** 91 | Attempts to pair up the user with other users who are also looking for a match. 92 | 93 | :param: minPlayers The minimum number of players required to create a match. 94 | :param: maxPlayers The maximum number of players allowed to create a match. 95 | :param: viewController The view controller to present required GameKit view controllers from. 96 | :param: delegate The delegate receiving data from GCHelper. 97 | */ 98 | public func findMatchWithMinPlayers(minPlayers: Int, maxPlayers: Int, viewController: UIViewController, delegate: GameCenterControllerDelegate) { 99 | self.matchStarted = false 100 | self.match = nil 101 | self.presentingViewController = viewController 102 | self.delegate = delegate 103 | presentingViewController.dismiss(animated: false, completion: nil) 104 | 105 | let request = GKMatchRequest() 106 | request.minPlayers = minPlayers 107 | request.maxPlayers = maxPlayers 108 | 109 | let mmvc = GKTurnBasedMatchmakerViewController(matchRequest: request) 110 | // mmvc.turnBasedMatchmakerDelegate = self 111 | 112 | presentingViewController.present(mmvc, animated: true, completion: nil) 113 | } 114 | 115 | /** 116 | Presents the game center view controller provided by GameKit. 117 | 118 | :param: viewController The view controller to present GameKit's view controller from. 119 | :param: viewState The state in which to present the new view controller. 120 | */ 121 | public func showGameCenter(viewController: UIViewController, viewState: GKGameCenterViewControllerState) { 122 | presentingViewController = viewController 123 | 124 | let gcvc = GKGameCenterViewController() 125 | gcvc.viewState = viewState 126 | gcvc.gameCenterDelegate = self 127 | presentingViewController.present(gcvc, animated: true, completion: nil) 128 | } 129 | } 130 | 131 | extension GameCenterController { 132 | @objc internal func authenticationChanged(sender: NSNotification) { 133 | if GKLocalPlayer.local.isAuthenticated && !authenticated { 134 | print("Authentication changed: player authenticated") 135 | authenticated = true 136 | } else { 137 | print("Authentication changed: player not authenticated") 138 | authenticated = false 139 | } 140 | } 141 | 142 | fileprivate func lookupPlayers() { 143 | let playerIDs = match.players.map { $0.playerID } 144 | 145 | GKPlayer.loadPlayers(forIdentifiers: playerIDs) { (players, error) -> Void in 146 | guard error == nil else { 147 | print("Error retrieving player info: \(String(describing: error?.localizedDescription))") 148 | self.matchStarted = false 149 | self.delegate?.matchEnded() 150 | return 151 | } 152 | 153 | guard let players = players else { 154 | print("Error retrieving players; returned nil") 155 | return 156 | } 157 | 158 | for player in players { 159 | print("Found player: \(String(describing: player.alias))") 160 | self.playersDict[player.playerID] = player 161 | } 162 | 163 | self.matchStarted = true 164 | GKMatchmaker.shared().finishMatchmaking(for: self.match) 165 | self.delegate?.matchStarted() 166 | } 167 | } 168 | } 169 | 170 | extension GameCenterController: GKGameCenterControllerDelegate { 171 | public func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) { 172 | presentingViewController.dismiss(animated: true, completion: nil) 173 | } 174 | } 175 | 176 | extension GameCenterController: GKMatchmakerViewControllerDelegate { 177 | public func matchmakerViewControllerWasCancelled(_ viewController: GKMatchmakerViewController) { 178 | presentingViewController.dismiss(animated: true, completion: nil) 179 | } 180 | 181 | public func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFailWithError error: Error) { 182 | presentingViewController.dismiss(animated: true, completion: nil) 183 | print("Error finding match: \(error.localizedDescription)") 184 | } 185 | 186 | public func matchmakerViewController(_ viewController: GKMatchmakerViewController, didFind match: GKMatch) { 187 | presentingViewController.dismiss(animated: true, completion: nil) 188 | self.match = match 189 | 190 | match.delegate = self 191 | if !matchStarted && match.expectedPlayerCount == 0 { 192 | print("Ready to start match!") 193 | self.lookupPlayers() 194 | } 195 | } 196 | } 197 | 198 | extension GameCenterController: GKMatchDelegate { 199 | public func match(_ match: GKMatch, didReceive data: Data, fromRemotePlayer player: GKPlayer) { 200 | if self.match != match { return } 201 | let playerID = player.playerID 202 | 203 | delegate?.match(match, didReceiveData: data, fromPlayer: playerID) 204 | } 205 | 206 | public func match(_ match: GKMatch, player: GKPlayer, didChange state: GKPlayerConnectionState) { 207 | if self.match != match { return } 208 | 209 | switch state { 210 | case .connected where !matchStarted && match.expectedPlayerCount == 0: 211 | lookupPlayers() 212 | case .disconnected: 213 | matchStarted = false 214 | delegate?.matchEnded() 215 | self.match = nil 216 | default: 217 | break 218 | } 219 | } 220 | 221 | public func match(_ match: GKMatch, didFailWithError error: Error?) { 222 | if self.match != match { return } 223 | 224 | print("Match failed with error: \(String(describing: error?.localizedDescription))") 225 | self.matchStarted = false 226 | delegate?.matchEnded() 227 | } 228 | } 229 | 230 | extension GameCenterController: GKLocalPlayerListener { 231 | public func player(_ player: GKPlayer, didAccept invite: GKInvite) { 232 | let mmvc = GKMatchmakerViewController(invite: invite)! 233 | mmvc.matchmakerDelegate = self 234 | presentingViewController.present(mmvc, animated: true, completion: nil) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /Shared/Multiplayer/MulitpeerController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MulitpeerController.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/12/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MultipeerConnectivity 11 | 12 | class MulitpeerController: NSObject { 13 | fileprivate let timeStarted = NSDate() 14 | 15 | fileprivate let serviceType = "ajs-ttt-1" 16 | 17 | lazy var peer: MCPeerID = { 18 | let displayName = UUID().uuidString 19 | let peer = MCPeerID(displayName: displayName) 20 | return peer 21 | }() 22 | 23 | lazy var session: MCSession = { 24 | let session = MCSession(peer: self.peer) 25 | return session 26 | }() 27 | 28 | lazy var browser: MCNearbyServiceBrowser = { 29 | let browser = MCNearbyServiceBrowser(peer: self.peer, serviceType: self.serviceType) 30 | browser.delegate = self 31 | return browser 32 | }() 33 | 34 | lazy var advertiser: MCNearbyServiceAdvertiser = { 35 | let advertiser = MCNearbyServiceAdvertiser(peer: self.peer, discoveryInfo: nil, serviceType: self.serviceType) 36 | 37 | // advertiser.delegate = self 38 | return advertiser 39 | }() 40 | } 41 | 42 | extension MulitpeerController: MCNearbyServiceBrowserDelegate { 43 | func browser(_ browser: MCNearbyServiceBrowser, foundPeer peerID: MCPeerID, withDiscoveryInfo info: [String : String]?) { 44 | 45 | var runningTime = -timeStarted.timeIntervalSinceNow 46 | let data = NSData(bytes: &runningTime, length: MemoryLayout.size) 47 | let context = data as Data 48 | 49 | browser.invitePeer(peerID, to: session, withContext: context, timeout: 30) 50 | } 51 | 52 | func browser(_ browser: MCNearbyServiceBrowser, lostPeer peerID: MCPeerID) { 53 | print("lost peer: \(peerID)") 54 | } 55 | 56 | func browser(_ browser: MCNearbyServiceBrowser, didNotStartBrowsingForPeers error: Error) { 57 | print("error browsing for peers: \(error)") 58 | } 59 | } 60 | 61 | //extension MulitpeerController: MCNearbyServiceAdvertiserDelegate { 62 | // 63 | //// func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didReceiveInvitationFromPeer peerID: MCPeerID, withContext context: Data?, invitationHandler: (Bool, MCSession?) -> Void) { 64 | //// 65 | //// guard let context = context else { return } 66 | //// let data = context as NSData 67 | //// 68 | //// let runningTime = -timeStarted.timeIntervalSinceNow 69 | //// var peerRunningTime = TimeInterval() 70 | //// data.getBytes(&peerRunningTime, length: MemoryLayout.size) 71 | //// 72 | //// let isPeerOlder = (peerRunningTime > runningTime) 73 | //// 74 | //// if isPeerOlder { 75 | //// print("older") 76 | //// } else { 77 | //// print("younger") 78 | //// } 79 | //// } 80 | // 81 | // func advertiser(_ advertiser: MCNearbyServiceAdvertiser, didNotStartAdvertisingPeer error: Error) { 82 | // print("error starting advertiser: \(error)") 83 | // } 84 | //} 85 | -------------------------------------------------------------------------------- /Shared/Nodes/ButtonNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonNode.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | typealias ButtonAction = () -> Void 12 | 13 | class ButtonNode: SKNode { 14 | fileprivate let delay = 0.20 15 | 16 | fileprivate(set) var title: String 17 | fileprivate(set) var size: CGSize 18 | fileprivate(set) var action: ButtonAction 19 | 20 | lazy var focusRing: SKSpriteNode = { 21 | let node = SKSpriteNode(imageNamed: "focusRing") 22 | return node 23 | }() 24 | 25 | lazy var background: SKShapeNode = { 26 | let size = self.size 27 | let height = size.height / 2 28 | let radius = (height.truncatingRemainder(dividingBy: 2) == 0) ? height : height - 1 29 | 30 | let node = SKShapeNode(rectOf: size, cornerRadius: radius) 31 | 32 | node.fillColor = Style.Colors.button 33 | node.strokeColor = Style.Colors.button 34 | 35 | return node 36 | }() 37 | 38 | init(title: String, size: CGSize, action: @escaping ButtonAction) { 39 | 40 | self.title = title 41 | self.action = action 42 | self.size = size 43 | super.init() 44 | 45 | addChild(self.background) 46 | 47 | self.isUserInteractionEnabled = true 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) not implemented") 52 | } 53 | 54 | var isHighlighted = false { 55 | // Animate to a pressed / unpressed state when the highlight state changes. 56 | didSet { 57 | // Guard against repeating the same action. 58 | guard oldValue != isHighlighted else { return } 59 | 60 | // Remove any existing animations that may be in progress. 61 | removeAllActions() 62 | 63 | // Create a scale action to make the button look like it is slightly depressed. 64 | let newScale: CGFloat = isHighlighted ? 0.99 : 1.01 65 | let scaleAction = SKAction.scale(by: newScale, duration: 0.15) 66 | 67 | // Create a color blend action to darken the button slightly when it is depressed. 68 | let newColorBlendFactor: CGFloat = isHighlighted ? 1.0 : 0.0 69 | let colorBlendAction = SKAction.colorize(withColorBlendFactor: newColorBlendFactor, duration: 0.15) 70 | 71 | // Run the two actions at the same time. 72 | run(SKAction.group([scaleAction, colorBlendAction])) 73 | } 74 | } 75 | 76 | /** 77 | Input focus shows which button will be triggered when the action 78 | button is pressed on indirect input devices such as game controllers 79 | and keyboards. 80 | */ 81 | var isFocused = false { 82 | didSet { 83 | if isFocused { 84 | run(SKAction.scale(to: 1.08, duration: 0.20)) 85 | 86 | focusRing.alpha = 0.0 87 | focusRing.isHidden = false 88 | focusRing.run(SKAction.fadeIn(withDuration: 0.2)) 89 | } 90 | else { 91 | run(SKAction.scale(to: 1.0, duration: 0.20)) 92 | 93 | focusRing.isHidden = true 94 | } 95 | } 96 | } 97 | } 98 | 99 | extension ButtonNode { 100 | #if os(iOS) || os(tvOS) 101 | 102 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 103 | super.touchesBegan(touches, with: event) 104 | 105 | isHighlighted = true 106 | } 107 | 108 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 109 | super.touchesEnded(touches, with: event) 110 | 111 | isHighlighted = false 112 | if containsTouches(touches) { 113 | let delay = DispatchTime.now() + Double(Int64(self.delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 114 | DispatchQueue.main.asyncAfter(deadline: delay) { [unowned self] in 115 | self.action() 116 | } 117 | } 118 | } 119 | 120 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 121 | super.touchesCancelled(touches, with: event) 122 | 123 | isHighlighted = false 124 | } 125 | 126 | fileprivate func containsTouches(_ touches: Set) -> Bool { 127 | guard let scene = scene else { fatalError("Button must be used within a scene.") } 128 | 129 | return touches.contains { touch in 130 | let touchPoint = touch.location(in: scene) 131 | let touchedNode = scene.atPoint(touchPoint) 132 | return touchedNode === self || touchedNode.inParentHierarchy(self) 133 | } 134 | } 135 | 136 | #elseif os(OSX) 137 | 138 | override func mouseDown(with event: NSEvent) { 139 | isHighlighted = true 140 | } 141 | 142 | override func mouseUp(with event: NSEvent) { 143 | isHighlighted = false 144 | if containsLocationForEvent(event: event) { 145 | let delay = DispatchTime.now() + Double(Int64(self.delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 146 | DispatchQueue.main.asyncAfter(deadline: delay, execute: { [unowned self] in 147 | self.action() 148 | }) 149 | } 150 | } 151 | 152 | private func containsLocationForEvent(event: NSEvent) -> Bool { 153 | guard let scene = scene else { fatalError("Button must be used within a scene.") } 154 | 155 | let location = event.location(in: scene) 156 | let clickedNode = scene.atPoint(location) 157 | return clickedNode === self || clickedNode.inParentHierarchy(self) 158 | } 159 | 160 | #endif 161 | } 162 | -------------------------------------------------------------------------------- /Shared/Nodes/GameButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameButton.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class GameButton: ButtonNode { 12 | 13 | lazy var label: SKLabelNode = { 14 | let node = SKLabelNode(text: self.title) 15 | 16 | node.verticalAlignmentMode = .center 17 | node.fontName = "MarkerFelt-Wide" 18 | node.zPosition = 5 19 | node.fontSize = 20 20 | node.fontColor = Style.Colors.text 21 | 22 | return node 23 | }() 24 | 25 | override init(title: String, size: CGSize, action: @escaping ButtonAction) { 26 | super.init(title: title, size: size, action: action) 27 | 28 | addChild(self.label) 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) not implemented") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Shared/Nodes/GlyphNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GlyphNode.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/24/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | import CoreText 11 | 12 | class GlyphNode: SKShapeNode { 13 | let glyph: String 14 | 15 | lazy var font: Font = { 16 | guard let font = Font(name: "MarkerFelt-Wide", size: 96.0) else { 17 | fatalError("missing font") 18 | } 19 | 20 | return font 21 | }() 22 | 23 | init(glyph: String) { 24 | self.glyph = glyph 25 | 26 | super.init() 27 | 28 | configure() 29 | } 30 | 31 | required init?(coder aDecoder: NSCoder) { 32 | fatalError("init(coder:) has not been implemented") 33 | } 34 | 35 | lazy var glyphPath: CGPath? = { 36 | guard self.glyph.count > 0 else { return nil } 37 | 38 | var unichars = [UniChar](self.glyph.utf16) 39 | var glyphs = [CGGlyph](repeating: 0, count: unichars.count) 40 | let foundGlyphs = CTFontGetGlyphsForCharacters(self.font, &unichars, &glyphs, unichars.count) 41 | 42 | guard foundGlyphs else { return nil } 43 | 44 | let path = CTFontCreatePathForGlyph(self.font, glyphs[0], nil)! 45 | return path 46 | }() 47 | } 48 | 49 | extension GlyphNode { 50 | fileprivate func configure() { 51 | self.path = glyphPath 52 | 53 | guard let path = glyphPath else { return } 54 | let body = SKPhysicsBody(polygonFrom: path) 55 | body.affectedByGravity = false 56 | 57 | self.physicsBody = body 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Shared/Nodes/MenuButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuButton.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class MenuButton: ButtonNode { 12 | 13 | lazy var label: SKLabelNode = { 14 | let node = SKLabelNode(text: self.title) 15 | 16 | node.verticalAlignmentMode = .center 17 | node.fontName = "MarkerFelt-Wide" 18 | node.zPosition = 5 19 | node.fontColor = Style.Colors.text 20 | 21 | return node 22 | }() 23 | 24 | override init(title: String, size: CGSize, action: @escaping ButtonAction) { 25 | super.init(title: title, size: size, action: action) 26 | 27 | addChild(self.label) 28 | } 29 | 30 | required init?(coder aDecoder: NSCoder) { 31 | fatalError("init(coder:) not implemented") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Shared/Nodes/PositionNode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PositionNode.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | private class DebugLabel: SKLabelNode { 12 | override init() { 13 | super.init() 14 | 15 | self.fontSize = CGFloat(Style.Font.debug.size) 16 | self.fontName = Style.Font.debug.name 17 | self.zPosition = 100 18 | self.isUserInteractionEnabled = false 19 | self.verticalAlignmentMode = .center 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | } 26 | 27 | class PositionNode: SKSpriteNode { 28 | 29 | let row: Int 30 | let column: Int 31 | 32 | private lazy var debugLabel: DebugLabel = { 33 | let node = DebugLabel() 34 | 35 | node.text = "\(self.row), \(self.column)" 36 | node.position = CGPoint(x: self.frame.midX, y: self.frame.midY) 37 | 38 | return node 39 | }() 40 | 41 | init(row: Int, column: Int, size: CGSize) { 42 | self.row = row 43 | self.column = column 44 | 45 | super.init(texture: nil, color: Color.clear, size: size) 46 | 47 | // self.addChild(debugLabel) 48 | } 49 | 50 | required init?(coder aDecoder: NSCoder) { 51 | fatalError("init(coder:) has not been implemented") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Shared/ScenePresentationDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScenePresentationDelegate.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/13/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | 12 | protocol ScenePresentationDelegate { 13 | func shouldDismissScene(_ scene: SKScene) -> Void 14 | } 15 | -------------------------------------------------------------------------------- /Shared/Scenes/GameScene+Input.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene+Input.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | #if os(iOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import AppKit 13 | #endif 14 | 15 | extension GameScene { 16 | #if os(iOS) 17 | 18 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 19 | super.touchesEnded(touches, with: event) 20 | 21 | if containsTouches(touches) { 22 | guard let touch = touches.first else { return } 23 | guard let scene = scene else { return } 24 | 25 | let touchPoint = touch.location(in: scene) 26 | let node = scene.atPoint(touchPoint) 27 | 28 | guard node is PositionNode else { return } 29 | 30 | 31 | 32 | self.handleUIEventOn(node) 33 | } 34 | } 35 | 36 | fileprivate func containsTouches(_ touches: Set) -> Bool { 37 | guard let scene = scene else { fatalError("Button must be used within a scene.") } 38 | 39 | return touches.contains { touch in 40 | let touchPoint = touch.location(in: scene) 41 | let touchedNode = scene.atPoint(touchPoint) 42 | return touchedNode === self || touchedNode.inParentHierarchy(self) 43 | } 44 | } 45 | 46 | #elseif os(OSX) 47 | 48 | override func mouseUp(with event: NSEvent) { 49 | if containsLocationForEvent(event: event) { 50 | guard let scene = scene else { return } 51 | let location = event.location(in: scene) 52 | let node = scene.atPoint(location) 53 | 54 | guard node is PositionNode else { return } 55 | 56 | self.handleUIEventOn(node) 57 | } 58 | } 59 | 60 | private func containsLocationForEvent(event: NSEvent) -> Bool { 61 | guard let scene = scene else { fatalError("Button must be used within a scene.") } 62 | 63 | let location = event.location(in: scene) 64 | let clickedNode = scene.atPoint(location) 65 | return clickedNode === self || clickedNode.inParentHierarchy(self) 66 | } 67 | 68 | #endif 69 | } 70 | -------------------------------------------------------------------------------- /Shared/Scenes/GameScene+Private.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene+Private.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/12/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | extension GameScene { 12 | 13 | internal func quitGameScene() { 14 | self.removeGamePieces() 15 | self.model.resetGameBoard() 16 | self.gameStateMachine.resetToInitialState() 17 | 18 | self.presentationDelegate?.shouldDismissScene(self) 19 | } 20 | 21 | internal func restartGameScene() { 22 | self.moveLabel.text = "" 23 | animateNodesOffScreen() 24 | 25 | let delay = DispatchTime.now() + Double(Int64(0.55 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 26 | DispatchQueue.main.asyncAfter(deadline: delay) { [unowned self] in 27 | self.removeGamePieces() 28 | self.model.resetGameBoard() 29 | self.gameStateMachine.resetToInitialState() 30 | } 31 | } 32 | 33 | internal func setupEmptyGame() { 34 | positionPieces() 35 | #if os(iOS) 36 | positionButtons() 37 | #endif 38 | positionLabels() 39 | 40 | self.model.resetGameBoard() 41 | gameStateMachine.resetToInitialState() 42 | } 43 | 44 | internal func placePiece(_ piece: TTTPiece, row: Int, column: Int) { 45 | let node = nodeAt(row, column: column) 46 | addPiece(piece, on: node) 47 | } 48 | 49 | internal func nodeAt(_ row: Int, column: Int) -> PositionNode { 50 | //FIXME: shouldn't these be board node children? 51 | 52 | let positions = self.children.filter { 53 | return $0 is PositionNode 54 | }.compactMap { 55 | return $0 as? PositionNode 56 | }.filter { 57 | return $0.column == column && $0.row == row 58 | } 59 | 60 | assert(positions.count == 1) 61 | 62 | guard let node = positions.first else { fatalError() } 63 | 64 | return node 65 | } 66 | } 67 | 68 | extension GameScene { 69 | 70 | fileprivate func positionPieces() { 71 | let size = Board.pieceSize 72 | let dimension = Board.dimension 73 | 74 | for row in 0.. Bool { 126 | let state = gameStateMachine.currentState 127 | return state is PlayerXTurnState || state is PlayerOTurnState 128 | } 129 | 130 | fileprivate func isGameOver() -> Bool { 131 | return gameStateMachine.currentState is GameOverState 132 | } 133 | 134 | fileprivate func currentGamePiece() -> TTTPiece { 135 | guard let player = self.model.activePlayer as? TTTPlayer else { fatalError() } 136 | 137 | return player.piece 138 | } 139 | 140 | fileprivate func addPiece(_ piece: TTTPiece, on node: SKNode) { 141 | let sprite = GlyphNode(glyph: piece.glyph) 142 | let color = Color.hexColor(piece.hexColor) 143 | 144 | sprite.fillColor = color 145 | sprite.strokeColor = color 146 | 147 | let frame = sprite.calculateAccumulatedFrame() 148 | 149 | sprite.position = CGPoint(x: -frame.midX, y: -frame.midY) 150 | node.addChild(sprite) 151 | } 152 | 153 | fileprivate func placePieceOn(_ node: SKNode) -> Bool { 154 | guard let node = node as? PositionNode else { return false } 155 | guard node.children.count == 0 else { return false } 156 | guard canAddPiece() else { return false } 157 | 158 | let piece = currentGamePiece() 159 | placePiece(piece, row: node.row, column: node.column) 160 | 161 | return true 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Shared/Scenes/GameScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameScene.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright (c) 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | import GameplayKit 11 | 12 | enum GameType { 13 | case onePlayer 14 | case twoPlayer 15 | } 16 | 17 | internal struct Board { 18 | static let pieceSize = CGSize(width: 100.0, height: 100.0) 19 | static let dimension = 3 20 | } 21 | 22 | class GameScene: SKScene { 23 | lazy var boardNode: SKSpriteNode = { 24 | let node = SKSpriteNode(imageNamed: "board3") 25 | node.position = CGPoint(x: 0.0, y: 0.0) 26 | node.zPosition = -1 27 | node.isUserInteractionEnabled = false 28 | return node 29 | }() 30 | 31 | // could refactor, hide these in a different layer... button node layer 32 | lazy var quitButton: GameButton = { 33 | let title = NSLocalizedString("Quit", comment: "Quit") 34 | let size = CGSize(width: 84, height: 34) 35 | let action = self.quitGameScene 36 | let button = GameButton(title: title, size: size, action: action) 37 | 38 | return button 39 | }() 40 | 41 | lazy var restartButton: GameButton = { 42 | let title = NSLocalizedString("Restart", comment: "Restart") 43 | let size = CGSize(width: 84, height: 34) 44 | let action = self.restartGameScene 45 | let button = GameButton(title: title, size: size, action: action) 46 | 47 | return button 48 | }() 49 | 50 | lazy var moveLabel: SKLabelNode = { 51 | let node = SKLabelNode(text: "") 52 | node.fontName = Style.Font.piece.name 53 | node.fontSize = CGFloat(Style.Font.piece.size) 54 | 55 | return node 56 | }() 57 | 58 | // seems like you want a player state and computer state? 59 | // or should player X and O be configured as different opponent types? 60 | 61 | lazy var onePlayerStates: [GKState] = { 62 | let states = [ 63 | SelectNextPlayerState(scene: self), 64 | PlayerXTurnState(scene: self, isComputerPlayer: false), 65 | PlayerOTurnState(scene: self, isComputerPlayer: true), 66 | CheckBoardState(scene: self), 67 | GameOverState(scene: self) 68 | ] 69 | 70 | return states 71 | }() 72 | 73 | lazy var twoPlayerStates: [GKState] = { 74 | let states = [ 75 | SelectNextPlayerState(scene: self), 76 | PlayerXTurnState(scene: self, isComputerPlayer: false), 77 | PlayerOTurnState(scene: self, isComputerPlayer: false), 78 | CheckBoardState(scene: self), 79 | GameOverState(scene: self) 80 | ] 81 | 82 | return states 83 | }() 84 | 85 | lazy var gameStateMachine: InPlayStateMachine = { 86 | let states = (self.type == .onePlayer) ? self.onePlayerStates : self.twoPlayerStates 87 | let machine = InPlayStateMachine(states: states) 88 | return machine 89 | }() 90 | 91 | lazy var strategist: GKMinmaxStrategist = { 92 | let strategist = GKMinmaxStrategist() 93 | strategist.gameModel = self.model 94 | strategist.maxLookAheadDepth = 3 95 | strategist.randomSource = GKMersenneTwisterRandomSource() 96 | 97 | return strategist 98 | }() 99 | 100 | var presentationDelegate: ScenePresentationDelegate? 101 | 102 | fileprivate(set) var model: TTTModel 103 | 104 | let playerX: TTTPlayer 105 | let playerO: TTTPlayer 106 | let type: GameType 107 | 108 | init(size: CGSize, type: GameType) { 109 | self.type = type 110 | 111 | // should the state machine own these? 112 | // the AI computer state needs to manipulate model 113 | // the model needs to track activePlayer... 114 | self.playerX = TTTPlayer(playerId: 1, piece: .x) 115 | self.playerO = TTTPlayer(playerId: 2, piece: .o) 116 | self.model = TTTModel(players: [playerO, playerX]) 117 | 118 | super.init(size: size) 119 | } 120 | 121 | required init?(coder aDecoder: NSCoder) { 122 | fatalError("init(coder:) has not been implemented") 123 | } 124 | 125 | override func didMove(to view: SKView) { 126 | self.backgroundColor = Style.Colors.background 127 | self.anchorPoint = CGPoint(x: 0.5, y: 0.5) 128 | self.physicsWorld.gravity = CGVector(dx: 0.0, dy: -30.0) 129 | 130 | self.addChild(boardNode) 131 | self.setupEmptyGame() 132 | } 133 | 134 | override func update(_ currentTime: TimeInterval) { 135 | /* Called before each frame is rendered */ 136 | } 137 | } 138 | 139 | extension GameScene { 140 | func handleUIEventOn(_ node: SKNode) { 141 | guard let node = node as? PositionNode else { return } 142 | guard node.children.count == 0 else { return } 143 | guard let player = model.activePlayer as? TTTPlayer else { return } 144 | 145 | let column = node.column 146 | let row = node.row 147 | let index = column + row * model.board.rows 148 | 149 | let piece = player.piece 150 | let move = TTTMove(index: index, piece: piece) 151 | 152 | makeMoveForActivePlayer(move) 153 | } 154 | 155 | func makeMoveForActivePlayer(_ move: GKGameModelUpdate) { 156 | guard let move = move as? TTTMove else { return } 157 | 158 | let column = move.index % model.board.rows 159 | let row = move.index / model.board.rows 160 | 161 | self.placePiece(move.piece, row: row, column: column) 162 | 163 | model.apply(move) 164 | gameStateMachine.enter(CheckBoardState.self) 165 | } 166 | 167 | func wiggleNodeAt(_ row: Int, column: Int) { 168 | let node = self.nodeAt(row, column: column) 169 | 170 | let wiggleInX = SKAction.scaleX(to: 1.0, duration: 0.2) 171 | let wiggleOutX = SKAction.scaleX(to: 1.2, duration: 0.2) 172 | 173 | let wiggleInY = SKAction.scaleY(to: 1.0, duration: 0.2) 174 | let wiggleOutY = SKAction.scaleY(to: 1.2, duration: 0.2) 175 | 176 | let wiggleX = SKAction.sequence([wiggleInX, wiggleOutX]) 177 | let wiggleY = SKAction.sequence([wiggleInY, wiggleOutY]) 178 | 179 | let wiggle = SKAction.group([wiggleX, wiggleY]) 180 | let wiggleRepeat = SKAction.repeatForever(wiggle) 181 | 182 | node.run(wiggleRepeat, withKey: "wiggleRepeat") 183 | } 184 | 185 | func animateNodesOffScreen() { 186 | for child in self.children { 187 | if let positionNode = child as? PositionNode { 188 | for child in positionNode.children { 189 | if child is GlyphNode { 190 | child.physicsBody?.affectedByGravity = true 191 | 192 | child.physicsBody?.applyTorque(0.32) 193 | child.physicsBody?.applyAngularImpulse(0.0001) 194 | } 195 | } 196 | } 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /Shared/Scenes/GameSelectionScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameSelectionScene.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/12/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import SpriteKit 10 | 11 | class GameSelectionScene: SKScene { 12 | 13 | let controller = MulitpeerController() 14 | 15 | override func didMove(to view: SKView) { 16 | super.didMove(to: view) 17 | 18 | // self.backgroundColor = UIColor.red 19 | 20 | controller.browser.startBrowsingForPeers() 21 | controller.advertiser.startAdvertisingPeer() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Shared/States/CheckBoardState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckBoardState.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | 11 | class CheckBoardState: InPlayState { 12 | 13 | override func didEnter(from previousState: GKState?) { 14 | let piece = playerPiece() 15 | 16 | if model.board.isWin(forPiece: piece) { 17 | self.stateMachine?.enter(GameOverState.self) 18 | } else if model.board.hasEmptyPlaces() == false { 19 | self.stateMachine?.enter(GameOverState.self) 20 | } else { 21 | self.stateMachine?.enter(SelectNextPlayerState.self) 22 | } 23 | } 24 | 25 | override func isValidNextState(_ stateClass: AnyClass) -> Bool { 26 | return (stateClass is GameOverState.Type || stateClass is SelectNextPlayerState.Type) 27 | } 28 | 29 | fileprivate func playerPiece() -> TTTPiece { 30 | guard let machine = self.stateMachine as? InPlayStateMachine else { fatalError() } 31 | 32 | switch machine.lastPlayerState { 33 | case is PlayerOTurnState.Type: 34 | return TTTPiece.o 35 | case is PlayerXTurnState.Type: 36 | return TTTPiece.x 37 | default: 38 | fatalError() 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Shared/States/GameOverState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameOverState.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | import SpriteKit 11 | 12 | class GameOverState: InPlayState { 13 | 14 | override func didEnter(from previousState: GKState?) { 15 | guard let scene = self.scene as? GameScene else { return } 16 | 17 | let board = scene.model.board 18 | 19 | if board.isWin(forPiece: .x) { 20 | let title = NSLocalizedString("Player X wins!", comment: "Player X wins!") 21 | scene.moveLabel.text = title 22 | scene.moveLabel.fontColor = Style.Colors.orange 23 | } 24 | else if board.isWin(forPiece: .o) { 25 | let title = NSLocalizedString("Player O wins!", comment: "Player O wins!") 26 | scene.moveLabel.text = title 27 | scene.moveLabel.fontColor = Style.Colors.blue 28 | } 29 | else { 30 | let title = NSLocalizedString("It's a Draw!", comment: "It's a Draw!") 31 | scene.moveLabel.text = title 32 | scene.moveLabel.fontColor = Style.Colors.blue 33 | } 34 | 35 | // digging too deep in restart button... hide this? 36 | let title = NSLocalizedString("Again?", comment: "Again?") 37 | scene.restartButton.label.text = title 38 | 39 | guard let winningCombo = board.winningCombo() else { return } 40 | 41 | winningCombo.forEach { (index) in 42 | let column = index % board.rows 43 | let row = index / board.rows 44 | scene.wiggleNodeAt(row, column: column) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Shared/States/InPlayStateMachine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InPlayStateMachine.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | 11 | 12 | class InPlayStateMachine: GKStateMachine { 13 | 14 | var lastPlayerState: GKState.Type? 15 | 16 | fileprivate(set) var moveCount: Int = 0 17 | 18 | func resetToInitialState() { 19 | self.moveCount = 0 20 | self.lastPlayerState = nil 21 | 22 | self.enter(SelectNextPlayerState.self) 23 | } 24 | 25 | var glyphForState: String { 26 | return currentState is PlayerXTurnState ? "X" : "O" 27 | } 28 | 29 | var glyphColorForState: Color { 30 | let colorForX = Color.hexColor("#EF946C") 31 | let colorForO = Color.hexColor("#00FCDB") 32 | 33 | return currentState is PlayerXTurnState ? colorForX : colorForO 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Shared/States/InPlayStateType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InPlayState.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/11/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SpriteKit 11 | import GameplayKit 12 | 13 | protocol InPlayStateType { 14 | var scene: SKScene { get } 15 | var model: TTTModel { get } 16 | 17 | init(scene: SKScene) 18 | } 19 | 20 | extension InPlayStateType where Self: GKState { 21 | var model: TTTModel { 22 | guard let scene = scene as? GameScene else { fatalError() } 23 | return scene.model 24 | } 25 | } 26 | 27 | class InPlayState: GKState, InPlayStateType { 28 | fileprivate(set) unowned var scene: SKScene 29 | 30 | required init(scene: SKScene) { 31 | self.scene = scene 32 | super.init() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Shared/States/PlayerState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerState.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 7/11/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import GameplayKit 11 | 12 | class PlayerState: InPlayState { 13 | fileprivate let isComputerPlayer: Bool 14 | 15 | required init(scene: SKScene, isComputerPlayer: Bool = false) { 16 | self.isComputerPlayer = isComputerPlayer 17 | super.init(scene: scene) 18 | } 19 | 20 | required init(scene: SKScene) { 21 | self.isComputerPlayer = false 22 | super.init(scene: scene) 23 | } 24 | 25 | override func isValidNextState(_ stateClass: AnyClass) -> Bool { 26 | return true 27 | } 28 | 29 | override func didEnter(from previousState: GKState?) { 30 | if isComputerPlayer { 31 | guard let scene = scene as? GameScene else { return } 32 | guard let player = scene.model.activePlayer as? TTTPlayer else { return } 33 | let glyph = player.piece.glyph 34 | 35 | let title = NSLocalizedString("Player \(glyph) is making a move...", comment: "") 36 | scene.moveLabel.text = title 37 | scene.moveLabel.fontColor = Style.Colors.blue 38 | 39 | let delay = DispatchTime.now() + Double(Int64(1 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC) 40 | DispatchQueue.main.asyncAfter(deadline: delay) { 41 | if let move = scene.strategist.bestMove(for: player) { 42 | scene.makeMoveForActivePlayer(move) 43 | } 44 | else { 45 | fatalError("no moves found...") 46 | } 47 | } 48 | } 49 | else { 50 | // if the player isn't artificial, the UI will transition 51 | // states when the user makes a move. 52 | } 53 | } 54 | } 55 | 56 | class PlayerOTurnState: PlayerState { } 57 | class PlayerXTurnState: PlayerState { } 58 | -------------------------------------------------------------------------------- /Shared/States/SelectNextPlayerState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SelectNextPlayerState.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/29/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import GameplayKit 10 | import SpriteKit 11 | 12 | class SelectNextPlayerState: InPlayState { 13 | 14 | override func didEnter(from previousState: GKState?) { 15 | guard let machine = self.stateMachine as? InPlayStateMachine else { return } 16 | guard let scene = self.scene as? GameScene else { return } 17 | 18 | guard let players = scene.model.players as? [TTTPlayer] else { return } 19 | let playerX = players.filter { $0.piece == TTTPiece.x }.first! 20 | let playerO = players.filter { $0.piece == TTTPiece.o }.first! 21 | 22 | if let previousState = previousState { 23 | if previousState is GameOverState { 24 | let title = NSLocalizedString("Restart", comment: "Restart") 25 | scene.restartButton.label.text = title 26 | } 27 | } 28 | 29 | if machine.lastPlayerState is PlayerXTurnState.Type { 30 | let title = NSLocalizedString("Next move, Player O", comment: "Next move, Player O") 31 | scene.moveLabel.text = title 32 | scene.moveLabel.fontColor = Style.Colors.blue 33 | 34 | scene.model.activePlayer = playerO 35 | 36 | machine.lastPlayerState = PlayerOTurnState.self 37 | machine.enter(PlayerOTurnState.self) 38 | } 39 | else { 40 | let title = NSLocalizedString("Next move, Player X", comment: "Next move, Player X") 41 | scene.moveLabel.text = title 42 | scene.moveLabel.fontColor = Style.Colors.orange 43 | 44 | scene.model.activePlayer = playerX 45 | 46 | machine.lastPlayerState = PlayerXTurnState.self 47 | machine.enter(PlayerXTurnState.self) 48 | } 49 | } 50 | 51 | override func isValidNextState(_ stateClass: AnyClass) -> Bool { 52 | return stateClass is PlayerXTurnState.Type || stateClass is PlayerOTurnState.Type 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Shared/Style/HexColors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+HexColors.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | #if os(iOS) || os(tvOS) 12 | import UIKit 13 | #endif 14 | 15 | extension Color { 16 | 17 | class func hexColor(_ string: String) -> Color { 18 | let set = CharacterSet.whitespacesAndNewlines 19 | var colorString = string.trimmingCharacters(in: set).uppercased() 20 | 21 | if (colorString.hasPrefix("#")) { 22 | let index = colorString.index(after: colorString.startIndex) 23 | colorString = String(colorString[index..> 16) / 255.0, 35 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 36 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 37 | alpha: CGFloat(1.0) 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Shared/Style/SharedTypes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SharedTypes.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | #if os(iOS) || os(tvOS) 10 | import UIKit 11 | #elseif os(OSX) 12 | import AppKit 13 | #endif 14 | 15 | #if os(iOS) || os(tvOS) 16 | 17 | typealias Font = UIFont 18 | typealias Color = UIColor 19 | 20 | #elseif os(OSX) 21 | 22 | typealias Font = NSFont 23 | typealias Color = NSColor 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /Shared/Style/Style.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Style.swift 3 | // TicTacToad 4 | // 5 | // Created by Andrew Shepard on 6/28/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | 10 | struct Style { 11 | struct Colors { 12 | static let background = Color.hexColor("#323845") 13 | static let button = Color.hexColor("#E3F09B") 14 | static let text = Color.hexColor("#323845") 15 | 16 | static let orange = Color.hexColor("#EF946C") 17 | static let blue = Color.hexColor("#00FCDB") 18 | } 19 | 20 | struct Font { 21 | static let piece = (name: "MarkerFelt-Wide", size: 24.0) 22 | static let debug = (name: "Helvetica", size: 12.0) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /TicTacToe-iOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 17 | 18 | return true 19 | } 20 | 21 | func applicationWillResignActive(_ application: UIApplication) { 22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 23 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | func applicationWillEnterForeground(_ application: UIApplication) { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | func applicationDidBecomeActive(_ application: UIApplication) { 36 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 37 | } 38 | 39 | func applicationWillTerminate(_ application: UIApplication) { 40 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 41 | } 42 | 43 | 44 | } 45 | 46 | -------------------------------------------------------------------------------- /TicTacToe-iOS/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /TicTacToe-iOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /TicTacToe-iOS/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright (c) 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import GameplayKit 12 | import GameKit 13 | 14 | class GameViewController: UIViewController { 15 | 16 | var type: GameType? = nil 17 | 18 | lazy var skView: SKView = { 19 | guard let skView = self.view as? SKView else { fatalError() } 20 | return skView 21 | }() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | guard let type = type else { fatalError("expected game type") } 27 | 28 | self.navigationController?.navigationBar.isHidden = true 29 | 30 | self.setNeedsStatusBarAppearanceUpdate() 31 | 32 | switch type { 33 | case .onePlayer: 34 | let size = skView.frame.size 35 | let scene = GameScene(size: size, type: .onePlayer) 36 | scene.presentationDelegate = self 37 | 38 | self.skView.presentScene(scene) 39 | case .twoPlayer: 40 | setupTwoPlayerGame() 41 | } 42 | } 43 | 44 | override var shouldAutorotate: Bool { 45 | return true 46 | } 47 | 48 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 49 | if UIDevice.current.userInterfaceIdiom == .phone { 50 | return .allButUpsideDown 51 | } else { 52 | return .all 53 | } 54 | } 55 | 56 | override var prefersStatusBarHidden: Bool { 57 | return true 58 | } 59 | } 60 | 61 | extension GameViewController: ScenePresentationDelegate { 62 | func shouldDismissScene(_ scene: SKScene) { 63 | let _ = self.navigationController?.popViewController(animated: true) 64 | } 65 | } 66 | 67 | extension GameViewController { 68 | fileprivate func setupTwoPlayerGame() { 69 | let request = GKMatchRequest() 70 | request.minPlayers = 2 71 | request.maxPlayers = 2 72 | 73 | let viewController = GKTurnBasedMatchmakerViewController(matchRequest: request) 74 | viewController.turnBasedMatchmakerDelegate = self 75 | 76 | self.present(viewController, animated: true, completion: nil) 77 | } 78 | } 79 | 80 | extension GameViewController: GKTurnBasedMatchmakerViewControllerDelegate { 81 | func turnBasedMatchmakerViewControllerWasCancelled(_ viewController: GKTurnBasedMatchmakerViewController) { 82 | viewController.dismiss(animated: true, completion: nil) 83 | let _ = self.navigationController?.popViewController(animated: true) 84 | } 85 | 86 | func turnBasedMatchmakerViewController(_ viewController: GKTurnBasedMatchmakerViewController, didFailWithError error: Error) { 87 | print("error: \(error)") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /TicTacToe-iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | TicTacToe 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UIRequiredDeviceCapabilities 32 | 33 | armv7 34 | gamekit 35 | 36 | UIStatusBarHidden 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /TicTacToe-iOS/MatchesDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MatchesDataSource.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 9/27/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GameKit 11 | 12 | typealias LoadedMatchesCompletion = ([GKTurnBasedMatch]?, Error?) -> Void 13 | 14 | class MatchesDataSource: NSObject { 15 | private(set) var matches: [AnyObject] = [] 16 | 17 | func loadMatches(completion: @escaping LoadedMatchesCompletion) -> Void { 18 | GKTurnBasedMatch.loadMatches { (matches, error) in 19 | completion(matches, error) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TicTacToe-iOS/MatchesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MatchesViewController.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 9/27/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GameKit 11 | 12 | class MatchesViewController: UIViewController { 13 | 14 | let dataSource = MatchesDataSource() 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | dataSource.loadMatches { (matches, error) in 20 | // 21 | } 22 | } 23 | 24 | override func didReceiveMemoryWarning() { 25 | super.didReceiveMemoryWarning() 26 | // Dispose of any resources that can be recreated. 27 | } 28 | 29 | 30 | /* 31 | // MARK: - Navigation 32 | 33 | // In a storyboard-based application, you will often want to do a little preparation before navigation 34 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 35 | // Get the new view controller using segue.destinationViewController. 36 | // Pass the selected object to the new view controller. 37 | } 38 | */ 39 | 40 | } 41 | -------------------------------------------------------------------------------- /TicTacToe-iOS/MenuTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuTableViewCell.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 1/12/17. 6 | // Copyright © 2017 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QuartzCore 11 | 12 | class MenuTableViewCell: UITableViewCell { 13 | 14 | @IBOutlet var roundView: RoundCellView! 15 | @IBOutlet var titleLabel: UILabel! 16 | 17 | override func awakeFromNib() { 18 | super.awakeFromNib() 19 | // Initialization code 20 | 21 | self.backgroundColor = UIColor.clear 22 | self.roundView.backgroundColor = UIColor.clear 23 | 24 | self.titleLabel.font = UIFont(name: "MarkerFelt-Wide", size: 20) 25 | self.titleLabel.textColor = Style.Colors.text 26 | 27 | self.selectionStyle = .none 28 | } 29 | 30 | override func setSelected(_ selected: Bool, animated: Bool) { 31 | super.setSelected(selected, animated: animated) 32 | 33 | // Configure the view for the selected state 34 | } 35 | 36 | override func setHighlighted(_ highlighted: Bool, animated: Bool) { 37 | super.setHighlighted(highlighted, animated: animated) 38 | 39 | let duration = 0.15 40 | let newScale: CGFloat = highlighted ? 0.99 : 1.0 41 | 42 | UIView.animate(withDuration: duration) { 43 | self.roundView.transform = CGAffineTransform(scaleX: newScale, y: newScale) 44 | } 45 | } 46 | 47 | override func prepareForReuse() { 48 | super.prepareForReuse() 49 | 50 | self.titleLabel.text = "" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /TicTacToe-iOS/MenuTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /TicTacToe-iOS/MenuViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MenuViewController.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 7/13/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import GameKit 11 | 12 | class MenuViewController: UIViewController { 13 | 14 | @IBOutlet var tableView: UITableView! 15 | 16 | fileprivate let menuCellIdentifier = "MenuCellIdentifier" 17 | 18 | fileprivate var options: [String] = { 19 | return [ 20 | NSLocalizedString("One Player", comment: "One Player"), 21 | NSLocalizedString("Two Player", comment: "Two Player"), 22 | NSLocalizedString("Settings", comment: "Settings") 23 | ] 24 | }() 25 | 26 | fileprivate lazy var matchesViewController: MatchesViewController = { 27 | let identifier = String(describing: MatchesViewController.self) 28 | guard let viewController = self.storyboard?.instantiateViewController(withIdentifier: identifier) as? MatchesViewController else { 29 | fatalError("missing \(identifier)") 30 | } 31 | 32 | return viewController 33 | }() 34 | 35 | override func viewDidLoad() { 36 | super.viewDidLoad() 37 | 38 | self.navigationController?.navigationBar.barStyle = .black 39 | self.navigationController?.navigationBar.isHidden = true 40 | 41 | self.view.backgroundColor = Style.Colors.background 42 | 43 | self.tableView.bounces = false 44 | self.tableView.backgroundColor = .clear 45 | 46 | self.tableView.dataSource = self 47 | self.tableView.delegate = self 48 | 49 | let nib = UINib(nibName: "MenuTableViewCell", bundle: nil) 50 | self.tableView.register(nib, forCellReuseIdentifier: menuCellIdentifier) 51 | 52 | self.tableView.separatorStyle = .none 53 | 54 | // GameCenterController.shared.authenticateLocalUser() 55 | } 56 | 57 | override func viewWillAppear(_ animated: Bool) { 58 | super.viewWillAppear(animated) 59 | 60 | if let indexPath = tableView.indexPathForSelectedRow { 61 | tableView.deselectRow(at: indexPath, animated: true) 62 | } 63 | } 64 | 65 | override var prefersStatusBarHidden: Bool { 66 | return false 67 | } 68 | } 69 | 70 | extension MenuViewController: UITableViewDataSource { 71 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 72 | return self.options.count 73 | } 74 | 75 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 76 | guard let cell = tableView.dequeueReusableCell(withIdentifier: menuCellIdentifier) as? MenuTableViewCell else { fatalError() } 77 | 78 | let title = self.options[indexPath.row] 79 | cell.titleLabel.text = title 80 | 81 | return cell 82 | } 83 | } 84 | 85 | extension MenuViewController: UITableViewDelegate { 86 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 87 | 88 | tableView.deselectRow(at: tableView.indexPathForSelectedRow!, animated: true) 89 | 90 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.10) { [unowned self] in 91 | 92 | let identifier = String(describing: GameViewController.self) 93 | guard let gameViewController = self.storyboard?.instantiateViewController(withIdentifier: identifier) as? GameViewController else { 94 | fatalError("missing \(identifier)") 95 | } 96 | 97 | if indexPath.row == 0 { 98 | // one player 99 | gameViewController.type = GameType.onePlayer 100 | self.navigationController?.pushViewController(gameViewController, animated: true) 101 | } 102 | else if indexPath.row == 1 { 103 | // two player 104 | gameViewController.type = GameType.twoPlayer 105 | self.navigationController?.pushViewController(gameViewController, animated: true) 106 | } 107 | } 108 | } 109 | 110 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 111 | return 75.0 112 | } 113 | } 114 | 115 | extension MenuViewController: GameCenterControllerDelegate { 116 | func matchEnded() { 117 | // 118 | } 119 | 120 | func matchStarted() { 121 | // 122 | } 123 | 124 | func match(_ match: GKMatch, didReceiveData: Data, fromPlayer: String) { 125 | print("data: \(didReceiveData)") 126 | } 127 | } 128 | 129 | extension MenuViewController: GKTurnBasedMatchmakerViewControllerDelegate { 130 | func turnBasedMatchmakerViewControllerWasCancelled(_ viewController: GKTurnBasedMatchmakerViewController) { 131 | viewController.dismiss(animated: true, completion: nil) 132 | } 133 | 134 | func turnBasedMatchmakerViewController(_ viewController: GKTurnBasedMatchmakerViewController, didFailWithError error: Error) { 135 | print("error: \(error)") 136 | viewController.dismiss(animated: true, completion: nil) 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /TicTacToe-iOS/RoundCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RoundCellView.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 1/12/17. 6 | // Copyright © 2017 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class RoundCellView: UIView { 12 | 13 | var selected: Bool = false { 14 | didSet { 15 | self.setNeedsDisplay() 16 | } 17 | } 18 | 19 | var highlighted: Bool = false { 20 | didSet { 21 | self.setNeedsDisplay() 22 | } 23 | } 24 | 25 | override func draw(_ rect: CGRect) { 26 | Style.Colors.button.setFill() 27 | 28 | let height = rect.height 29 | let radius = (height.truncatingRemainder(dividingBy: 2) == 0) ? height : height - 1 30 | 31 | let path = UIBezierPath(roundedRect: rect, cornerRadius: radius) 32 | path.addClip() 33 | path.fill() 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /TicTacToe-macOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright (c) 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | 10 | import Cocoa 11 | import SpriteKit 12 | import GameplayKit 13 | 14 | @NSApplicationMain 15 | class AppDelegate: NSObject, NSApplicationDelegate { 16 | 17 | @IBOutlet weak var window: NSWindow! 18 | @IBOutlet weak var skView: SKView! 19 | 20 | func applicationDidFinishLaunching(_ notification: Notification) { 21 | // 22 | } 23 | 24 | func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 25 | return true 26 | } 27 | 28 | override func awakeFromNib() { 29 | window.aspectRatio = window.frame.size 30 | } 31 | 32 | @IBAction func handleNewGame(_ sender: AnyObject) -> Void { 33 | print("new game!") 34 | 35 | let size = skView.frame.size 36 | let scene = GameScene(size: size, type: .onePlayer) 37 | scene.scaleMode = .aspectFit 38 | // scene.presentationDelegate = self 39 | 40 | self.skView.presentScene(scene) 41 | 42 | self.window.setIsVisible(true) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /TicTacToe-macOS/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /TicTacToe-macOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016 Andrew Shepard. All rights reserved. 29 | NSMainNibFile 30 | MainMenu 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /TicTacToe-tvOS/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright © 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /TicTacToe-tvOS/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /TicTacToe-tvOS/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // TicTacToe 4 | // 5 | // Created by Andrew Shepard on 6/27/16. 6 | // Copyright (c) 2016 Andrew Shepard. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SpriteKit 11 | import GameplayKit 12 | 13 | class GameViewController: UIViewController { 14 | 15 | lazy var skView: SKView = { 16 | guard let skView = self.view as? SKView else { fatalError() } 17 | return skView 18 | }() 19 | 20 | lazy var sceneStateMachine: GKStateMachine = { 21 | let states = [ 22 | MenuState(view: self.skView), 23 | OnePlayerState(view: self.skView), 24 | TwoPlayerState(view: self.skView) 25 | ] 26 | 27 | return GKStateMachine(states: states) 28 | }() 29 | 30 | override func viewDidLoad() { 31 | super.viewDidLoad() 32 | 33 | self.sceneStateMachine.enterState(MenuState.self) 34 | } 35 | 36 | override func didReceiveMemoryWarning() { 37 | super.didReceiveMemoryWarning() 38 | // Release any cached data, images, etc that aren't in use. 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TicTacToe-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | TicTacToe 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | arm64 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /TicTacToe.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F734B8C91F0594FC001B77F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8C71F0594FC001B77F5 /* AppDelegate.swift */; }; 11 | F734B8CD1F059509001B77F5 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F734B8CB1F059509001B77F5 /* MainMenu.xib */; }; 12 | F734B8D11F059592001B77F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8CE1F059592001B77F5 /* AppDelegate.swift */; }; 13 | F734B8D21F059592001B77F5 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8CF1F059592001B77F5 /* GameViewController.swift */; }; 14 | F734B8D61F059599001B77F5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F734B8D41F059599001B77F5 /* Main.storyboard */; }; 15 | F734B8E01F0595F9001B77F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8D71F0595F9001B77F5 /* AppDelegate.swift */; }; 16 | F734B8E11F0595F9001B77F5 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8D81F0595F9001B77F5 /* GameViewController.swift */; }; 17 | F734B8E31F0595F9001B77F5 /* MatchesDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8DA1F0595F9001B77F5 /* MatchesDataSource.swift */; }; 18 | F734B8E41F0595F9001B77F5 /* MatchesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8DB1F0595F9001B77F5 /* MatchesViewController.swift */; }; 19 | F734B8E51F0595F9001B77F5 /* MenuTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8DC1F0595F9001B77F5 /* MenuTableViewCell.swift */; }; 20 | F734B8E61F0595F9001B77F5 /* MenuTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = F734B8DD1F0595F9001B77F5 /* MenuTableViewCell.xib */; }; 21 | F734B8E71F0595F9001B77F5 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8DE1F0595F9001B77F5 /* MenuViewController.swift */; }; 22 | F734B8E81F0595F9001B77F5 /* RoundCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F734B8DF1F0595F9001B77F5 /* RoundCellView.swift */; }; 23 | F734B8ED1F059600001B77F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F734B8E91F059600001B77F5 /* LaunchScreen.storyboard */; }; 24 | F734B8EE1F059600001B77F5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F734B8EB1F059600001B77F5 /* Main.storyboard */; }; 25 | F73573521D98427200C6EA7D /* ScenePresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDFE1D36ECCE00643BE0 /* ScenePresentationDelegate.swift */; }; 26 | F77021DA1D26EB9D00CEFFF5 /* TTTBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D61D26EB9D00CEFFF5 /* TTTBoard.swift */; }; 27 | F77021DB1D26EB9D00CEFFF5 /* TTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D71D26EB9D00CEFFF5 /* TTTModel.swift */; }; 28 | F77021DC1D26EB9D00CEFFF5 /* TTTMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D81D26EB9D00CEFFF5 /* TTTMove.swift */; }; 29 | F77021DD1D26EB9D00CEFFF5 /* TTTPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D91D26EB9D00CEFFF5 /* TTTPlayer.swift */; }; 30 | F77021E11D26EBB600CEFFF5 /* ButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DE1D26EBB600CEFFF5 /* ButtonNode.swift */; }; 31 | F77021E21D26EBB600CEFFF5 /* GlyphNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DF1D26EBB600CEFFF5 /* GlyphNode.swift */; }; 32 | F77021E31D26EBB600CEFFF5 /* PositionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021E01D26EBB600CEFFF5 /* PositionNode.swift */; }; 33 | F77021E41D26EBC500CEFFF5 /* ButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DE1D26EBB600CEFFF5 /* ButtonNode.swift */; }; 34 | F77021E51D26EBC500CEFFF5 /* GlyphNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DF1D26EBB600CEFFF5 /* GlyphNode.swift */; }; 35 | F77021E61D26EBC500CEFFF5 /* PositionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021E01D26EBB600CEFFF5 /* PositionNode.swift */; }; 36 | F77021E71D26EBC500CEFFF5 /* ButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DE1D26EBB600CEFFF5 /* ButtonNode.swift */; }; 37 | F77021E81D26EBC500CEFFF5 /* GlyphNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021DF1D26EBB600CEFFF5 /* GlyphNode.swift */; }; 38 | F77021E91D26EBC500CEFFF5 /* PositionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021E01D26EBB600CEFFF5 /* PositionNode.swift */; }; 39 | F77021EA1D26EBC900CEFFF5 /* TTTBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D61D26EB9D00CEFFF5 /* TTTBoard.swift */; }; 40 | F77021EB1D26EBC900CEFFF5 /* TTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D71D26EB9D00CEFFF5 /* TTTModel.swift */; }; 41 | F77021EC1D26EBC900CEFFF5 /* TTTMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D81D26EB9D00CEFFF5 /* TTTMove.swift */; }; 42 | F77021ED1D26EBC900CEFFF5 /* TTTPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D91D26EB9D00CEFFF5 /* TTTPlayer.swift */; }; 43 | F77021EE1D26EBC900CEFFF5 /* TTTBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D61D26EB9D00CEFFF5 /* TTTBoard.swift */; }; 44 | F77021EF1D26EBC900CEFFF5 /* TTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D71D26EB9D00CEFFF5 /* TTTModel.swift */; }; 45 | F77021F01D26EBC900CEFFF5 /* TTTMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D81D26EB9D00CEFFF5 /* TTTMove.swift */; }; 46 | F77021F11D26EBC900CEFFF5 /* TTTPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021D91D26EB9D00CEFFF5 /* TTTPlayer.swift */; }; 47 | F77021FB1D26EBF600CEFFF5 /* CheckBoardState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F21D26EBF600CEFFF5 /* CheckBoardState.swift */; }; 48 | F77021FC1D26EBF600CEFFF5 /* GameOverState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F31D26EBF600CEFFF5 /* GameOverState.swift */; }; 49 | F77021FD1D26EBF600CEFFF5 /* InPlayStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F41D26EBF600CEFFF5 /* InPlayStateMachine.swift */; }; 50 | F77022021D26EBF600CEFFF5 /* SelectNextPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F91D26EBF600CEFFF5 /* SelectNextPlayerState.swift */; }; 51 | F77022091D26EC6C00CEFFF5 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022041D26EC6C00CEFFF5 /* GameScene.swift */; }; 52 | F770220A1D26EC6C00CEFFF5 /* GameScene+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022051D26EC6C00CEFFF5 /* GameScene+Input.swift */; }; 53 | F77022101D26EC9300CEFFF5 /* GameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220E1D26EC9300CEFFF5 /* GameButton.swift */; }; 54 | F77022111D26EC9300CEFFF5 /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220F1D26EC9300CEFFF5 /* MenuButton.swift */; }; 55 | F77022121D26EC9A00CEFFF5 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022041D26EC6C00CEFFF5 /* GameScene.swift */; }; 56 | F77022131D26EC9A00CEFFF5 /* GameScene+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022051D26EC6C00CEFFF5 /* GameScene+Input.swift */; }; 57 | F77022161D26EC9A00CEFFF5 /* GameScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022041D26EC6C00CEFFF5 /* GameScene.swift */; }; 58 | F77022171D26EC9A00CEFFF5 /* GameScene+Input.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77022051D26EC6C00CEFFF5 /* GameScene+Input.swift */; }; 59 | F770221A1D26EC9E00CEFFF5 /* CheckBoardState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F21D26EBF600CEFFF5 /* CheckBoardState.swift */; }; 60 | F770221B1D26EC9E00CEFFF5 /* GameOverState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F31D26EBF600CEFFF5 /* GameOverState.swift */; }; 61 | F770221C1D26EC9E00CEFFF5 /* InPlayStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F41D26EBF600CEFFF5 /* InPlayStateMachine.swift */; }; 62 | F77022211D26EC9E00CEFFF5 /* SelectNextPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F91D26EBF600CEFFF5 /* SelectNextPlayerState.swift */; }; 63 | F77022231D26EC9E00CEFFF5 /* CheckBoardState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F21D26EBF600CEFFF5 /* CheckBoardState.swift */; }; 64 | F77022241D26EC9E00CEFFF5 /* GameOverState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F31D26EBF600CEFFF5 /* GameOverState.swift */; }; 65 | F77022251D26EC9E00CEFFF5 /* InPlayStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F41D26EBF600CEFFF5 /* InPlayStateMachine.swift */; }; 66 | F770222A1D26EC9E00CEFFF5 /* SelectNextPlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F77021F91D26EBF600CEFFF5 /* SelectNextPlayerState.swift */; }; 67 | F770222C1D26ECA300CEFFF5 /* GameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220E1D26EC9300CEFFF5 /* GameButton.swift */; }; 68 | F770222D1D26ECA300CEFFF5 /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220F1D26EC9300CEFFF5 /* MenuButton.swift */; }; 69 | F770222E1D26ECA400CEFFF5 /* GameButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220E1D26EC9300CEFFF5 /* GameButton.swift */; }; 70 | F770222F1D26ECA400CEFFF5 /* MenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F770220F1D26EC9300CEFFF5 /* MenuButton.swift */; }; 71 | F773EDD41D36BBAB00643BE0 /* MulitpeerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDD21D36BBAB00643BE0 /* MulitpeerController.swift */; }; 72 | F773EDD51D36BBAD00643BE0 /* GameCenterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDD11D36BBAB00643BE0 /* GameCenterController.swift */; }; 73 | F773EDD61D36BBAD00643BE0 /* MulitpeerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDD21D36BBAB00643BE0 /* MulitpeerController.swift */; }; 74 | F773EDD71D36BBAE00643BE0 /* GameCenterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDD11D36BBAB00643BE0 /* GameCenterController.swift */; }; 75 | F773EDD81D36BBAE00643BE0 /* MulitpeerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDD21D36BBAB00643BE0 /* MulitpeerController.swift */; }; 76 | F773EDDD1D36BBC300643BE0 /* HexColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDA1D36BBC300643BE0 /* HexColors.swift */; }; 77 | F773EDDE1D36BBC300643BE0 /* SharedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDB1D36BBC300643BE0 /* SharedTypes.swift */; }; 78 | F773EDDF1D36BBC300643BE0 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDC1D36BBC300643BE0 /* Style.swift */; }; 79 | F773EDE01D36BBC500643BE0 /* HexColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDA1D36BBC300643BE0 /* HexColors.swift */; }; 80 | F773EDE11D36BBC500643BE0 /* SharedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDB1D36BBC300643BE0 /* SharedTypes.swift */; }; 81 | F773EDE21D36BBC500643BE0 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDC1D36BBC300643BE0 /* Style.swift */; }; 82 | F773EDE31D36BBC500643BE0 /* HexColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDA1D36BBC300643BE0 /* HexColors.swift */; }; 83 | F773EDE41D36BBC500643BE0 /* SharedTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDB1D36BBC300643BE0 /* SharedTypes.swift */; }; 84 | F773EDE51D36BBC500643BE0 /* Style.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDDC1D36BBC300643BE0 /* Style.swift */; }; 85 | F773EDE91D36BBD500643BE0 /* GameScene+Private.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE61D36BBD500643BE0 /* GameScene+Private.swift */; }; 86 | F773EDEB1D36BBD500643BE0 /* GameSelectionScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE81D36BBD500643BE0 /* GameSelectionScene.swift */; }; 87 | F773EDEE1D36BBFD00643BE0 /* InPlayStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDEC1D36BBFD00643BE0 /* InPlayStateType.swift */; }; 88 | F773EDEF1D36BBFD00643BE0 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDED1D36BBFD00643BE0 /* PlayerState.swift */; }; 89 | F773EDF01D36BBFF00643BE0 /* InPlayStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDEC1D36BBFD00643BE0 /* InPlayStateType.swift */; }; 90 | F773EDF11D36BBFF00643BE0 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDED1D36BBFD00643BE0 /* PlayerState.swift */; }; 91 | F773EDF21D36BBFF00643BE0 /* InPlayStateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDEC1D36BBFD00643BE0 /* InPlayStateType.swift */; }; 92 | F773EDF31D36BBFF00643BE0 /* PlayerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDED1D36BBFD00643BE0 /* PlayerState.swift */; }; 93 | F773EDF81D36BC2D00643BE0 /* GameSelectionScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE81D36BBD500643BE0 /* GameSelectionScene.swift */; }; 94 | F773EDF91D36BC2E00643BE0 /* GameSelectionScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE81D36BBD500643BE0 /* GameSelectionScene.swift */; }; 95 | F773EDFA1D36BC3B00643BE0 /* GameScene+Private.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE61D36BBD500643BE0 /* GameScene+Private.swift */; }; 96 | F773EDFB1D36BC3D00643BE0 /* GameScene+Private.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDE61D36BBD500643BE0 /* GameScene+Private.swift */; }; 97 | F773EDFF1D36ECCE00643BE0 /* ScenePresentationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F773EDFE1D36ECCE00643BE0 /* ScenePresentationDelegate.swift */; }; 98 | F786B4F81D21DF5B004706BC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F786B4F51D21DF5B004706BC /* Assets.xcassets */; }; 99 | F786B4FB1D21DF5F004706BC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F786B4F51D21DF5B004706BC /* Assets.xcassets */; }; 100 | F786B52D1D224299004706BC /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F786B4F51D21DF5B004706BC /* Assets.xcassets */; }; 101 | F7D071DA1D35E1BC0044E7E0 /* GameKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F7D071D91D35E1BC0044E7E0 /* GameKit.framework */; }; 102 | /* End PBXBuildFile section */ 103 | 104 | /* Begin PBXFileReference section */ 105 | F734B8C71F0594FC001B77F5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "TicTacToe-macOS/AppDelegate.swift"; sourceTree = SOURCE_ROOT; }; 106 | F734B8C81F0594FC001B77F5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "TicTacToe-macOS/Info.plist"; sourceTree = SOURCE_ROOT; }; 107 | F734B8CC1F059509001B77F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "TicTacToe-macOS/Base.lproj/MainMenu.xib"; sourceTree = SOURCE_ROOT; }; 108 | F734B8CE1F059592001B77F5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "TicTacToe-tvOS/AppDelegate.swift"; sourceTree = SOURCE_ROOT; }; 109 | F734B8CF1F059592001B77F5 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameViewController.swift; path = "TicTacToe-tvOS/GameViewController.swift"; sourceTree = SOURCE_ROOT; }; 110 | F734B8D01F059592001B77F5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "TicTacToe-tvOS/Info.plist"; sourceTree = SOURCE_ROOT; }; 111 | F734B8D51F059599001B77F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "TicTacToe-tvOS/Base.lproj/Main.storyboard"; sourceTree = SOURCE_ROOT; }; 112 | F734B8D71F0595F9001B77F5 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = "TicTacToe-iOS/AppDelegate.swift"; sourceTree = SOURCE_ROOT; }; 113 | F734B8D81F0595F9001B77F5 /* GameViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameViewController.swift; path = "TicTacToe-iOS/GameViewController.swift"; sourceTree = SOURCE_ROOT; }; 114 | F734B8D91F0595F9001B77F5 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = "TicTacToe-iOS/Info.plist"; sourceTree = SOURCE_ROOT; }; 115 | F734B8DA1F0595F9001B77F5 /* MatchesDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MatchesDataSource.swift; path = "TicTacToe-iOS/MatchesDataSource.swift"; sourceTree = SOURCE_ROOT; }; 116 | F734B8DB1F0595F9001B77F5 /* MatchesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MatchesViewController.swift; path = "TicTacToe-iOS/MatchesViewController.swift"; sourceTree = SOURCE_ROOT; }; 117 | F734B8DC1F0595F9001B77F5 /* MenuTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MenuTableViewCell.swift; path = "TicTacToe-iOS/MenuTableViewCell.swift"; sourceTree = SOURCE_ROOT; }; 118 | F734B8DD1F0595F9001B77F5 /* MenuTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; name = MenuTableViewCell.xib; path = "TicTacToe-iOS/MenuTableViewCell.xib"; sourceTree = SOURCE_ROOT; }; 119 | F734B8DE1F0595F9001B77F5 /* MenuViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MenuViewController.swift; path = "TicTacToe-iOS/MenuViewController.swift"; sourceTree = SOURCE_ROOT; }; 120 | F734B8DF1F0595F9001B77F5 /* RoundCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RoundCellView.swift; path = "TicTacToe-iOS/RoundCellView.swift"; sourceTree = SOURCE_ROOT; }; 121 | F734B8EA1F059600001B77F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "TicTacToe-iOS/Base.lproj/LaunchScreen.storyboard"; sourceTree = SOURCE_ROOT; }; 122 | F734B8EC1F059600001B77F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = "TicTacToe-iOS/Base.lproj/Main.storyboard"; sourceTree = SOURCE_ROOT; }; 123 | F77021D61D26EB9D00CEFFF5 /* TTTBoard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TTTBoard.swift; path = Shared/Model/TTTBoard.swift; sourceTree = ""; }; 124 | F77021D71D26EB9D00CEFFF5 /* TTTModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TTTModel.swift; path = Shared/Model/TTTModel.swift; sourceTree = ""; }; 125 | F77021D81D26EB9D00CEFFF5 /* TTTMove.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TTTMove.swift; path = Shared/Model/TTTMove.swift; sourceTree = ""; }; 126 | F77021D91D26EB9D00CEFFF5 /* TTTPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TTTPlayer.swift; path = Shared/Model/TTTPlayer.swift; sourceTree = ""; }; 127 | F77021DE1D26EBB600CEFFF5 /* ButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ButtonNode.swift; path = Shared/Nodes/ButtonNode.swift; sourceTree = ""; }; 128 | F77021DF1D26EBB600CEFFF5 /* GlyphNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GlyphNode.swift; path = Shared/Nodes/GlyphNode.swift; sourceTree = ""; }; 129 | F77021E01D26EBB600CEFFF5 /* PositionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PositionNode.swift; path = Shared/Nodes/PositionNode.swift; sourceTree = ""; }; 130 | F77021F21D26EBF600CEFFF5 /* CheckBoardState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheckBoardState.swift; path = Shared/States/CheckBoardState.swift; sourceTree = ""; }; 131 | F77021F31D26EBF600CEFFF5 /* GameOverState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameOverState.swift; path = Shared/States/GameOverState.swift; sourceTree = ""; }; 132 | F77021F41D26EBF600CEFFF5 /* InPlayStateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InPlayStateMachine.swift; path = Shared/States/InPlayStateMachine.swift; sourceTree = ""; }; 133 | F77021F91D26EBF600CEFFF5 /* SelectNextPlayerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SelectNextPlayerState.swift; path = Shared/States/SelectNextPlayerState.swift; sourceTree = ""; }; 134 | F77022041D26EC6C00CEFFF5 /* GameScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameScene.swift; path = Shared/Scenes/GameScene.swift; sourceTree = ""; }; 135 | F77022051D26EC6C00CEFFF5 /* GameScene+Input.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "GameScene+Input.swift"; path = "Shared/Scenes/GameScene+Input.swift"; sourceTree = ""; }; 136 | F770220E1D26EC9300CEFFF5 /* GameButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameButton.swift; path = Shared/Nodes/GameButton.swift; sourceTree = ""; }; 137 | F770220F1D26EC9300CEFFF5 /* MenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MenuButton.swift; path = Shared/Nodes/MenuButton.swift; sourceTree = ""; }; 138 | F773EDD11D36BBAB00643BE0 /* GameCenterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameCenterController.swift; path = Shared/Multiplayer/GameCenterController.swift; sourceTree = ""; }; 139 | F773EDD21D36BBAB00643BE0 /* MulitpeerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MulitpeerController.swift; path = Shared/Multiplayer/MulitpeerController.swift; sourceTree = ""; }; 140 | F773EDDA1D36BBC300643BE0 /* HexColors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HexColors.swift; path = Shared/Style/HexColors.swift; sourceTree = ""; }; 141 | F773EDDB1D36BBC300643BE0 /* SharedTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SharedTypes.swift; path = Shared/Style/SharedTypes.swift; sourceTree = ""; }; 142 | F773EDDC1D36BBC300643BE0 /* Style.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Style.swift; path = Shared/Style/Style.swift; sourceTree = ""; }; 143 | F773EDE61D36BBD500643BE0 /* GameScene+Private.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "GameScene+Private.swift"; path = "Shared/Scenes/GameScene+Private.swift"; sourceTree = ""; }; 144 | F773EDE81D36BBD500643BE0 /* GameSelectionScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GameSelectionScene.swift; path = Shared/Scenes/GameSelectionScene.swift; sourceTree = ""; }; 145 | F773EDEC1D36BBFD00643BE0 /* InPlayStateType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InPlayStateType.swift; path = Shared/States/InPlayStateType.swift; sourceTree = ""; }; 146 | F773EDED1D36BBFD00643BE0 /* PlayerState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PlayerState.swift; path = Shared/States/PlayerState.swift; sourceTree = ""; }; 147 | F773EDFE1D36ECCE00643BE0 /* ScenePresentationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ScenePresentationDelegate.swift; path = Shared/ScenePresentationDelegate.swift; sourceTree = ""; }; 148 | F786B4DE1D21DF18004706BC /* TicTacToe-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TicTacToe-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 149 | F786B4F51D21DF5B004706BC /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Shared/Assets.xcassets; sourceTree = ""; }; 150 | F786B5131D224218004706BC /* TicTacToe-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TicTacToe-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 151 | F7D071D91D35E1BC0044E7E0 /* GameKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.0.sdk/System/Library/Frameworks/GameKit.framework; sourceTree = DEVELOPER_DIR; }; 152 | F7EE22D71D21D9BF006C6029 /* TicTacToe-macOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "TicTacToe-macOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 153 | /* End PBXFileReference section */ 154 | 155 | /* Begin PBXFrameworksBuildPhase section */ 156 | F786B4DB1D21DF18004706BC /* Frameworks */ = { 157 | isa = PBXFrameworksBuildPhase; 158 | buildActionMask = 2147483647; 159 | files = ( 160 | F7D071DA1D35E1BC0044E7E0 /* GameKit.framework in Frameworks */, 161 | ); 162 | runOnlyForDeploymentPostprocessing = 0; 163 | }; 164 | F786B5101D224218004706BC /* Frameworks */ = { 165 | isa = PBXFrameworksBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | ); 169 | runOnlyForDeploymentPostprocessing = 0; 170 | }; 171 | F7EE22D41D21D9BF006C6029 /* Frameworks */ = { 172 | isa = PBXFrameworksBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXFrameworksBuildPhase section */ 179 | 180 | /* Begin PBXGroup section */ 181 | F773EDD91D36BBB900643BE0 /* Style */ = { 182 | isa = PBXGroup; 183 | children = ( 184 | F773EDDA1D36BBC300643BE0 /* HexColors.swift */, 185 | F773EDDB1D36BBC300643BE0 /* SharedTypes.swift */, 186 | F773EDDC1D36BBC300643BE0 /* Style.swift */, 187 | ); 188 | name = Style; 189 | sourceTree = ""; 190 | }; 191 | F786B4DF1D21DF18004706BC /* TicTacToe-iOS */ = { 192 | isa = PBXGroup; 193 | children = ( 194 | F734B8D71F0595F9001B77F5 /* AppDelegate.swift */, 195 | F734B8D81F0595F9001B77F5 /* GameViewController.swift */, 196 | F734B8DA1F0595F9001B77F5 /* MatchesDataSource.swift */, 197 | F734B8DB1F0595F9001B77F5 /* MatchesViewController.swift */, 198 | F734B8DC1F0595F9001B77F5 /* MenuTableViewCell.swift */, 199 | F734B8DD1F0595F9001B77F5 /* MenuTableViewCell.xib */, 200 | F734B8DE1F0595F9001B77F5 /* MenuViewController.swift */, 201 | F734B8DF1F0595F9001B77F5 /* RoundCellView.swift */, 202 | F734B8E91F059600001B77F5 /* LaunchScreen.storyboard */, 203 | F734B8EB1F059600001B77F5 /* Main.storyboard */, 204 | F734B8D91F0595F9001B77F5 /* Info.plist */, 205 | ); 206 | path = "TicTacToe-iOS"; 207 | sourceTree = ""; 208 | }; 209 | F786B4F41D21DF46004706BC /* Shared */ = { 210 | isa = PBXGroup; 211 | children = ( 212 | F773EDD91D36BBB900643BE0 /* Style */, 213 | F7D071D11D35B18D0044E7E0 /* Multiplayer */, 214 | F7B399BE1D242C93002FEF9A /* Model */, 215 | F7D0F5081D2371A100B6AF6D /* Nodes */, 216 | F786B5081D2238F0004706BC /* States */, 217 | F786B5041D223879004706BC /* Scenes */, 218 | F786B4F51D21DF5B004706BC /* Assets.xcassets */, 219 | ); 220 | name = Shared; 221 | sourceTree = ""; 222 | }; 223 | F786B5041D223879004706BC /* Scenes */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | F77022041D26EC6C00CEFFF5 /* GameScene.swift */, 227 | F773EDE61D36BBD500643BE0 /* GameScene+Private.swift */, 228 | F77022051D26EC6C00CEFFF5 /* GameScene+Input.swift */, 229 | F773EDE81D36BBD500643BE0 /* GameSelectionScene.swift */, 230 | F773EDFE1D36ECCE00643BE0 /* ScenePresentationDelegate.swift */, 231 | ); 232 | name = Scenes; 233 | sourceTree = ""; 234 | }; 235 | F786B5081D2238F0004706BC /* States */ = { 236 | isa = PBXGroup; 237 | children = ( 238 | F773EDEC1D36BBFD00643BE0 /* InPlayStateType.swift */, 239 | F773EDED1D36BBFD00643BE0 /* PlayerState.swift */, 240 | F77021F21D26EBF600CEFFF5 /* CheckBoardState.swift */, 241 | F77021F31D26EBF600CEFFF5 /* GameOverState.swift */, 242 | F77021F41D26EBF600CEFFF5 /* InPlayStateMachine.swift */, 243 | F77021F91D26EBF600CEFFF5 /* SelectNextPlayerState.swift */, 244 | ); 245 | name = States; 246 | sourceTree = ""; 247 | }; 248 | F786B5141D224218004706BC /* TicTacToe-tvOS */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | F734B8CE1F059592001B77F5 /* AppDelegate.swift */, 252 | F734B8CF1F059592001B77F5 /* GameViewController.swift */, 253 | F734B8D41F059599001B77F5 /* Main.storyboard */, 254 | F734B8D01F059592001B77F5 /* Info.plist */, 255 | ); 256 | path = "TicTacToe-tvOS"; 257 | sourceTree = ""; 258 | }; 259 | F7B399BE1D242C93002FEF9A /* Model */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | F77021D61D26EB9D00CEFFF5 /* TTTBoard.swift */, 263 | F77021D71D26EB9D00CEFFF5 /* TTTModel.swift */, 264 | F77021D81D26EB9D00CEFFF5 /* TTTMove.swift */, 265 | F77021D91D26EB9D00CEFFF5 /* TTTPlayer.swift */, 266 | ); 267 | name = Model; 268 | sourceTree = ""; 269 | }; 270 | F7D071D11D35B18D0044E7E0 /* Multiplayer */ = { 271 | isa = PBXGroup; 272 | children = ( 273 | F773EDD11D36BBAB00643BE0 /* GameCenterController.swift */, 274 | F773EDD21D36BBAB00643BE0 /* MulitpeerController.swift */, 275 | ); 276 | name = Multiplayer; 277 | sourceTree = ""; 278 | }; 279 | F7D071D61D35E0A10044E7E0 /* Frameworks */ = { 280 | isa = PBXGroup; 281 | children = ( 282 | F7D071D91D35E1BC0044E7E0 /* GameKit.framework */, 283 | ); 284 | name = Frameworks; 285 | sourceTree = ""; 286 | }; 287 | F7D0F5081D2371A100B6AF6D /* Nodes */ = { 288 | isa = PBXGroup; 289 | children = ( 290 | F770220E1D26EC9300CEFFF5 /* GameButton.swift */, 291 | F770220F1D26EC9300CEFFF5 /* MenuButton.swift */, 292 | F77021DE1D26EBB600CEFFF5 /* ButtonNode.swift */, 293 | F77021DF1D26EBB600CEFFF5 /* GlyphNode.swift */, 294 | F77021E01D26EBB600CEFFF5 /* PositionNode.swift */, 295 | ); 296 | name = Nodes; 297 | sourceTree = ""; 298 | }; 299 | F7EE22CE1D21D9BF006C6029 = { 300 | isa = PBXGroup; 301 | children = ( 302 | F786B4F41D21DF46004706BC /* Shared */, 303 | F786B4DF1D21DF18004706BC /* TicTacToe-iOS */, 304 | F7EE22D91D21D9BF006C6029 /* TicTacToe-macOS */, 305 | F786B5141D224218004706BC /* TicTacToe-tvOS */, 306 | F7EE22D81D21D9BF006C6029 /* Products */, 307 | F7D071D61D35E0A10044E7E0 /* Frameworks */, 308 | ); 309 | sourceTree = ""; 310 | }; 311 | F7EE22D81D21D9BF006C6029 /* Products */ = { 312 | isa = PBXGroup; 313 | children = ( 314 | F7EE22D71D21D9BF006C6029 /* TicTacToe-macOS.app */, 315 | F786B4DE1D21DF18004706BC /* TicTacToe-iOS.app */, 316 | F786B5131D224218004706BC /* TicTacToe-tvOS.app */, 317 | ); 318 | name = Products; 319 | sourceTree = ""; 320 | }; 321 | F7EE22D91D21D9BF006C6029 /* TicTacToe-macOS */ = { 322 | isa = PBXGroup; 323 | children = ( 324 | F734B8CB1F059509001B77F5 /* MainMenu.xib */, 325 | F734B8C71F0594FC001B77F5 /* AppDelegate.swift */, 326 | F734B8C81F0594FC001B77F5 /* Info.plist */, 327 | ); 328 | path = "TicTacToe-macOS"; 329 | sourceTree = ""; 330 | }; 331 | /* End PBXGroup section */ 332 | 333 | /* Begin PBXNativeTarget section */ 334 | F786B4DD1D21DF18004706BC /* TicTacToe-iOS */ = { 335 | isa = PBXNativeTarget; 336 | buildConfigurationList = F786B4F11D21DF18004706BC /* Build configuration list for PBXNativeTarget "TicTacToe-iOS" */; 337 | buildPhases = ( 338 | F786B4DA1D21DF18004706BC /* Sources */, 339 | F786B4DB1D21DF18004706BC /* Frameworks */, 340 | F786B4DC1D21DF18004706BC /* Resources */, 341 | ); 342 | buildRules = ( 343 | ); 344 | dependencies = ( 345 | ); 346 | name = "TicTacToe-iOS"; 347 | productName = "TicTacToad-iOS"; 348 | productReference = F786B4DE1D21DF18004706BC /* TicTacToe-iOS.app */; 349 | productType = "com.apple.product-type.application"; 350 | }; 351 | F786B5121D224218004706BC /* TicTacToe-tvOS */ = { 352 | isa = PBXNativeTarget; 353 | buildConfigurationList = F786B5251D224218004706BC /* Build configuration list for PBXNativeTarget "TicTacToe-tvOS" */; 354 | buildPhases = ( 355 | F786B50F1D224218004706BC /* Sources */, 356 | F786B5101D224218004706BC /* Frameworks */, 357 | F786B5111D224218004706BC /* Resources */, 358 | ); 359 | buildRules = ( 360 | ); 361 | dependencies = ( 362 | ); 363 | name = "TicTacToe-tvOS"; 364 | productName = "TicTacToad-tvOS"; 365 | productReference = F786B5131D224218004706BC /* TicTacToe-tvOS.app */; 366 | productType = "com.apple.product-type.application"; 367 | }; 368 | F7EE22D61D21D9BF006C6029 /* TicTacToe-macOS */ = { 369 | isa = PBXNativeTarget; 370 | buildConfigurationList = F7EE22E81D21D9BF006C6029 /* Build configuration list for PBXNativeTarget "TicTacToe-macOS" */; 371 | buildPhases = ( 372 | F7EE22D31D21D9BF006C6029 /* Sources */, 373 | F7EE22D41D21D9BF006C6029 /* Frameworks */, 374 | F7EE22D51D21D9BF006C6029 /* Resources */, 375 | ); 376 | buildRules = ( 377 | ); 378 | dependencies = ( 379 | ); 380 | name = "TicTacToe-macOS"; 381 | productName = TicTacToad; 382 | productReference = F7EE22D71D21D9BF006C6029 /* TicTacToe-macOS.app */; 383 | productType = "com.apple.product-type.application"; 384 | }; 385 | /* End PBXNativeTarget section */ 386 | 387 | /* Begin PBXProject section */ 388 | F7EE22CF1D21D9BF006C6029 /* Project object */ = { 389 | isa = PBXProject; 390 | attributes = { 391 | LastSwiftUpdateCheck = 0730; 392 | LastUpgradeCheck = 1020; 393 | ORGANIZATIONNAME = "Andrew Shepard"; 394 | TargetAttributes = { 395 | F786B4DD1D21DF18004706BC = { 396 | CreatedOnToolsVersion = 7.3.1; 397 | DevelopmentTeamName = "Andrew Shepard"; 398 | LastSwiftMigration = 1020; 399 | ProvisioningStyle = Manual; 400 | SystemCapabilities = { 401 | com.apple.GameCenter = { 402 | enabled = 1; 403 | }; 404 | }; 405 | }; 406 | F786B5121D224218004706BC = { 407 | CreatedOnToolsVersion = 7.3.1; 408 | }; 409 | F7EE22D61D21D9BF006C6029 = { 410 | CreatedOnToolsVersion = 7.3.1; 411 | LastSwiftMigration = 0800; 412 | }; 413 | }; 414 | }; 415 | buildConfigurationList = F7EE22D21D21D9BF006C6029 /* Build configuration list for PBXProject "TicTacToe" */; 416 | compatibilityVersion = "Xcode 3.2"; 417 | developmentRegion = en; 418 | hasScannedForEncodings = 0; 419 | knownRegions = ( 420 | en, 421 | Base, 422 | ); 423 | mainGroup = F7EE22CE1D21D9BF006C6029; 424 | productRefGroup = F7EE22D81D21D9BF006C6029 /* Products */; 425 | projectDirPath = ""; 426 | projectRoot = ""; 427 | targets = ( 428 | F7EE22D61D21D9BF006C6029 /* TicTacToe-macOS */, 429 | F786B4DD1D21DF18004706BC /* TicTacToe-iOS */, 430 | F786B5121D224218004706BC /* TicTacToe-tvOS */, 431 | ); 432 | }; 433 | /* End PBXProject section */ 434 | 435 | /* Begin PBXResourcesBuildPhase section */ 436 | F786B4DC1D21DF18004706BC /* Resources */ = { 437 | isa = PBXResourcesBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | F734B8E61F0595F9001B77F5 /* MenuTableViewCell.xib in Resources */, 441 | F734B8EE1F059600001B77F5 /* Main.storyboard in Resources */, 442 | F734B8ED1F059600001B77F5 /* LaunchScreen.storyboard in Resources */, 443 | F786B4FB1D21DF5F004706BC /* Assets.xcassets in Resources */, 444 | ); 445 | runOnlyForDeploymentPostprocessing = 0; 446 | }; 447 | F786B5111D224218004706BC /* Resources */ = { 448 | isa = PBXResourcesBuildPhase; 449 | buildActionMask = 2147483647; 450 | files = ( 451 | F734B8D61F059599001B77F5 /* Main.storyboard in Resources */, 452 | F786B52D1D224299004706BC /* Assets.xcassets in Resources */, 453 | ); 454 | runOnlyForDeploymentPostprocessing = 0; 455 | }; 456 | F7EE22D51D21D9BF006C6029 /* Resources */ = { 457 | isa = PBXResourcesBuildPhase; 458 | buildActionMask = 2147483647; 459 | files = ( 460 | F786B4F81D21DF5B004706BC /* Assets.xcassets in Resources */, 461 | F734B8CD1F059509001B77F5 /* MainMenu.xib in Resources */, 462 | ); 463 | runOnlyForDeploymentPostprocessing = 0; 464 | }; 465 | /* End PBXResourcesBuildPhase section */ 466 | 467 | /* Begin PBXSourcesBuildPhase section */ 468 | F786B4DA1D21DF18004706BC /* Sources */ = { 469 | isa = PBXSourcesBuildPhase; 470 | buildActionMask = 2147483647; 471 | files = ( 472 | F77021EB1D26EBC900CEFFF5 /* TTTModel.swift in Sources */, 473 | F773EDF81D36BC2D00643BE0 /* GameSelectionScene.swift in Sources */, 474 | F734B8E31F0595F9001B77F5 /* MatchesDataSource.swift in Sources */, 475 | F770221B1D26EC9E00CEFFF5 /* GameOverState.swift in Sources */, 476 | F77021EC1D26EBC900CEFFF5 /* TTTMove.swift in Sources */, 477 | F77022211D26EC9E00CEFFF5 /* SelectNextPlayerState.swift in Sources */, 478 | F77021ED1D26EBC900CEFFF5 /* TTTPlayer.swift in Sources */, 479 | F734B8E71F0595F9001B77F5 /* MenuViewController.swift in Sources */, 480 | F773EDF01D36BBFF00643BE0 /* InPlayStateType.swift in Sources */, 481 | F773EDD51D36BBAD00643BE0 /* GameCenterController.swift in Sources */, 482 | F734B8E01F0595F9001B77F5 /* AppDelegate.swift in Sources */, 483 | F773EDF11D36BBFF00643BE0 /* PlayerState.swift in Sources */, 484 | F773EDFF1D36ECCE00643BE0 /* ScenePresentationDelegate.swift in Sources */, 485 | F770222C1D26ECA300CEFFF5 /* GameButton.swift in Sources */, 486 | F734B8E41F0595F9001B77F5 /* MatchesViewController.swift in Sources */, 487 | F734B8E51F0595F9001B77F5 /* MenuTableViewCell.swift in Sources */, 488 | F77021E61D26EBC500CEFFF5 /* PositionNode.swift in Sources */, 489 | F734B8E81F0595F9001B77F5 /* RoundCellView.swift in Sources */, 490 | F734B8E11F0595F9001B77F5 /* GameViewController.swift in Sources */, 491 | F773EDE01D36BBC500643BE0 /* HexColors.swift in Sources */, 492 | F773EDFA1D36BC3B00643BE0 /* GameScene+Private.swift in Sources */, 493 | F770222D1D26ECA300CEFFF5 /* MenuButton.swift in Sources */, 494 | F77022171D26EC9A00CEFFF5 /* GameScene+Input.swift in Sources */, 495 | F773EDD61D36BBAD00643BE0 /* MulitpeerController.swift in Sources */, 496 | F77022161D26EC9A00CEFFF5 /* GameScene.swift in Sources */, 497 | F77021E51D26EBC500CEFFF5 /* GlyphNode.swift in Sources */, 498 | F773EDE21D36BBC500643BE0 /* Style.swift in Sources */, 499 | F770221C1D26EC9E00CEFFF5 /* InPlayStateMachine.swift in Sources */, 500 | F773EDE11D36BBC500643BE0 /* SharedTypes.swift in Sources */, 501 | F77021EA1D26EBC900CEFFF5 /* TTTBoard.swift in Sources */, 502 | F77021E41D26EBC500CEFFF5 /* ButtonNode.swift in Sources */, 503 | F770221A1D26EC9E00CEFFF5 /* CheckBoardState.swift in Sources */, 504 | ); 505 | runOnlyForDeploymentPostprocessing = 0; 506 | }; 507 | F786B50F1D224218004706BC /* Sources */ = { 508 | isa = PBXSourcesBuildPhase; 509 | buildActionMask = 2147483647; 510 | files = ( 511 | F77021EF1D26EBC900CEFFF5 /* TTTModel.swift in Sources */, 512 | F773EDF91D36BC2E00643BE0 /* GameSelectionScene.swift in Sources */, 513 | F77022241D26EC9E00CEFFF5 /* GameOverState.swift in Sources */, 514 | F77021F01D26EBC900CEFFF5 /* TTTMove.swift in Sources */, 515 | F734B8D21F059592001B77F5 /* GameViewController.swift in Sources */, 516 | F770222A1D26EC9E00CEFFF5 /* SelectNextPlayerState.swift in Sources */, 517 | F77021F11D26EBC900CEFFF5 /* TTTPlayer.swift in Sources */, 518 | F773EDF21D36BBFF00643BE0 /* InPlayStateType.swift in Sources */, 519 | F773EDD71D36BBAE00643BE0 /* GameCenterController.swift in Sources */, 520 | F77021E91D26EBC500CEFFF5 /* PositionNode.swift in Sources */, 521 | F773EDF31D36BBFF00643BE0 /* PlayerState.swift in Sources */, 522 | F770222E1D26ECA400CEFFF5 /* GameButton.swift in Sources */, 523 | F770222F1D26ECA400CEFFF5 /* MenuButton.swift in Sources */, 524 | F773EDE31D36BBC500643BE0 /* HexColors.swift in Sources */, 525 | F734B8D11F059592001B77F5 /* AppDelegate.swift in Sources */, 526 | F773EDFB1D36BC3D00643BE0 /* GameScene+Private.swift in Sources */, 527 | F77022131D26EC9A00CEFFF5 /* GameScene+Input.swift in Sources */, 528 | F773EDD81D36BBAE00643BE0 /* MulitpeerController.swift in Sources */, 529 | F77022121D26EC9A00CEFFF5 /* GameScene.swift in Sources */, 530 | F77021E81D26EBC500CEFFF5 /* GlyphNode.swift in Sources */, 531 | F773EDE51D36BBC500643BE0 /* Style.swift in Sources */, 532 | F77022251D26EC9E00CEFFF5 /* InPlayStateMachine.swift in Sources */, 533 | F773EDE41D36BBC500643BE0 /* SharedTypes.swift in Sources */, 534 | F77021EE1D26EBC900CEFFF5 /* TTTBoard.swift in Sources */, 535 | F77021E71D26EBC500CEFFF5 /* ButtonNode.swift in Sources */, 536 | F77022231D26EC9E00CEFFF5 /* CheckBoardState.swift in Sources */, 537 | ); 538 | runOnlyForDeploymentPostprocessing = 0; 539 | }; 540 | F7EE22D31D21D9BF006C6029 /* Sources */ = { 541 | isa = PBXSourcesBuildPhase; 542 | buildActionMask = 2147483647; 543 | files = ( 544 | F773EDEE1D36BBFD00643BE0 /* InPlayStateType.swift in Sources */, 545 | F773EDEB1D36BBD500643BE0 /* GameSelectionScene.swift in Sources */, 546 | F773EDDE1D36BBC300643BE0 /* SharedTypes.swift in Sources */, 547 | F77021E11D26EBB600CEFFF5 /* ButtonNode.swift in Sources */, 548 | F77021DB1D26EB9D00CEFFF5 /* TTTModel.swift in Sources */, 549 | F770220A1D26EC6C00CEFFF5 /* GameScene+Input.swift in Sources */, 550 | F77021DC1D26EB9D00CEFFF5 /* TTTMove.swift in Sources */, 551 | F77022021D26EBF600CEFFF5 /* SelectNextPlayerState.swift in Sources */, 552 | F773EDEF1D36BBFD00643BE0 /* PlayerState.swift in Sources */, 553 | F77021FD1D26EBF600CEFFF5 /* InPlayStateMachine.swift in Sources */, 554 | F77021E31D26EBB600CEFFF5 /* PositionNode.swift in Sources */, 555 | F773EDDD1D36BBC300643BE0 /* HexColors.swift in Sources */, 556 | F77021FC1D26EBF600CEFFF5 /* GameOverState.swift in Sources */, 557 | F734B8C91F0594FC001B77F5 /* AppDelegate.swift in Sources */, 558 | F77022091D26EC6C00CEFFF5 /* GameScene.swift in Sources */, 559 | F77022101D26EC9300CEFFF5 /* GameButton.swift in Sources */, 560 | F773EDE91D36BBD500643BE0 /* GameScene+Private.swift in Sources */, 561 | F773EDDF1D36BBC300643BE0 /* Style.swift in Sources */, 562 | F77021DA1D26EB9D00CEFFF5 /* TTTBoard.swift in Sources */, 563 | F773EDD41D36BBAB00643BE0 /* MulitpeerController.swift in Sources */, 564 | F77021DD1D26EB9D00CEFFF5 /* TTTPlayer.swift in Sources */, 565 | F77021E21D26EBB600CEFFF5 /* GlyphNode.swift in Sources */, 566 | F77022111D26EC9300CEFFF5 /* MenuButton.swift in Sources */, 567 | F73573521D98427200C6EA7D /* ScenePresentationDelegate.swift in Sources */, 568 | F77021FB1D26EBF600CEFFF5 /* CheckBoardState.swift in Sources */, 569 | ); 570 | runOnlyForDeploymentPostprocessing = 0; 571 | }; 572 | /* End PBXSourcesBuildPhase section */ 573 | 574 | /* Begin PBXVariantGroup section */ 575 | F734B8CB1F059509001B77F5 /* MainMenu.xib */ = { 576 | isa = PBXVariantGroup; 577 | children = ( 578 | F734B8CC1F059509001B77F5 /* Base */, 579 | ); 580 | name = MainMenu.xib; 581 | sourceTree = ""; 582 | }; 583 | F734B8D41F059599001B77F5 /* Main.storyboard */ = { 584 | isa = PBXVariantGroup; 585 | children = ( 586 | F734B8D51F059599001B77F5 /* Base */, 587 | ); 588 | name = Main.storyboard; 589 | sourceTree = ""; 590 | }; 591 | F734B8E91F059600001B77F5 /* LaunchScreen.storyboard */ = { 592 | isa = PBXVariantGroup; 593 | children = ( 594 | F734B8EA1F059600001B77F5 /* Base */, 595 | ); 596 | name = LaunchScreen.storyboard; 597 | sourceTree = ""; 598 | }; 599 | F734B8EB1F059600001B77F5 /* Main.storyboard */ = { 600 | isa = PBXVariantGroup; 601 | children = ( 602 | F734B8EC1F059600001B77F5 /* Base */, 603 | ); 604 | name = Main.storyboard; 605 | sourceTree = ""; 606 | }; 607 | /* End PBXVariantGroup section */ 608 | 609 | /* Begin XCBuildConfiguration section */ 610 | F786B4F21D21DF18004706BC /* Debug */ = { 611 | isa = XCBuildConfiguration; 612 | buildSettings = { 613 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 614 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 615 | CODE_SIGN_STYLE = Manual; 616 | DEVELOPMENT_TEAM = ""; 617 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-iOS/Info.plist"; 618 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 619 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 620 | PRODUCT_BUNDLE_IDENTIFIER = "org.andyshep.TicTacToad-iOS"; 621 | PRODUCT_NAME = "$(TARGET_NAME)"; 622 | PROVISIONING_PROFILE = ""; 623 | PROVISIONING_PROFILE_SPECIFIER = ""; 624 | SDKROOT = iphoneos; 625 | SWIFT_VERSION = 5.0; 626 | TARGETED_DEVICE_FAMILY = "1,2"; 627 | }; 628 | name = Debug; 629 | }; 630 | F786B4F31D21DF18004706BC /* Release */ = { 631 | isa = XCBuildConfiguration; 632 | buildSettings = { 633 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 634 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 635 | CODE_SIGN_STYLE = Manual; 636 | DEVELOPMENT_TEAM = ""; 637 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-iOS/Info.plist"; 638 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 639 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 640 | PRODUCT_BUNDLE_IDENTIFIER = "org.andyshep.TicTacToad-iOS"; 641 | PRODUCT_NAME = "$(TARGET_NAME)"; 642 | PROVISIONING_PROFILE = ""; 643 | PROVISIONING_PROFILE_SPECIFIER = ""; 644 | SDKROOT = iphoneos; 645 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 646 | SWIFT_VERSION = 5.0; 647 | TARGETED_DEVICE_FAMILY = "1,2"; 648 | VALIDATE_PRODUCT = YES; 649 | }; 650 | name = Release; 651 | }; 652 | F786B5261D224218004706BC /* Debug */ = { 653 | isa = XCBuildConfiguration; 654 | buildSettings = { 655 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 656 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 657 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-tvOS/Info.plist"; 658 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 659 | PRODUCT_BUNDLE_IDENTIFIER = "org.andyshep.TicTacToad-tvOS"; 660 | PRODUCT_NAME = "$(TARGET_NAME)"; 661 | SDKROOT = appletvos; 662 | SWIFT_VERSION = 4.0; 663 | TARGETED_DEVICE_FAMILY = 3; 664 | TVOS_DEPLOYMENT_TARGET = 10.2; 665 | }; 666 | name = Debug; 667 | }; 668 | F786B5271D224218004706BC /* Release */ = { 669 | isa = XCBuildConfiguration; 670 | buildSettings = { 671 | ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image"; 672 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 673 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-tvOS/Info.plist"; 674 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 675 | PRODUCT_BUNDLE_IDENTIFIER = "org.andyshep.TicTacToad-tvOS"; 676 | PRODUCT_NAME = "$(TARGET_NAME)"; 677 | SDKROOT = appletvos; 678 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 679 | SWIFT_VERSION = 4.0; 680 | TARGETED_DEVICE_FAMILY = 3; 681 | TVOS_DEPLOYMENT_TARGET = 10.2; 682 | VALIDATE_PRODUCT = YES; 683 | }; 684 | name = Release; 685 | }; 686 | F7EE22E61D21D9BF006C6029 /* Debug */ = { 687 | isa = XCBuildConfiguration; 688 | buildSettings = { 689 | ALWAYS_SEARCH_USER_PATHS = NO; 690 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 691 | CLANG_ANALYZER_NONNULL = YES; 692 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 693 | CLANG_CXX_LIBRARY = "libc++"; 694 | CLANG_ENABLE_MODULES = YES; 695 | CLANG_ENABLE_OBJC_ARC = YES; 696 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 697 | CLANG_WARN_BOOL_CONVERSION = YES; 698 | CLANG_WARN_COMMA = YES; 699 | CLANG_WARN_CONSTANT_CONVERSION = YES; 700 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 701 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 702 | CLANG_WARN_EMPTY_BODY = YES; 703 | CLANG_WARN_ENUM_CONVERSION = YES; 704 | CLANG_WARN_INFINITE_RECURSION = YES; 705 | CLANG_WARN_INT_CONVERSION = YES; 706 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 707 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 708 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 709 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 710 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 711 | CLANG_WARN_STRICT_PROTOTYPES = YES; 712 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 713 | CLANG_WARN_UNREACHABLE_CODE = YES; 714 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 715 | CODE_SIGN_IDENTITY = "-"; 716 | COPY_PHASE_STRIP = NO; 717 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 718 | ENABLE_STRICT_OBJC_MSGSEND = YES; 719 | ENABLE_TESTABILITY = YES; 720 | GCC_C_LANGUAGE_STANDARD = gnu99; 721 | GCC_DYNAMIC_NO_PIC = NO; 722 | GCC_NO_COMMON_BLOCKS = YES; 723 | GCC_OPTIMIZATION_LEVEL = 0; 724 | GCC_PREPROCESSOR_DEFINITIONS = ( 725 | "DEBUG=1", 726 | "$(inherited)", 727 | ); 728 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 729 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 730 | GCC_WARN_UNDECLARED_SELECTOR = YES; 731 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 732 | GCC_WARN_UNUSED_FUNCTION = YES; 733 | GCC_WARN_UNUSED_VARIABLE = YES; 734 | MACOSX_DEPLOYMENT_TARGET = 10.11; 735 | MTL_ENABLE_DEBUG_INFO = YES; 736 | ONLY_ACTIVE_ARCH = YES; 737 | SDKROOT = macosx; 738 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 739 | SWIFT_VERSION = 4.0; 740 | }; 741 | name = Debug; 742 | }; 743 | F7EE22E71D21D9BF006C6029 /* Release */ = { 744 | isa = XCBuildConfiguration; 745 | buildSettings = { 746 | ALWAYS_SEARCH_USER_PATHS = NO; 747 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 748 | CLANG_ANALYZER_NONNULL = YES; 749 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 750 | CLANG_CXX_LIBRARY = "libc++"; 751 | CLANG_ENABLE_MODULES = YES; 752 | CLANG_ENABLE_OBJC_ARC = YES; 753 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 754 | CLANG_WARN_BOOL_CONVERSION = YES; 755 | CLANG_WARN_COMMA = YES; 756 | CLANG_WARN_CONSTANT_CONVERSION = YES; 757 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 758 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 759 | CLANG_WARN_EMPTY_BODY = YES; 760 | CLANG_WARN_ENUM_CONVERSION = YES; 761 | CLANG_WARN_INFINITE_RECURSION = YES; 762 | CLANG_WARN_INT_CONVERSION = YES; 763 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 764 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 765 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 766 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 767 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 768 | CLANG_WARN_STRICT_PROTOTYPES = YES; 769 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 770 | CLANG_WARN_UNREACHABLE_CODE = YES; 771 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 772 | CODE_SIGN_IDENTITY = "-"; 773 | COPY_PHASE_STRIP = NO; 774 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 775 | ENABLE_NS_ASSERTIONS = NO; 776 | ENABLE_STRICT_OBJC_MSGSEND = YES; 777 | GCC_C_LANGUAGE_STANDARD = gnu99; 778 | GCC_NO_COMMON_BLOCKS = YES; 779 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 780 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 781 | GCC_WARN_UNDECLARED_SELECTOR = YES; 782 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 783 | GCC_WARN_UNUSED_FUNCTION = YES; 784 | GCC_WARN_UNUSED_VARIABLE = YES; 785 | MACOSX_DEPLOYMENT_TARGET = 10.11; 786 | MTL_ENABLE_DEBUG_INFO = NO; 787 | SDKROOT = macosx; 788 | SWIFT_VERSION = 4.0; 789 | }; 790 | name = Release; 791 | }; 792 | F7EE22E91D21D9BF006C6029 /* Debug */ = { 793 | isa = XCBuildConfiguration; 794 | buildSettings = { 795 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 796 | CLANG_ENABLE_MODULES = YES; 797 | COMBINE_HIDPI_IMAGES = YES; 798 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-macOS/Info.plist"; 799 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 800 | MACOSX_DEPLOYMENT_TARGET = 10.12; 801 | PRODUCT_BUNDLE_IDENTIFIER = org.andyshep.TicTacToad; 802 | PRODUCT_NAME = "$(TARGET_NAME)"; 803 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 804 | SWIFT_VERSION = 4.0; 805 | }; 806 | name = Debug; 807 | }; 808 | F7EE22EA1D21D9BF006C6029 /* Release */ = { 809 | isa = XCBuildConfiguration; 810 | buildSettings = { 811 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 812 | CLANG_ENABLE_MODULES = YES; 813 | COMBINE_HIDPI_IMAGES = YES; 814 | INFOPLIST_FILE = "$(SRCROOT)/TicTacToe-macOS/Info.plist"; 815 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; 816 | MACOSX_DEPLOYMENT_TARGET = 10.12; 817 | PRODUCT_BUNDLE_IDENTIFIER = org.andyshep.TicTacToad; 818 | PRODUCT_NAME = "$(TARGET_NAME)"; 819 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 820 | SWIFT_VERSION = 4.0; 821 | }; 822 | name = Release; 823 | }; 824 | /* End XCBuildConfiguration section */ 825 | 826 | /* Begin XCConfigurationList section */ 827 | F786B4F11D21DF18004706BC /* Build configuration list for PBXNativeTarget "TicTacToe-iOS" */ = { 828 | isa = XCConfigurationList; 829 | buildConfigurations = ( 830 | F786B4F21D21DF18004706BC /* Debug */, 831 | F786B4F31D21DF18004706BC /* Release */, 832 | ); 833 | defaultConfigurationIsVisible = 0; 834 | defaultConfigurationName = Release; 835 | }; 836 | F786B5251D224218004706BC /* Build configuration list for PBXNativeTarget "TicTacToe-tvOS" */ = { 837 | isa = XCConfigurationList; 838 | buildConfigurations = ( 839 | F786B5261D224218004706BC /* Debug */, 840 | F786B5271D224218004706BC /* Release */, 841 | ); 842 | defaultConfigurationIsVisible = 0; 843 | defaultConfigurationName = Release; 844 | }; 845 | F7EE22D21D21D9BF006C6029 /* Build configuration list for PBXProject "TicTacToe" */ = { 846 | isa = XCConfigurationList; 847 | buildConfigurations = ( 848 | F7EE22E61D21D9BF006C6029 /* Debug */, 849 | F7EE22E71D21D9BF006C6029 /* Release */, 850 | ); 851 | defaultConfigurationIsVisible = 0; 852 | defaultConfigurationName = Release; 853 | }; 854 | F7EE22E81D21D9BF006C6029 /* Build configuration list for PBXNativeTarget "TicTacToe-macOS" */ = { 855 | isa = XCConfigurationList; 856 | buildConfigurations = ( 857 | F7EE22E91D21D9BF006C6029 /* Debug */, 858 | F7EE22EA1D21D9BF006C6029 /* Release */, 859 | ); 860 | defaultConfigurationIsVisible = 0; 861 | defaultConfigurationName = Release; 862 | }; 863 | /* End XCConfigurationList section */ 864 | }; 865 | rootObject = F7EE22CF1D21D9BF006C6029 /* Project object */; 866 | } 867 | --------------------------------------------------------------------------------