Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
abdel-17 committed May 13, 2022
0 parents commit edcc82f
Show file tree
Hide file tree
Showing 11 changed files with 913 additions and 0 deletions.
11 changes: 11 additions & 0 deletions Shared/Assets.xcassets/AccentColor.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
148 changes: 148 additions & 0 deletions Shared/Assets.xcassets/AppIcon.appiconset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "16x16"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "32x32"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "128x128"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "256x256"
},
{
"idiom" : "mac",
"scale" : "1x",
"size" : "512x512"
},
{
"idiom" : "mac",
"scale" : "2x",
"size" : "512x512"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
6 changes: 6 additions & 0 deletions Shared/Assets.xcassets/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
126 changes: 126 additions & 0 deletions Shared/ContentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import SwiftUI

struct ContentView : View {
@State private var game = TicTacToe()

/// The number of wins of each player.
@State private var score = (x: 0, o: 0)

/// True iff no player has played yet.
@State private var isFirstTurn = true

var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)

VStack {
HStack {
Spacer()
VStack {
Text("X")
.foregroundColor(.teal)

Text(String(score.x))
}
Spacer()

Text(displayedMessage)

Spacer()
VStack {
Text("O")
.foregroundColor(.yellow)

Text(String(score.o))
}
Spacer()
}

GeometryReader { bounds in
grid3x3(size: 0.85 * min(bounds.size.width, bounds.size.height))
.frame(width: bounds.size.width,
height: bounds.size.height)
}

VStack {
Button(isFirstTurn ? "RESET" : "NEW GAME") {
withAnimation {
if isFirstTurn {
score = (0, 0)
} else {
game.reset()
isFirstTurn = true
}
}
}
.foregroundColor(.red)
.buttonStyle(PlainButtonStyle())

}
}
.font(.title.bold())
.foregroundColor(.white)
.padding()
.frame(minWidth: 300, minHeight: 400)
}
}

/// The message displayed to the player.
private var displayedMessage: String {
game.hasEnded ?
game.playerHasWon ? "You win!" : "Draw" :
"Player \(game.player.description)"
}

/// Returns a centered, evenly-spaced 3x3 grid of buttons.
private func grid3x3(size: CGFloat) -> some View {
ZStack {
Color.white
.frame(width: size, height: size)

VStack(spacing: 0.025 * size) {
ForEach(0..<3) { row in
HStack(spacing: 0.025 * size) {
ForEach(0..<3) { column in
button(row, column, size: 0.95 * size / 3)
}
}
}
}
}
}

/// Returns a button having the given size,
/// positioned at the given row and column.
private func button(_ row: Int, _ column: Int, size: CGFloat) -> some View {
Button {
withAnimation {
game.playAt(row, column)
isFirstTurn = false
if game.playerHasWon {
switch game.player {
case .x:
score.x += 1
case .o:
score.o += 1
}
}
}
} label: {
Text(game[row, column]?.description ?? "")
.frame(width: size, height: size)
}
.font(.system(size: size / 2))
.foregroundColor(game.isMatchingAt(row, column) ? .green :
game[row, column] == .x ? .teal : .yellow)
.background(.black)
.buttonStyle(BorderlessButtonStyle())
.disabled(game.hasEnded || game[row, column] != nil)
}
}

struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
100 changes: 100 additions & 0 deletions Shared/TicTacToe.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
struct TicTacToe {
/// A tic-tac-toe player.
enum Player : CustomStringConvertible {
case x, o

var description: String {
switch self {
case .x:
return "X"
case .o:
return "O"
}
}
}

/// An array of 9 players, initially set to `nil`.
///
/// `nil` descibes an empty position.
private var grid: [Player?] = Array(repeating: nil,
count: 9)

/// The indices of the matching elements in the grid.
private var matchingIndices: Set<Int> = []

/// The numbers of turns that passed.
private var turns: Int = 0

/// The current-turn player.
private(set) var player: Player = .x

/// True iff the current player has won.
var playerHasWon: Bool {
!matchingIndices.isEmpty
}

/// True iff this game has ended.
var hasEnded: Bool {
playerHasWon || turns == 9
}

/// Returns the player at the given row and column.
subscript(row: Int, column: Int) -> Player? {
grid[3 * row + column]
}

/// Returns true if the player won by matching
/// the given row and column.
func isMatchingAt(_ row: Int, _ column: Int) -> Bool {
matchingIndices.contains(3 * row + column)
}

/// Determines the winner by matching the values
/// at the given indices.
private mutating func match(_ i: Int, _ j: Int, _ k: Int) {
if grid[i] == grid[j] && grid[j] == grid[k] {
for index in [i, j, k] {
matchingIndices.insert(index)
}
}
}

/// Plays this game at the given row and column.
mutating func playAt(_ row: Int, _ column: Int) {
let index = 3 * row + column
grid[index] = player
// Try to match along all possible directions.
//
// 0 1 2
// 0 [0] [1] [2]
// 1 [3] [4] [5]
// 2 [6] [7] [8]
//
// Match along the given row.
match(3 * row, 3 * row + 1, 3 * row + 2)
// Match along the given column.
match(column, column + 3, column + 6)
// If we are on the diagnonal (0, 4, 8):
if row == column {
match(0, 4, 8)
}
// If we are on the diagnonal (2, 4, 6):
if row + column == 2 {
match(2, 4, 6)
}
turns += 1
if !playerHasWon {
player = (player == .x) ? .o : .x
}
}

/// Starts a new game with the same starting player
/// as the current one.
mutating func reset() {
for index in grid.indices {
grid[index] = nil
}
matchingIndices.removeAll()
turns = 0
}
}
Loading

0 comments on commit edcc82f

Please sign in to comment.