Skip to content

Instantly share code, notes, and snippets.

@epaga
Last active July 3, 2019 06:57
Show Gist options
  • Save epaga/720e8c8c31e53808dbf8b2243a9c8fea to your computer and use it in GitHub Desktop.
Save epaga/720e8c8c31e53808dbf8b2243a9c8fea to your computer and use it in GitHub Desktop.
A simple SwiftUI Tic Tac Toe game I made together with my son to learn SwiftUI together
//
// ContentView.swift
// TicTacToe
//
// Created by John Goering on 08.06.19.
// Copyright © 2019 John Goering. All rights reserved.
//
import SwiftUI
import Combine
class GameData : BindableObject {
var turnIsX = true {
didSet {
didChange.send(Void())
}
}
var game:[String] {
didSet {
didChange.send(Void())
}
}
init(game:[String] = [
" "," "," ",
" "," "," ",
" "," "," "
]) {
self.game = game
}
var didChange = PassthroughSubject<Void, Never>()
func reset() {
game = [
" "," "," ",
" "," "," ",
" "," "," "
]
turnIsX = true
}
var winningIndexes: [Int]? {
get {
let waysToWin:[[Int]] = [
[0,1,2],
[3,4,5],
[6,7,8],
[0,3,6],
[1,4,7],
[2,5,8],
[0,4,8],
[2,4,6]
]
return waysToWin.first{
wayToWin in
return game[wayToWin[0]] == game[wayToWin[1]] &&
game[wayToWin[1]] == game[wayToWin[2]] &&
game[wayToWin[0]] != " "
}
}
}
var gameIsOver: Bool {
get {
return winningIndexes != nil ||
game.first {$0 == " "} == nil
}
}
}
struct ContentView : View {
@ObjectBinding var game:GameData
var body: some View {
let turnMessage = game.gameIsOver ? "Game Over!" :
"It's \(game.turnIsX ? "X" : "O")'s turn!"
return ZStack {
VStack(spacing: 0) {
Text(turnMessage)
.font(.largeTitle)
.padding()
Spacer()
Row(rowIndex: 0, game:game)
Row(rowIndex: 1, game:game)
Row(rowIndex: 2, game:game)
Spacer()
}.background(Color(white: 0.8))
if game.gameIsOver {
ResetButton(game: game)
}
}
}
}
struct ResetButton : View {
@ObjectBinding var game:GameData
var body: some View {
VStack {
Spacer()
Button(action: {
self.game.reset()
}) {
Text("Reset")
.font(.largeTitle)
}
.padding()
.background(Color.black)
.cornerRadius(10)
.offset(x: 0, y: -20)
}
}
}
struct Row : View {
var rowIndex:Int
@ObjectBinding var game:GameData
var body: some View {
HStack(spacing: 0) {
Field(rowIndex: rowIndex, colIndex: 0, game:game)
Field(rowIndex: rowIndex, colIndex: 1, game:game)
Field(rowIndex: rowIndex, colIndex: 2, game:game)
}
}
}
struct Field : View {
var rowIndex:Int
var colIndex:Int
@ObjectBinding var game:GameData
var body: some View {
let gameIndex = rowIndex * 3 + colIndex
let isWinningIndex = (game.winningIndexes ?? []).contains(gameIndex)
return ZStack {
if isWinningIndex {
Color.gray
.border(Color.black)
.animation(.basic())
} else {
Color.white
.border(Color.black)
.animation(.basic())
}
Text(game.game[gameIndex])
.font(.system(size: 100))
.color(isWinningIndex ? Color.red : Color.black )
}
.tapAction {
if self.game.game[gameIndex] == " " {
if self.game.turnIsX {
self.game.game[gameIndex] = "X"
} else {
self.game.game[gameIndex] = "O"
}
self.game.turnIsX.toggle()
}
}
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView(game:GameData(game: [
"X"," "," ",
" ","O"," ",
" "," ","X"
]))
}
}
#endif
@edward-greenaway
Copy link

After closer inspection I have made some changes to the two platform initialisers and to the debug code initialisation:

  1. HostingController.swift for watchOS
class HostingController : WKHostingController<ContentView> {
    override var body: ContentView {
        return ContentView(game: GameData())          // EJG: initialise added for watchOS hosting
    }
}

or, 2. SceneDelegate.swift for iOS (this would have been in AppDelegate.swift in the past, but this split was split out for SwiftUI

// Use a UIHostingController as window root view controller
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: ContentView(game:GameData())) // EJG: initialise added for iOS hosting
            self.window = window
            window.makeKeyAndVisible()
        }

And I noticed that debug game with its two Xs and one O was incorrectly initialised (given that the default for turnIsX = true); so I could have initialised the board to all " " (and at one stage I did), or rescued it as above from two Xs and an O to simple one X and one O; however I decided to set ip up follows, as it enables a discussion around forking as a strategy to win:

  1. for both iOS and watchOS the debug section now reads as follows:
#if DEBUG
struct ContentView_Previews : PreviewProvider {
    static var previews: some View {
        ContentView(game:GameData(game: [
            "X"," "," ",
            " ","O"," ",
            "O"," ","X"
            ]))
    }
}
#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment