├── Chess.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── jaredcassoutt.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── Chess ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 128.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 16.png │ │ ├── 167.png │ │ ├── 172.png │ │ ├── 180.png │ │ ├── 196.png │ │ ├── 20.png │ │ ├── 216.png │ │ ├── 256.png │ │ ├── 29.png │ │ ├── 32.png │ │ ├── 40.png │ │ ├── 48.png │ │ ├── 50.png │ │ ├── 512.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 │ │ └── Contents.json │ ├── Chess Pieces │ │ ├── Contents.json │ │ ├── bishop_black.imageset │ │ │ ├── Contents.json │ │ │ └── bishop_black.png │ │ ├── bishop_white.imageset │ │ │ ├── Contents.json │ │ │ └── bishop_white.png │ │ ├── king_black.imageset │ │ │ ├── Contents.json │ │ │ └── king_black.png │ │ ├── king_white.imageset │ │ │ ├── Contents.json │ │ │ └── king_white.png │ │ ├── knight_black.imageset │ │ │ ├── Contents.json │ │ │ └── knight_black.png │ │ ├── knight_white.imageset │ │ │ ├── Contents.json │ │ │ └── knight_white.png │ │ ├── pawn_black.imageset │ │ │ ├── Contents.json │ │ │ └── pawn_black.png │ │ ├── pawn_white.imageset │ │ │ ├── Contents.json │ │ │ └── pawn_white.png │ │ ├── queen_black.imageset │ │ │ ├── Contents.json │ │ │ └── queen_black.png │ │ ├── queen_white.imageset │ │ │ ├── Contents.json │ │ │ └── queen_white.png │ │ ├── rook_black.imageset │ │ │ ├── Contents.json │ │ │ └── rook_black.png │ │ └── rook_white.imageset │ │ │ ├── Contents.json │ │ │ └── rook_white.png │ └── Contents.json ├── ChessApp.swift ├── ChessGame.swift ├── ChessView.swift ├── DifficultySelectionView.swift ├── OpponentChessEngine.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json └── PromitionView.swift ├── ChessTests └── ChessTests.swift ├── ChessUITests ├── ChessUITests.swift └── ChessUITestsLaunchTests.swift └── README.md /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.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Chess.xcodeproj/xcuserdata/jaredcassoutt.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/128.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/16.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/256.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/32.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/512.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/64.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/66.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/AppIcon.appiconset/92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/AppIcon.appiconset/92.png -------------------------------------------------------------------------------- /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/Assets.xcassets/Chess Pieces/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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_black.imageset/bishop_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/bishop_black.imageset/bishop_black.png -------------------------------------------------------------------------------- /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/bishop_white.imageset/bishop_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/bishop_white.imageset/bishop_white.png -------------------------------------------------------------------------------- /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_black.imageset/king_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/king_black.imageset/king_black.png -------------------------------------------------------------------------------- /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/king_white.imageset/king_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/king_white.imageset/king_white.png -------------------------------------------------------------------------------- /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_black.imageset/knight_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/knight_black.imageset/knight_black.png -------------------------------------------------------------------------------- /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/knight_white.imageset/knight_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/knight_white.imageset/knight_white.png -------------------------------------------------------------------------------- /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_black.imageset/pawn_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/pawn_black.imageset/pawn_black.png -------------------------------------------------------------------------------- /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/pawn_white.imageset/pawn_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/pawn_white.imageset/pawn_white.png -------------------------------------------------------------------------------- /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_black.imageset/queen_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/queen_black.imageset/queen_black.png -------------------------------------------------------------------------------- /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/Assets.xcassets/Chess Pieces/queen_white.imageset/queen_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/queen_white.imageset/queen_white.png -------------------------------------------------------------------------------- /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_black.imageset/rook_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/rook_black.imageset/rook_black.png -------------------------------------------------------------------------------- /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/rook_white.imageset/rook_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcassoutt/chess_swiftui/9cc040dd5598b9bdfbb11554a90f5d9816a3bc60/Chess/Assets.xcassets/Chess Pieces/rook_white.imageset/rook_white.png -------------------------------------------------------------------------------- /Chess/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | Preview 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 | --------------------------------------------------------------------------------