├── Chess
├── Assets.xcassets
│ ├── Contents.json
│ ├── Chess Pieces
│ │ ├── Contents.json
│ │ ├── king_black.imageset
│ │ │ ├── king_black.png
│ │ │ └── Contents.json
│ │ ├── king_white.imageset
│ │ │ ├── king_white.png
│ │ │ └── Contents.json
│ │ ├── pawn_black.imageset
│ │ │ ├── pawn_black.png
│ │ │ └── Contents.json
│ │ ├── pawn_white.imageset
│ │ │ ├── pawn_white.png
│ │ │ └── Contents.json
│ │ ├── rook_black.imageset
│ │ │ ├── rook_black.png
│ │ │ └── Contents.json
│ │ ├── rook_white.imageset
│ │ │ ├── rook_white.png
│ │ │ └── Contents.json
│ │ ├── queen_black.imageset
│ │ │ ├── queen_black.png
│ │ │ └── Contents.json
│ │ ├── queen_white.imageset
│ │ │ ├── queen_white.png
│ │ │ └── Contents.json
│ │ ├── bishop_black.imageset
│ │ │ ├── bishop_black.png
│ │ │ └── Contents.json
│ │ ├── bishop_white.imageset
│ │ │ ├── bishop_white.png
│ │ │ └── Contents.json
│ │ ├── knight_black.imageset
│ │ │ ├── knight_black.png
│ │ │ └── Contents.json
│ │ └── knight_white.imageset
│ │ │ ├── knight_white.png
│ │ │ └── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── 16.png
│ │ ├── 20.png
│ │ ├── 29.png
│ │ ├── 32.png
│ │ ├── 40.png
│ │ ├── 48.png
│ │ ├── 50.png
│ │ ├── 55.png
│ │ ├── 57.png
│ │ ├── 58.png
│ │ ├── 60.png
│ │ ├── 64.png
│ │ ├── 66.png
│ │ ├── 72.png
│ │ ├── 76.png
│ │ ├── 80.png
│ │ ├── 87.png
│ │ ├── 88.png
│ │ ├── 92.png
│ │ ├── 100.png
│ │ ├── 1024.png
│ │ ├── 114.png
│ │ ├── 120.png
│ │ ├── 128.png
│ │ ├── 144.png
│ │ ├── 152.png
│ │ ├── 167.png
│ │ ├── 172.png
│ │ ├── 180.png
│ │ ├── 196.png
│ │ ├── 216.png
│ │ ├── 256.png
│ │ ├── 512.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── ChessApp.swift
├── PromitionView.swift
├── DifficultySelectionView.swift
├── ChessView.swift
├── OpponentChessEngine.swift
└── ChessGame.swift
├── Chess.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── jaredcassoutt.xcuserdatad
│ │ ├── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
└── project.pbxproj
├── ChessTests
└── ChessTests.swift
├── ChessUITests
├── ChessUITestsLaunchTests.swift
└── ChessUITests.swift
└── README.md
/Chess/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Chess/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/16.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/20.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/29.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/32.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/40.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/48.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/50.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/55.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/57.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/58.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/60.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/64.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/66.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/66.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/72.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/76.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/80.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/87.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/88.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/88.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/92.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/92.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/100.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/1024.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/114.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/120.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/128.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/144.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/152.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/167.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/172.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/172.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/180.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/196.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/216.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/216.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/256.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/AppIcon.appiconset/512.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/king_black.imageset/king_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/king_black.imageset/king_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/king_white.imageset/king_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/king_white.imageset/king_white.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/pawn_black.imageset/pawn_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/pawn_black.imageset/pawn_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/pawn_white.imageset/pawn_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/pawn_white.imageset/pawn_white.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/rook_black.imageset/rook_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/rook_black.imageset/rook_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/rook_white.imageset/rook_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/rook_white.imageset/rook_white.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/queen_black.imageset/queen_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/queen_black.imageset/queen_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/queen_white.imageset/queen_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/queen_white.imageset/queen_white.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/bishop_black.imageset/bishop_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/bishop_black.imageset/bishop_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/bishop_white.imageset/bishop_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/bishop_white.imageset/bishop_white.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/knight_black.imageset/knight_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/knight_black.imageset/knight_black.png
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/knight_white.imageset/knight_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/HEAD/Chess/Assets.xcassets/Chess Pieces/knight_white.imageset/knight_white.png
--------------------------------------------------------------------------------
/Chess.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Chess.xcodeproj/xcuserdata/jaredcassoutt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/Chess/ChessApp.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessApp.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | @main
11 | struct ChessApp: App {
12 | var body: some Scene {
13 | WindowGroup {
14 | ChessView()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ChessTests/ChessTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessTests.swift
3 | // ChessTests
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import Testing
9 | @testable import Chess
10 |
11 | struct ChessTests {
12 |
13 | @Test func example() async throws {
14 | // Write your test here and use APIs like `#expect(...)` to check expected conditions.
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/king_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "king_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/king_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "king_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/pawn_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pawn_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/pawn_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "pawn_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/rook_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rook_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/rook_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rook_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/bishop_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "bishop_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/bishop_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "bishop_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/knight_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "knight_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/knight_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "knight_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/queen_black.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "queen_black.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/Chess Pieces/queen_white.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "queen_white.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Chess.xcodeproj/xcuserdata/jaredcassoutt.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Chess.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/ChessUITests/ChessUITestsLaunchTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessUITestsLaunchTests.swift
3 | // ChessUITests
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import XCTest
9 |
10 | final class ChessUITestsLaunchTests: XCTestCase {
11 |
12 | override class var runsForEachTargetApplicationUIConfiguration: Bool {
13 | true
14 | }
15 |
16 | override func setUpWithError() throws {
17 | continueAfterFailure = false
18 | }
19 |
20 | @MainActor
21 | func testLaunch() throws {
22 | let app = XCUIApplication()
23 | app.launch()
24 |
25 | // Insert steps here to perform after app launch but before taking a screenshot,
26 | // such as logging into a test account or navigating somewhere in the app
27 |
28 | let attachment = XCTAttachment(screenshot: app.screenshot())
29 | attachment.name = "Launch Screen"
30 | attachment.lifetime = .keepAlways
31 | add(attachment)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ChessUITests/ChessUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessUITests.swift
3 | // ChessUITests
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import XCTest
9 |
10 | final class ChessUITests: XCTestCase {
11 |
12 | override func setUpWithError() throws {
13 | // Put setup code here. This method is called before the invocation of each test method in the class.
14 |
15 | // In UI tests it is usually best to stop immediately when a failure occurs.
16 | continueAfterFailure = false
17 |
18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
19 | }
20 |
21 | override func tearDownWithError() throws {
22 | // Put teardown code here. This method is called after the invocation of each test method in the class.
23 | }
24 |
25 | @MainActor
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use XCTAssert and related functions to verify your tests produce the correct results.
32 | }
33 |
34 | @MainActor
35 | func testLaunchPerformance() throws {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTApplicationLaunchMetric()]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Chess/PromitionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PromitionView.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct PromotionView: View {
11 | @ObservedObject var game: ChessGame
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | var body: some View {
15 | VStack {
16 | Text("Promote your pawn to:")
17 | .font(.headline)
18 | .padding()
19 |
20 | HStack {
21 | ForEach([PieceType.queen, .rook, .bishop, .knight], id: \.self) { type in
22 | Button(action: {
23 | game.promotePawn(to: type)
24 | if game.isCheckmate || game.isStalemate {
25 | // Game over after promotion
26 | return
27 | }
28 | self.game.currentPlayer = self.game.currentPlayer.opponent
29 | if self.game.currentPlayer == .black {
30 | _ = self.game.opponentMove()
31 | }
32 | // Dismiss the view after promotion
33 | presentationMode.wrappedValue.dismiss()
34 | }) {
35 | Image("\(type.rawValue)_\(game.promotionPending?.color.rawValue ?? "white")")
36 | .resizable()
37 | .frame(width: 50, height: 50)
38 | }
39 | }
40 | }
41 | .padding()
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Chess/DifficultySelectionView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DifficultySelectionView.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/28/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct DifficultySelectionView: View {
11 | @Binding var selectedDifficulty: DifficultyLevel
12 | @Environment(\.presentationMode) var presentationMode
13 |
14 | var body: some View {
15 | VStack {
16 | Text("Select your opponent's difficulty")
17 | .font(.headline)
18 | .padding()
19 |
20 | HStack(spacing: 20) {
21 | // Easy
22 | VStack {
23 | Button(action: {
24 | selectedDifficulty = .easy
25 | presentationMode.wrappedValue.dismiss()
26 | }) {
27 | Image("pawn_black")
28 | .resizable()
29 | .frame(width: 60, height: 60)
30 | }
31 | Text("Easy")
32 | .font(.subheadline)
33 | }
34 |
35 | // Medium
36 | VStack {
37 | Button(action: {
38 | selectedDifficulty = .medium
39 | presentationMode.wrappedValue.dismiss()
40 | }) {
41 | Image("knight_black")
42 | .resizable()
43 | .frame(width: 60, height: 60)
44 | }
45 | Text("Medium")
46 | .font(.subheadline)
47 | }
48 |
49 | // Hard
50 | VStack {
51 | Button(action: {
52 | selectedDifficulty = .hard
53 | presentationMode.wrappedValue.dismiss()
54 | }) {
55 | Image("queen_black")
56 | .resizable()
57 | .frame(width: 60, height: 60)
58 | }
59 | Text("Hard")
60 | .font(.subheadline)
61 | }
62 | }
63 | .padding()
64 | }
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUI Chess Game
2 | A full-featured chess game built with SwiftUI, where you can play against AI opponents with varying difficulty levels. Designed to be easy to play, fun, and a bit of a brain teaser, this app includes all the classic rules of chess—check, checkmate, castling, en passant, and pawn promotion.
3 |
4 | ## Preview
5 |
6 |
7 |
8 |
9 | ## Features
10 | - **Three AI Difficulty Levels:** Choose from Easy, Medium, and Hard to match your skill level.
11 | - **Dynamic Board and Smooth Animations:** Responsive chessboard layout with SwiftUI animations.
12 | - **Classic Chess Mechanics:** Includes check, checkmate, stalemate detection, castling, en passant, and pawn promotion.
13 | - **User-Friendly Alerts:** Game-over alerts for checkmate, stalemate, and options to reset the game or change difficulty.
14 |
15 | ## Getting Started
16 | 1. Clone the Repository:
17 | ```bash
18 | git clone https://github.com/jaredcassoutt/chess_swiftui.git
19 |
20 | 2. Open the Project in Xcode:
21 | - Open Chess.xcodeproj in Xcode.
22 | - Make sure you’re running Xcode 12 or later.
23 | 3. Run the App:
24 | - Build and run on the simulator or an actual device.
25 | - Select a difficulty and start playing!
26 |
27 | ## How It Works
28 | The project contains three main components:
29 | - **ChessView.swift:** The UI layout for the board, including piece placement and interactive moves.
30 | - **ChessGame.swift:** The game engine that handles the rules, piece movement logic, and game states.
31 | - **OpponentChessEngine.swift:** The AI engine, using minimax for advanced moves in Hard mode and capturing strategies in Medium mode.
32 |
33 | ## Future Enhancements
34 | - **Online Multiplayer:** For real-time games with friends.
35 | - **Advanced AI:** A deeper minimax algorithm for a stronger Hard mode.
36 | - **Sound Effects and Animations:** Adding flair to captures and game-end scenarios.
37 |
38 | ## Contributing
39 | Feel free to fork the project, submit issues, or suggest improvements. I’d love to hear your feedback or collaborate on new features.
40 |
41 | ## License
42 | This project is open-source under the MIT License.
43 |
44 | Thanks for checking out my SwiftUI chess game! Dive into the code, play a few rounds, and let me know if you spot any sneaky moves I might have missed.
45 |
--------------------------------------------------------------------------------
/Chess/ChessView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessView.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 | struct ChessView: View {
11 | @StateObject private var game: ChessGame
12 | @State private var showGameOverAlert = false
13 | @State private var showPromotionSheet = false
14 | @State private var showResetButton = false
15 | @State private var showDifficultySelection = true
16 | @State private var selectedDifficulty: DifficultyLevel = .easy
17 |
18 | init() {
19 | _game = StateObject(wrappedValue: ChessGame(difficulty: .easy))
20 | }
21 |
22 | var gameOverMessage: String {
23 | if game.isCheckmate {
24 | if game.currentPlayer == .white {
25 | return "Checkmate. You lose 😭"
26 | } else {
27 | return "Checkmate! You win 🎉"
28 | }
29 | } else if game.isStalemate {
30 | return "Stalemate!"
31 | } else {
32 | return ""
33 | }
34 | }
35 |
36 | var body: some View {
37 | GeometryReader { geometry in
38 | let squareSize = min(geometry.size.width, geometry.size.height) / 8
39 | let boardSize = squareSize * 8
40 |
41 | VStack {
42 | Spacer()
43 | VStack(spacing: 0) {
44 | ForEach((0..<8).reversed(), id: \.self) { row in
45 | HStack(spacing: 0) {
46 | ForEach(0..<8, id: \.self) { column in
47 | ZStack {
48 | // Square color
49 | Rectangle()
50 | .fill((row + column) % 2 == 0 ? Color.white : Color.gray)
51 | .frame(width: squareSize, height: squareSize)
52 |
53 | // Piece Image
54 | if let piece = game.board[row][column] {
55 | Image("\(piece.type.rawValue)_\(piece.color.rawValue)")
56 | .resizable()
57 | .frame(width: squareSize * 0.8, height: squareSize * 0.8)
58 | .onTapGesture {
59 | if piece.color == game.currentPlayer {
60 | withAnimation(.easeInOut(duration: 0.3)) {
61 | game.selectPiece(at: (row, column))
62 | }
63 | }
64 | }
65 | }
66 |
67 | // Highlight possible moves
68 | if game.possibleMoves.contains(where: { $0 == (row, column) }) {
69 | Circle()
70 | .fill(Color.blue.opacity(0.5))
71 | .frame(width: squareSize * 0.6, height: squareSize * 0.6)
72 | .onTapGesture {
73 | if let _ = game.selectedPiece {
74 | withAnimation(.easeInOut(duration: 0.3)) {
75 | if !game.movePiece(to: (row, column)) {
76 | // Checkmate or stalemate
77 | showGameOverAlert = true
78 | } else {
79 | // Check for promotion
80 | if game.promotionPending != nil {
81 | showPromotionSheet = true
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 | }
93 | .frame(width: boardSize, height: boardSize)
94 | .border(Color.black, width: 2)
95 | Spacer()
96 |
97 | // Conditionally display the Reset button
98 | if showResetButton {
99 | Button(action: {
100 | showResetButton = false
101 | showDifficultySelection = true
102 | }) {
103 | Text("Reset")
104 | .font(.headline)
105 | .padding()
106 | .frame(width: 200)
107 | .background(Color.blue)
108 | .foregroundColor(.white)
109 | .cornerRadius(8)
110 | }
111 | .padding()
112 | }
113 | }
114 | .frame(maxWidth: .infinity, maxHeight: .infinity)
115 | }
116 | .onChange(of: game.isCheckmate) { isCheckmate in
117 | if isCheckmate {
118 | showGameOverAlert = true
119 | }
120 | }
121 | .onChange(of: game.isStalemate) { isStalemate in
122 | if isStalemate {
123 | showGameOverAlert = true
124 | }
125 | }
126 | .onChange(of: game.promotionPending) { _ in
127 | if let promotion = game.promotionPending, promotion.color == .white {
128 | showPromotionSheet = true
129 | } else {
130 | showPromotionSheet = false
131 | }
132 | }
133 | .alert(isPresented: $showGameOverAlert) {
134 | Alert(
135 | title: Text("Game Over"),
136 | message: Text(gameOverMessage),
137 | primaryButton: .default(Text("Reset")) {
138 | showDifficultySelection = true
139 | },
140 | secondaryButton: .cancel {
141 | showResetButton = true // Show the Reset button when Cancel is tapped
142 | }
143 | )
144 | }
145 | .sheet(isPresented: $showPromotionSheet) {
146 | PromotionView(game: game)
147 | }
148 | .sheet(isPresented: $showDifficultySelection, onDismiss: {
149 | game.resetGame(difficulty: selectedDifficulty)
150 | }) {
151 | DifficultySelectionView(selectedDifficulty: $selectedDifficulty)
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/Chess/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "29.png",
17 | "idiom" : "iphone",
18 | "scale" : "1x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "58.png",
23 | "idiom" : "iphone",
24 | "scale" : "2x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "87.png",
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "29x29"
32 | },
33 | {
34 | "filename" : "80.png",
35 | "idiom" : "iphone",
36 | "scale" : "2x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "120.png",
41 | "idiom" : "iphone",
42 | "scale" : "3x",
43 | "size" : "40x40"
44 | },
45 | {
46 | "filename" : "57.png",
47 | "idiom" : "iphone",
48 | "scale" : "1x",
49 | "size" : "57x57"
50 | },
51 | {
52 | "filename" : "114.png",
53 | "idiom" : "iphone",
54 | "scale" : "2x",
55 | "size" : "57x57"
56 | },
57 | {
58 | "filename" : "120.png",
59 | "idiom" : "iphone",
60 | "scale" : "2x",
61 | "size" : "60x60"
62 | },
63 | {
64 | "filename" : "180.png",
65 | "idiom" : "iphone",
66 | "scale" : "3x",
67 | "size" : "60x60"
68 | },
69 | {
70 | "filename" : "20.png",
71 | "idiom" : "ipad",
72 | "scale" : "1x",
73 | "size" : "20x20"
74 | },
75 | {
76 | "filename" : "40.png",
77 | "idiom" : "ipad",
78 | "scale" : "2x",
79 | "size" : "20x20"
80 | },
81 | {
82 | "filename" : "29.png",
83 | "idiom" : "ipad",
84 | "scale" : "1x",
85 | "size" : "29x29"
86 | },
87 | {
88 | "filename" : "58.png",
89 | "idiom" : "ipad",
90 | "scale" : "2x",
91 | "size" : "29x29"
92 | },
93 | {
94 | "filename" : "40.png",
95 | "idiom" : "ipad",
96 | "scale" : "1x",
97 | "size" : "40x40"
98 | },
99 | {
100 | "filename" : "80.png",
101 | "idiom" : "ipad",
102 | "scale" : "2x",
103 | "size" : "40x40"
104 | },
105 | {
106 | "filename" : "50.png",
107 | "idiom" : "ipad",
108 | "scale" : "1x",
109 | "size" : "50x50"
110 | },
111 | {
112 | "filename" : "100.png",
113 | "idiom" : "ipad",
114 | "scale" : "2x",
115 | "size" : "50x50"
116 | },
117 | {
118 | "filename" : "72.png",
119 | "idiom" : "ipad",
120 | "scale" : "1x",
121 | "size" : "72x72"
122 | },
123 | {
124 | "filename" : "144.png",
125 | "idiom" : "ipad",
126 | "scale" : "2x",
127 | "size" : "72x72"
128 | },
129 | {
130 | "filename" : "76.png",
131 | "idiom" : "ipad",
132 | "scale" : "1x",
133 | "size" : "76x76"
134 | },
135 | {
136 | "filename" : "152.png",
137 | "idiom" : "ipad",
138 | "scale" : "2x",
139 | "size" : "76x76"
140 | },
141 | {
142 | "filename" : "167.png",
143 | "idiom" : "ipad",
144 | "scale" : "2x",
145 | "size" : "83.5x83.5"
146 | },
147 | {
148 | "filename" : "1024.png",
149 | "idiom" : "ios-marketing",
150 | "scale" : "1x",
151 | "size" : "1024x1024"
152 | },
153 | {
154 | "filename" : "16.png",
155 | "idiom" : "mac",
156 | "scale" : "1x",
157 | "size" : "16x16"
158 | },
159 | {
160 | "filename" : "32.png",
161 | "idiom" : "mac",
162 | "scale" : "2x",
163 | "size" : "16x16"
164 | },
165 | {
166 | "filename" : "32.png",
167 | "idiom" : "mac",
168 | "scale" : "1x",
169 | "size" : "32x32"
170 | },
171 | {
172 | "filename" : "64.png",
173 | "idiom" : "mac",
174 | "scale" : "2x",
175 | "size" : "32x32"
176 | },
177 | {
178 | "filename" : "128.png",
179 | "idiom" : "mac",
180 | "scale" : "1x",
181 | "size" : "128x128"
182 | },
183 | {
184 | "filename" : "256.png",
185 | "idiom" : "mac",
186 | "scale" : "2x",
187 | "size" : "128x128"
188 | },
189 | {
190 | "filename" : "256.png",
191 | "idiom" : "mac",
192 | "scale" : "1x",
193 | "size" : "256x256"
194 | },
195 | {
196 | "filename" : "512.png",
197 | "idiom" : "mac",
198 | "scale" : "2x",
199 | "size" : "256x256"
200 | },
201 | {
202 | "filename" : "512.png",
203 | "idiom" : "mac",
204 | "scale" : "1x",
205 | "size" : "512x512"
206 | },
207 | {
208 | "filename" : "1024.png",
209 | "idiom" : "mac",
210 | "scale" : "2x",
211 | "size" : "512x512"
212 | },
213 | {
214 | "filename" : "48.png",
215 | "idiom" : "watch",
216 | "role" : "notificationCenter",
217 | "scale" : "2x",
218 | "size" : "24x24",
219 | "subtype" : "38mm"
220 | },
221 | {
222 | "filename" : "55.png",
223 | "idiom" : "watch",
224 | "role" : "notificationCenter",
225 | "scale" : "2x",
226 | "size" : "27.5x27.5",
227 | "subtype" : "42mm"
228 | },
229 | {
230 | "filename" : "58.png",
231 | "idiom" : "watch",
232 | "role" : "companionSettings",
233 | "scale" : "2x",
234 | "size" : "29x29"
235 | },
236 | {
237 | "filename" : "87.png",
238 | "idiom" : "watch",
239 | "role" : "companionSettings",
240 | "scale" : "3x",
241 | "size" : "29x29"
242 | },
243 | {
244 | "filename" : "66.png",
245 | "idiom" : "watch",
246 | "role" : "notificationCenter",
247 | "scale" : "2x",
248 | "size" : "33x33",
249 | "subtype" : "45mm"
250 | },
251 | {
252 | "filename" : "80.png",
253 | "idiom" : "watch",
254 | "role" : "appLauncher",
255 | "scale" : "2x",
256 | "size" : "40x40",
257 | "subtype" : "38mm"
258 | },
259 | {
260 | "filename" : "88.png",
261 | "idiom" : "watch",
262 | "role" : "appLauncher",
263 | "scale" : "2x",
264 | "size" : "44x44",
265 | "subtype" : "40mm"
266 | },
267 | {
268 | "filename" : "92.png",
269 | "idiom" : "watch",
270 | "role" : "appLauncher",
271 | "scale" : "2x",
272 | "size" : "46x46",
273 | "subtype" : "41mm"
274 | },
275 | {
276 | "filename" : "100.png",
277 | "idiom" : "watch",
278 | "role" : "appLauncher",
279 | "scale" : "2x",
280 | "size" : "50x50",
281 | "subtype" : "44mm"
282 | },
283 | {
284 | "idiom" : "watch",
285 | "role" : "appLauncher",
286 | "scale" : "2x",
287 | "size" : "51x51",
288 | "subtype" : "45mm"
289 | },
290 | {
291 | "idiom" : "watch",
292 | "role" : "appLauncher",
293 | "scale" : "2x",
294 | "size" : "54x54",
295 | "subtype" : "49mm"
296 | },
297 | {
298 | "filename" : "172.png",
299 | "idiom" : "watch",
300 | "role" : "quickLook",
301 | "scale" : "2x",
302 | "size" : "86x86",
303 | "subtype" : "38mm"
304 | },
305 | {
306 | "filename" : "196.png",
307 | "idiom" : "watch",
308 | "role" : "quickLook",
309 | "scale" : "2x",
310 | "size" : "98x98",
311 | "subtype" : "42mm"
312 | },
313 | {
314 | "filename" : "216.png",
315 | "idiom" : "watch",
316 | "role" : "quickLook",
317 | "scale" : "2x",
318 | "size" : "108x108",
319 | "subtype" : "44mm"
320 | },
321 | {
322 | "idiom" : "watch",
323 | "role" : "quickLook",
324 | "scale" : "2x",
325 | "size" : "117x117",
326 | "subtype" : "45mm"
327 | },
328 | {
329 | "idiom" : "watch",
330 | "role" : "quickLook",
331 | "scale" : "2x",
332 | "size" : "129x129",
333 | "subtype" : "49mm"
334 | },
335 | {
336 | "filename" : "1024.png",
337 | "idiom" : "watch-marketing",
338 | "scale" : "1x",
339 | "size" : "1024x1024"
340 | }
341 | ],
342 | "info" : {
343 | "author" : "xcode",
344 | "version" : 1
345 | }
346 | }
347 |
--------------------------------------------------------------------------------
/Chess/OpponentChessEngine.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OpponentChessEngine.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import Foundation
9 |
10 | enum DifficultyLevel {
11 | case easy
12 | case medium
13 | case hard
14 | }
15 |
16 | class OpponentChessEngine {
17 | unowned var game: ChessGame
18 | var difficulty: DifficultyLevel
19 |
20 | init(game: ChessGame, difficulty: DifficultyLevel) {
21 | self.game = game
22 | self.difficulty = difficulty
23 | }
24 |
25 | func makeMove() -> Bool {
26 | switch difficulty {
27 | case .easy:
28 | return makeRandomMove()
29 | case .medium:
30 | return makeMediumMove()
31 | case .hard:
32 | return makeHardMove()
33 | }
34 | }
35 |
36 | // MARK: - Easy Level: Random Move
37 |
38 | func makeRandomMove() -> Bool {
39 | let opponentPieces = game.board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == game.currentPlayer }
40 |
41 | // Clear En passant target
42 | game.enPassantTarget = nil
43 |
44 | for piece in opponentPieces.shuffled() {
45 | let moves = game.calculateLegalMoves(for: piece)
46 | if let move = moves.randomElement() {
47 | game.saveCurrentBoardState()
48 | game.performMove(piece: piece, to: move)
49 | return true
50 | }
51 | }
52 |
53 | // No valid moves found
54 | print("Opponent has no valid moves")
55 | game.currentPlayer = game.currentPlayer.opponent
56 | game.updateGameState()
57 | return false
58 | }
59 |
60 | // MARK: - Medium Level: Capture High-Value Pieces
61 |
62 | func makeMediumMove() -> Bool {
63 | let opponentPieces = game.board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == game.currentPlayer }
64 | var possibleMoves: [(piece: ChessPiece, destination: (Int, Int), score: Int)] = []
65 |
66 | // Clear En passant target
67 | game.enPassantTarget = nil
68 |
69 | for piece in opponentPieces {
70 | let moves = game.calculateLegalMoves(for: piece)
71 | for move in moves {
72 | // Check for captured piece
73 | let capturedPiece = game.board[move.0][move.1]
74 | let score = capturedPiece != nil ? game.pieceValue(capturedPiece!.type) : 0
75 | possibleMoves.append((piece, move, score))
76 | }
77 | }
78 |
79 | if possibleMoves.isEmpty {
80 | // No valid moves found
81 | print("Opponent has no valid moves")
82 | game.currentPlayer = game.currentPlayer.opponent
83 | game.updateGameState()
84 | return false
85 | }
86 |
87 | // Prefer capturing moves
88 | let captureMoves = possibleMoves.filter { $0.score > 0 }
89 | let selectedMove: (piece: ChessPiece, destination: (Int, Int), score: Int)
90 | if !captureMoves.isEmpty {
91 | selectedMove = captureMoves.randomElement()!
92 | } else {
93 | selectedMove = possibleMoves.randomElement()!
94 | }
95 |
96 | game.saveCurrentBoardState()
97 | game.performMove(piece: selectedMove.piece, to: selectedMove.destination)
98 | return true
99 | }
100 |
101 | // MARK: - Hard Level: Minimax Algorithm
102 | func makeHardMove() -> Bool {
103 | let depth = 2 // Adjust for performance vs. strength
104 | guard let bestMove = minimaxRoot(depth: depth, isMaximizingPlayer: true) else {
105 | // No valid moves found
106 | print("Opponent has no valid moves")
107 | game.currentPlayer = game.currentPlayer.opponent
108 | game.updateGameState()
109 | return false
110 | }
111 |
112 | // Execute the best move
113 | game.saveCurrentBoardState()
114 | game.performMove(piece: bestMove.piece, to: bestMove.destination)
115 | return true
116 | }
117 |
118 | func minimaxRoot(depth: Int, isMaximizingPlayer: Bool) -> (piece: ChessPiece, destination: (Int, Int))? {
119 | var bestScore = Int.min
120 | var bestMoves: [(piece: ChessPiece, destination: (Int, Int))] = []
121 |
122 | let opponentPieces = game.board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == game.currentPlayer }
123 |
124 | for piece in opponentPieces {
125 | let moves = game.calculateLegalMoves(for: piece)
126 | for move in moves {
127 | // Save the current state
128 | let originalPiece = game.board[move.0][move.1]
129 | let originalPosition = piece.position
130 |
131 | // Make the move
132 | game.board[piece.position.0][piece.position.1] = nil
133 | game.board[move.0][move.1] = ChessPiece(
134 | type: piece.type,
135 | color: piece.color,
136 | position: move,
137 | hasMoved: true
138 | )
139 |
140 | let score = minimax(depth: depth - 1, isMaximizingPlayer: false)
141 |
142 | // Undo the move
143 | game.board[piece.position.0][piece.position.1] = ChessPiece(
144 | type: piece.type,
145 | color: piece.color,
146 | position: originalPosition,
147 | hasMoved: piece.hasMoved
148 | )
149 | game.board[move.0][move.1] = originalPiece
150 |
151 | if score > bestScore {
152 | bestScore = score
153 | bestMoves = [(piece, move)]
154 | } else if score == bestScore {
155 | bestMoves.append((piece, move))
156 | }
157 | }
158 | }
159 |
160 | if bestMoves.isEmpty {
161 | return nil
162 | } else {
163 | // Introduce a bit of randomness among the best moves
164 | let selectedMove = bestMoves.randomElement()!
165 | return selectedMove
166 | }
167 | }
168 |
169 | func minimax(depth: Int, isMaximizingPlayer: Bool) -> Int {
170 | if depth == 0 {
171 | return evaluateBoard()
172 | }
173 |
174 | let currentColor = isMaximizingPlayer ? game.currentPlayer : game.currentPlayer.opponent
175 | let pieces = game.board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == currentColor }
176 |
177 | if isMaximizingPlayer {
178 | var bestScore = Int.min
179 | for piece in pieces {
180 | let moves = game.calculateLegalMoves(for: piece)
181 | for move in moves {
182 | // Save the current state
183 | let originalPiece = game.board[move.0][move.1]
184 | let originalPosition = piece.position
185 |
186 | // Make the move
187 | game.board[piece.position.0][piece.position.1] = nil
188 | game.board[move.0][move.1] = ChessPiece(
189 | type: piece.type,
190 | color: piece.color,
191 | position: move,
192 | hasMoved: true
193 | )
194 |
195 | let score = minimax(depth: depth - 1, isMaximizingPlayer: false)
196 |
197 | // Undo the move
198 | game.board[piece.position.0][piece.position.1] = ChessPiece(
199 | type: piece.type,
200 | color: piece.color,
201 | position: originalPosition,
202 | hasMoved: piece.hasMoved
203 | )
204 | game.board[move.0][move.1] = originalPiece
205 |
206 | bestScore = max(bestScore, score)
207 | }
208 | }
209 | return bestScore
210 | } else {
211 | var bestScore = Int.max
212 | for piece in pieces {
213 | let moves = game.calculateLegalMoves(for: piece)
214 | for move in moves {
215 | // Save the current state
216 | let originalPiece = game.board[move.0][move.1]
217 | let originalPosition = piece.position
218 |
219 | // Make the move
220 | game.board[piece.position.0][piece.position.1] = nil
221 | game.board[move.0][move.1] = ChessPiece(
222 | type: piece.type,
223 | color: piece.color,
224 | position: move,
225 | hasMoved: true
226 | )
227 |
228 | let score = minimax(depth: depth - 1, isMaximizingPlayer: true)
229 |
230 | // Undo the move
231 | game.board[piece.position.0][piece.position.1] = ChessPiece(
232 | type: piece.type,
233 | color: piece.color,
234 | position: originalPosition,
235 | hasMoved: piece.hasMoved
236 | )
237 | game.board[move.0][move.1] = originalPiece
238 |
239 | bestScore = min(bestScore, score)
240 | }
241 | }
242 | return bestScore
243 | }
244 | }
245 |
246 | func evaluateBoard() -> Int {
247 | var totalScore = 0
248 | for row in game.board {
249 | for piece in row.compactMap({ $0 }) {
250 | let value = game.pieceValue(piece.type)
251 | totalScore += piece.color == game.currentPlayer ? value : -value
252 | }
253 | }
254 | // Introduce a small random factor (up to +/- 5%)
255 | let randomFactor = Int(Double(totalScore) * Double.random(in: -0.05...0.05))
256 | return totalScore + randomFactor
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/Chess.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 77;
7 | objects = {
8 |
9 | /* Begin PBXContainerItemProxy section */
10 | D09596842CCEA7F1001420AB /* PBXContainerItemProxy */ = {
11 | isa = PBXContainerItemProxy;
12 | containerPortal = D095966B2CCEA7EF001420AB /* Project object */;
13 | proxyType = 1;
14 | remoteGlobalIDString = D09596722CCEA7EF001420AB;
15 | remoteInfo = Chess;
16 | };
17 | D095968E2CCEA7F1001420AB /* PBXContainerItemProxy */ = {
18 | isa = PBXContainerItemProxy;
19 | containerPortal = D095966B2CCEA7EF001420AB /* Project object */;
20 | proxyType = 1;
21 | remoteGlobalIDString = D09596722CCEA7EF001420AB;
22 | remoteInfo = Chess;
23 | };
24 | /* End PBXContainerItemProxy section */
25 |
26 | /* Begin PBXFileReference section */
27 | D09596732CCEA7EF001420AB /* Chess.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Chess.app; sourceTree = BUILT_PRODUCTS_DIR; };
28 | D09596832CCEA7F1001420AB /* ChessTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChessTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
29 | D095968D2CCEA7F1001420AB /* ChessUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChessUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
30 | /* End PBXFileReference section */
31 |
32 | /* Begin PBXFileSystemSynchronizedRootGroup section */
33 | D09596752CCEA7EF001420AB /* Chess */ = {
34 | isa = PBXFileSystemSynchronizedRootGroup;
35 | path = Chess;
36 | sourceTree = "";
37 | };
38 | D09596862CCEA7F1001420AB /* ChessTests */ = {
39 | isa = PBXFileSystemSynchronizedRootGroup;
40 | path = ChessTests;
41 | sourceTree = "";
42 | };
43 | D09596902CCEA7F1001420AB /* ChessUITests */ = {
44 | isa = PBXFileSystemSynchronizedRootGroup;
45 | path = ChessUITests;
46 | sourceTree = "";
47 | };
48 | /* End PBXFileSystemSynchronizedRootGroup section */
49 |
50 | /* Begin PBXFrameworksBuildPhase section */
51 | D09596702CCEA7EF001420AB /* Frameworks */ = {
52 | isa = PBXFrameworksBuildPhase;
53 | buildActionMask = 2147483647;
54 | files = (
55 | );
56 | runOnlyForDeploymentPostprocessing = 0;
57 | };
58 | D09596802CCEA7F1001420AB /* Frameworks */ = {
59 | isa = PBXFrameworksBuildPhase;
60 | buildActionMask = 2147483647;
61 | files = (
62 | );
63 | runOnlyForDeploymentPostprocessing = 0;
64 | };
65 | D095968A2CCEA7F1001420AB /* Frameworks */ = {
66 | isa = PBXFrameworksBuildPhase;
67 | buildActionMask = 2147483647;
68 | files = (
69 | );
70 | runOnlyForDeploymentPostprocessing = 0;
71 | };
72 | /* End PBXFrameworksBuildPhase section */
73 |
74 | /* Begin PBXGroup section */
75 | D095966A2CCEA7EF001420AB = {
76 | isa = PBXGroup;
77 | children = (
78 | D09596752CCEA7EF001420AB /* Chess */,
79 | D09596862CCEA7F1001420AB /* ChessTests */,
80 | D09596902CCEA7F1001420AB /* ChessUITests */,
81 | D09596742CCEA7EF001420AB /* Products */,
82 | );
83 | sourceTree = "";
84 | };
85 | D09596742CCEA7EF001420AB /* Products */ = {
86 | isa = PBXGroup;
87 | children = (
88 | D09596732CCEA7EF001420AB /* Chess.app */,
89 | D09596832CCEA7F1001420AB /* ChessTests.xctest */,
90 | D095968D2CCEA7F1001420AB /* ChessUITests.xctest */,
91 | );
92 | name = Products;
93 | sourceTree = "";
94 | };
95 | /* End PBXGroup section */
96 |
97 | /* Begin PBXNativeTarget section */
98 | D09596722CCEA7EF001420AB /* Chess */ = {
99 | isa = PBXNativeTarget;
100 | buildConfigurationList = D09596972CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "Chess" */;
101 | buildPhases = (
102 | D095966F2CCEA7EF001420AB /* Sources */,
103 | D09596702CCEA7EF001420AB /* Frameworks */,
104 | D09596712CCEA7EF001420AB /* Resources */,
105 | );
106 | buildRules = (
107 | );
108 | dependencies = (
109 | );
110 | fileSystemSynchronizedGroups = (
111 | D09596752CCEA7EF001420AB /* Chess */,
112 | );
113 | name = Chess;
114 | packageProductDependencies = (
115 | );
116 | productName = Chess;
117 | productReference = D09596732CCEA7EF001420AB /* Chess.app */;
118 | productType = "com.apple.product-type.application";
119 | };
120 | D09596822CCEA7F1001420AB /* ChessTests */ = {
121 | isa = PBXNativeTarget;
122 | buildConfigurationList = D095969A2CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "ChessTests" */;
123 | buildPhases = (
124 | D095967F2CCEA7F1001420AB /* Sources */,
125 | D09596802CCEA7F1001420AB /* Frameworks */,
126 | D09596812CCEA7F1001420AB /* Resources */,
127 | );
128 | buildRules = (
129 | );
130 | dependencies = (
131 | D09596852CCEA7F1001420AB /* PBXTargetDependency */,
132 | );
133 | fileSystemSynchronizedGroups = (
134 | D09596862CCEA7F1001420AB /* ChessTests */,
135 | );
136 | name = ChessTests;
137 | packageProductDependencies = (
138 | );
139 | productName = ChessTests;
140 | productReference = D09596832CCEA7F1001420AB /* ChessTests.xctest */;
141 | productType = "com.apple.product-type.bundle.unit-test";
142 | };
143 | D095968C2CCEA7F1001420AB /* ChessUITests */ = {
144 | isa = PBXNativeTarget;
145 | buildConfigurationList = D095969D2CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "ChessUITests" */;
146 | buildPhases = (
147 | D09596892CCEA7F1001420AB /* Sources */,
148 | D095968A2CCEA7F1001420AB /* Frameworks */,
149 | D095968B2CCEA7F1001420AB /* Resources */,
150 | );
151 | buildRules = (
152 | );
153 | dependencies = (
154 | D095968F2CCEA7F1001420AB /* PBXTargetDependency */,
155 | );
156 | fileSystemSynchronizedGroups = (
157 | D09596902CCEA7F1001420AB /* ChessUITests */,
158 | );
159 | name = ChessUITests;
160 | packageProductDependencies = (
161 | );
162 | productName = ChessUITests;
163 | productReference = D095968D2CCEA7F1001420AB /* ChessUITests.xctest */;
164 | productType = "com.apple.product-type.bundle.ui-testing";
165 | };
166 | /* End PBXNativeTarget section */
167 |
168 | /* Begin PBXProject section */
169 | D095966B2CCEA7EF001420AB /* Project object */ = {
170 | isa = PBXProject;
171 | attributes = {
172 | BuildIndependentTargetsInParallel = 1;
173 | LastSwiftUpdateCheck = 1600;
174 | LastUpgradeCheck = 1600;
175 | TargetAttributes = {
176 | D09596722CCEA7EF001420AB = {
177 | CreatedOnToolsVersion = 16.0;
178 | };
179 | D09596822CCEA7F1001420AB = {
180 | CreatedOnToolsVersion = 16.0;
181 | TestTargetID = D09596722CCEA7EF001420AB;
182 | };
183 | D095968C2CCEA7F1001420AB = {
184 | CreatedOnToolsVersion = 16.0;
185 | TestTargetID = D09596722CCEA7EF001420AB;
186 | };
187 | };
188 | };
189 | buildConfigurationList = D095966E2CCEA7EF001420AB /* Build configuration list for PBXProject "Chess" */;
190 | developmentRegion = en;
191 | hasScannedForEncodings = 0;
192 | knownRegions = (
193 | en,
194 | Base,
195 | );
196 | mainGroup = D095966A2CCEA7EF001420AB;
197 | minimizedProjectReferenceProxies = 1;
198 | preferredProjectObjectVersion = 77;
199 | productRefGroup = D09596742CCEA7EF001420AB /* Products */;
200 | projectDirPath = "";
201 | projectRoot = "";
202 | targets = (
203 | D09596722CCEA7EF001420AB /* Chess */,
204 | D09596822CCEA7F1001420AB /* ChessTests */,
205 | D095968C2CCEA7F1001420AB /* ChessUITests */,
206 | );
207 | };
208 | /* End PBXProject section */
209 |
210 | /* Begin PBXResourcesBuildPhase section */
211 | D09596712CCEA7EF001420AB /* Resources */ = {
212 | isa = PBXResourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | );
216 | runOnlyForDeploymentPostprocessing = 0;
217 | };
218 | D09596812CCEA7F1001420AB /* Resources */ = {
219 | isa = PBXResourcesBuildPhase;
220 | buildActionMask = 2147483647;
221 | files = (
222 | );
223 | runOnlyForDeploymentPostprocessing = 0;
224 | };
225 | D095968B2CCEA7F1001420AB /* Resources */ = {
226 | isa = PBXResourcesBuildPhase;
227 | buildActionMask = 2147483647;
228 | files = (
229 | );
230 | runOnlyForDeploymentPostprocessing = 0;
231 | };
232 | /* End PBXResourcesBuildPhase section */
233 |
234 | /* Begin PBXSourcesBuildPhase section */
235 | D095966F2CCEA7EF001420AB /* Sources */ = {
236 | isa = PBXSourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | );
240 | runOnlyForDeploymentPostprocessing = 0;
241 | };
242 | D095967F2CCEA7F1001420AB /* Sources */ = {
243 | isa = PBXSourcesBuildPhase;
244 | buildActionMask = 2147483647;
245 | files = (
246 | );
247 | runOnlyForDeploymentPostprocessing = 0;
248 | };
249 | D09596892CCEA7F1001420AB /* Sources */ = {
250 | isa = PBXSourcesBuildPhase;
251 | buildActionMask = 2147483647;
252 | files = (
253 | );
254 | runOnlyForDeploymentPostprocessing = 0;
255 | };
256 | /* End PBXSourcesBuildPhase section */
257 |
258 | /* Begin PBXTargetDependency section */
259 | D09596852CCEA7F1001420AB /* PBXTargetDependency */ = {
260 | isa = PBXTargetDependency;
261 | target = D09596722CCEA7EF001420AB /* Chess */;
262 | targetProxy = D09596842CCEA7F1001420AB /* PBXContainerItemProxy */;
263 | };
264 | D095968F2CCEA7F1001420AB /* PBXTargetDependency */ = {
265 | isa = PBXTargetDependency;
266 | target = D09596722CCEA7EF001420AB /* Chess */;
267 | targetProxy = D095968E2CCEA7F1001420AB /* PBXContainerItemProxy */;
268 | };
269 | /* End PBXTargetDependency section */
270 |
271 | /* Begin XCBuildConfiguration section */
272 | D09596952CCEA7F1001420AB /* Debug */ = {
273 | isa = XCBuildConfiguration;
274 | buildSettings = {
275 | ALWAYS_SEARCH_USER_PATHS = NO;
276 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
277 | CLANG_ANALYZER_NONNULL = YES;
278 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
279 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
280 | CLANG_ENABLE_MODULES = YES;
281 | CLANG_ENABLE_OBJC_ARC = YES;
282 | CLANG_ENABLE_OBJC_WEAK = YES;
283 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
284 | CLANG_WARN_BOOL_CONVERSION = YES;
285 | CLANG_WARN_COMMA = YES;
286 | CLANG_WARN_CONSTANT_CONVERSION = YES;
287 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
288 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
289 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
290 | CLANG_WARN_EMPTY_BODY = YES;
291 | CLANG_WARN_ENUM_CONVERSION = YES;
292 | CLANG_WARN_INFINITE_RECURSION = YES;
293 | CLANG_WARN_INT_CONVERSION = YES;
294 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
295 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
296 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
297 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
298 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
299 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
300 | CLANG_WARN_STRICT_PROTOTYPES = YES;
301 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
302 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
303 | CLANG_WARN_UNREACHABLE_CODE = YES;
304 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
305 | COPY_PHASE_STRIP = NO;
306 | DEBUG_INFORMATION_FORMAT = dwarf;
307 | ENABLE_STRICT_OBJC_MSGSEND = YES;
308 | ENABLE_TESTABILITY = YES;
309 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
310 | GCC_C_LANGUAGE_STANDARD = gnu17;
311 | GCC_DYNAMIC_NO_PIC = NO;
312 | GCC_NO_COMMON_BLOCKS = YES;
313 | GCC_OPTIMIZATION_LEVEL = 0;
314 | GCC_PREPROCESSOR_DEFINITIONS = (
315 | "DEBUG=1",
316 | "$(inherited)",
317 | );
318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
320 | GCC_WARN_UNDECLARED_SELECTOR = YES;
321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
322 | GCC_WARN_UNUSED_FUNCTION = YES;
323 | GCC_WARN_UNUSED_VARIABLE = YES;
324 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
325 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
326 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
327 | MTL_FAST_MATH = YES;
328 | ONLY_ACTIVE_ARCH = YES;
329 | SDKROOT = iphoneos;
330 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
331 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
332 | };
333 | name = Debug;
334 | };
335 | D09596962CCEA7F1001420AB /* Release */ = {
336 | isa = XCBuildConfiguration;
337 | buildSettings = {
338 | ALWAYS_SEARCH_USER_PATHS = NO;
339 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
340 | CLANG_ANALYZER_NONNULL = YES;
341 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
342 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
343 | CLANG_ENABLE_MODULES = YES;
344 | CLANG_ENABLE_OBJC_ARC = YES;
345 | CLANG_ENABLE_OBJC_WEAK = YES;
346 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
347 | CLANG_WARN_BOOL_CONVERSION = YES;
348 | CLANG_WARN_COMMA = YES;
349 | CLANG_WARN_CONSTANT_CONVERSION = YES;
350 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
352 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
353 | CLANG_WARN_EMPTY_BODY = YES;
354 | CLANG_WARN_ENUM_CONVERSION = YES;
355 | CLANG_WARN_INFINITE_RECURSION = YES;
356 | CLANG_WARN_INT_CONVERSION = YES;
357 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
358 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
359 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
360 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
361 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
362 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
363 | CLANG_WARN_STRICT_PROTOTYPES = YES;
364 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
365 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
366 | CLANG_WARN_UNREACHABLE_CODE = YES;
367 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
368 | COPY_PHASE_STRIP = NO;
369 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
370 | ENABLE_NS_ASSERTIONS = NO;
371 | ENABLE_STRICT_OBJC_MSGSEND = YES;
372 | ENABLE_USER_SCRIPT_SANDBOXING = YES;
373 | GCC_C_LANGUAGE_STANDARD = gnu17;
374 | GCC_NO_COMMON_BLOCKS = YES;
375 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
376 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
377 | GCC_WARN_UNDECLARED_SELECTOR = YES;
378 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
379 | GCC_WARN_UNUSED_FUNCTION = YES;
380 | GCC_WARN_UNUSED_VARIABLE = YES;
381 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
382 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
383 | MTL_ENABLE_DEBUG_INFO = NO;
384 | MTL_FAST_MATH = YES;
385 | SDKROOT = iphoneos;
386 | SWIFT_COMPILATION_MODE = wholemodule;
387 | VALIDATE_PRODUCT = YES;
388 | };
389 | name = Release;
390 | };
391 | D09596982CCEA7F1001420AB /* Debug */ = {
392 | isa = XCBuildConfiguration;
393 | buildSettings = {
394 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
395 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
396 | CODE_SIGN_STYLE = Automatic;
397 | CURRENT_PROJECT_VERSION = 1;
398 | DEVELOPMENT_ASSET_PATHS = "\"Chess/Preview Content\"";
399 | DEVELOPMENT_TEAM = 3L56DRQ363;
400 | ENABLE_PREVIEWS = YES;
401 | GENERATE_INFOPLIST_FILE = YES;
402 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
403 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
404 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
405 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
406 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
407 | LD_RUNPATH_SEARCH_PATHS = (
408 | "$(inherited)",
409 | "@executable_path/Frameworks",
410 | );
411 | MARKETING_VERSION = 1.0;
412 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.Chess;
413 | PRODUCT_NAME = "$(TARGET_NAME)";
414 | SWIFT_EMIT_LOC_STRINGS = YES;
415 | SWIFT_VERSION = 5.0;
416 | TARGETED_DEVICE_FAMILY = "1,2";
417 | };
418 | name = Debug;
419 | };
420 | D09596992CCEA7F1001420AB /* Release */ = {
421 | isa = XCBuildConfiguration;
422 | buildSettings = {
423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
424 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
425 | CODE_SIGN_STYLE = Automatic;
426 | CURRENT_PROJECT_VERSION = 1;
427 | DEVELOPMENT_ASSET_PATHS = "\"Chess/Preview Content\"";
428 | DEVELOPMENT_TEAM = 3L56DRQ363;
429 | ENABLE_PREVIEWS = YES;
430 | GENERATE_INFOPLIST_FILE = YES;
431 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
432 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
433 | INFOPLIST_KEY_UILaunchScreen_Generation = YES;
434 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
435 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
436 | LD_RUNPATH_SEARCH_PATHS = (
437 | "$(inherited)",
438 | "@executable_path/Frameworks",
439 | );
440 | MARKETING_VERSION = 1.0;
441 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.Chess;
442 | PRODUCT_NAME = "$(TARGET_NAME)";
443 | SWIFT_EMIT_LOC_STRINGS = YES;
444 | SWIFT_VERSION = 5.0;
445 | TARGETED_DEVICE_FAMILY = "1,2";
446 | };
447 | name = Release;
448 | };
449 | D095969B2CCEA7F1001420AB /* Debug */ = {
450 | isa = XCBuildConfiguration;
451 | buildSettings = {
452 | BUNDLE_LOADER = "$(TEST_HOST)";
453 | CODE_SIGN_STYLE = Automatic;
454 | CURRENT_PROJECT_VERSION = 1;
455 | DEVELOPMENT_TEAM = 3L56DRQ363;
456 | GENERATE_INFOPLIST_FILE = YES;
457 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
458 | MARKETING_VERSION = 1.0;
459 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.ChessTests;
460 | PRODUCT_NAME = "$(TARGET_NAME)";
461 | SWIFT_EMIT_LOC_STRINGS = NO;
462 | SWIFT_VERSION = 5.0;
463 | TARGETED_DEVICE_FAMILY = "1,2";
464 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chess.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Chess";
465 | };
466 | name = Debug;
467 | };
468 | D095969C2CCEA7F1001420AB /* Release */ = {
469 | isa = XCBuildConfiguration;
470 | buildSettings = {
471 | BUNDLE_LOADER = "$(TEST_HOST)";
472 | CODE_SIGN_STYLE = Automatic;
473 | CURRENT_PROJECT_VERSION = 1;
474 | DEVELOPMENT_TEAM = 3L56DRQ363;
475 | GENERATE_INFOPLIST_FILE = YES;
476 | IPHONEOS_DEPLOYMENT_TARGET = 18.0;
477 | MARKETING_VERSION = 1.0;
478 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.ChessTests;
479 | PRODUCT_NAME = "$(TARGET_NAME)";
480 | SWIFT_EMIT_LOC_STRINGS = NO;
481 | SWIFT_VERSION = 5.0;
482 | TARGETED_DEVICE_FAMILY = "1,2";
483 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Chess.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Chess";
484 | };
485 | name = Release;
486 | };
487 | D095969E2CCEA7F1001420AB /* Debug */ = {
488 | isa = XCBuildConfiguration;
489 | buildSettings = {
490 | CODE_SIGN_STYLE = Automatic;
491 | CURRENT_PROJECT_VERSION = 1;
492 | DEVELOPMENT_TEAM = 3L56DRQ363;
493 | GENERATE_INFOPLIST_FILE = YES;
494 | MARKETING_VERSION = 1.0;
495 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.ChessUITests;
496 | PRODUCT_NAME = "$(TARGET_NAME)";
497 | SWIFT_EMIT_LOC_STRINGS = NO;
498 | SWIFT_VERSION = 5.0;
499 | TARGETED_DEVICE_FAMILY = "1,2";
500 | TEST_TARGET_NAME = Chess;
501 | };
502 | name = Debug;
503 | };
504 | D095969F2CCEA7F1001420AB /* Release */ = {
505 | isa = XCBuildConfiguration;
506 | buildSettings = {
507 | CODE_SIGN_STYLE = Automatic;
508 | CURRENT_PROJECT_VERSION = 1;
509 | DEVELOPMENT_TEAM = 3L56DRQ363;
510 | GENERATE_INFOPLIST_FILE = YES;
511 | MARKETING_VERSION = 1.0;
512 | PRODUCT_BUNDLE_IDENTIFIER = com.Haplo.ChessUITests;
513 | PRODUCT_NAME = "$(TARGET_NAME)";
514 | SWIFT_EMIT_LOC_STRINGS = NO;
515 | SWIFT_VERSION = 5.0;
516 | TARGETED_DEVICE_FAMILY = "1,2";
517 | TEST_TARGET_NAME = Chess;
518 | };
519 | name = Release;
520 | };
521 | /* End XCBuildConfiguration section */
522 |
523 | /* Begin XCConfigurationList section */
524 | D095966E2CCEA7EF001420AB /* Build configuration list for PBXProject "Chess" */ = {
525 | isa = XCConfigurationList;
526 | buildConfigurations = (
527 | D09596952CCEA7F1001420AB /* Debug */,
528 | D09596962CCEA7F1001420AB /* Release */,
529 | );
530 | defaultConfigurationIsVisible = 0;
531 | defaultConfigurationName = Release;
532 | };
533 | D09596972CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "Chess" */ = {
534 | isa = XCConfigurationList;
535 | buildConfigurations = (
536 | D09596982CCEA7F1001420AB /* Debug */,
537 | D09596992CCEA7F1001420AB /* Release */,
538 | );
539 | defaultConfigurationIsVisible = 0;
540 | defaultConfigurationName = Release;
541 | };
542 | D095969A2CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "ChessTests" */ = {
543 | isa = XCConfigurationList;
544 | buildConfigurations = (
545 | D095969B2CCEA7F1001420AB /* Debug */,
546 | D095969C2CCEA7F1001420AB /* Release */,
547 | );
548 | defaultConfigurationIsVisible = 0;
549 | defaultConfigurationName = Release;
550 | };
551 | D095969D2CCEA7F1001420AB /* Build configuration list for PBXNativeTarget "ChessUITests" */ = {
552 | isa = XCConfigurationList;
553 | buildConfigurations = (
554 | D095969E2CCEA7F1001420AB /* Debug */,
555 | D095969F2CCEA7F1001420AB /* Release */,
556 | );
557 | defaultConfigurationIsVisible = 0;
558 | defaultConfigurationName = Release;
559 | };
560 | /* End XCConfigurationList section */
561 | };
562 | rootObject = D095966B2CCEA7EF001420AB /* Project object */;
563 | }
564 |
--------------------------------------------------------------------------------
/Chess/ChessGame.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ChessGame.swift
3 | // Chess
4 | //
5 | // Created by Jared Cassoutt on 10/27/24.
6 | //
7 |
8 | import SwiftUI
9 |
10 |
11 | enum PieceType: String {
12 | case pawn, knight, bishop, rook, queen, king
13 | }
14 |
15 | enum PieceColor: String, Equatable {
16 | case white, black
17 |
18 | var opponent: PieceColor {
19 | return self == .white ? .black : .white
20 | }
21 | }
22 |
23 | struct ChessPiece {
24 | let type: PieceType
25 | let color: PieceColor
26 | var position: (Int, Int)
27 | var hasMoved: Bool = false // To track pawn's initial move
28 | }
29 |
30 | struct PromotionPending: Equatable {
31 | let position: (Int, Int)
32 | let color: PieceColor
33 |
34 | static func == (lhs: PromotionPending, rhs: PromotionPending) -> Bool {
35 | return lhs.position == rhs.position && lhs.color == rhs.color
36 | }
37 | }
38 |
39 | class ChessGame: ObservableObject {
40 | @Published var board: [[ChessPiece?]] = Array(repeating: Array(repeating: nil, count: 8), count: 8)
41 | @Published var selectedPiece: ChessPiece?
42 | @Published var possibleMoves: [(Int, Int)] = []
43 | @Published var isInCheck: Bool = false
44 | @Published var isCheckmate: Bool = false
45 | @Published var isStalemate: Bool = false // Added stalemate tracking
46 | @Published var promotionPending: PromotionPending?
47 | @Published var currentPlayer: PieceColor = .white
48 |
49 | // Track castling rights
50 | var whiteKingMoved = false
51 | var blackKingMoved = false
52 | var whiteRookMoved = (left: false, right: false)
53 | var blackRookMoved = (left: false, right: false)
54 |
55 | // En passant tracking
56 | var enPassantTarget: (position: (Int, Int), color: PieceColor)?
57 |
58 | // Opponent engine
59 | var opponentEngine: OpponentChessEngine!
60 |
61 | // Historical log of game
62 | var boardHistory: [[[ChessPiece?]]] = []
63 |
64 | init(difficulty: DifficultyLevel = .easy) {
65 | setupBoard()
66 | self.opponentEngine = OpponentChessEngine(game: self, difficulty: difficulty)
67 | }
68 |
69 | func setupBoard() {
70 | // Setting up pawns
71 | for i in 0..<8 {
72 | board[1][i] = ChessPiece(type: .pawn, color: .white, position: (1, i))
73 | board[6][i] = ChessPiece(type: .pawn, color: .black, position: (6, i))
74 | }
75 |
76 | // Rooks
77 | board[0][0] = ChessPiece(type: .rook, color: .white, position: (0, 0))
78 | board[0][7] = ChessPiece(type: .rook, color: .white, position: (0, 7))
79 | board[7][0] = ChessPiece(type: .rook, color: .black, position: (7, 0))
80 | board[7][7] = ChessPiece(type: .rook, color: .black, position: (7, 7))
81 |
82 | // Knights
83 | board[0][1] = ChessPiece(type: .knight, color: .white, position: (0, 1))
84 | board[0][6] = ChessPiece(type: .knight, color: .white, position: (0, 6))
85 | board[7][1] = ChessPiece(type: .knight, color: .black, position: (7, 1))
86 | board[7][6] = ChessPiece(type: .knight, color: .black, position: (7, 6))
87 |
88 | // Bishops
89 | board[0][2] = ChessPiece(type: .bishop, color: .white, position: (0, 2))
90 | board[0][5] = ChessPiece(type: .bishop, color: .white, position: (0, 5))
91 | board[7][2] = ChessPiece(type: .bishop, color: .black, position: (7, 2))
92 | board[7][5] = ChessPiece(type: .bishop, color: .black, position: (7, 5))
93 |
94 | // Queens
95 | board[0][3] = ChessPiece(type: .queen, color: .white, position: (0, 3))
96 | board[7][3] = ChessPiece(type: .queen, color: .black, position: (7, 3))
97 |
98 | // Kings
99 | board[0][4] = ChessPiece(type: .king, color: .white, position: (0, 4))
100 | board[7][4] = ChessPiece(type: .king, color: .black, position: (7, 4))
101 | }
102 |
103 | func pieceValue(_ type: PieceType) -> Int {
104 | switch type {
105 | case .pawn:
106 | return 1
107 | case .knight, .bishop:
108 | return 3
109 | case .rook:
110 | return 5
111 | case .queen:
112 | return 9
113 | case .king:
114 | return 1000 // High value to represent the king's importance
115 | }
116 | }
117 |
118 | func selectPiece(at position: (Int, Int)) {
119 | if let piece = board[position.0][position.1], piece.color == currentPlayer {
120 | selectedPiece = piece
121 | possibleMoves = calculateLegalMoves(for: piece)
122 | }
123 | }
124 |
125 | func calculateLegalMoves(for piece: ChessPiece) -> [(Int, Int)] {
126 | let moves = calculateMoves(for: piece)
127 | // Filter out moves that would leave the king in check
128 | return moves.filter { move in
129 | willResolveCheck(for: piece, to: move)
130 | }
131 | }
132 |
133 | func calculateMoves(for piece: ChessPiece) -> [(Int, Int)] {
134 | var moves: [(Int, Int)] = []
135 | let position = piece.position
136 |
137 | switch piece.type {
138 | case .pawn:
139 | moves += calculatePawnMoves(for: piece)
140 |
141 | case .rook:
142 | moves += directionalMoves(for: piece, directions: [(1, 0), (-1, 0), (0, 1), (0, -1)])
143 |
144 | case .knight:
145 | let knightMoves = [(2, 1), (2, -1), (-2, 1), (-2, -1),
146 | (1, 2), (1, -2), (-1, 2), (-1, -2)]
147 | for move in knightMoves {
148 | let newRow = position.0 + move.0
149 | let newCol = position.1 + move.1
150 | if isValidPosition(newRow, newCol), board[newRow][newCol]?.color != piece.color {
151 | moves.append((newRow, newCol))
152 | }
153 | }
154 |
155 | case .bishop:
156 | moves += directionalMoves(for: piece, directions: [(1, 1), (1, -1), (-1, 1), (-1, -1)])
157 |
158 | case .queen:
159 | moves += directionalMoves(for: piece, directions: [(1, 0), (-1, 0), (0, 1), (0, -1),
160 | (1, 1), (1, -1), (-1, 1), (-1, -1)])
161 |
162 | case .king:
163 | let kingMoves = [(1, 0), (-1, 0), (0, 1), (0, -1),
164 | (1, 1), (1, -1), (-1, 1), (-1, -1)]
165 | for move in kingMoves {
166 | let newRow = position.0 + move.0
167 | let newCol = position.1 + move.1
168 | if isValidPosition(newRow, newCol), board[newRow][newCol]?.color != piece.color {
169 | if !isSquareUnderAttack((newRow, newCol), byColor: piece.color.opponent) {
170 | moves.append((newRow, newCol))
171 | }
172 | }
173 | }
174 |
175 | // Castling
176 | moves += calculateCastlingMoves(for: piece)
177 | }
178 |
179 | return moves
180 | }
181 |
182 | func calculatePawnMoves(for piece: ChessPiece) -> [(Int, Int)] {
183 | var moves: [(Int, Int)] = []
184 | let position = piece.position
185 | let direction = piece.color == .white ? 1 : -1
186 | let startRow = piece.color == .white ? 1 : 6
187 | let nextRow = position.0 + direction
188 |
189 | // Normal forward move
190 | if isValidPosition(nextRow, position.1), board[nextRow][position.1] == nil {
191 | moves.append((nextRow, position.1))
192 |
193 | // Double move from starting row
194 | let twoStepsRow = position.0 + 2 * direction
195 | if position.0 == startRow, board[twoStepsRow][position.1] == nil, board[nextRow][position.1] == nil {
196 | moves.append((twoStepsRow, position.1))
197 | }
198 | }
199 |
200 | // Capturing diagonally
201 | for dx in [-1, 1] {
202 | let newCol = position.1 + dx
203 | if isValidPosition(nextRow, newCol) {
204 | // Normal capture
205 | if let targetPiece = board[nextRow][newCol], targetPiece.color == piece.color.opponent {
206 | moves.append((nextRow, newCol))
207 | }
208 |
209 | // En passant capture
210 | if let enPassant = enPassantTarget,
211 | enPassant.position == (position.0, newCol),
212 | enPassant.color == piece.color.opponent {
213 | moves.append((nextRow, newCol))
214 | }
215 | }
216 | }
217 |
218 | return moves
219 | }
220 |
221 | // Helper function to calculate moves in a straight line (used for rooks, bishops, and queens)
222 | func directionalMoves(for piece: ChessPiece, directions: [(Int, Int)]) -> [(Int, Int)] {
223 | var moves: [(Int, Int)] = []
224 |
225 | for direction in directions {
226 | var newRow = piece.position.0 + direction.0
227 | var newCol = piece.position.1 + direction.1
228 |
229 | while isValidPosition(newRow, newCol) {
230 | if let targetPiece = board[newRow][newCol] {
231 | if targetPiece.color != piece.color {
232 | moves.append((newRow, newCol))
233 | }
234 | break
235 | }
236 | moves.append((newRow, newCol))
237 | newRow += direction.0
238 | newCol += direction.1
239 | }
240 | }
241 |
242 | return moves
243 | }
244 |
245 | func calculateCastlingMoves(for king: ChessPiece) -> [(Int, Int)] {
246 | guard king.type == .king else { return [] }
247 | var castlingMoves: [(Int, Int)] = []
248 |
249 | let row = king.color == .white ? 0 : 7
250 | let kingMoved = king.color == .white ? whiteKingMoved : blackKingMoved
251 | let rookMoved = king.color == .white ? whiteRookMoved : blackRookMoved
252 | let opponentColor = king.color.opponent
253 |
254 | if kingMoved || isSquareUnderAttack((row, 4), byColor: opponentColor) {
255 | return castlingMoves
256 | }
257 |
258 | // Kingside castling
259 | if !rookMoved.right,
260 | board[row][5] == nil,
261 | board[row][6] == nil,
262 | !isSquareUnderAttack((row, 5), byColor: opponentColor),
263 | !isSquareUnderAttack((row, 6), byColor: opponentColor),
264 | board[row][7]?.type == .rook,
265 | board[row][7]?.color == king.color {
266 | castlingMoves.append((row, 6))
267 | }
268 |
269 | // Queenside castling
270 | if !rookMoved.left,
271 | board[row][1] == nil,
272 | board[row][2] == nil,
273 | board[row][3] == nil,
274 | !isSquareUnderAttack((row, 3), byColor: opponentColor),
275 | !isSquareUnderAttack((row, 2), byColor: opponentColor),
276 | board[row][0]?.type == .rook,
277 | board[row][0]?.color == king.color {
278 | castlingMoves.append((row, 2))
279 | }
280 |
281 | return castlingMoves
282 | }
283 |
284 | func isSquareUnderAttack(_ position: (Int, Int), byColor attackingColor: PieceColor) -> Bool {
285 | for row in 0..<8 {
286 | for col in 0..<8 {
287 | if let piece = board[row][col], piece.color == attackingColor {
288 | let attackSquares = calculateAttackSquares(for: piece)
289 | if attackSquares.contains(where: { $0 == position }) {
290 | return true
291 | }
292 | }
293 | }
294 | }
295 | return false
296 | }
297 |
298 | func calculateAttackSquares(for piece: ChessPiece) -> [(Int, Int)] {
299 | var attackSquares: [(Int, Int)] = []
300 | let position = piece.position
301 |
302 | switch piece.type {
303 | case .pawn:
304 | let direction = piece.color == .white ? 1 : -1
305 | for dx in [-1, 1] {
306 | let newRow = position.0 + direction
307 | let newCol = position.1 + dx
308 | if isValidPosition(newRow, newCol) {
309 | attackSquares.append((newRow, newCol))
310 | }
311 | }
312 |
313 | case .knight:
314 | let knightMoves = [(2, 1), (2, -1), (-2, 1), (-2, -1),
315 | (1, 2), (1, -2), (-1, 2), (-1, -2)]
316 | for move in knightMoves {
317 | let newRow = position.0 + move.0
318 | let newCol = position.1 + move.1
319 | if isValidPosition(newRow, newCol) {
320 | attackSquares.append((newRow, newCol))
321 | }
322 | }
323 |
324 | case .bishop:
325 | attackSquares += attackDirectionalMoves(for: piece, directions: [(1, 1), (1, -1), (-1, 1), (-1, -1)])
326 |
327 | case .rook:
328 | attackSquares += attackDirectionalMoves(for: piece, directions: [(1, 0), (-1, 0), (0, 1), (0, -1)])
329 |
330 | case .queen:
331 | attackSquares += attackDirectionalMoves(for: piece, directions: [(1, 0), (-1, 0), (0, 1), (0, -1),
332 | (1, 1), (1, -1), (-1, 1), (-1, -1)])
333 |
334 | case .king:
335 | let kingMoves = [(1, 0), (-1, 0), (0, 1), (0, -1),
336 | (1, 1), (1, -1), (-1, 1), (-1, -1)]
337 | for move in kingMoves {
338 | let newRow = position.0 + move.0
339 | let newCol = position.1 + move.1
340 | if isValidPosition(newRow, newCol) {
341 | attackSquares.append((newRow, newCol))
342 | }
343 | }
344 | }
345 |
346 | return attackSquares
347 | }
348 |
349 | func attackDirectionalMoves(for piece: ChessPiece, directions: [(Int, Int)]) -> [(Int, Int)] {
350 | var attackSquares: [(Int, Int)] = []
351 |
352 | for direction in directions {
353 | var newRow = piece.position.0 + direction.0
354 | var newCol = piece.position.1 + direction.1
355 |
356 | while isValidPosition(newRow, newCol) {
357 | attackSquares.append((newRow, newCol))
358 | if board[newRow][newCol] != nil {
359 | break
360 | }
361 | newRow += direction.0
362 | newCol += direction.1
363 | }
364 | }
365 |
366 | return attackSquares
367 | }
368 |
369 | func willResolveCheck(for piece: ChessPiece, to destination: (Int, Int)) -> Bool {
370 | // Temporarily move piece to check if move resolves check
371 | let originalPosition = piece.position
372 | let targetPiece = board[destination.0][destination.1]
373 | board[originalPosition.0][originalPosition.1] = nil
374 | board[destination.0][destination.1] = ChessPiece(type: piece.type, color: piece.color, position: destination)
375 |
376 | let kingPosition = piece.type == .king ? destination : findKingPosition(color: piece.color)
377 | let isStillInCheck = isSquareUnderAttack(kingPosition, byColor: piece.color.opponent)
378 |
379 | // Undo the move
380 | board[originalPosition.0][originalPosition.1] = ChessPiece(type: piece.type, color: piece.color, position: originalPosition)
381 | board[destination.0][destination.1] = targetPiece
382 |
383 | return !isStillInCheck
384 | }
385 |
386 | func findKingPosition(color: PieceColor) -> (Int, Int) {
387 | for row in 0..<8 {
388 | for col in 0..<8 {
389 | if let piece = board[row][col], piece.type == .king, piece.color == color {
390 | return (row, col)
391 | }
392 | }
393 | }
394 | return (-1, -1) // Should never happen if board setup is correct
395 | }
396 |
397 | func movePiece(to position: (Int, Int)) -> Bool {
398 | guard let selectedPiece = selectedPiece else { return false }
399 |
400 | if possibleMoves.contains(where: { $0 == position }) {
401 | // Save the current board state before making the move
402 | saveCurrentBoardState()
403 |
404 | performMove(piece: selectedPiece, to: position)
405 |
406 | // Deselect the piece and clear possible moves
407 | self.selectedPiece = nil
408 | possibleMoves = []
409 |
410 | return true
411 | }
412 |
413 | return false
414 | }
415 |
416 |
417 | func promotePawn(to newType: PieceType) {
418 | guard let promotion = promotionPending else { return }
419 | let position = promotion.position
420 | board[position.0][position.1] = ChessPiece(type: newType, color: promotion.color, position: position)
421 | promotionPending = nil
422 |
423 | // Update game state after promotion
424 | updateGameState()
425 |
426 | // Check if game over after promotion
427 | if isCheckmate || isStalemate {
428 | return
429 | }
430 |
431 | // Switch to opponent's turn
432 | currentPlayer = currentPlayer.opponent
433 |
434 | // If opponent's turn, perform move
435 | if currentPlayer == .black {
436 | _ = opponentEngine.makeMove()
437 | }
438 | }
439 |
440 | func performMove(piece: ChessPiece, to position: (Int, Int)) {
441 | let startRow = piece.position.0
442 | let startCol = piece.position.1
443 |
444 | // Inside performMove function
445 | if piece.type == .pawn {
446 | // If pawn moved two steps, set En passant target
447 | if abs(position.0 - startRow) == 2 {
448 | enPassantTarget = (position: position, color: piece.color)
449 | }
450 |
451 | if (piece.color == .white && position.0 == 7) ||
452 | (piece.color == .black && position.0 == 0) {
453 | // Pawn reaches the opposite side
454 | if piece.color == .white {
455 | promotionPending = PromotionPending(position: position, color: piece.color)
456 | } else {
457 | // Automatically promote opponent's pawn to queen
458 | board[position.0][position.1] = ChessPiece(
459 | type: .queen,
460 | color: piece.color,
461 | position: position
462 | )
463 | }
464 | }
465 | }
466 |
467 | // Handle castling move
468 | if piece.type == .king, abs(position.1 - startCol) == 2 {
469 | let rookStartCol = position.1 == 6 ? 7 : 0
470 | let rookEndCol = position.1 == 6 ? 5 : 3
471 | if let rook = board[startRow][rookStartCol] {
472 | board[startRow][rookEndCol] = ChessPiece(type: rook.type, color: rook.color, position: (startRow, rookEndCol))
473 | board[startRow][rookStartCol] = nil
474 | }
475 | }
476 |
477 | // Update the board
478 | board[startRow][startCol] = nil
479 | board[position.0][position.1] = ChessPiece(
480 | type: piece.type,
481 | color: piece.color,
482 | position: position,
483 | hasMoved: true
484 | )
485 |
486 | // Update castling rights
487 | if piece.type == .king {
488 | if piece.color == .white {
489 | whiteKingMoved = true
490 | } else {
491 | blackKingMoved = true
492 | }
493 | }
494 |
495 | if piece.type == .rook {
496 | if piece.color == .white {
497 | if startCol == 0 {
498 | whiteRookMoved.left = true
499 | } else if startCol == 7 {
500 | whiteRookMoved.right = true
501 | }
502 | } else {
503 | if startCol == 0 {
504 | blackRookMoved.left = true
505 | } else if startCol == 7 {
506 | blackRookMoved.right = true
507 | }
508 | }
509 | }
510 |
511 | // Check for pawn promotion
512 | if piece.type == .pawn {
513 | // If pawn moved two steps, set En passant target
514 | if abs(position.0 - startRow) == 2 {
515 | enPassantTarget = (position: position, color: piece.color)
516 | }
517 |
518 | if (piece.color == .white && position.0 == 7) ||
519 | (piece.color == .black && position.0 == 0) {
520 | // Pawn reaches the opposite side
521 | promotionPending = PromotionPending(position: position, color: piece.color)
522 | }
523 | }
524 |
525 | // Switch to opponent's turn
526 | currentPlayer = currentPlayer.opponent
527 |
528 | // Update game state after switching current player
529 | updateGameState()
530 |
531 | // If game is over after move, handle accordingly
532 | if isCheckmate || isStalemate {
533 | return
534 | }
535 |
536 | // If it's the player's turn after move, no further action needed
537 | if currentPlayer == .white {
538 | return
539 | }
540 |
541 | // If opponent's turn, make their move
542 | _ = opponentEngine.makeMove()
543 | }
544 |
545 |
546 | func opponentMove() -> Bool { // Returns true if a valid move exists
547 | let opponentPieces = board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == currentPlayer }
548 |
549 | // Clear En passant target
550 | enPassantTarget = nil
551 |
552 | for piece in opponentPieces.shuffled() { // Shuffle to randomize selection
553 | let moves = calculateLegalMoves(for: piece)
554 | if let move = moves.randomElement() {
555 | let startRow = piece.position.0
556 | let startCol = piece.position.1
557 |
558 | // Handle En passant capture (for simplicity, AI doesn't perform En passant)
559 |
560 | // Update the board
561 | board[startRow][startCol] = nil
562 | board[move.0][move.1] = ChessPiece(
563 | type: piece.type,
564 | color: piece.color,
565 | position: move,
566 | hasMoved: true
567 | )
568 |
569 | // Update castling rights if necessary
570 | if piece.type == .king {
571 | blackKingMoved = true
572 | }
573 | if piece.type == .rook {
574 | if startCol == 0 {
575 | blackRookMoved.left = true
576 | } else if startCol == 7 {
577 | blackRookMoved.right = true
578 | }
579 | }
580 |
581 | // Check for pawn promotion
582 | if piece.type == .pawn {
583 | // If pawn moved two steps, set En passant target
584 | if abs(move.0 - startRow) == 2 {
585 | enPassantTarget = (position: move, color: piece.color)
586 | }
587 |
588 | if move.0 == 0 {
589 | // Automatically promote to queen
590 | board[move.0][move.1] = ChessPiece(
591 | type: .queen,
592 | color: piece.color,
593 | position: move
594 | )
595 | }
596 | }
597 |
598 | // Switch back to player's turn
599 | currentPlayer = currentPlayer.opponent
600 |
601 | // Update game state after switching current player
602 | updateGameState()
603 |
604 | // Check if game over after opponent's move
605 | if isCheckmate || isStalemate {
606 | return false
607 | }
608 |
609 | return true
610 | }
611 | }
612 |
613 | // If no moves are found, opponent is in stalemate or checkmate
614 | print("Opponent has no valid moves")
615 |
616 | // Switch back to player's turn
617 | currentPlayer = currentPlayer.opponent
618 |
619 | // Update game state after opponent's inability to move
620 | updateGameState()
621 |
622 | return false
623 | }
624 |
625 |
626 | func updateGameState() {
627 | // Check if current player is in check
628 | let kingPosition = findKingPosition(color: currentPlayer)
629 | isInCheck = isSquareUnderAttack(kingPosition, byColor: currentPlayer.opponent)
630 | isCheckmate = isInCheck && !hasLegalMoves(forColor: currentPlayer)
631 | isStalemate = !isInCheck && !hasLegalMoves(forColor: currentPlayer)
632 | }
633 |
634 | func saveCurrentBoardState() {
635 | let boardCopy = board.map { row in
636 | row.compactMap { piece in
637 | if let piece = piece {
638 | return ChessPiece(
639 | type: piece.type,
640 | color: piece.color,
641 | position: piece.position,
642 | hasMoved: piece.hasMoved
643 | )
644 | } else {
645 | return nil
646 | }
647 | }
648 | }
649 | boardHistory.append(boardCopy)
650 | }
651 |
652 | func undoMove() {
653 | // Check if there is a previous board state
654 | guard !boardHistory.isEmpty else { return }
655 |
656 | // Remove the last board state from history and set it as the current board
657 | boardHistory.removeLast()
658 | if let lastBoard = boardHistory.last {
659 | board = lastBoard
660 | }
661 |
662 | // Switch the current player back
663 | currentPlayer = currentPlayer.opponent
664 |
665 | // Recalculate possible moves and game state
666 | selectedPiece = nil
667 | possibleMoves = []
668 | updateGameState()
669 | }
670 |
671 | func hasLegalMoves(forColor color: PieceColor) -> Bool {
672 | let pieces = board.flatMap { $0 }.compactMap { $0 }.filter { $0.color == color }
673 |
674 | for piece in pieces {
675 | let moves = calculateLegalMoves(for: piece)
676 | if !moves.isEmpty {
677 | return true
678 | }
679 | }
680 | return false
681 | }
682 |
683 | func resetGame(difficulty: DifficultyLevel) {
684 | board = Array(repeating: Array(repeating: nil, count: 8), count: 8)
685 | selectedPiece = nil
686 | possibleMoves = []
687 | isInCheck = false
688 | isCheckmate = false
689 | isStalemate = false
690 | promotionPending = nil
691 | currentPlayer = .white
692 | enPassantTarget = nil
693 | whiteKingMoved = false
694 | blackKingMoved = false
695 | whiteRookMoved = (left: false, right: false)
696 | blackRookMoved = (left: false, right: false)
697 | setupBoard()
698 | opponentEngine.difficulty = difficulty
699 | }
700 |
701 | // Helper function to check if a position is within the board boundaries
702 | func isValidPosition(_ row: Int, _ col: Int) -> Bool {
703 | return (0..<8).contains(row) && (0..<8).contains(col)
704 | }
705 | }
706 |
--------------------------------------------------------------------------------