Skip to content

Instantly share code, notes, and snippets.

@nerdo
Last active March 23, 2018 16:37
Show Gist options
  • Save nerdo/24d24a432bcde285f42e69328bbcbca4 to your computer and use it in GitHub Desktop.
Save nerdo/24d24a432bcde285f42e69328bbcbca4 to your computer and use it in GitHub Desktop.

Revisions

  1. nerdo revised this gist Mar 23, 2018. 1 changed file with 12 additions and 7 deletions.
    19 changes: 12 additions & 7 deletions TicTacToe.swift
    Original file line number Diff line number Diff line change
    @@ -11,7 +11,7 @@ import ReSwift
    open class TicTacToe {
    public typealias Winner = (player: Player, path: [Int])

    public static var store = Store<State>(reducer: Reducer.game, state: nil)
    public static var store = Store<State>(reducer: Reducer.ticTacToe, state: nil)

    public enum Player: String {
    case x = "X"
    @@ -34,17 +34,16 @@ open class TicTacToe {
    }

    open class Reducer {
    static func game(action: ReSwift.Action, state: State?) -> State {
    static func ticTacToe(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    state = self.checkForReset(action: action, state: state)
    state = self.updateBoard(action: action, state: state)
    state = self.checkForWinner(action: action, state: state)
    state = self.reset(action: action, state: state)
    state = self.move(action: action, state: state)

    return state
    }

    private static func checkForReset(action: ReSwift.Action, state: State?) -> State {
    private static func reset(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    guard let action = action as? Action else {
    @@ -58,7 +57,13 @@ open class TicTacToe {
    return state
    }

    private static func updateBoard(action: ReSwift.Action, state: State?) -> State {
    private static func move(action: ReSwift.Action, state: State?) -> State {
    var state = self.handlePlayerMove(action: action, state: state)
    state = self.checkForWinner(action: action, state: state)
    return state
    }

    private static func handlePlayerMove(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    guard let action = action as? Action else {
  2. nerdo created this gist Mar 22, 2018.
    214 changes: 214 additions & 0 deletions Main.storyboard
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,214 @@
    <?xml version="1.0" encoding="UTF-8"?>
    <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
    <adaptation id="fullscreen"/>
    </device>
    <dependencies>
    <deployment identifier="iOS"/>
    <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
    <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
    <capability name="Safe area layout guides" minToolsVersion="9.0"/>
    <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
    <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
    <!--View Controller-->
    <scene sceneID="tne-QT-ifu">
    <objects>
    <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="TicTacToe" customModuleProvider="target" sceneMemberID="viewController">
    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
    <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
    <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
    <subviews>
    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="GYC-JQ-41v">
    <rect key="frame" x="15" y="161" width="345" height="345"/>
    <subviews>
    <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="v7b-c4-5Ke">
    <rect key="frame" x="0.0" y="0.0" width="345" height="109.5"/>
    <subviews>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2cU-au-Mow">
    <rect key="frame" x="0.0" y="0.0" width="109.5" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button0Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="82X-ZM-363"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="drs-Bo-49n">
    <rect key="frame" x="117.5" y="0.0" width="110" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button1Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="y6O-wU-B7p"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="C4m-5v-2rO">
    <rect key="frame" x="235.5" y="0.0" width="109.5" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button2Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="KVg-VM-FO8"/>
    </connections>
    </button>
    </subviews>
    </stackView>
    <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="IDm-oR-oQE">
    <rect key="frame" x="0.0" y="117.5" width="345" height="110"/>
    <subviews>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="YtA-u5-gCw">
    <rect key="frame" x="0.0" y="0.0" width="109.5" height="110"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button3Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="M7o-Ua-KhF"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lnr-Ld-pnz">
    <rect key="frame" x="117.5" y="0.0" width="110" height="110"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button4Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="wNY-9B-ocj"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="tTU-Xd-RTN">
    <rect key="frame" x="235.5" y="0.0" width="109.5" height="110"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button5Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="yNV-Mf-v4H"/>
    </connections>
    </button>
    </subviews>
    </stackView>
    <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="wO4-X9-a35">
    <rect key="frame" x="0.0" y="235.5" width="345" height="109.5"/>
    <subviews>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="S5y-Gp-PXp">
    <rect key="frame" x="0.0" y="0.0" width="109.5" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button6Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="fwN-Ea-0nP"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DZE-sb-KSR">
    <rect key="frame" x="117.5" y="0.0" width="110" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button7Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="qKa-ck-me6"/>
    </connections>
    </button>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="HN8-ZY-CpP">
    <rect key="frame" x="235.5" y="0.0" width="109.5" height="109.5"/>
    <color key="backgroundColor" red="0.92143100499999997" green="0.92145264149999995" blue="0.92144101860000005" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" weight="heavy" pointSize="32"/>
    <state key="normal">
    <color key="titleColor" red="0.12984204290000001" green="0.12984612579999999" blue="0.12984395030000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    </state>
    <connections>
    <action selector="button8Pressed:" destination="BYZ-38-t0r" eventType="touchUpInside" id="Ule-JU-klE"/>
    </connections>
    </button>
    </subviews>
    </stackView>
    </subviews>
    <constraints>
    <constraint firstAttribute="width" secondItem="GYC-JQ-41v" secondAttribute="height" multiplier="1:1" id="1Ir-Zn-ZiX"/>
    </constraints>
    </stackView>
    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="YVy-GY-iZt">
    <rect key="frame" x="111.5" y="104" width="152" height="49"/>
    <color key="backgroundColor" red="0.016804177310000001" green="0.19835099580000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <fontDescription key="fontDescription" type="system" pointSize="24"/>
    <inset key="contentEdgeInsets" minX="10" minY="10" maxX="10" maxY="10"/>
    <state key="normal" title="PLAY AGAIN">
    <color key="titleColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
    </state>
    <connections>
    <action selector="playAgain:" destination="BYZ-38-t0r" eventType="touchUpInside" id="y0M-27-msF"/>
    </connections>
    </button>
    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Z Won!" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XL9-cY-Sce">
    <rect key="frame" x="138" y="514" width="99" height="38.5"/>
    <fontDescription key="fontDescription" type="system" pointSize="32"/>
    <nil key="textColor"/>
    <nil key="highlightedColor"/>
    </label>
    </subviews>
    <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
    <constraints>
    <constraint firstItem="XL9-cY-Sce" firstAttribute="centerX" secondItem="GYC-JQ-41v" secondAttribute="centerX" id="0S8-2B-MAc"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="centerY" secondItem="8bC-Xf-vdC" secondAttribute="centerY" id="5my-Tr-Ff6"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="top" relation="greaterThanOrEqual" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="65" id="D0B-8f-seN"/>
    <constraint firstItem="XL9-cY-Sce" firstAttribute="top" secondItem="GYC-JQ-41v" secondAttribute="bottom" constant="8" symbolic="YES" id="Ecy-HS-Eaw"/>
    <constraint firstItem="6Tk-OE-BBY" firstAttribute="trailing" secondItem="GYC-JQ-41v" secondAttribute="trailing" constant="15" id="SAa-sC-3vU"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="height" secondItem="6Tk-OE-BBY" secondAttribute="height" priority="750" id="SGK-vz-AuA"/>
    <constraint firstItem="YVy-GY-iZt" firstAttribute="centerX" secondItem="GYC-JQ-41v" secondAttribute="centerX" id="XeQ-MS-qYm"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="top" secondItem="YVy-GY-iZt" secondAttribute="bottom" constant="8" symbolic="YES" id="cua-PS-2Ah"/>
    <constraint firstItem="6Tk-OE-BBY" firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="GYC-JQ-41v" secondAttribute="bottom" constant="50" id="pDf-n6-QyB"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="leading" secondItem="6Tk-OE-BBY" secondAttribute="leading" constant="15" id="rP6-im-sYS"/>
    <constraint firstItem="GYC-JQ-41v" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="y7y-qw-7Jy"/>
    </constraints>
    <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
    <variation key="default">
    <mask key="constraints">
    <exclude reference="pDf-n6-QyB"/>
    <exclude reference="D0B-8f-seN"/>
    <exclude reference="SGK-vz-AuA"/>
    </mask>
    </variation>
    <variation key="heightClass=compact">
    <mask key="constraints">
    <exclude reference="SAa-sC-3vU"/>
    <include reference="pDf-n6-QyB"/>
    <include reference="D0B-8f-seN"/>
    <include reference="SGK-vz-AuA"/>
    <exclude reference="rP6-im-sYS"/>
    </mask>
    </variation>
    </view>
    <connections>
    <outlet property="button0" destination="2cU-au-Mow" id="udq-uF-Eak"/>
    <outlet property="button1" destination="drs-Bo-49n" id="PrU-gF-4oa"/>
    <outlet property="button2" destination="C4m-5v-2rO" id="fdE-sp-oC5"/>
    <outlet property="button3" destination="YtA-u5-gCw" id="3Vz-n5-w01"/>
    <outlet property="button4" destination="lnr-Ld-pnz" id="rZc-UI-8Sc"/>
    <outlet property="button5" destination="tTU-Xd-RTN" id="cIr-hZ-04Q"/>
    <outlet property="button6" destination="S5y-Gp-PXp" id="yMU-9m-uCS"/>
    <outlet property="button7" destination="DZE-sb-KSR" id="HcP-JR-cxR"/>
    <outlet property="button8" destination="HN8-ZY-CpP" id="410-BS-gY0"/>
    <outlet property="playAgainButton" destination="YVy-GY-iZt" id="czv-PK-bW4"/>
    <outlet property="winnerLabel" destination="XL9-cY-Sce" id="dG6-ga-Uyr"/>
    </connections>
    </viewController>
    <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
    </objects>
    </scene>
    </scenes>
    </document>
    112 changes: 112 additions & 0 deletions TicTacToe.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,112 @@
    //
    // TicTacToe.swift
    // TicTacToe
    //
    // Created by Dannel Albert on 3/22/18.
    // Copyright © 2018 Dannel Albert. All rights reserved.
    //

    import ReSwift

    open class TicTacToe {
    public typealias Winner = (player: Player, path: [Int])

    public static var store = Store<State>(reducer: Reducer.game, state: nil)

    public enum Player: String {
    case x = "X"
    case o = "O"
    }

    public struct State: ReSwift.StateType {
    var board: [Player?] = [
    nil, nil, nil,
    nil, nil, nil,
    nil, nil, nil
    ]
    var currentPlayer: Player = .x
    var winner: Winner?
    }

    public enum Action: ReSwift.Action {
    case move(location: Int)
    case reset
    }

    open class Reducer {
    static func game(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    state = self.checkForReset(action: action, state: state)
    state = self.updateBoard(action: action, state: state)
    state = self.checkForWinner(action: action, state: state)

    return state
    }

    private static func checkForReset(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    guard let action = action as? Action else {
    return state
    }

    if case .reset = action {
    state = State()
    }

    return state
    }

    private static func updateBoard(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()

    guard let action = action as? Action else {
    return state
    }

    if case let .move(location) = action {
    if location >= 0 && location < 9 && state.board[location] == nil {
    state.board[location] = state.currentPlayer

    switch state.currentPlayer {
    case .x:
    state.currentPlayer = .o
    case .o:
    state.currentPlayer = .x
    }
    }
    }

    return state
    }

    private static func checkForWinner(action: ReSwift.Action, state: State?) -> State {
    var state = state ?? State()
    var winner: Winner?

    winner = winner ?? self.getWinner(state.board, 0, 1, 2)
    winner = winner ?? self.getWinner(state.board, 3, 4, 5)
    winner = winner ?? self.getWinner(state.board, 6, 7, 8)
    winner = winner ?? self.getWinner(state.board, 0, 3, 6)
    winner = winner ?? self.getWinner(state.board, 1, 4, 7)
    winner = winner ?? self.getWinner(state.board, 2, 5, 8)
    winner = winner ?? self.getWinner(state.board, 0, 4, 8)
    winner = winner ?? self.getWinner(state.board, 2, 4, 6)

    state.winner = winner

    return state
    }

    private static func getWinner(_ board: [Player?], _ a: Int, _ b: Int, _ c: Int) -> Winner? {
    if board[a] == nil || board[b] == nil || board[c] == nil {
    return nil
    }
    if board[a] == board[b] && board[b] == board[c] {
    return (player: board[a]!, path: [a, b, c])
    }
    return nil
    }
    }
    }
    168 changes: 168 additions & 0 deletions ViewController.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,168 @@
    //
    // ViewController.swift
    // TicTacToe
    //
    // Created by Dannel Albert on 3/22/18.
    // Copyright © 2018 Dannel Albert. All rights reserved.
    //

    import UIKit
    import ReSwift

    class ViewController: UIViewController, StoreSubscriber {
    @IBOutlet weak var button0: UIButton!
    @IBOutlet weak var button1: UIButton!
    @IBOutlet weak var button2: UIButton!
    @IBOutlet weak var button3: UIButton!
    @IBOutlet weak var button4: UIButton!
    @IBOutlet weak var button5: UIButton!
    @IBOutlet weak var button6: UIButton!
    @IBOutlet weak var button7: UIButton!
    @IBOutlet weak var button8: UIButton!
    @IBOutlet weak var playAgainButton: UIButton!
    @IBOutlet weak var winnerLabel: UILabel!

    var buttons: [UIButton]!

    override func viewDidLoad() {
    super.viewDidLoad()
    setupButtons()
    TicTacToe.store.subscribe(self)
    }

    func newState(state: TicTacToe.State) {
    DispatchQueue.main.async {
    self.render(state: state)
    }

    if state.currentPlayer == .o {
    DispatchQueue.global(qos: .background).async {
    self.computerMove(state: state)
    }
    }
    }

    func render(state: TicTacToe.State) {
    let thereIsAWinner = state.winner != nil
    let hasMovesLeft = state.board.filter { $0 == nil }.count > 0

    playAgainButton.isHidden = !thereIsAWinner && hasMovesLeft
    winnerLabel.isHidden = !thereIsAWinner && hasMovesLeft

    for (i, player) in state.board.enumerated() {
    if let player = player {
    buttons[i].setTitle(player.rawValue, for: .normal)
    } else {
    buttons[i].setTitle(nil, for: .normal)
    }

    if state.winner?.path.contains(i) == true {
    buttons[i].backgroundColor = .green
    } else {
    buttons[i].backgroundColor = .lightGray
    }
    }

    if let winner = state.winner {
    winnerLabel.text = "\(winner.player.rawValue) Won!"
    } else if !hasMovesLeft {
    winnerLabel.text = "It's a draw!"
    }

    for button in buttons {
    button.isEnabled = state.currentPlayer == .x && !thereIsAWinner && hasMovesLeft
    }

    #if DEBUG
    renderTextBoard(state: state)
    #endif
    }

    func renderTextBoard(state: TicTacToe.State) {
    let board = state.board.map { $0?.rawValue ?? " " }
    print()
    print(" \(board[0]) | \(board[1]) | \(board[2])")
    print("-----------")
    print(" \(board[3]) | \(board[4]) | \(board[5])")
    print("-----------")
    print(" \(board[6]) | \(board[7]) | \(board[8])")
    if let winner = state.winner {
    print("\(winner.player.rawValue) Won!")
    }
    print()
    }

    private func setupButtons() {
    buttons = [
    button0, button1, button2,
    button3, button4, button5,
    button6, button7, button8
    ]
    }

    func computerMove(state: TicTacToe.State) {
    if state.winner != nil {
    return
    }

    let emptyLocations = state.board
    .enumerated()
    .map { (player: $1, location: $0) }
    .filter { $0.player == nil }
    .map { $0.location }

    if emptyLocations.count == 0 {
    return
    }

    let location = emptyLocations[Int(arc4random_uniform(UInt32(emptyLocations.count)))]
    TicTacToe.store.dispatch(
    TicTacToe.Action.move(location: location)
    )
    }

    @IBAction func button0Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 0))
    }

    @IBAction func button1Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 1))
    }

    @IBAction func button2Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 2))
    }

    @IBAction func button3Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 3))
    }

    @IBAction func button4Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 4))
    }

    @IBAction func button5Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 5))
    }

    @IBAction func button6Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 6))
    }

    @IBAction func button7Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 7))
    }

    @IBAction func button8Pressed(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.move(location: 8))
    }

    @IBAction func playAgain(_ sender: Any) {
    TicTacToe.store.dispatch(TicTacToe.Action.reset)
    }

    deinit {
    TicTacToe.store.unsubscribe(self)
    }
    }