├── TicTacToe
├── State
│ ├── Tile.swift
│ ├── GameState.swift
│ ├── Player.swift
│ └── Board.swift
├── Assets.xcassets
│ ├── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Input
│ └── GameInput.swift
├── Component
│ └── GameComponent.swift
├── View
│ ├── DrawView.swift
│ ├── WinnerView.swift
│ ├── PlayingView.swift
│ ├── GameView.swift
│ ├── TileButtonStyle.swift
│ ├── TileView.swift
│ └── BoardView.swift
├── Dispatcher
│ └── GameDispatcher.swift
├── AppDelegate.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Info.plist
└── SceneDelegate.swift
├── README.md
├── TicTacToe.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── xcuserdata
│ └── tomasruizlopez.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
└── TicTacToeTests
├── Info.plist
└── TicTacToeTests.swift
/TicTacToe/State/Tile.swift:
--------------------------------------------------------------------------------
1 | enum Tile: Equatable {
2 | case empty
3 | case used(Player)
4 | }
5 |
--------------------------------------------------------------------------------
/TicTacToe/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TicTacToe/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/TicTacToe/Input/GameInput.swift:
--------------------------------------------------------------------------------
1 | enum GameInput {
2 | case tapTile(Board.VerticalPosition, Board.HorizontalPosition)
3 | case tapPlayAgain
4 | }
5 |
--------------------------------------------------------------------------------
/TicTacToe/State/GameState.swift:
--------------------------------------------------------------------------------
1 | enum GameState {
2 | case playing(Board, turn: Player)
3 | case finished(winner: Player)
4 | case draw
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Tic Tac Toe
2 |
3 | This repository contains a sample app with the game Tic Tac Toe implemented using SwiftUI and Bow Arch. For more information about the library, check [its documentation](https://arch.bow-swift.io).
4 |
--------------------------------------------------------------------------------
/TicTacToe.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TicTacToe/State/Player.swift:
--------------------------------------------------------------------------------
1 | enum Player: String {
2 | case one = "Player one"
3 | case two = "Player two"
4 |
5 | var other: Player {
6 | (self == .one)
7 | ? .two
8 | : .one
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/TicTacToe/Component/GameComponent.swift:
--------------------------------------------------------------------------------
1 | import BowArch
2 |
3 | typealias GameComponent = StoreComponent
4 |
5 | let gameComponent = GameComponent(
6 | initialState: .playing(Board(), turn: .one),
7 | dispatcher: gameDispatcher,
8 | render: GameView.init)
9 |
--------------------------------------------------------------------------------
/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/TicTacToe/View/DrawView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct DrawView: View {
4 | let handle: (GameInput) -> Void
5 |
6 | var body: some View {
7 | VStack {
8 | Spacer()
9 |
10 | Text("It's a draw!")
11 |
12 | Button("Play again") {
13 | self.handle(.tapPlayAgain)
14 | }
15 |
16 | Spacer()
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/TicTacToe.xcodeproj/xcuserdata/tomasruizlopez.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | TicTacToe.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/TicTacToe/View/WinnerView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct WinnerView: View {
4 | let winner: Player
5 | let handle: (GameInput) -> Void
6 |
7 | var body: some View {
8 | VStack {
9 | Spacer()
10 |
11 | Text("\(winner.rawValue) won the game!")
12 |
13 | Button("Play again") {
14 | self.handle(.tapPlayAgain)
15 | }
16 |
17 | Spacer()
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TicTacToe/View/PlayingView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct PlayingView: View {
4 | let board: Board
5 | let turn: Player
6 | let handle: (GameInput) -> Void
7 |
8 | var body: some View {
9 | VStack {
10 | Spacer()
11 |
12 | BoardView(board: board, handle: self.handle)
13 | .aspectRatio(1, contentMode: .fit)
14 | .padding()
15 |
16 | Spacer()
17 |
18 | Text("It's \(turn.rawValue)'s turn.")
19 | .padding()
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/TicTacToe/View/GameView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct GameView: View {
4 | let state: GameState
5 | let handle: (GameInput) -> Void
6 |
7 | var body: some View {
8 | switch state {
9 |
10 | case let .playing(board, turn: turn):
11 | return AnyView(PlayingView(board: board, turn: turn, handle: handle))
12 |
13 | case .finished(winner: let winner):
14 | return AnyView(WinnerView(winner: winner, handle: handle))
15 |
16 | case .draw:
17 | return AnyView(DrawView(handle: handle))
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/TicTacToeTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/TicTacToe/View/TileButtonStyle.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct TileButtonStyle: ButtonStyle {
4 | func makeBody(configuration: Configuration) -> some View {
5 | ZStack {
6 | configuration.label
7 | }
8 | .aspectRatio(1, contentMode: .fill)
9 | .frame(maxWidth: .infinity,
10 | maxHeight: .infinity)
11 | .background(
12 | configuration.isPressed
13 | ? Color.gray.opacity(0.3)
14 | : Color.gray.opacity(0.15))
15 | .mask(RoundedRectangle(cornerRadius: 8))
16 | .contentShape(RoundedRectangle(cornerRadius: 8))
17 | .shadow(
18 | color: Color.black.opacity(0.2),
19 | radius: configuration.isPressed ? 1 : 3,
20 | x: 1,
21 | y: 1)
22 | }
23 | }
24 |
25 | extension View {
26 | func tileButtonStyle() -> some View {
27 | self.buttonStyle(TileButtonStyle())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/TicTacToeTests/TicTacToeTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TicTacToeTests.swift
3 | // TicTacToeTests
4 | //
5 | // Created by Tomás Ruiz López on 20/05/2020.
6 | // Copyright © 2020 Tomás Ruiz López. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import TicTacToe
11 |
12 | class TicTacToeTests: XCTestCase {
13 |
14 | override func setUpWithError() throws {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDownWithError() throws {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() throws {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() throws {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/TicTacToe/Dispatcher/GameDispatcher.swift:
--------------------------------------------------------------------------------
1 | import Bow
2 | import BowArch
3 |
4 | typealias GameDispatcher = StateDispatcher
5 |
6 | let gameDispatcher = GameDispatcher.pure { input in
7 | switch input {
8 | case let .tapTile(x, y):
9 | return tapTile(x, y)
10 | case .tapPlayAgain:
11 | return newGame()
12 | }
13 | }
14 |
15 | func tapTile(
16 | _ x: Board.VerticalPosition,
17 | _ y: Board.HorizontalPosition
18 | ) -> State {
19 | .modify { state in
20 | switch state {
21 | case let .playing(board, turn: turn):
22 | if board[x, y] == .empty {
23 | let newBoard = board.set(value: .used(turn), at: x, y)
24 | if let winner = newBoard.checkWinner() {
25 | return .finished(winner: winner)
26 | } else if !newBoard.hasEmptySpaces {
27 | return .draw
28 | } else {
29 | return .playing(newBoard, turn: turn.other)
30 | }
31 | } else {
32 | return state
33 | }
34 | case .finished, .draw:
35 | return state
36 | }
37 | }^
38 | }
39 |
40 | func newGame() -> State {
41 | .set(
42 | GameState.playing(Board(), turn: .one)
43 | )^
44 | }
45 |
--------------------------------------------------------------------------------
/TicTacToe.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Bow",
6 | "repositoryURL": "https://github.com/bow-swift/bow.git",
7 | "state": {
8 | "branch": "master",
9 | "revision": "ee58f517ac6ac5a31fe69b6919e37536319329a3",
10 | "version": null
11 | }
12 | },
13 | {
14 | "package": "BowArch",
15 | "repositoryURL": "https://github.com/bow-swift/bow-arch.git",
16 | "state": {
17 | "branch": "master",
18 | "revision": "506646dd859ba11a8271d75267103f1fdfdd47b2",
19 | "version": null
20 | }
21 | },
22 | {
23 | "package": "RxSwift",
24 | "repositoryURL": "https://github.com/ReactiveX/RxSwift.git",
25 | "state": {
26 | "branch": null,
27 | "revision": "002d325b0bdee94e7882e1114af5ff4fe1e96afa",
28 | "version": "5.1.1"
29 | }
30 | },
31 | {
32 | "package": "SwiftCheck",
33 | "repositoryURL": "https://github.com/bow-swift/SwiftCheck.git",
34 | "state": {
35 | "branch": null,
36 | "revision": "748359f9a95edf94d0c4664102f104f56b1ff1fb",
37 | "version": "0.12.1"
38 | }
39 | }
40 | ]
41 | },
42 | "version": 1
43 | }
44 |
--------------------------------------------------------------------------------
/TicTacToe/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // TicTacToe
4 | //
5 | // Created by Tomás Ruiz López on 20/05/2020.
6 | // Copyright © 2020 Tomás Ruiz López. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/TicTacToe/View/TileView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct TileView: View {
4 | let tile: Tile
5 |
6 | var body: some View {
7 | Group {
8 | if tile == .empty {
9 | EmptyView()
10 | } else {
11 | Image(systemName: tile.imageName)
12 | .font(.largeTitle)
13 | .foregroundColor(tile.color)
14 | }
15 | }
16 | }
17 | }
18 |
19 | private extension Tile {
20 | var imageName: String {
21 | switch self {
22 | case .empty: return ""
23 | case .used(let player): return player.imageName
24 | }
25 | }
26 |
27 | var color: Color {
28 | switch self {
29 | case .empty: return .clear
30 | case .used(let player): return player.color
31 | }
32 | }
33 | }
34 |
35 | private extension Player {
36 | var imageName: String {
37 | switch self {
38 | case .one: return "circle"
39 | case .two: return "xmark"
40 | }
41 | }
42 |
43 | var color: Color {
44 | switch self {
45 | case .one: return .red
46 | case .two: return .blue
47 | }
48 | }
49 | }
50 |
51 | #if DEBUG
52 | struct TileView_Previews: PreviewProvider {
53 | static var previews: some View {
54 | Group {
55 | TileView(tile: .empty)
56 |
57 | TileView(tile: .used(.one))
58 |
59 | TileView(tile: .used(.two))
60 | }.previewLayout(.fixed(width: 80, height: 80))
61 | }
62 | }
63 | #endif
64 |
--------------------------------------------------------------------------------
/TicTacToe/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 |
--------------------------------------------------------------------------------
/TicTacToe/View/BoardView.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 |
3 | struct BoardView: View {
4 | let board: Board
5 | let handle: (GameInput) -> Void
6 |
7 | var body: some View {
8 | VStack {
9 | self.row(at: .top)
10 | self.row(at: .middle)
11 | self.row(at: .bottom)
12 | }
13 | }
14 |
15 | var isOver: Bool {
16 | return false
17 | }
18 |
19 | private func row(at x: Board.VerticalPosition) -> some View {
20 | HStack {
21 | self.buttonForTile(at: x, .leading)
22 | self.buttonForTile(at: x, .center)
23 | self.buttonForTile(at: x, .trailing)
24 | }
25 | }
26 |
27 | private func buttonForTile(
28 | at x: Board.VerticalPosition,
29 | _ y: Board.HorizontalPosition) -> some View {
30 | Button(action: { self.handle(.tapTile(x, y)) }) {
31 | TileView(tile: board[x, y])
32 | }
33 | .tileButtonStyle()
34 | }
35 | }
36 |
37 | #if DEBUG
38 | struct BoardView_Previews: PreviewProvider {
39 | static let emptyBoard = Board()
40 | static let midGameBoard = Board {
41 | row {
42 | Tile.empty
43 | Tile.used(.one)
44 | Tile.empty
45 | }
46 | row {
47 | Tile.used(.one)
48 | Tile.used(.two)
49 | Tile.used(.two)
50 | }
51 | row {
52 | Tile.empty
53 | Tile.empty
54 | Tile.used(.one)
55 | }
56 | }
57 |
58 | static var previews: some View {
59 | Group {
60 | BoardView(board: emptyBoard) { _ in }
61 |
62 | BoardView(board: midGameBoard) { _ in }
63 | }.padding()
64 | .previewLayout(.fixed(width: 300, height: 300))
65 | }
66 | }
67 | #endif
68 |
--------------------------------------------------------------------------------
/TicTacToe/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/TicTacToe/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 |
37 |
38 |
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UISupportedInterfaceOrientations~ipad
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationPortraitUpsideDown
56 | UIInterfaceOrientationLandscapeLeft
57 | UIInterfaceOrientationLandscapeRight
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/TicTacToe/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // TicTacToe
4 | //
5 | // Created by Tomás Ruiz López on 20/05/2020.
6 | // Copyright © 2020 Tomás Ruiz López. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import SwiftUI
11 |
12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
21 |
22 | // Create the SwiftUI view that provides the window contents.
23 | let contentView = gameComponent
24 |
25 | // Use a UIHostingController as window root view controller.
26 | if let windowScene = scene as? UIWindowScene {
27 | let window = UIWindow(windowScene: windowScene)
28 | window.rootViewController = UIHostingController(rootView: contentView)
29 | self.window = window
30 | window.makeKeyAndVisible()
31 | }
32 | }
33 |
34 | func sceneDidDisconnect(_ scene: UIScene) {
35 | // Called as the scene is being released by the system.
36 | // This occurs shortly after the scene enters the background, or when its session is discarded.
37 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
39 | }
40 |
41 | func sceneDidBecomeActive(_ scene: UIScene) {
42 | // Called when the scene has moved from an inactive state to an active state.
43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
44 | }
45 |
46 | func sceneWillResignActive(_ scene: UIScene) {
47 | // Called when the scene will move from an active state to an inactive state.
48 | // This may occur due to temporary interruptions (ex. an incoming phone call).
49 | }
50 |
51 | func sceneWillEnterForeground(_ scene: UIScene) {
52 | // Called as the scene transitions from the background to the foreground.
53 | // Use this method to undo the changes made on entering the background.
54 | }
55 |
56 | func sceneDidEnterBackground(_ scene: UIScene) {
57 | // Called as the scene transitions from the foreground to the background.
58 | // Use this method to save data, release shared resources, and store enough scene-specific state information
59 | // to restore the scene back to its current state.
60 | }
61 |
62 |
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/TicTacToe/State/Board.swift:
--------------------------------------------------------------------------------
1 | struct Board {
2 | enum VerticalPosition: Int {
3 | case top = 0
4 | case middle = 1
5 | case bottom = 2
6 | }
7 |
8 | enum HorizontalPosition: Int {
9 | case leading = 0
10 | case center = 1
11 | case trailing = 2
12 | }
13 |
14 | private let grid: [[Tile]]
15 |
16 | init() {
17 | self.grid = Array(
18 | repeating: Array(repeating: .empty, count: 3),
19 | count: 3)
20 | }
21 |
22 | init(@BoardBuilder builder: () -> [[Tile]]) {
23 | self.grid = builder()
24 | }
25 |
26 | fileprivate init(grid: [[Tile]]) {
27 | self.grid = grid
28 | }
29 |
30 | subscript(_ x: VerticalPosition, _ y: HorizontalPosition) -> Tile {
31 | self.grid[x.rawValue][y.rawValue]
32 | }
33 |
34 | func set(value: Tile, at x: VerticalPosition, _ y: HorizontalPosition) -> Board {
35 | var newGrid = self.grid
36 | newGrid[x.rawValue][y.rawValue] = value
37 | return Board(grid: newGrid)
38 | }
39 |
40 | func checkWinner() -> Player? {
41 | if checkWinningPositions(for: .one) {
42 | return .one
43 | } else if checkWinningPositions(for: .two) {
44 | return .two
45 | } else {
46 | return nil
47 | }
48 | }
49 |
50 | var hasEmptySpaces: Bool {
51 | grid.map { row in row.contains(.empty) }.combineAll()
52 | }
53 |
54 | private func checkWinningPositions(for player: Player) -> Bool {
55 | let winningPositions: [[(VerticalPosition, HorizontalPosition)]] = [
56 | [(.top, .leading), (.top, .center), (.top, .trailing)],
57 | [(.middle, .leading), (.middle, .center), (.middle, .trailing)],
58 | [(.bottom, .leading), (.bottom, .center), (.bottom, .trailing)],
59 |
60 | [(.top, .leading), (.middle, .leading), (.bottom, .leading)],
61 | [(.top, .center), (.middle, .center), (.bottom, .center)],
62 | [(.top, .trailing), (.middle, .trailing), (.bottom, .trailing)],
63 |
64 | [(.top, .leading), (.middle, .center), (.bottom, .trailing)],
65 | [(.top, .trailing), (.middle, .center), (.bottom, .leading)]
66 | ]
67 |
68 | return winningPositions.map { positions in
69 | positions.map { coordinates in
70 | self[coordinates.0, coordinates.1]
71 | }.allSatisfy { tile in tile == .used(player) }
72 | }.first { $0 == true } ?? false
73 | }
74 | }
75 |
76 | @_functionBuilder
77 | struct RowBuilder {
78 | static func buildBlock(
79 | _ leading: Tile,
80 | _ center: Tile,
81 | _ trailing: Tile) -> [Tile] {
82 | [leading, center, trailing]
83 | }
84 | }
85 |
86 | func row(@RowBuilder f: () -> [Tile]) -> [Tile] {
87 | f()
88 | }
89 |
90 | @_functionBuilder
91 | struct BoardBuilder {
92 | static func buildBlock(
93 | _ top: [Tile],
94 | _ middle: [Tile],
95 | _ bottom: [Tile]) -> [[Tile]] {
96 | [top, middle, bottom]
97 | }
98 | }
99 |
100 |
--------------------------------------------------------------------------------
/TicTacToe.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1156EC862475336000C78B96 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156EC852475336000C78B96 /* AppDelegate.swift */; };
11 | 1156EC882475336000C78B96 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156EC872475336000C78B96 /* SceneDelegate.swift */; };
12 | 1156EC8C2475336100C78B96 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1156EC8B2475336100C78B96 /* Assets.xcassets */; };
13 | 1156EC8F2475336100C78B96 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1156EC8E2475336100C78B96 /* Preview Assets.xcassets */; };
14 | 1156EC922475336100C78B96 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1156EC902475336100C78B96 /* LaunchScreen.storyboard */; };
15 | 1156EC9D2475336100C78B96 /* TicTacToeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156EC9C2475336100C78B96 /* TicTacToeTests.swift */; };
16 | 1156ECA92475341900C78B96 /* BowArch in Frameworks */ = {isa = PBXBuildFile; productRef = 1156ECA82475341900C78B96 /* BowArch */; };
17 | 1156ECAC2475343400C78B96 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECAB2475343400C78B96 /* Player.swift */; };
18 | 1156ECAE2475347000C78B96 /* Tile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECAD2475347000C78B96 /* Tile.swift */; };
19 | 1156ECB0247534C400C78B96 /* Board.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECAF247534C400C78B96 /* Board.swift */; };
20 | 1156ECB2247538BB00C78B96 /* GameState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECB1247538BB00C78B96 /* GameState.swift */; };
21 | 1156ECB52475392100C78B96 /* BoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECB42475392100C78B96 /* BoardView.swift */; };
22 | 1156ECB72475396500C78B96 /* TileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECB62475396500C78B96 /* TileView.swift */; };
23 | 1156ECBB24756AE200C78B96 /* TileButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECBA24756AE200C78B96 /* TileButtonStyle.swift */; };
24 | 1156ECBD24756EB500C78B96 /* PlayingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECBC24756EB500C78B96 /* PlayingView.swift */; };
25 | 1156ECBF24756F8A00C78B96 /* WinnerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECBE24756F8A00C78B96 /* WinnerView.swift */; };
26 | 1156ECC22475706F00C78B96 /* GameInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECC12475706F00C78B96 /* GameInput.swift */; };
27 | 1156ECC52475716900C78B96 /* GameDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECC42475716900C78B96 /* GameDispatcher.swift */; };
28 | 1156ECC72475719100C78B96 /* GameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECC62475719100C78B96 /* GameView.swift */; };
29 | 1156ECC9247574B100C78B96 /* DrawView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECC8247574B100C78B96 /* DrawView.swift */; };
30 | 1156ECCC2475791400C78B96 /* GameComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1156ECCB2475791400C78B96 /* GameComponent.swift */; };
31 | /* End PBXBuildFile section */
32 |
33 | /* Begin PBXContainerItemProxy section */
34 | 1156EC992475336100C78B96 /* PBXContainerItemProxy */ = {
35 | isa = PBXContainerItemProxy;
36 | containerPortal = 1156EC7A2475336000C78B96 /* Project object */;
37 | proxyType = 1;
38 | remoteGlobalIDString = 1156EC812475336000C78B96;
39 | remoteInfo = TicTacToe;
40 | };
41 | /* End PBXContainerItemProxy section */
42 |
43 | /* Begin PBXFileReference section */
44 | 1156EC822475336000C78B96 /* TicTacToe.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TicTacToe.app; sourceTree = BUILT_PRODUCTS_DIR; };
45 | 1156EC852475336000C78B96 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
46 | 1156EC872475336000C78B96 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
47 | 1156EC8B2475336100C78B96 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
48 | 1156EC8E2475336100C78B96 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
49 | 1156EC912475336100C78B96 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
50 | 1156EC932475336100C78B96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
51 | 1156EC982475336100C78B96 /* TicTacToeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TicTacToeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
52 | 1156EC9C2475336100C78B96 /* TicTacToeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TicTacToeTests.swift; sourceTree = ""; };
53 | 1156EC9E2475336100C78B96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
54 | 1156ECAB2475343400C78B96 /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = ""; };
55 | 1156ECAD2475347000C78B96 /* Tile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tile.swift; sourceTree = ""; };
56 | 1156ECAF247534C400C78B96 /* Board.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Board.swift; sourceTree = ""; };
57 | 1156ECB1247538BB00C78B96 /* GameState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameState.swift; sourceTree = ""; };
58 | 1156ECB42475392100C78B96 /* BoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoardView.swift; sourceTree = ""; };
59 | 1156ECB62475396500C78B96 /* TileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileView.swift; sourceTree = ""; };
60 | 1156ECBA24756AE200C78B96 /* TileButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileButtonStyle.swift; sourceTree = ""; };
61 | 1156ECBC24756EB500C78B96 /* PlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayingView.swift; sourceTree = ""; };
62 | 1156ECBE24756F8A00C78B96 /* WinnerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WinnerView.swift; sourceTree = ""; };
63 | 1156ECC12475706F00C78B96 /* GameInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameInput.swift; sourceTree = ""; };
64 | 1156ECC42475716900C78B96 /* GameDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameDispatcher.swift; sourceTree = ""; };
65 | 1156ECC62475719100C78B96 /* GameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameView.swift; sourceTree = ""; };
66 | 1156ECC8247574B100C78B96 /* DrawView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DrawView.swift; sourceTree = ""; };
67 | 1156ECCB2475791400C78B96 /* GameComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameComponent.swift; sourceTree = ""; };
68 | /* End PBXFileReference section */
69 |
70 | /* Begin PBXFrameworksBuildPhase section */
71 | 1156EC7F2475336000C78B96 /* Frameworks */ = {
72 | isa = PBXFrameworksBuildPhase;
73 | buildActionMask = 2147483647;
74 | files = (
75 | 1156ECA92475341900C78B96 /* BowArch in Frameworks */,
76 | );
77 | runOnlyForDeploymentPostprocessing = 0;
78 | };
79 | 1156EC952475336100C78B96 /* Frameworks */ = {
80 | isa = PBXFrameworksBuildPhase;
81 | buildActionMask = 2147483647;
82 | files = (
83 | );
84 | runOnlyForDeploymentPostprocessing = 0;
85 | };
86 | /* End PBXFrameworksBuildPhase section */
87 |
88 | /* Begin PBXGroup section */
89 | 1156EC792475336000C78B96 = {
90 | isa = PBXGroup;
91 | children = (
92 | 1156EC842475336000C78B96 /* TicTacToe */,
93 | 1156EC9B2475336100C78B96 /* TicTacToeTests */,
94 | 1156EC832475336000C78B96 /* Products */,
95 | );
96 | sourceTree = "";
97 | };
98 | 1156EC832475336000C78B96 /* Products */ = {
99 | isa = PBXGroup;
100 | children = (
101 | 1156EC822475336000C78B96 /* TicTacToe.app */,
102 | 1156EC982475336100C78B96 /* TicTacToeTests.xctest */,
103 | );
104 | name = Products;
105 | sourceTree = "";
106 | };
107 | 1156EC842475336000C78B96 /* TicTacToe */ = {
108 | isa = PBXGroup;
109 | children = (
110 | 1156EC932475336100C78B96 /* Info.plist */,
111 | 1156EC852475336000C78B96 /* AppDelegate.swift */,
112 | 1156EC872475336000C78B96 /* SceneDelegate.swift */,
113 | 1156EC8B2475336100C78B96 /* Assets.xcassets */,
114 | 1156ECCA2475790300C78B96 /* Component */,
115 | 1156ECC32475715800C78B96 /* Dispatcher */,
116 | 1156ECC02475705E00C78B96 /* Input */,
117 | 1156EC902475336100C78B96 /* LaunchScreen.storyboard */,
118 | 1156EC8D2475336100C78B96 /* Preview Content */,
119 | 1156ECAA2475342400C78B96 /* State */,
120 | 1156ECB32475390100C78B96 /* View */,
121 | );
122 | path = TicTacToe;
123 | sourceTree = "";
124 | };
125 | 1156EC8D2475336100C78B96 /* Preview Content */ = {
126 | isa = PBXGroup;
127 | children = (
128 | 1156EC8E2475336100C78B96 /* Preview Assets.xcassets */,
129 | );
130 | path = "Preview Content";
131 | sourceTree = "";
132 | };
133 | 1156EC9B2475336100C78B96 /* TicTacToeTests */ = {
134 | isa = PBXGroup;
135 | children = (
136 | 1156EC9C2475336100C78B96 /* TicTacToeTests.swift */,
137 | 1156EC9E2475336100C78B96 /* Info.plist */,
138 | );
139 | path = TicTacToeTests;
140 | sourceTree = "";
141 | };
142 | 1156ECAA2475342400C78B96 /* State */ = {
143 | isa = PBXGroup;
144 | children = (
145 | 1156ECAF247534C400C78B96 /* Board.swift */,
146 | 1156ECB1247538BB00C78B96 /* GameState.swift */,
147 | 1156ECAB2475343400C78B96 /* Player.swift */,
148 | 1156ECAD2475347000C78B96 /* Tile.swift */,
149 | );
150 | path = State;
151 | sourceTree = "";
152 | };
153 | 1156ECB32475390100C78B96 /* View */ = {
154 | isa = PBXGroup;
155 | children = (
156 | 1156ECB42475392100C78B96 /* BoardView.swift */,
157 | 1156ECC8247574B100C78B96 /* DrawView.swift */,
158 | 1156ECC62475719100C78B96 /* GameView.swift */,
159 | 1156ECBC24756EB500C78B96 /* PlayingView.swift */,
160 | 1156ECBA24756AE200C78B96 /* TileButtonStyle.swift */,
161 | 1156ECB62475396500C78B96 /* TileView.swift */,
162 | 1156ECBE24756F8A00C78B96 /* WinnerView.swift */,
163 | );
164 | path = View;
165 | sourceTree = "";
166 | };
167 | 1156ECC02475705E00C78B96 /* Input */ = {
168 | isa = PBXGroup;
169 | children = (
170 | 1156ECC12475706F00C78B96 /* GameInput.swift */,
171 | );
172 | path = Input;
173 | sourceTree = "";
174 | };
175 | 1156ECC32475715800C78B96 /* Dispatcher */ = {
176 | isa = PBXGroup;
177 | children = (
178 | 1156ECC42475716900C78B96 /* GameDispatcher.swift */,
179 | );
180 | path = Dispatcher;
181 | sourceTree = "";
182 | };
183 | 1156ECCA2475790300C78B96 /* Component */ = {
184 | isa = PBXGroup;
185 | children = (
186 | 1156ECCB2475791400C78B96 /* GameComponent.swift */,
187 | );
188 | path = Component;
189 | sourceTree = "";
190 | };
191 | /* End PBXGroup section */
192 |
193 | /* Begin PBXNativeTarget section */
194 | 1156EC812475336000C78B96 /* TicTacToe */ = {
195 | isa = PBXNativeTarget;
196 | buildConfigurationList = 1156ECA12475336100C78B96 /* Build configuration list for PBXNativeTarget "TicTacToe" */;
197 | buildPhases = (
198 | 1156EC7E2475336000C78B96 /* Sources */,
199 | 1156EC7F2475336000C78B96 /* Frameworks */,
200 | 1156EC802475336000C78B96 /* Resources */,
201 | );
202 | buildRules = (
203 | );
204 | dependencies = (
205 | );
206 | name = TicTacToe;
207 | packageProductDependencies = (
208 | 1156ECA82475341900C78B96 /* BowArch */,
209 | );
210 | productName = TicTacToe;
211 | productReference = 1156EC822475336000C78B96 /* TicTacToe.app */;
212 | productType = "com.apple.product-type.application";
213 | };
214 | 1156EC972475336100C78B96 /* TicTacToeTests */ = {
215 | isa = PBXNativeTarget;
216 | buildConfigurationList = 1156ECA42475336100C78B96 /* Build configuration list for PBXNativeTarget "TicTacToeTests" */;
217 | buildPhases = (
218 | 1156EC942475336100C78B96 /* Sources */,
219 | 1156EC952475336100C78B96 /* Frameworks */,
220 | 1156EC962475336100C78B96 /* Resources */,
221 | );
222 | buildRules = (
223 | );
224 | dependencies = (
225 | 1156EC9A2475336100C78B96 /* PBXTargetDependency */,
226 | );
227 | name = TicTacToeTests;
228 | productName = TicTacToeTests;
229 | productReference = 1156EC982475336100C78B96 /* TicTacToeTests.xctest */;
230 | productType = "com.apple.product-type.bundle.unit-test";
231 | };
232 | /* End PBXNativeTarget section */
233 |
234 | /* Begin PBXProject section */
235 | 1156EC7A2475336000C78B96 /* Project object */ = {
236 | isa = PBXProject;
237 | attributes = {
238 | LastSwiftUpdateCheck = 1140;
239 | LastUpgradeCheck = 1140;
240 | ORGANIZATIONNAME = "Tomás Ruiz López";
241 | TargetAttributes = {
242 | 1156EC812475336000C78B96 = {
243 | CreatedOnToolsVersion = 11.4.1;
244 | };
245 | 1156EC972475336100C78B96 = {
246 | CreatedOnToolsVersion = 11.4.1;
247 | TestTargetID = 1156EC812475336000C78B96;
248 | };
249 | };
250 | };
251 | buildConfigurationList = 1156EC7D2475336000C78B96 /* Build configuration list for PBXProject "TicTacToe" */;
252 | compatibilityVersion = "Xcode 9.3";
253 | developmentRegion = en;
254 | hasScannedForEncodings = 0;
255 | knownRegions = (
256 | en,
257 | Base,
258 | );
259 | mainGroup = 1156EC792475336000C78B96;
260 | packageReferences = (
261 | 1156ECA72475341900C78B96 /* XCRemoteSwiftPackageReference "bow-arch" */,
262 | );
263 | productRefGroup = 1156EC832475336000C78B96 /* Products */;
264 | projectDirPath = "";
265 | projectRoot = "";
266 | targets = (
267 | 1156EC812475336000C78B96 /* TicTacToe */,
268 | 1156EC972475336100C78B96 /* TicTacToeTests */,
269 | );
270 | };
271 | /* End PBXProject section */
272 |
273 | /* Begin PBXResourcesBuildPhase section */
274 | 1156EC802475336000C78B96 /* Resources */ = {
275 | isa = PBXResourcesBuildPhase;
276 | buildActionMask = 2147483647;
277 | files = (
278 | 1156EC922475336100C78B96 /* LaunchScreen.storyboard in Resources */,
279 | 1156EC8F2475336100C78B96 /* Preview Assets.xcassets in Resources */,
280 | 1156EC8C2475336100C78B96 /* Assets.xcassets in Resources */,
281 | );
282 | runOnlyForDeploymentPostprocessing = 0;
283 | };
284 | 1156EC962475336100C78B96 /* Resources */ = {
285 | isa = PBXResourcesBuildPhase;
286 | buildActionMask = 2147483647;
287 | files = (
288 | );
289 | runOnlyForDeploymentPostprocessing = 0;
290 | };
291 | /* End PBXResourcesBuildPhase section */
292 |
293 | /* Begin PBXSourcesBuildPhase section */
294 | 1156EC7E2475336000C78B96 /* Sources */ = {
295 | isa = PBXSourcesBuildPhase;
296 | buildActionMask = 2147483647;
297 | files = (
298 | 1156ECB2247538BB00C78B96 /* GameState.swift in Sources */,
299 | 1156ECBF24756F8A00C78B96 /* WinnerView.swift in Sources */,
300 | 1156ECC9247574B100C78B96 /* DrawView.swift in Sources */,
301 | 1156ECAC2475343400C78B96 /* Player.swift in Sources */,
302 | 1156EC862475336000C78B96 /* AppDelegate.swift in Sources */,
303 | 1156ECC72475719100C78B96 /* GameView.swift in Sources */,
304 | 1156ECCC2475791400C78B96 /* GameComponent.swift in Sources */,
305 | 1156ECC52475716900C78B96 /* GameDispatcher.swift in Sources */,
306 | 1156ECBB24756AE200C78B96 /* TileButtonStyle.swift in Sources */,
307 | 1156ECB0247534C400C78B96 /* Board.swift in Sources */,
308 | 1156ECBD24756EB500C78B96 /* PlayingView.swift in Sources */,
309 | 1156ECB72475396500C78B96 /* TileView.swift in Sources */,
310 | 1156ECAE2475347000C78B96 /* Tile.swift in Sources */,
311 | 1156ECB52475392100C78B96 /* BoardView.swift in Sources */,
312 | 1156EC882475336000C78B96 /* SceneDelegate.swift in Sources */,
313 | 1156ECC22475706F00C78B96 /* GameInput.swift in Sources */,
314 | );
315 | runOnlyForDeploymentPostprocessing = 0;
316 | };
317 | 1156EC942475336100C78B96 /* Sources */ = {
318 | isa = PBXSourcesBuildPhase;
319 | buildActionMask = 2147483647;
320 | files = (
321 | 1156EC9D2475336100C78B96 /* TicTacToeTests.swift in Sources */,
322 | );
323 | runOnlyForDeploymentPostprocessing = 0;
324 | };
325 | /* End PBXSourcesBuildPhase section */
326 |
327 | /* Begin PBXTargetDependency section */
328 | 1156EC9A2475336100C78B96 /* PBXTargetDependency */ = {
329 | isa = PBXTargetDependency;
330 | target = 1156EC812475336000C78B96 /* TicTacToe */;
331 | targetProxy = 1156EC992475336100C78B96 /* PBXContainerItemProxy */;
332 | };
333 | /* End PBXTargetDependency section */
334 |
335 | /* Begin PBXVariantGroup section */
336 | 1156EC902475336100C78B96 /* LaunchScreen.storyboard */ = {
337 | isa = PBXVariantGroup;
338 | children = (
339 | 1156EC912475336100C78B96 /* Base */,
340 | );
341 | name = LaunchScreen.storyboard;
342 | sourceTree = "";
343 | };
344 | /* End PBXVariantGroup section */
345 |
346 | /* Begin XCBuildConfiguration section */
347 | 1156EC9F2475336100C78B96 /* Debug */ = {
348 | isa = XCBuildConfiguration;
349 | buildSettings = {
350 | ALWAYS_SEARCH_USER_PATHS = NO;
351 | CLANG_ANALYZER_NONNULL = YES;
352 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
353 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
354 | CLANG_CXX_LIBRARY = "libc++";
355 | CLANG_ENABLE_MODULES = YES;
356 | CLANG_ENABLE_OBJC_ARC = YES;
357 | CLANG_ENABLE_OBJC_WEAK = YES;
358 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
359 | CLANG_WARN_BOOL_CONVERSION = YES;
360 | CLANG_WARN_COMMA = YES;
361 | CLANG_WARN_CONSTANT_CONVERSION = YES;
362 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
363 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
364 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
365 | CLANG_WARN_EMPTY_BODY = YES;
366 | CLANG_WARN_ENUM_CONVERSION = YES;
367 | CLANG_WARN_INFINITE_RECURSION = YES;
368 | CLANG_WARN_INT_CONVERSION = YES;
369 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
370 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
371 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
372 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
373 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
374 | CLANG_WARN_STRICT_PROTOTYPES = YES;
375 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
376 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
377 | CLANG_WARN_UNREACHABLE_CODE = YES;
378 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
379 | COPY_PHASE_STRIP = NO;
380 | DEBUG_INFORMATION_FORMAT = dwarf;
381 | ENABLE_STRICT_OBJC_MSGSEND = YES;
382 | ENABLE_TESTABILITY = YES;
383 | GCC_C_LANGUAGE_STANDARD = gnu11;
384 | GCC_DYNAMIC_NO_PIC = NO;
385 | GCC_NO_COMMON_BLOCKS = YES;
386 | GCC_OPTIMIZATION_LEVEL = 0;
387 | GCC_PREPROCESSOR_DEFINITIONS = (
388 | "DEBUG=1",
389 | "$(inherited)",
390 | );
391 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
392 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
393 | GCC_WARN_UNDECLARED_SELECTOR = YES;
394 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
395 | GCC_WARN_UNUSED_FUNCTION = YES;
396 | GCC_WARN_UNUSED_VARIABLE = YES;
397 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
398 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
399 | MTL_FAST_MATH = YES;
400 | ONLY_ACTIVE_ARCH = YES;
401 | SDKROOT = iphoneos;
402 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
403 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
404 | };
405 | name = Debug;
406 | };
407 | 1156ECA02475336100C78B96 /* Release */ = {
408 | isa = XCBuildConfiguration;
409 | buildSettings = {
410 | ALWAYS_SEARCH_USER_PATHS = NO;
411 | CLANG_ANALYZER_NONNULL = YES;
412 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
413 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
414 | CLANG_CXX_LIBRARY = "libc++";
415 | CLANG_ENABLE_MODULES = YES;
416 | CLANG_ENABLE_OBJC_ARC = YES;
417 | CLANG_ENABLE_OBJC_WEAK = YES;
418 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
419 | CLANG_WARN_BOOL_CONVERSION = YES;
420 | CLANG_WARN_COMMA = YES;
421 | CLANG_WARN_CONSTANT_CONVERSION = YES;
422 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
423 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
424 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
425 | CLANG_WARN_EMPTY_BODY = YES;
426 | CLANG_WARN_ENUM_CONVERSION = YES;
427 | CLANG_WARN_INFINITE_RECURSION = YES;
428 | CLANG_WARN_INT_CONVERSION = YES;
429 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
430 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
431 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
432 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
433 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
434 | CLANG_WARN_STRICT_PROTOTYPES = YES;
435 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
436 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
437 | CLANG_WARN_UNREACHABLE_CODE = YES;
438 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
439 | COPY_PHASE_STRIP = NO;
440 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
441 | ENABLE_NS_ASSERTIONS = NO;
442 | ENABLE_STRICT_OBJC_MSGSEND = YES;
443 | GCC_C_LANGUAGE_STANDARD = gnu11;
444 | GCC_NO_COMMON_BLOCKS = YES;
445 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
446 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
447 | GCC_WARN_UNDECLARED_SELECTOR = YES;
448 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
449 | GCC_WARN_UNUSED_FUNCTION = YES;
450 | GCC_WARN_UNUSED_VARIABLE = YES;
451 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
452 | MTL_ENABLE_DEBUG_INFO = NO;
453 | MTL_FAST_MATH = YES;
454 | SDKROOT = iphoneos;
455 | SWIFT_COMPILATION_MODE = wholemodule;
456 | SWIFT_OPTIMIZATION_LEVEL = "-O";
457 | VALIDATE_PRODUCT = YES;
458 | };
459 | name = Release;
460 | };
461 | 1156ECA22475336100C78B96 /* Debug */ = {
462 | isa = XCBuildConfiguration;
463 | buildSettings = {
464 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
465 | CODE_SIGN_STYLE = Automatic;
466 | DEVELOPMENT_ASSET_PATHS = "\"TicTacToe/Preview Content\"";
467 | ENABLE_PREVIEWS = YES;
468 | INFOPLIST_FILE = TicTacToe/Info.plist;
469 | LD_RUNPATH_SEARCH_PATHS = (
470 | "$(inherited)",
471 | "@executable_path/Frameworks",
472 | );
473 | PRODUCT_BUNDLE_IDENTIFIER = com.truizlop.TicTacToe;
474 | PRODUCT_NAME = "$(TARGET_NAME)";
475 | SWIFT_VERSION = 5.0;
476 | TARGETED_DEVICE_FAMILY = "1,2";
477 | };
478 | name = Debug;
479 | };
480 | 1156ECA32475336100C78B96 /* Release */ = {
481 | isa = XCBuildConfiguration;
482 | buildSettings = {
483 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
484 | CODE_SIGN_STYLE = Automatic;
485 | DEVELOPMENT_ASSET_PATHS = "\"TicTacToe/Preview Content\"";
486 | ENABLE_PREVIEWS = YES;
487 | INFOPLIST_FILE = TicTacToe/Info.plist;
488 | LD_RUNPATH_SEARCH_PATHS = (
489 | "$(inherited)",
490 | "@executable_path/Frameworks",
491 | );
492 | PRODUCT_BUNDLE_IDENTIFIER = com.truizlop.TicTacToe;
493 | PRODUCT_NAME = "$(TARGET_NAME)";
494 | SWIFT_VERSION = 5.0;
495 | TARGETED_DEVICE_FAMILY = "1,2";
496 | };
497 | name = Release;
498 | };
499 | 1156ECA52475336100C78B96 /* Debug */ = {
500 | isa = XCBuildConfiguration;
501 | buildSettings = {
502 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
503 | BUNDLE_LOADER = "$(TEST_HOST)";
504 | CODE_SIGN_STYLE = Automatic;
505 | INFOPLIST_FILE = TicTacToeTests/Info.plist;
506 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
507 | LD_RUNPATH_SEARCH_PATHS = (
508 | "$(inherited)",
509 | "@executable_path/Frameworks",
510 | "@loader_path/Frameworks",
511 | );
512 | PRODUCT_BUNDLE_IDENTIFIER = com.truizlop.TicTacToeTests;
513 | PRODUCT_NAME = "$(TARGET_NAME)";
514 | SWIFT_VERSION = 5.0;
515 | TARGETED_DEVICE_FAMILY = "1,2";
516 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TicTacToe.app/TicTacToe";
517 | };
518 | name = Debug;
519 | };
520 | 1156ECA62475336100C78B96 /* Release */ = {
521 | isa = XCBuildConfiguration;
522 | buildSettings = {
523 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
524 | BUNDLE_LOADER = "$(TEST_HOST)";
525 | CODE_SIGN_STYLE = Automatic;
526 | INFOPLIST_FILE = TicTacToeTests/Info.plist;
527 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
528 | LD_RUNPATH_SEARCH_PATHS = (
529 | "$(inherited)",
530 | "@executable_path/Frameworks",
531 | "@loader_path/Frameworks",
532 | );
533 | PRODUCT_BUNDLE_IDENTIFIER = com.truizlop.TicTacToeTests;
534 | PRODUCT_NAME = "$(TARGET_NAME)";
535 | SWIFT_VERSION = 5.0;
536 | TARGETED_DEVICE_FAMILY = "1,2";
537 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TicTacToe.app/TicTacToe";
538 | };
539 | name = Release;
540 | };
541 | /* End XCBuildConfiguration section */
542 |
543 | /* Begin XCConfigurationList section */
544 | 1156EC7D2475336000C78B96 /* Build configuration list for PBXProject "TicTacToe" */ = {
545 | isa = XCConfigurationList;
546 | buildConfigurations = (
547 | 1156EC9F2475336100C78B96 /* Debug */,
548 | 1156ECA02475336100C78B96 /* Release */,
549 | );
550 | defaultConfigurationIsVisible = 0;
551 | defaultConfigurationName = Release;
552 | };
553 | 1156ECA12475336100C78B96 /* Build configuration list for PBXNativeTarget "TicTacToe" */ = {
554 | isa = XCConfigurationList;
555 | buildConfigurations = (
556 | 1156ECA22475336100C78B96 /* Debug */,
557 | 1156ECA32475336100C78B96 /* Release */,
558 | );
559 | defaultConfigurationIsVisible = 0;
560 | defaultConfigurationName = Release;
561 | };
562 | 1156ECA42475336100C78B96 /* Build configuration list for PBXNativeTarget "TicTacToeTests" */ = {
563 | isa = XCConfigurationList;
564 | buildConfigurations = (
565 | 1156ECA52475336100C78B96 /* Debug */,
566 | 1156ECA62475336100C78B96 /* Release */,
567 | );
568 | defaultConfigurationIsVisible = 0;
569 | defaultConfigurationName = Release;
570 | };
571 | /* End XCConfigurationList section */
572 |
573 | /* Begin XCRemoteSwiftPackageReference section */
574 | 1156ECA72475341900C78B96 /* XCRemoteSwiftPackageReference "bow-arch" */ = {
575 | isa = XCRemoteSwiftPackageReference;
576 | repositoryURL = "https://github.com/bow-swift/bow-arch.git";
577 | requirement = {
578 | branch = master;
579 | kind = branch;
580 | };
581 | };
582 | /* End XCRemoteSwiftPackageReference section */
583 |
584 | /* Begin XCSwiftPackageProductDependency section */
585 | 1156ECA82475341900C78B96 /* BowArch */ = {
586 | isa = XCSwiftPackageProductDependency;
587 | package = 1156ECA72475341900C78B96 /* XCRemoteSwiftPackageReference "bow-arch" */;
588 | productName = BowArch;
589 | };
590 | /* End XCSwiftPackageProductDependency section */
591 | };
592 | rootObject = 1156EC7A2475336000C78B96 /* Project object */;
593 | }
594 |
--------------------------------------------------------------------------------