Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88dda592b9 | |||
| 69458e8a28 | |||
| ad5f9e9122 | |||
| 7311b9add1 | |||
| 8f5ae059b1 | |||
| cca6079e56 | |||
| 6d8bcf1211 | |||
| cc4b32dad6 | |||
| c50d4de8d7 | |||
| f75b7cbe9d | |||
| e7b92a6ceb | |||
| 216bbfe0dd | |||
| 455b6e8a8b | |||
| 7c4e85e855 | |||
| 73d3dbbd38 | |||
| 77925776b8 | |||
| 717de7b90c | |||
| 782269b3d5 | |||
| d04cffff36 | |||
| 2f2b6e2d39 | |||
| 17221a7f78 | |||
| 1f64711de9 | |||
| 8b92378030 | |||
| 50514237ff | |||
| 59eebff8ed | |||
| 2ab3ec8f98 | |||
| 3bf665e7d4 | |||
| 7a5c82b638 | |||
| 702ac1ce2c | |||
| 2c5c1e1494 | |||
| 30e274b05f | |||
| 55795bf00a | |||
| 2ee384172c | |||
| 97b10dfa7b | |||
| b3ab7cfbf0 | |||
| 82f68a6875 | |||
| c0cc240716 | |||
| 8c64655149 | |||
| ee073d5edf | |||
| 8592eb4928 | |||
| 1594973694 | |||
| 8c1ad9b6e5 | |||
| babc1cf062 | |||
| dde1d3f054 | |||
| e93f6758fc | |||
| aaec289f33 | |||
| 8fd84bea84 | |||
| fa2e34d026 | |||
| 8fa6ca0002 | |||
| b200acaffb | |||
| 150e763d87 | |||
| 5503a770b6 |
@@ -0,0 +1 @@
|
||||
3.0
|
||||
@@ -7,12 +7,16 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
09A4C0291E013ECB000CFBF4 /* BoardRaterThreatenedPiecesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A4C0281E013ECB000CFBF4 /* BoardRaterThreatenedPiecesTests.swift */; };
|
||||
09AE32551E03D71D00A149FE /* BoardRaterPawnProgressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AE32541E03D71D00A149FE /* BoardRaterPawnProgressionTests.swift */; };
|
||||
671989891DFFE0410053EA3D /* BoardRaterCenterOwnershipTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671989881DFFE0410053EA3D /* BoardRaterCenterOwnershipTests.swift */; };
|
||||
6719898D1DFFE0F40053EA3D /* BoardRaterBoardDominanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6719898C1DFFE0F40053EA3D /* BoardRaterBoardDominanceTests.swift */; };
|
||||
671989911DFFE8650053EA3D /* BoardRaterCenterDominanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 671989901DFFE8650053EA3D /* BoardRaterCenterDominanceTests.swift */; };
|
||||
673B95191E4CE5880086CA97 /* BoardRaterThreatenedPiecesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A4C0281E013ECB000CFBF4 /* BoardRaterThreatenedPiecesTests.swift */; };
|
||||
676902581E432380007C76D7 /* PerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676902571E432380007C76D7 /* PerformanceTests.swift */; };
|
||||
676C911B1E478A3A00985A4F /* SwiftChess.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67A9CA0D1DE64D6500510FB8 /* SwiftChess.framework */; };
|
||||
676C911C1E478A3A00985A4F /* SwiftChess.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 67A9CA0D1DE64D6500510FB8 /* SwiftChess.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
676EF7C51E15AC1700E275B4 /* BoardRaterKingSurroundingPossession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 676EF7C41E15AC1700E275B4 /* BoardRaterKingSurroundingPossession.swift */; };
|
||||
67A3EB161E3A926C00F6F01B /* BoardScenarios.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67A3EB121E3A826800F6F01B /* BoardScenarios.swift */; };
|
||||
67A9CA341DE64DD600510FB8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
|
||||
67A9CA351DE64DD800510FB8 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* MenuViewController.swift */; };
|
||||
67A9CA361DE64DDA00510FB8 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67AD33A61D7C67BF002730DF /* GameViewController.swift */; };
|
||||
@@ -24,7 +28,6 @@
|
||||
67A9CA3D1DE64E7100510FB8 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 607FACEA1AFB9204008FA782 /* Info.plist */; };
|
||||
67B73A9B1E15351900C19176 /* PromotionSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B73A9A1E15351900C19176 /* PromotionSelectionViewController.swift */; };
|
||||
67B73A9F1E154C1E00C19176 /* AIPlayerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67B73A9E1E154C1E00C19176 /* AIPlayerTests.swift */; };
|
||||
67D54A5C1DE7682900C12258 /* ASCIIBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A501DE7680E00C12258 /* ASCIIBoard.swift */; };
|
||||
67D54A5D1DE7682D00C12258 /* BoardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A511DE7680E00C12258 /* BoardTests.swift */; };
|
||||
67D54A5E1DE7683000C12258 /* GameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A521DE7680E00C12258 /* GameTests.swift */; };
|
||||
67D54A5F1DE7683300C12258 /* PieceMovementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A531DE7680E00C12258 /* PieceMovementTests.swift */; };
|
||||
@@ -33,13 +36,21 @@
|
||||
67D54A641DE976A900C12258 /* BoardRaterCountPiecesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A631DE976A900C12258 /* BoardRaterCountPiecesTests.swift */; };
|
||||
67D54A661DE986F700C12258 /* PieceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A651DE986F700C12258 /* PieceTests.swift */; };
|
||||
67D54A681DE9A2DD00C12258 /* PieceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67D54A671DE9A2DD00C12258 /* PieceView.swift */; };
|
||||
67F779201E1B923B00885B89 /* AIConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F7791F1E1B923B00885B89 /* AIConfigurationTests.swift */; };
|
||||
67F779261E1C32A400885B89 /* BoardRaterCenterFourOccupationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F779251E1C32A400885B89 /* BoardRaterCenterFourOccupationTests.swift */; };
|
||||
67F9DB6E1E1AC8DC00C7EC5A /* BoardRaterCheckMateOpportunityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F9DB6D1E1AC8DC00C7EC5A /* BoardRaterCheckMateOpportunityTests.swift */; };
|
||||
67F9DB751E1AD3BB00C7EC5A /* AIBehaviourTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F9DB741E1AD3BB00C7EC5A /* AIBehaviourTests.swift */; };
|
||||
67FD868D1E41099B0023335C /* BoardLocationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FD868C1E41099B0023335C /* BoardLocationTests.swift */; };
|
||||
67FD86911E4128F00023335C /* OpeningsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FD86901E4128F00023335C /* OpeningsTests.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
676C911D1E478A3A00985A4F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 67A9CA071DE64D6500510FB8 /* SwiftChess.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 67A9C9DD1DE64CD200510FB8;
|
||||
remoteInfo = SwiftChess;
|
||||
};
|
||||
67A9CA0C1DE64D6500510FB8 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 67A9CA071DE64D6500510FB8 /* SwiftChess.xcodeproj */;
|
||||
@@ -63,6 +74,20 @@
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
676C911F1E478A3A00985A4F /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
676C911C1E478A3A00985A4F /* SwiftChess.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
09A4C0281E013ECB000CFBF4 /* BoardRaterThreatenedPiecesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterThreatenedPiecesTests.swift; sourceTree = "<group>"; };
|
||||
09AE32541E03D71D00A149FE /* BoardRaterPawnProgressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterPawnProgressionTests.swift; sourceTree = "<group>"; };
|
||||
@@ -76,7 +101,9 @@
|
||||
671989881DFFE0410053EA3D /* BoardRaterCenterOwnershipTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCenterOwnershipTests.swift; sourceTree = "<group>"; };
|
||||
6719898C1DFFE0F40053EA3D /* BoardRaterBoardDominanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterBoardDominanceTests.swift; sourceTree = "<group>"; };
|
||||
671989901DFFE8650053EA3D /* BoardRaterCenterDominanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCenterDominanceTests.swift; sourceTree = "<group>"; };
|
||||
676902571E432380007C76D7 /* PerformanceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PerformanceTests.swift; sourceTree = "<group>"; };
|
||||
676EF7C41E15AC1700E275B4 /* BoardRaterKingSurroundingPossession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterKingSurroundingPossession.swift; sourceTree = "<group>"; };
|
||||
67A3EB121E3A826800F6F01B /* BoardScenarios.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardScenarios.swift; sourceTree = "<group>"; };
|
||||
67A9CA071DE64D6500510FB8 /* SwiftChess.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwiftChess.xcodeproj; path = ../SwiftChess/SwiftChess.xcodeproj; sourceTree = "<group>"; };
|
||||
67A9CA141DE64DAA00510FB8 /* SwiftChessExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftChessExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
67A9CA271DE64DAA00510FB8 /* SwiftChessExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftChessExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -84,7 +111,6 @@
|
||||
67AD33A81D7C67DF002730DF /* BoardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardView.swift; sourceTree = "<group>"; };
|
||||
67B73A9A1E15351900C19176 /* PromotionSelectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PromotionSelectionViewController.swift; sourceTree = "<group>"; };
|
||||
67B73A9E1E154C1E00C19176 /* AIPlayerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AIPlayerTests.swift; sourceTree = "<group>"; };
|
||||
67D54A501DE7680E00C12258 /* ASCIIBoard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCIIBoard.swift; sourceTree = "<group>"; };
|
||||
67D54A511DE7680E00C12258 /* BoardTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardTests.swift; sourceTree = "<group>"; };
|
||||
67D54A521DE7680E00C12258 /* GameTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GameTests.swift; sourceTree = "<group>"; };
|
||||
67D54A531DE7680E00C12258 /* PieceMovementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PieceMovementTests.swift; sourceTree = "<group>"; };
|
||||
@@ -97,6 +123,8 @@
|
||||
67F779251E1C32A400885B89 /* BoardRaterCenterFourOccupationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCenterFourOccupationTests.swift; sourceTree = "<group>"; };
|
||||
67F9DB6D1E1AC8DC00C7EC5A /* BoardRaterCheckMateOpportunityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCheckMateOpportunityTests.swift; sourceTree = "<group>"; };
|
||||
67F9DB741E1AD3BB00C7EC5A /* AIBehaviourTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AIBehaviourTests.swift; sourceTree = "<group>"; };
|
||||
67FD868C1E41099B0023335C /* BoardLocationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardLocationTests.swift; sourceTree = "<group>"; };
|
||||
67FD86901E4128F00023335C /* OpeningsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpeningsTests.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -105,6 +133,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
67A9CA3C1DE64E2B00510FB8 /* SwiftChess.framework in Frameworks */,
|
||||
676C911B1E478A3A00985A4F /* SwiftChess.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -173,6 +202,7 @@
|
||||
children = (
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */,
|
||||
67D54A651DE986F700C12258 /* PieceTests.swift */,
|
||||
67FD868C1E41099B0023335C /* BoardLocationTests.swift */,
|
||||
67D54A511DE7680E00C12258 /* BoardTests.swift */,
|
||||
67D54A521DE7680E00C12258 /* GameTests.swift */,
|
||||
67D54A531DE7680E00C12258 /* PieceMovementTests.swift */,
|
||||
@@ -180,6 +210,8 @@
|
||||
67B73A9E1E154C1E00C19176 /* AIPlayerTests.swift */,
|
||||
67F7791F1E1B923B00885B89 /* AIConfigurationTests.swift */,
|
||||
67F9DB741E1AD3BB00C7EC5A /* AIBehaviourTests.swift */,
|
||||
676902571E432380007C76D7 /* PerformanceTests.swift */,
|
||||
67FD86901E4128F00023335C /* OpeningsTests.swift */,
|
||||
67D54A621DE9768200C12258 /* BoardRaters */,
|
||||
67D54A551DE7680E00C12258 /* Tests.swift */,
|
||||
);
|
||||
@@ -189,8 +221,8 @@
|
||||
607FACE91AFB9204008FA782 /* Supporting Files */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
67D54A501DE7680E00C12258 /* ASCIIBoard.swift */,
|
||||
607FACEA1AFB9204008FA782 /* Info.plist */,
|
||||
67A3EB121E3A826800F6F01B /* BoardScenarios.swift */,
|
||||
);
|
||||
name = "Supporting Files";
|
||||
sourceTree = "<group>";
|
||||
@@ -241,10 +273,12 @@
|
||||
67A9CA101DE64DAA00510FB8 /* Sources */,
|
||||
67A9CA111DE64DAA00510FB8 /* Frameworks */,
|
||||
67A9CA121DE64DAA00510FB8 /* Resources */,
|
||||
676C911F1E478A3A00985A4F /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
676C911E1E478A3A00985A4F /* PBXTargetDependency */,
|
||||
);
|
||||
name = SwiftChessExample;
|
||||
productName = SwiftChessExample;
|
||||
@@ -281,6 +315,7 @@
|
||||
TargetAttributes = {
|
||||
67A9CA131DE64DAA00510FB8 = {
|
||||
CreatedOnToolsVersion = 8.0;
|
||||
DevelopmentTeam = VAA3W4LPY2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
67A9CA261DE64DAA00510FB8 = {
|
||||
@@ -372,21 +407,23 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
67D54A601DE7683800C12258 /* PlayerTests.swift in Sources */,
|
||||
67FD86911E4128F00023335C /* OpeningsTests.swift in Sources */,
|
||||
67D54A611DE7683A00C12258 /* Tests.swift in Sources */,
|
||||
676EF7C51E15AC1700E275B4 /* BoardRaterKingSurroundingPossession.swift in Sources */,
|
||||
67FD868D1E41099B0023335C /* BoardLocationTests.swift in Sources */,
|
||||
67D54A5D1DE7682D00C12258 /* BoardTests.swift in Sources */,
|
||||
67F9DB751E1AD3BB00C7EC5A /* AIBehaviourTests.swift in Sources */,
|
||||
671989911DFFE8650053EA3D /* BoardRaterCenterDominanceTests.swift in Sources */,
|
||||
6719898D1DFFE0F40053EA3D /* BoardRaterBoardDominanceTests.swift in Sources */,
|
||||
67D54A5E1DE7683000C12258 /* GameTests.swift in Sources */,
|
||||
673B95191E4CE5880086CA97 /* BoardRaterThreatenedPiecesTests.swift in Sources */,
|
||||
67D54A661DE986F700C12258 /* PieceTests.swift in Sources */,
|
||||
671989891DFFE0410053EA3D /* BoardRaterCenterOwnershipTests.swift in Sources */,
|
||||
67B73A9F1E154C1E00C19176 /* AIPlayerTests.swift in Sources */,
|
||||
67D54A5C1DE7682900C12258 /* ASCIIBoard.swift in Sources */,
|
||||
67F779201E1B923B00885B89 /* AIConfigurationTests.swift in Sources */,
|
||||
676902581E432380007C76D7 /* PerformanceTests.swift in Sources */,
|
||||
67D54A641DE976A900C12258 /* BoardRaterCountPiecesTests.swift in Sources */,
|
||||
67A3EB161E3A926C00F6F01B /* BoardScenarios.swift in Sources */,
|
||||
09AE32551E03D71D00A149FE /* BoardRaterPawnProgressionTests.swift in Sources */,
|
||||
09A4C0291E013ECB000CFBF4 /* BoardRaterThreatenedPiecesTests.swift in Sources */,
|
||||
67D54A5F1DE7683300C12258 /* PieceMovementTests.swift in Sources */,
|
||||
67F779261E1C32A400885B89 /* BoardRaterCenterFourOccupationTests.swift in Sources */,
|
||||
67F9DB6E1E1AC8DC00C7EC5A /* BoardRaterCheckMateOpportunityTests.swift in Sources */,
|
||||
@@ -396,6 +433,11 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
676C911E1E478A3A00985A4F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = SwiftChess;
|
||||
targetProxy = 676C911D1E478A3A00985A4F /* PBXContainerItemProxy */;
|
||||
};
|
||||
67A9CA291DE64DAA00510FB8 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 67A9CA131DE64DAA00510FB8 /* SwiftChessExample */;
|
||||
@@ -513,11 +555,13 @@
|
||||
67A9CA2F1DE64DAA00510FB8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = VAA3W4LPY2;
|
||||
INFOPLIST_FILE = SwiftChess/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -532,10 +576,12 @@
|
||||
67A9CA301DE64DAA00510FB8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVES = YES;
|
||||
DEVELOPMENT_TEAM = VAA3W4LPY2;
|
||||
INFOPLIST_FILE = SwiftChess/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>classNames</key>
|
||||
<dict>
|
||||
<key>PerformanceTests</key>
|
||||
<dict>
|
||||
<key>testBishopMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testCanAnyPieceMovePerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.033</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testKingMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.003</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testKnightMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.001</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testPawnMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.001</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testQueenMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0.003</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>testRookMoveValidationPerformance()</key>
|
||||
<dict>
|
||||
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
|
||||
<dict>
|
||||
<key>baselineAverage</key>
|
||||
<real>0</real>
|
||||
<key>baselineIntegrationDisplayName</key>
|
||||
<string>Local Baseline</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
+31
@@ -4,6 +4,37 @@
|
||||
<dict>
|
||||
<key>runDestinationsByUUID</key>
|
||||
<dict>
|
||||
<key>3E643A92-069B-413B-86C0-1FF211DBD9A9</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
<dict>
|
||||
<key>busSpeedInMHz</key>
|
||||
<integer>100</integer>
|
||||
<key>cpuCount</key>
|
||||
<integer>1</integer>
|
||||
<key>cpuKind</key>
|
||||
<string>Intel Core m7</string>
|
||||
<key>cpuSpeedInMHz</key>
|
||||
<integer>1300</integer>
|
||||
<key>logicalCPUCoresPerPackage</key>
|
||||
<integer>4</integer>
|
||||
<key>modelCode</key>
|
||||
<string>MacBook9,1</string>
|
||||
<key>physicalCPUCoresPerPackage</key>
|
||||
<integer>2</integer>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.macosx</string>
|
||||
</dict>
|
||||
<key>targetArchitecture</key>
|
||||
<string>i386</string>
|
||||
<key>targetDevice</key>
|
||||
<dict>
|
||||
<key>modelCode</key>
|
||||
<string>iPhone5,1</string>
|
||||
<key>platformIdentifier</key>
|
||||
<string>com.apple.platform.iphonesimulator</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>D188478B-4F41-448E-8BB9-06B1C7A6FB8B</key>
|
||||
<dict>
|
||||
<key>localComputer</key>
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11201" systemVersion="15G1212" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="o9i-0S-2Up">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="o9i-0S-2Up">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11161"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11757"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -39,20 +42,24 @@
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KRf-s7-H9a">
|
||||
<rect key="frame" x="133.5" y="264.5" width="108" height="138"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fGu-y2-Qxm">
|
||||
<rect key="frame" x="0.0" y="92" width="108" height="30"/>
|
||||
<state key="normal" title="AI vs AI"/>
|
||||
<connections>
|
||||
<action selector="AIvsAIButtonPressed:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="mrX-sU-WvD"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kEX-Ea-5Xq">
|
||||
<rect key="frame" x="0.0" y="46" width="108" height="30"/>
|
||||
<state key="normal" title="Player vs Player"/>
|
||||
<connections>
|
||||
<action selector="playerVsPlayerButtonPressed:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="xIZ-0U-CNW"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gcM-Gf-hHc">
|
||||
<rect key="frame" x="0.0" y="0.0" width="108" height="30"/>
|
||||
<state key="normal" title="Player vs AI"/>
|
||||
<connections>
|
||||
<action selector="playerVsAIButtonPressed:" destination="vXZ-lx-hvc" eventType="touchUpInside" id="QvL-y5-tdf"/>
|
||||
@@ -99,41 +106,50 @@
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NnZ-Ew-a2B" customClass="BoardView" customModule="SwiftChessExample" customModuleProvider="target">
|
||||
<rect key="frame" x="8" y="154" width="359" height="359"/>
|
||||
<color key="backgroundColor" red="0.46274509800000002" green="0.71372549019999998" blue="0.97254901959999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="NnZ-Ew-a2B" secondAttribute="height" multiplier="1:1" id="GNF-YV-5Vh"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="ZZ0-mR-VXd">
|
||||
<rect key="frame" x="8" y="116" width="44" height="30"/>
|
||||
<state key="normal" title="Castle"/>
|
||||
<connections>
|
||||
<action selector="blackKingSideCastleButtonPressedWithSender:" destination="M5V-oM-g1K" eventType="touchUpInside" id="rLk-f9-Sot"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eZD-P8-Hr3">
|
||||
<rect key="frame" x="323" y="116" width="44" height="30"/>
|
||||
<state key="normal" title="Castle"/>
|
||||
<connections>
|
||||
<action selector="blackQueenSideCastleButtonPressedWithSender:" destination="M5V-oM-g1K" eventType="touchUpInside" id="04c-Bn-Bkt"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Yxk-YT-5FA">
|
||||
<rect key="frame" x="8" y="521" width="44" height="30"/>
|
||||
<state key="normal" title="Castle"/>
|
||||
<connections>
|
||||
<action selector="whiteQueenSideCastleButtonPressedWithSender:" destination="M5V-oM-g1K" eventType="touchUpInside" id="3el-xi-NUE"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="FwI-ZC-g1Z">
|
||||
<rect key="frame" x="323" y="521" width="44" height="30"/>
|
||||
<state key="normal" title="Castle"/>
|
||||
<connections>
|
||||
<action selector="whiteKingSideCastleButtonPressedWithSender:" destination="M5V-oM-g1K" eventType="touchUpInside" id="geO-9J-0qc"/>
|
||||
</connections>
|
||||
</button>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="JEV-Ul-qRP">
|
||||
<rect key="frame" x="177" y="121" width="20" height="20"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="NnZ-Ew-a2B" firstAttribute="top" secondItem="eZD-P8-Hr3" secondAttribute="bottom" constant="8" id="8gI-BZ-sfM"/>
|
||||
<constraint firstItem="Yxk-YT-5FA" firstAttribute="leading" secondItem="NnZ-Ew-a2B" secondAttribute="leading" id="BxL-mm-tSn"/>
|
||||
<constraint firstItem="NnZ-Ew-a2B" firstAttribute="centerY" secondItem="0i2-vi-BZ9" secondAttribute="centerY" id="DzP-fd-RxD"/>
|
||||
<constraint firstItem="JEV-Ul-qRP" firstAttribute="centerY" secondItem="ZZ0-mR-VXd" secondAttribute="centerY" id="LLa-vl-Dcs"/>
|
||||
<constraint firstItem="Yxk-YT-5FA" firstAttribute="top" secondItem="NnZ-Ew-a2B" secondAttribute="bottom" constant="8" id="N2S-Kg-2L0"/>
|
||||
<constraint firstItem="ZZ0-mR-VXd" firstAttribute="leading" secondItem="NnZ-Ew-a2B" secondAttribute="leading" id="NAg-kl-feP"/>
|
||||
<constraint firstItem="eZD-P8-Hr3" firstAttribute="trailing" secondItem="NnZ-Ew-a2B" secondAttribute="trailing" id="NYh-P9-I1j"/>
|
||||
@@ -141,10 +157,12 @@
|
||||
<constraint firstItem="NnZ-Ew-a2B" firstAttribute="top" secondItem="ZZ0-mR-VXd" secondAttribute="bottom" constant="8" id="TgY-3v-zy1"/>
|
||||
<constraint firstItem="NnZ-Ew-a2B" firstAttribute="leading" secondItem="0i2-vi-BZ9" secondAttribute="leading" constant="8" id="Ve9-Wz-Mmx"/>
|
||||
<constraint firstItem="FwI-ZC-g1Z" firstAttribute="top" secondItem="NnZ-Ew-a2B" secondAttribute="bottom" constant="8" id="YnW-Zf-aL2"/>
|
||||
<constraint firstItem="JEV-Ul-qRP" firstAttribute="centerX" secondItem="0i2-vi-BZ9" secondAttribute="centerX" id="dOc-ve-oSq"/>
|
||||
<constraint firstItem="FwI-ZC-g1Z" firstAttribute="trailing" secondItem="NnZ-Ew-a2B" secondAttribute="trailing" id="rx5-mS-bp0"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="activityIndicator" destination="JEV-Ul-qRP" id="93U-eY-Z18"/>
|
||||
<outlet property="blackKingSideCastleButton" destination="ZZ0-mR-VXd" id="jhh-iW-CTM"/>
|
||||
<outlet property="blackQueenSideCastleButton" destination="eZD-P8-Hr3" id="yY5-WP-Yvk"/>
|
||||
<outlet property="boardView" destination="NnZ-Ew-a2B" id="qeo-BV-9YO"/>
|
||||
|
||||
@@ -16,6 +16,7 @@ class GameViewController: UIViewController {
|
||||
@IBOutlet weak var whiteQueenSideCastleButton: UIButton!
|
||||
@IBOutlet weak var blackKingSideCastleButton: UIButton!
|
||||
@IBOutlet weak var blackQueenSideCastleButton: UIButton!
|
||||
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
|
||||
|
||||
var pieceViews = [PieceView]()
|
||||
var game: Game!
|
||||
@@ -60,6 +61,9 @@ class GameViewController: UIViewController {
|
||||
addPieceView(at: location.x, y: location.y, piece: piece)
|
||||
}
|
||||
|
||||
// Activity Indicator
|
||||
activityIndicator.hidesWhenStopped = true
|
||||
|
||||
// Update castle buttons visibility
|
||||
updateCastleButtonsVisibility()
|
||||
|
||||
@@ -71,7 +75,7 @@ class GameViewController: UIViewController {
|
||||
// Take go if the first player is an AI player
|
||||
if !self.hasMadeInitialAppearance {
|
||||
if let player = game.currentPlayer as? AIPlayer {
|
||||
player.makeMove()
|
||||
player.makeMoveAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,7 +364,7 @@ extension GameViewController: GameDelegate {
|
||||
}
|
||||
|
||||
func gameDidEndUpdates(game: Game) {
|
||||
// do nothing
|
||||
activityIndicator.stopAnimating()
|
||||
}
|
||||
|
||||
func gameWonByPlayer(game: Game, player: Player) {
|
||||
@@ -394,7 +398,8 @@ extension GameViewController: GameDelegate {
|
||||
func tellAIToTakeGo() {
|
||||
|
||||
if let player = game.currentPlayer as? AIPlayer {
|
||||
player.makeMove()
|
||||
activityIndicator.startAnimating();
|
||||
player.makeMoveAsync()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class MenuViewController: UIViewController {
|
||||
print("Player vs AI button pressed")
|
||||
|
||||
let whitePlayer = Human(color: .white)
|
||||
let blackPlayer = AIPlayer(color: .black)
|
||||
let blackPlayer = AIPlayer(color: .black, configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer)
|
||||
startGame(game: game)
|
||||
@@ -46,8 +46,8 @@ class MenuViewController: UIViewController {
|
||||
@IBAction func AIvsAIButtonPressed(_ sender: UIButton){
|
||||
print("AI vs AI button pressed")
|
||||
|
||||
let whitePlayer = AIPlayer(color: .white)
|
||||
let blackPlayer = AIPlayer(color: .black)
|
||||
let whitePlayer = AIPlayer(color: .white, configuration: AIConfiguration(difficulty: .hard))
|
||||
let blackPlayer = AIPlayer(color: .black, configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer)
|
||||
startGame(game: game)
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
/*
|
||||
|
||||
AI behaviour tests are tests that try to avoid or encourage certain behaviours in the AI.
|
||||
These are complex outcomes, so may require changing several variables to manipulate outcomes.
|
||||
*/
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftChess
|
||||
|
||||
@@ -21,13 +27,144 @@ class AIBehaviourTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
func makeGameWithBoard(board: Board, colorToMove: Color) -> Game {
|
||||
|
||||
let whitePlayer = AIPlayer(color: .white, configuration: AIConfiguration(difficulty: .hard))
|
||||
let blackPlayer = AIPlayer(color: .black, configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer, board: board, colorToMove: colorToMove)
|
||||
|
||||
return game
|
||||
}
|
||||
|
||||
func findMovedPieceLocation(startBoard: Board, endBoard: Board, color: Color) -> BoardLocation {
|
||||
|
||||
for location in BoardLocation.all {
|
||||
|
||||
let startBoardPiece = startBoard.getPiece(at: location)
|
||||
let endBoardPiece = endBoard.getPiece(at: location)
|
||||
|
||||
// If there is no end board piece, this is not the location
|
||||
if endBoardPiece == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the end piece exists, but start doesn't, it's the moved piece
|
||||
if endBoardPiece != nil && startBoardPiece == nil {
|
||||
return location
|
||||
}
|
||||
|
||||
// If both pieces exist, and are not the same, then it's the location
|
||||
if endBoardPiece != nil && startBoardPiece != nil {
|
||||
|
||||
if endBoardPiece!.color != startBoardPiece!.color
|
||||
&& endBoardPiece!.type != startBoardPiece!.type{
|
||||
return location
|
||||
}
|
||||
}
|
||||
|
||||
// Else continue - this isn't the location
|
||||
continue
|
||||
}
|
||||
|
||||
fatalError("Failed to find moved location")
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Scenario Tests
|
||||
|
||||
func test_ScenarioOne_BlackShouldNotGiveAwayBishop() {
|
||||
|
||||
|
||||
// In the following example, the black player can move the bishop at (5,7) to the * location (2,4).
|
||||
// The can look like a good move because the bishop will then threaten the white queen at (3,3).
|
||||
// The white queen will also be threatening the black bishop, but because this is a lower value piece black can think that it is in a more preferrable position.
|
||||
// In reality, because the black bishop is unprotected, the white queen will take it.
|
||||
|
||||
let board = ASCIIBoard(pieces: "r - b - q b - r" +
|
||||
"p p g - - p p p" +
|
||||
"- - - - - k - -" +
|
||||
"- - * - - - - -" +
|
||||
"- - p Q P - - -" +
|
||||
"- - - - - - - -" +
|
||||
"P P G - - P P P" +
|
||||
"R K B - - B K R" )
|
||||
|
||||
|
||||
let location = board .locationOfCharacter("*")
|
||||
|
||||
let game = makeGameWithBoard(board: board.board, colorToMove: .black)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
XCTFail("Expected an AI Player")
|
||||
return
|
||||
}
|
||||
|
||||
player.makeMoveSync()
|
||||
|
||||
// If there is not piece at the location, then test has passed
|
||||
guard let piece = game.board.getPiece(at: location) else {
|
||||
return
|
||||
}
|
||||
|
||||
// If the piece is not black, or is not a bishop, test passed
|
||||
if piece.type != .bishop || piece.color != .black {
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, black has moved the bishop to the *, test failed
|
||||
XCTFail("Black moved bishop")
|
||||
}
|
||||
|
||||
func testBlackShouldTradePawnForQueen() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "g p - - - - - -" +
|
||||
"p p - - - - - -" +
|
||||
"- - - - p - - -" +
|
||||
"- - - - - Q - -" +
|
||||
"- - - - B - - -" +
|
||||
"- - - - - - - -" +
|
||||
"P P - - - - - -" +
|
||||
"G P - - - - - -" )
|
||||
|
||||
let queenLocation = board.locationOfCharacter("Q")
|
||||
let game = makeGameWithBoard(board: board.board, colorToMove: .black)
|
||||
|
||||
guard let player = game.blackPlayer as? AIPlayer else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
player.makeMoveSync()
|
||||
|
||||
XCTAssertEqual(game.board.getPiece(at: queenLocation)?.type, .pawn)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func testBlackShouldTakeWhiteQueenWithPawn() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - p g" +
|
||||
"- - - b - - p p" +
|
||||
"- - - - q - - -" +
|
||||
"- B p k P p - -" +
|
||||
"- - - P Q - - -" +
|
||||
"- - - - - - - -" +
|
||||
"P P - - - - - -" +
|
||||
"G P - - - - - -" )
|
||||
|
||||
let queenLocation = board.locationOfCharacter("Q")
|
||||
let game = makeGameWithBoard(board: board.board, colorToMove: .black)
|
||||
|
||||
guard let player = game.blackPlayer as? AIPlayer else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
player.makeMoveSync()
|
||||
|
||||
XCTAssertEqual(game.board.getPiece(at: queenLocation)?.type, .pawn)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,11 +25,168 @@ class AIPlayerTests: XCTestCase {
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||
}
|
||||
|
||||
func testPerformanceExample() {
|
||||
// This is an example of a performance test case.
|
||||
self.measure {
|
||||
// Put the code you want to measure the time of here.
|
||||
}
|
||||
// func testPerformanceExample() {
|
||||
// // This is an example of a performance test case.
|
||||
// self.measure {
|
||||
// // Put the code you want to measure the time of here.
|
||||
// }
|
||||
// }
|
||||
|
||||
// MARK: - Test Cannot move in the check
|
||||
|
||||
func makeTestGame(board: Board, colorToMove: Color) -> Game {
|
||||
|
||||
let whitePlayer = AIPlayer(color: .white, configuration: AIConfiguration(difficulty: .hard))
|
||||
let blackPlayer = AIPlayer(color: .black, configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer, board: board, colorToMove: colorToMove)
|
||||
return game
|
||||
}
|
||||
|
||||
func testKnightCannotPutOwnKingInToCheck() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "r - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - * - - - - -" +
|
||||
"K - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - - - - - -" )
|
||||
|
||||
let knightLocation = board.locationOfCharacter("K")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: knightLocation, toLocation: testLocation))
|
||||
}
|
||||
|
||||
func testKingCannotMoveInToCheck() {
|
||||
|
||||
// This is a complex scenario because it was one that I observed from an actual game
|
||||
|
||||
let board = ASCIIBoard(pieces: "r k - - q b - r" +
|
||||
"p p p g k - - p" +
|
||||
"- - * p b p - -" +
|
||||
"P P - - p - p P" +
|
||||
"- - P - P - - -" +
|
||||
"- B - - Q P - -" +
|
||||
"- B - P K G P -" +
|
||||
"R K - - - - - R" )
|
||||
|
||||
let kingLocation = board.locationOfCharacter("g")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .black)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: kingLocation, toLocation: testLocation))
|
||||
}
|
||||
|
||||
func testPawnCannotPutOwnKingInToCheck() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - * - - - -" +
|
||||
"G - - P - - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let pawnLocation = board.locationOfCharacter("P")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: pawnLocation, toLocation: testLocation))
|
||||
|
||||
}
|
||||
|
||||
func testQueenCannotPutOwnKingInToCheck() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - * - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - Q - - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = board.locationOfCharacter("Q")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: queenLocation, toLocation: testLocation))
|
||||
}
|
||||
|
||||
func testRookCannotPutOwnKingInToCheck() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - * - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - R - - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let rookLocation = board.locationOfCharacter("R")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: rookLocation, toLocation: testLocation))
|
||||
}
|
||||
|
||||
func testBishopCannotPutOwnKingInToCheck() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - *" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - B - - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let bishopLocation = board.locationOfCharacter("B")
|
||||
let testLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = makeTestGame(board: board.board, colorToMove: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? AIPlayer else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertFalse(player.canAIMovePiece(fromLocation: bishopLocation, toLocation: testLocation))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// BoardLocationTests.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Steve Barnegren on 31/01/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftChess
|
||||
|
||||
class BoardLocationTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testBoardLocationFromGridPositionResultsInCorrectIndex() {
|
||||
|
||||
let testCases = [
|
||||
// row 1
|
||||
(BoardLocation.GridPosition.a1, 0),
|
||||
(BoardLocation.GridPosition.b1, 1),
|
||||
(BoardLocation.GridPosition.c1, 2),
|
||||
(BoardLocation.GridPosition.d1, 3),
|
||||
(BoardLocation.GridPosition.e1, 4),
|
||||
(BoardLocation.GridPosition.f1, 5),
|
||||
(BoardLocation.GridPosition.g1, 6),
|
||||
(BoardLocation.GridPosition.h1, 7),
|
||||
// row 2
|
||||
(BoardLocation.GridPosition.a2, 8),
|
||||
(BoardLocation.GridPosition.b2, 9),
|
||||
(BoardLocation.GridPosition.c2, 10),
|
||||
(BoardLocation.GridPosition.d2, 11),
|
||||
(BoardLocation.GridPosition.e2, 12),
|
||||
(BoardLocation.GridPosition.f2, 13),
|
||||
(BoardLocation.GridPosition.g2, 14),
|
||||
(BoardLocation.GridPosition.h2, 15),
|
||||
// row 3
|
||||
(BoardLocation.GridPosition.a3, 16),
|
||||
(BoardLocation.GridPosition.b3, 17),
|
||||
(BoardLocation.GridPosition.c3, 18),
|
||||
(BoardLocation.GridPosition.d3, 19),
|
||||
(BoardLocation.GridPosition.e3, 20),
|
||||
(BoardLocation.GridPosition.f3, 21),
|
||||
(BoardLocation.GridPosition.g3, 22),
|
||||
(BoardLocation.GridPosition.h3, 23),
|
||||
// row 4
|
||||
(BoardLocation.GridPosition.a4, 24),
|
||||
(BoardLocation.GridPosition.b4, 25),
|
||||
(BoardLocation.GridPosition.c4, 26),
|
||||
(BoardLocation.GridPosition.d4, 27),
|
||||
(BoardLocation.GridPosition.e4, 28),
|
||||
(BoardLocation.GridPosition.f4, 29),
|
||||
(BoardLocation.GridPosition.g4, 30),
|
||||
(BoardLocation.GridPosition.h4, 31),
|
||||
// row 5
|
||||
(BoardLocation.GridPosition.a5, 32),
|
||||
(BoardLocation.GridPosition.b5, 33),
|
||||
(BoardLocation.GridPosition.c5, 34),
|
||||
(BoardLocation.GridPosition.d5, 35),
|
||||
(BoardLocation.GridPosition.e5, 36),
|
||||
(BoardLocation.GridPosition.f5, 37),
|
||||
(BoardLocation.GridPosition.g5, 38),
|
||||
(BoardLocation.GridPosition.h5, 39),
|
||||
// row 6
|
||||
(BoardLocation.GridPosition.a6, 40),
|
||||
(BoardLocation.GridPosition.b6, 41),
|
||||
(BoardLocation.GridPosition.c6, 42),
|
||||
(BoardLocation.GridPosition.d6, 43),
|
||||
(BoardLocation.GridPosition.e6, 44),
|
||||
(BoardLocation.GridPosition.f6, 45),
|
||||
(BoardLocation.GridPosition.g6, 46),
|
||||
(BoardLocation.GridPosition.h6, 47),
|
||||
// row 7
|
||||
(BoardLocation.GridPosition.a7, 48),
|
||||
(BoardLocation.GridPosition.b7, 49),
|
||||
(BoardLocation.GridPosition.c7, 50),
|
||||
(BoardLocation.GridPosition.d7, 51),
|
||||
(BoardLocation.GridPosition.e7, 52),
|
||||
(BoardLocation.GridPosition.f7, 53),
|
||||
(BoardLocation.GridPosition.g7, 54),
|
||||
(BoardLocation.GridPosition.h7, 55),
|
||||
// row 8
|
||||
(BoardLocation.GridPosition.a8, 56),
|
||||
(BoardLocation.GridPosition.b8, 57),
|
||||
(BoardLocation.GridPosition.c8, 58),
|
||||
(BoardLocation.GridPosition.d8, 59),
|
||||
(BoardLocation.GridPosition.e8, 60),
|
||||
(BoardLocation.GridPosition.f8, 61),
|
||||
(BoardLocation.GridPosition.g8, 62),
|
||||
(BoardLocation.GridPosition.h8, 63),
|
||||
]
|
||||
|
||||
|
||||
for (grid, index) in testCases {
|
||||
XCTAssertEqual(BoardLocation(gridPosition: grid).index, index)
|
||||
XCTAssertEqual(BoardLocation(index: index).gridPosition, grid)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testMoveLocationsForColorReturnsCorrectLocations() {
|
||||
|
||||
class FakeOpening : Opening {
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.e2, .e4), // white moves pawn to e4
|
||||
(.e7, .e5), // black moves pawn to e5
|
||||
(.g1, .f3), // white moves knight to f3
|
||||
(.b8, .c6), // black moves knight to c6
|
||||
(.f1, .b5), // white moves bishop to b5
|
||||
]
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
let expectedWhiteLocations: [(fromLocation: BoardLocation, toLocation: BoardLocation)] = [
|
||||
(BoardLocation(gridPosition: .e2), BoardLocation(gridPosition: .e4)),
|
||||
(BoardLocation(gridPosition: .g1), BoardLocation(gridPosition: .f3)),
|
||||
(BoardLocation(gridPosition: .f1), BoardLocation(gridPosition: .b5)),
|
||||
]
|
||||
|
||||
let expectedBlackLocations: [(fromLocation: BoardLocation, toLocation: BoardLocation)] = [
|
||||
(BoardLocation(gridPosition: .e7), BoardLocation(gridPosition: .e5)),
|
||||
(BoardLocation(gridPosition: .b8), BoardLocation(gridPosition: .c6)),
|
||||
]
|
||||
|
||||
let opening = FakeOpening()
|
||||
|
||||
let whiteMoves = opening.moves(forColor: .white)
|
||||
XCTAssertEqual(whiteMoves.count, expectedWhiteLocations.count)
|
||||
|
||||
let blackMoves = opening.moves(forColor: .black)
|
||||
XCTAssertEqual(blackMoves.count, expectedBlackLocations.count)
|
||||
|
||||
for i in 0..<whiteMoves.count {
|
||||
let expected = expectedWhiteLocations[i]
|
||||
let actual = whiteMoves[i]
|
||||
|
||||
XCTAssertEqual(expected.fromLocation, actual.fromLocation)
|
||||
XCTAssertEqual(expected.toLocation, actual.toLocation)
|
||||
}
|
||||
|
||||
for i in 0..<blackMoves.count {
|
||||
let expected = expectedBlackLocations[i]
|
||||
let actual = blackMoves[i]
|
||||
|
||||
XCTAssertEqual(expected.fromLocation, actual.fromLocation)
|
||||
XCTAssertEqual(expected.toLocation, actual.toLocation)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testIsDarkSquareReturnsExpectedValue() {
|
||||
|
||||
let board = ASCIIBoard(colors: "! * ! * ! * ! *" +
|
||||
"* ! * ! * ! * !" +
|
||||
"! * ! * ! * ! *" +
|
||||
"* ! * ! * ! * !" +
|
||||
"! * ! * ! * ! *" +
|
||||
"* ! * ! * ! * !" +
|
||||
"! * ! * ! * ! *" +
|
||||
"* ! * ! * ! * !" )
|
||||
|
||||
let darkLocatons = board.locationsWithCharacter("*")
|
||||
let lightLocations = board.locationsWithCharacter("!")
|
||||
|
||||
darkLocatons.forEach{
|
||||
XCTAssertTrue($0.isDarkSquare, "Expected \($0) to be dark")
|
||||
}
|
||||
|
||||
lightLocations.forEach{
|
||||
XCTAssertFalse($0.isDarkSquare, "Expected \($0) to be light")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,7 @@ class BoardRaterBoardDominanceTests: XCTestCase {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
self.boardRater = BoardRaterBoardDominance(configuration: AIConfiguration());
|
||||
self.boardRater = BoardRaterBoardDominance(configuration: AIConfiguration(difficulty: .hard));
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -16,7 +16,7 @@ class BoardRaterCenterDominanceTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
boardRater = BoardRaterCenterDominance(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterCenterDominance(configuration: AIConfiguration(difficulty: .hard))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -24,7 +24,7 @@ class BoardRaterCenterFourOccupationTests: XCTestCase {
|
||||
|
||||
func defaultBoardRater() -> BoardRaterCenterFourOccupation {
|
||||
|
||||
let configuration = AIConfiguration()
|
||||
let configuration = AIConfiguration(difficulty: .hard)
|
||||
let boardRater = BoardRaterCenterFourOccupation(configuration: configuration)
|
||||
return boardRater
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class BoardRaterCenterOwnershipTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
boardRater = BoardRaterCenterOwnership(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterCenterOwnership(configuration: AIConfiguration(difficulty: .hard))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -16,7 +16,7 @@ class BoardRaterCheckMateOpportunityTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
boardRater = BoardRaterCheckMateOpportunity(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterCheckMateOpportunity(configuration: AIConfiguration(difficulty: .hard))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -17,7 +17,7 @@ class BoardRaterCountPiecesTests: XCTestCase {
|
||||
super.setUp()
|
||||
|
||||
// Initiailise a new board rater for each test
|
||||
boardRater = BoardRaterCountPieces(configuration: AIConfiguration());
|
||||
boardRater = BoardRaterCountPieces(configuration: AIConfiguration(difficulty: .hard));
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
@@ -16,7 +16,7 @@ class BoardRaterKingSurroundingPossessionTests: XCTestCase {
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
boardRater = BoardRaterKingSurroundingPossession(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterKingSurroundingPossession(configuration: AIConfiguration(difficulty: .hard))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -357,34 +357,57 @@ class BoardRaterKingSurroundingPossessionTests: XCTestCase {
|
||||
XCTAssertGreaterThan(rating, 0)
|
||||
}
|
||||
|
||||
func testThatPiecesSurroundingOpponentKingResultsInNegativeRatingForWhite() {
|
||||
func testThatPiecesSurroundingOpponentKingResultsInMoreNegativeRatingForWhite() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - p g p - -" +
|
||||
"- - - p p p - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - G - - -" )
|
||||
let openKingBoard = ASCIIBoard(pieces: "- - - - g - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - G - - -" )
|
||||
|
||||
let rating = boardRater.ratingfor(board: board.board, color: .white)
|
||||
XCTAssertLessThan(rating, 0)
|
||||
let surroundedKingBoard = ASCIIBoard(pieces: "- - - p g p - -" +
|
||||
"- - - p p p - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - G - - -" )
|
||||
|
||||
|
||||
let openKingRating = boardRater.ratingfor(board: openKingBoard.board, color: .white)
|
||||
let surroundedKingRating = boardRater.ratingfor(board: surroundedKingBoard.board, color: .white)
|
||||
|
||||
XCTAssertLessThan(surroundedKingRating, openKingRating)
|
||||
}
|
||||
|
||||
func testThatPiecesSurroundingOpponentKingResultsInNegativeRatingForBlack() {
|
||||
func testThatPiecesSurroundingOpponentKingResultsInMoreNegativeRatingForBlack() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - g - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - P P P - -" +
|
||||
"- - - P G P - -" )
|
||||
let openKingBoard = ASCIIBoard(pieces: "- - - - g - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - G - - -" )
|
||||
|
||||
let rating = boardRater.ratingfor(board: board.board, color: .black)
|
||||
XCTAssertLessThan(rating, 0)
|
||||
let surroundedKingBoard = ASCIIBoard(pieces: "- - - - g - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - P P P - -" +
|
||||
"- - - P G P - -" )
|
||||
|
||||
|
||||
let openKingRating = boardRater.ratingfor(board: openKingBoard.board, color: .black)
|
||||
let surroundedKingRating = boardRater.ratingfor(board: surroundedKingBoard.board, color: .black)
|
||||
XCTAssertLessThan(surroundedKingRating, openKingRating)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class BoardRaterPawnProgressionTests: XCTestCase {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
boardRater = BoardRaterPawnProgression(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterPawnProgression(configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
boardRater = BoardRaterThreatenedPieces(configuration: AIConfiguration())
|
||||
boardRater = BoardRaterThreatenedPieces(configuration: AIConfiguration(difficulty: .hard))
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -54,9 +54,9 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
|
||||
let rating = boardRater.ratingfor(board: board.board, color: .white)
|
||||
|
||||
XCTAssert(rating < 0);
|
||||
XCTAssertLessThan(rating, 0)
|
||||
}
|
||||
|
||||
|
||||
func testBoardRaterThreatenedPiecesReturnsPositiveValueIfThreateningOpponant() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
@@ -70,11 +70,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
|
||||
let rating = boardRater.ratingfor(board: board.board, color: .white)
|
||||
|
||||
XCTAssert(rating > 0);
|
||||
XCTAssertGreaterThan(rating, 0)
|
||||
}
|
||||
|
||||
func testBoardRaterThreatenedPiecesReturnsHigherThreatValueForHigherValuePieces() {
|
||||
|
||||
/*
|
||||
|
||||
let queenBoard = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
@@ -109,32 +111,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
|
||||
|
||||
XCTAssert(queenRating > knightRating);
|
||||
}
|
||||
/*
|
||||
func testOwnPiecesMultiplerShouldIncreaceValueOfIncomingThreats() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"q - - - - - Q -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
boardRater.ownPiecesMultipler = 1
|
||||
let expectedLowRating = boardRater.ratingfor(board: board.board, color: .white)
|
||||
|
||||
boardRater.ownPiecesMultipler = 2
|
||||
let expectedHighRating = boardRater.ratingfor(board: board.board, color: .white)
|
||||
|
||||
// Higher threat levels result in negative ratings!
|
||||
XCTAssert(expectedHighRating < expectedLowRating)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
func testBoardRaterThreatenedPiecesReturnsMoreNegativeThreatValueForFavourableTrade() {
|
||||
|
||||
/*
|
||||
|
||||
// Good trade (White rook can be taken by the black queen, but the white pawn will then take the queen)
|
||||
let goodTradeBoard = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
@@ -162,6 +145,8 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
let badTradeRating = boardRater.threatRatingForPiece(at: rookLocation, board: badTradeBoard.board, color: .white)
|
||||
|
||||
XCTAssert(goodTradeRating < badTradeRating);
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
// MARK - Get protected pieces tests
|
||||
@@ -186,9 +171,9 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
|
||||
let protectingLocations = boardRater.protectingPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let gameBoard = board.board
|
||||
let protectingLocations = boardRater.getPieces(protecting: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: board.board).map{ $0.location }
|
||||
|
||||
// Check all of the expected locations appeared in the protecting locations array
|
||||
for expectedIndex in expectedIndexes {
|
||||
@@ -229,12 +214,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- B - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let protectingLocations = boardRater.protectingPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let protectingPieces = boardRater.getPieces(protecting: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
// None of the pieces are protecting the queen, so expect count to be zero
|
||||
XCTAssertTrue(protectingLocations.count == 0)
|
||||
XCTAssertTrue(protectingPieces.count == 0)
|
||||
}
|
||||
|
||||
func testGetProtectingPiecesDoesntReturnThreateningPiecesOfOppositeColor() {
|
||||
@@ -249,12 +235,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - b - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let protectingLocations = boardRater.protectingPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let protectingPieces = boardRater.getPieces(protecting: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
// The black pieces cannot protect the white queen, so expect count to be zero
|
||||
XCTAssertTrue(protectingLocations.count == 0)
|
||||
XCTAssertTrue(protectingPieces.count == 0)
|
||||
}
|
||||
|
||||
func testGetProtectingPiecesDoesntReturnPawnsMovingStraightAhead() {
|
||||
@@ -269,11 +256,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let protectingLocations = boardRater.protectingPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let protectingPieces = boardRater.getPieces(protecting: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
XCTAssertTrue(protectingLocations.count == 0)
|
||||
XCTAssertTrue(protectingPieces.count == 0)
|
||||
|
||||
}
|
||||
|
||||
func testGetProtectingPiecesReturnsPawnsMovingDiagonally() {
|
||||
@@ -288,11 +277,12 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let protectingLocations = boardRater.protectingPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let protectingPieces = boardRater.getPieces(protecting: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
XCTAssertTrue(protectingLocations.count == 1)
|
||||
XCTAssertTrue(protectingPieces.count == 1)
|
||||
}
|
||||
|
||||
// MARK - Get threatening pieces tests
|
||||
@@ -317,9 +307,10 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
|
||||
let threateningLocations = boardRater.threateningPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let gameBoard = board.board
|
||||
|
||||
let threateningLocations = boardRater.getPieces(threatening: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard).map{ $0.location }
|
||||
|
||||
// Check all of the expected locations appeared in the protecting locations array
|
||||
for expectedIndex in expectedIndexes {
|
||||
@@ -360,12 +351,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- b - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let threateningLocations = boardRater.threateningPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let threateningPieces = boardRater.getPieces(threatening: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
// None of the pieces are threatening the queen, so expect count to be zero
|
||||
XCTAssertTrue(threateningLocations.count == 0)
|
||||
XCTAssertTrue(threateningPieces.count == 0)
|
||||
}
|
||||
|
||||
func testGetThreateningPiecesDoesntReturnProtectingPiecesOfSameColor() {
|
||||
@@ -380,12 +372,13 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - B - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("Q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let threateningLocations = boardRater.threateningPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let threateningPieces = boardRater.getPieces(threatening: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
// The white pieces cannot threaten the white queen, so expect count to be zero
|
||||
XCTAssertTrue(threateningLocations.count == 0)
|
||||
XCTAssertTrue(threateningPieces.count == 0)
|
||||
}
|
||||
|
||||
func testGetThreateningPiecesDoesntReturnPawnsMovingStraightAhead() {
|
||||
@@ -400,11 +393,12 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let threateningLocations = boardRater.threateningPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let threateningPieces = boardRater.getPieces(threatening: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
XCTAssertTrue(threateningLocations.count == 0)
|
||||
XCTAssertTrue(threateningPieces.count == 0)
|
||||
}
|
||||
|
||||
func testGetThreateningPiecesReturnsPawnsMovingDiagonally() {
|
||||
@@ -419,11 +413,12 @@ class BoardRaterThreatenedPiecesTests: XCTestCase {
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = BoardLocation(index: board.indexOfCharacter("q"))
|
||||
let gameBoard = board.board
|
||||
|
||||
let threateningLocations = boardRater.threateningPiecesLocationsforPiece(at: queenLocation,
|
||||
on: board.board)
|
||||
let threateningPieces = boardRater.getPieces(threatening: gameBoard.getPiece(at: queenLocation)!,
|
||||
onBoard: gameBoard)
|
||||
|
||||
XCTAssertTrue(threateningLocations.count == 1)
|
||||
XCTAssertTrue(threateningPieces.count == 1)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// BoardScenarios.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Steve Barnegren on 26/01/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftChess
|
||||
|
||||
extension Board {
|
||||
|
||||
public static func whiteInStaleMateScenario() -> Board {
|
||||
|
||||
return ASCIIBoard(pieces: "- - r - r - - g" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"q - - - - - - -" +
|
||||
"- - - G - - - -" ).board
|
||||
}
|
||||
|
||||
public static func blackInStaleMateScenario() -> Board {
|
||||
|
||||
return ASCIIBoard(pieces: "- - R - R - - G" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"Q - - - - - - -" +
|
||||
"- - - g - - - -" ).board
|
||||
}
|
||||
|
||||
public static func whiteInCheckMateScenario() -> Board {
|
||||
|
||||
return ASCIIBoard(pieces: "- p g - - - - K" +
|
||||
"- - - - - P - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"r - - - - - - -" +
|
||||
"r - - G - - - -" ).board
|
||||
}
|
||||
|
||||
public static func blackInCheckMateScenario() -> Board {
|
||||
|
||||
return ASCIIBoard(pieces: "- - - g - - - R" +
|
||||
"- - - - - - - R" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- G - - - - - -" ).board
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
+153
-37
@@ -29,10 +29,9 @@ class BoardTests: XCTestCase {
|
||||
|
||||
let board = Board(state: .empty)
|
||||
|
||||
for index in 0..<64 {
|
||||
|
||||
let piece = board.getPiece(at: BoardLocation(index: index))
|
||||
XCTAssert(piece == nil, "Expected piece at index \(index) to be nil")
|
||||
(0..<64).forEach {
|
||||
let piece = board.getPiece(at: BoardLocation(index: $0))
|
||||
XCTAssert(piece == nil, "Expected piece at index \($0) to be nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +95,29 @@ class BoardTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Board Location
|
||||
|
||||
func testBoardLocationStrideToReturnsExpectedStride() {
|
||||
|
||||
let fromLocation = BoardLocation(x: 1, y: 1)
|
||||
let toLocation = BoardLocation(x: 3, y: 4)
|
||||
|
||||
let stride = fromLocation.strideTo(location: toLocation)
|
||||
XCTAssertEqual(stride.x, 2)
|
||||
XCTAssertEqual(stride.y, 3)
|
||||
}
|
||||
|
||||
func testBoardLocationStrideFromReturnsExpectedStride() {
|
||||
|
||||
let fromLocation = BoardLocation(x: 1, y: 1)
|
||||
let toLocation = BoardLocation(x: 3, y: 4)
|
||||
|
||||
let stride = toLocation.strideFrom(location: fromLocation)
|
||||
XCTAssertEqual(stride.x, 2)
|
||||
XCTAssertEqual(stride.y, 3)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Piece Manipulation
|
||||
|
||||
func testSetAndGetPiece() {
|
||||
@@ -116,6 +138,17 @@ class BoardTests: XCTestCase {
|
||||
|
||||
}
|
||||
|
||||
func testRemovePieceRemovesPiece() {
|
||||
|
||||
var board = Board(state: .newGame)
|
||||
let location = BoardLocation(index: 10)
|
||||
board.setPiece(Piece(type: .pawn, color: .white),
|
||||
at: location)
|
||||
board.removePiece(atLocation: location)
|
||||
|
||||
XCTAssertNil(board.getPiece(at: location))
|
||||
}
|
||||
|
||||
func testMovePieceResultsInPieceMoved() {
|
||||
|
||||
var board = Board(state: .empty);
|
||||
@@ -145,8 +178,8 @@ class BoardTests: XCTestCase {
|
||||
let blackPieces = board.getPieces(color: .black)
|
||||
let allPieces = whitePieces + blackPieces
|
||||
|
||||
for piece in allPieces {
|
||||
XCTAssertFalse(piece.hasMoved)
|
||||
allPieces.forEach{
|
||||
XCTAssertFalse($0.hasMoved)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +191,12 @@ class BoardTests: XCTestCase {
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"* - - - - - - -" +
|
||||
"W - - - - - - -" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let fromLocation = BoardLocation(index: board.indexOfCharacter("W") )
|
||||
let toLocation = BoardLocation(x: 4, y: 4)
|
||||
let fromLocation = board.locationOfCharacter("W")
|
||||
let toLocation = board.locationOfCharacter("*")
|
||||
|
||||
var gameBoard = board.board
|
||||
|
||||
@@ -250,44 +283,27 @@ class BoardTests: XCTestCase {
|
||||
|
||||
var board = Board(state: .empty)
|
||||
|
||||
for location in whiteLocations {
|
||||
board.setPiece(Piece(type: .pawn, color: .white), at: location)
|
||||
whiteLocations.forEach{
|
||||
board.setPiece(Piece(type: .pawn, color: .white), at: $0)
|
||||
}
|
||||
|
||||
for location in blackLocations {
|
||||
board.setPiece(Piece(type: .pawn, color: .black), at: location)
|
||||
blackLocations.forEach{
|
||||
board.setPiece(Piece(type: .pawn, color: .black), at: $0)
|
||||
}
|
||||
|
||||
let returnedWhitelocations = board.getLocationsOfColor(.white)
|
||||
XCTAssert(returnedWhitelocations.count == whiteLocations.count,
|
||||
"Expected white count to be \(whiteLocations.count), was \(returnedWhitelocations.count)")
|
||||
XCTAssertEqual(whiteLocations.count, returnedWhitelocations.count);
|
||||
|
||||
let returnedBlacklocations = board.getLocationsOfColor(.black)
|
||||
XCTAssert(returnedBlacklocations.count == blackLocations.count,
|
||||
"Expected white count to be \(blackLocations.count), was \(returnedBlacklocations.count)")
|
||||
XCTAssertEqual(blackLocations.count, returnedBlacklocations.count);
|
||||
|
||||
|
||||
for location in whiteLocations {
|
||||
|
||||
XCTAssert(
|
||||
returnedWhitelocations.filter({ (returnedLocation: BoardLocation) -> Bool in
|
||||
return location == returnedLocation
|
||||
}).count == 1,
|
||||
"Expected only one location to exist"
|
||||
)
|
||||
whiteLocations.forEach { (location) in
|
||||
XCTAssertEqual(returnedWhitelocations.filter{ location == $0 }.count, 1)
|
||||
}
|
||||
|
||||
|
||||
for location in blackLocations {
|
||||
|
||||
XCTAssert(
|
||||
returnedBlacklocations.filter({ (returnedLocation: BoardLocation) -> Bool in
|
||||
return location == returnedLocation
|
||||
}).count == 1,
|
||||
"Expected only one location to exist"
|
||||
)
|
||||
blackLocations.forEach { (location) in
|
||||
XCTAssertEqual(returnedBlacklocations.filter{ location == $0 }.count, 1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testIsColorInCheckReturnsTrueWhenInCheck() {
|
||||
@@ -980,7 +996,107 @@ class BoardTests: XCTestCase {
|
||||
XCTAssertFalse(board.doesColorOccupyLocation(color: .white, location: location))
|
||||
XCTAssertFalse(board.doesColorOccupyLocation(color: .black, location: location))
|
||||
}
|
||||
|
||||
// MARK: Test Possible Move Locations
|
||||
|
||||
func testPossibleMoveLocationsReturnsExpectedLocations(){
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - * - * - - -" +
|
||||
"- * - - - * - -" +
|
||||
"- - - K - - - -" +
|
||||
"- * - - - * - -" +
|
||||
"- - * - * - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" );
|
||||
|
||||
let board = asciiBoard.board;
|
||||
|
||||
let sourceLocation = asciiBoard.locationOfCharacter("K")
|
||||
let locations = board.possibleMoveLocationsForPiece(atLocation: sourceLocation)
|
||||
XCTAssertEqual(locations.count, 8)
|
||||
|
||||
let expectedLocations = asciiBoard.locationsWithCharacter("*")
|
||||
|
||||
expectedLocations.forEach{ expectedLocation in
|
||||
XCTAssertEqual(locations.filter( { $0 == expectedLocation} ).count, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Board Equality
|
||||
|
||||
func testEqualBoardsAreEqual() {
|
||||
|
||||
var board1 = Board(state: .newGame)
|
||||
var board2 = Board(state: .newGame)
|
||||
|
||||
let sourceLocation = BoardLocation(x: 0, y: 1)
|
||||
let targetLocation = BoardLocation(x: 0, y: 3)
|
||||
|
||||
board1.movePiece(fromLocation: sourceLocation, toLocation: targetLocation)
|
||||
board2.movePiece(fromLocation: sourceLocation, toLocation: targetLocation)
|
||||
|
||||
XCTAssertEqual(board1, board2)
|
||||
}
|
||||
|
||||
func testNonEqualBoardsAreNotEqual() {
|
||||
|
||||
var board1 = Board(state: .newGame)
|
||||
board1.movePiece(fromLocation: BoardLocation(x: 0, y: 1),
|
||||
toLocation: BoardLocation(x: 0, y: 3))
|
||||
|
||||
let board2 = Board(state: .newGame)
|
||||
|
||||
XCTAssertNotEqual(board1, board2)
|
||||
}
|
||||
|
||||
// MARK: - Piece Location tests
|
||||
|
||||
func testPieceLocationsAreCorrectForNewBoard() {
|
||||
|
||||
let board = Board(state: .newGame)
|
||||
|
||||
for location in BoardLocation.all {
|
||||
|
||||
guard let piece = board.getPiece(at: location) else {
|
||||
continue
|
||||
}
|
||||
|
||||
XCTAssertEqual(piece.location, location)
|
||||
}
|
||||
}
|
||||
|
||||
func testPieceLocationsAreCorrectAfterMoves() {
|
||||
|
||||
var board = Board(state: .newGame)
|
||||
board.movePiece(fromLocation: BoardLocation(gridPosition: .e2),
|
||||
toLocation: BoardLocation(gridPosition: .e4))
|
||||
|
||||
board.movePiece(fromLocation: BoardLocation(gridPosition: .d7),
|
||||
toLocation: BoardLocation(gridPosition: .d5))
|
||||
|
||||
for location in BoardLocation.all {
|
||||
|
||||
guard let piece = board.getPiece(at: location) else {
|
||||
continue
|
||||
}
|
||||
|
||||
XCTAssertEqual(piece.location, location)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,20 +7,22 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SwiftChess
|
||||
@testable import SwiftChess
|
||||
|
||||
class GameTests: XCTestCase {
|
||||
|
||||
var whitePlayer: Human!
|
||||
var blackPlayer: Human!
|
||||
var game: Game!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
|
||||
let firstPlayer = Human(color: .white)
|
||||
let secondPlayer = Human(color: .black)
|
||||
whitePlayer = Human(color: .white)
|
||||
blackPlayer = Human(color: .black)
|
||||
|
||||
game = Game(firstPlayer: firstPlayer, secondPlayer: secondPlayer)
|
||||
game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
@@ -28,20 +30,81 @@ class GameTests: XCTestCase {
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
/*
|
||||
func testCurrentPlayerChangesAfterMoveTaken() {
|
||||
|
||||
let firstPlayer = game.currentPlayer
|
||||
|
||||
// White player move leftmost pawn
|
||||
try! game.currentPlayer.movePiece(fromLocation: BoardLocation(x: 0, y: 1),
|
||||
toLocation: BoardLocation(x: 0, y: 2))
|
||||
|
||||
XCTAssert(game.currentPlayer !== firstPlayer)
|
||||
|
||||
}
|
||||
*/
|
||||
// MARK: Game State Tests
|
||||
|
||||
|
||||
func testGameStateEquatableReturnsCorrectEquability() {
|
||||
|
||||
// Match in progress
|
||||
XCTAssertTrue(Game.State.inProgress == Game.State.inProgress)
|
||||
|
||||
// Match stale mate
|
||||
XCTAssertTrue(Game.State.staleMate(color: .white) == Game.State.staleMate(color: .white))
|
||||
XCTAssertTrue(Game.State.staleMate(color: .black) == Game.State.staleMate(color: .black))
|
||||
XCTAssertFalse(Game.State.staleMate(color: .white) == Game.State.staleMate(color: .black))
|
||||
XCTAssertFalse(Game.State.staleMate(color: .black) == Game.State.staleMate(color: .white))
|
||||
|
||||
// Match won
|
||||
XCTAssertTrue(Game.State.won(color: .white) == Game.State.won(color: .white))
|
||||
XCTAssertTrue(Game.State.won(color: .black) == Game.State.won(color: .black))
|
||||
XCTAssertFalse(Game.State.won(color: .white) == Game.State.won(color: .black))
|
||||
XCTAssertFalse(Game.State.won(color: .black) == Game.State.won(color: .white))
|
||||
|
||||
// Mismatches
|
||||
XCTAssertFalse(Game.State.inProgress == Game.State.won(color: .black))
|
||||
XCTAssertFalse(Game.State.inProgress == Game.State.staleMate(color: .white))
|
||||
XCTAssertFalse(Game.State.staleMate(color: .white) == Game.State.won(color: .white))
|
||||
|
||||
}
|
||||
|
||||
func testGameStartsInProgress() {
|
||||
XCTAssertTrue(game.state == Game.State.inProgress)
|
||||
}
|
||||
|
||||
func testAfterMovesGameIsStillInProgressState() {
|
||||
|
||||
// White player moves pawn
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: BoardLocation(x: 0, y: 1),
|
||||
toLocation: BoardLocation(x: 0, y: 2))
|
||||
} catch {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
// Black player moves pawn
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: BoardLocation(x: 7, y: 6),
|
||||
toLocation: BoardLocation(x: 7, y: 5))
|
||||
} catch {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertTrue(game.state == Game.State.inProgress)
|
||||
}
|
||||
|
||||
func testStaleMateScenarioResultsInStaleMateState() {
|
||||
|
||||
game = Game(firstPlayer: whitePlayer,
|
||||
secondPlayer: blackPlayer,
|
||||
board: Board.whiteInStaleMateScenario(),
|
||||
colorToMove: .black)
|
||||
|
||||
game.playerDidMakeMove(player: blackPlayer, boardOperations: [])
|
||||
|
||||
XCTAssertTrue(game.state == Game.State.staleMate(color: .white))
|
||||
}
|
||||
|
||||
func testCheckMateScenarioResultsInWonState() {
|
||||
|
||||
game = Game(firstPlayer: whitePlayer,
|
||||
secondPlayer: blackPlayer,
|
||||
board: Board.whiteInCheckMateScenario(),
|
||||
colorToMove: .black)
|
||||
|
||||
game.playerDidMakeMove(player: blackPlayer, boardOperations: [])
|
||||
|
||||
XCTAssertTrue(game.state == Game.State.won(color: .black))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// OpeningsTests.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Steve Barnegren on 31/01/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftChess
|
||||
|
||||
class OpeningsTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testOpeningsContainValidMoves() {
|
||||
|
||||
let openings = Opening.allOpenings()
|
||||
|
||||
for opening in openings {
|
||||
|
||||
print("Testing \(opening)")
|
||||
|
||||
var board = Board(state: .newGame)
|
||||
for (source, target) in opening.moveLocations(){
|
||||
|
||||
// Assert that the piece exists
|
||||
guard let piece = board.getPiece(at: source) else {
|
||||
XCTFail("No piece at \(source)")
|
||||
break
|
||||
}
|
||||
|
||||
// Assert the piece can move
|
||||
if piece.movement.canPieceMove(fromLocation: source, toLocation: target, board: board) {
|
||||
board.movePiece(fromLocation: source, toLocation: target)
|
||||
}
|
||||
else{
|
||||
XCTFail("Cannot move piece from \(source) to \(target)")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
//
|
||||
// PerformanceTests.swift
|
||||
// Example
|
||||
//
|
||||
// Created by Steve Barnegren on 02/02/2017.
|
||||
// Copyright © 2017 CocoaPods. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import SwiftChess
|
||||
|
||||
class PerformanceTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testPawnMoveValidationPerformance() {
|
||||
|
||||
let board = Board(state: .newGame)
|
||||
let pawnLocation = BoardLocation(x: 0, y: 1)
|
||||
|
||||
guard let pawn = board.getPiece(at: pawnLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(pawn.type == .pawn)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
pawn.movement.canPieceMove(fromLocation: pawnLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQueenMoveValidationPerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - R - - p -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - Q - - - r" +
|
||||
"- - - - - - - -" +
|
||||
"P P - - - - - -" +
|
||||
"G - - - - - - -" )
|
||||
|
||||
let queenLocation = asciiBoard.locationOfCharacter("Q")
|
||||
let board = asciiBoard.board
|
||||
|
||||
guard let queen = board.getPiece(at: queenLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(queen.type == .queen)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
queen.movement.canPieceMove(fromLocation: queenLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testKingMoveValidationPerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - R - - p -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - Q - - - r" +
|
||||
"- - - - - - - -" +
|
||||
"- - - P - - - -" +
|
||||
"- - - G - - - -" )
|
||||
|
||||
let kingLocation = asciiBoard.locationOfCharacter("G")
|
||||
let board = asciiBoard.board
|
||||
|
||||
guard let king = board.getPiece(at: kingLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(king.type == .king)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
king.movement.canPieceMove(fromLocation: kingLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testKnightMoveValidationPerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - R - - p -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - Q - K - r" +
|
||||
"- - - - - - - -" +
|
||||
"- - - P - - - -" +
|
||||
"- - - G - - - -" )
|
||||
|
||||
let knightLocation = asciiBoard.locationOfCharacter("K")
|
||||
let board = asciiBoard.board
|
||||
|
||||
guard let knight = board.getPiece(at: knightLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(knight.type == .knight)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
knight.movement.canPieceMove(fromLocation: knightLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testBishopMoveValidationPerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - R - - p -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - Q - K - r" +
|
||||
"- - - - - B - -" +
|
||||
"- - - P - - - -" +
|
||||
"- - - G - - - -" )
|
||||
|
||||
let bishopLocation = asciiBoard.locationOfCharacter("B")
|
||||
let board = asciiBoard.board
|
||||
|
||||
guard let bishop = board.getPiece(at: bishopLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(bishop.type == .bishop)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
bishop.movement.canPieceMove(fromLocation: bishopLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testRookMoveValidationPerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - - - - p -" +
|
||||
"- - - - - - - -" +
|
||||
"- R - - - - - -" +
|
||||
"- - - Q - K - r" +
|
||||
"- - - - - B - -" +
|
||||
"- - - P - - - -" +
|
||||
"- - - G - - - -" )
|
||||
|
||||
let rookLocation = asciiBoard.locationOfCharacter("R")
|
||||
let board = asciiBoard.board
|
||||
|
||||
guard let rook = board.getPiece(at: rookLocation) else {
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssert(rook.type == .rook)
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
rook.movement.canPieceMove(fromLocation: rookLocation,
|
||||
toLocation: $0,
|
||||
board: board)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCanAnyPieceMovePerformance() {
|
||||
|
||||
let asciiBoard = ASCIIBoard(pieces: "r - b q g b - r" +
|
||||
"p p p - - p - -" +
|
||||
"k - - - p - - k" +
|
||||
"- - - p - - - -" +
|
||||
"- - - P - - - -" +
|
||||
"K - - - P - - -" +
|
||||
"P P P - - P P -" +
|
||||
"R - B Q G B K R" )
|
||||
|
||||
let board = asciiBoard.board;
|
||||
|
||||
self.measure {
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
board.canColorMoveAnyPieceToLocation(color: .white, location: $0)
|
||||
board.canColorMoveAnyPieceToLocation(color: .black, location: $0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SwiftChess
|
||||
@testable import SwiftChess
|
||||
|
||||
class PieceMovementTests: XCTestCase {
|
||||
|
||||
@@ -683,7 +683,6 @@ class PieceMovementTests: XCTestCase {
|
||||
let movement = PieceMovementKnight()
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - King Movement
|
||||
|
||||
@@ -811,7 +810,8 @@ class PieceMovementTests: XCTestCase {
|
||||
let movement = PieceMovementKing()
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: - Pawn Movement
|
||||
|
||||
@@ -1147,6 +1147,275 @@ class PieceMovementTests: XCTestCase {
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
// MARK: - Test pawn En Passant
|
||||
|
||||
func makeGame(board: Board, colorToMove: Color) -> Game {
|
||||
|
||||
let whitePlayer = Human(color: .white)
|
||||
let blackPlayer = Human(color: .black)
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer, board: board, colorToMove: colorToMove)
|
||||
return game
|
||||
}
|
||||
|
||||
func testPawnEnPassantFlagIsTrueAfterMoveTwoSpaces() {
|
||||
|
||||
let board = Board(state: .newGame)
|
||||
|
||||
let startLocation = BoardLocation(x: 0, y: 1)
|
||||
let targetLocation = BoardLocation(x: 0, y: 3)
|
||||
|
||||
let game = makeGame(board: board, colorToMove: .white)
|
||||
|
||||
guard let whitePlayer = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: startLocation, toLocation: targetLocation)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
guard let piece = game.board.getPiece(at: targetLocation) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertTrue(piece.color == .white)
|
||||
XCTAssertTrue(piece.type == .pawn)
|
||||
XCTAssertTrue(piece.canBeTakenByEnPassant == true)
|
||||
}
|
||||
|
||||
func testPawnEnPassantFlagIsFalseAfterMoveOneSpace() {
|
||||
|
||||
let board = Board(state: .newGame)
|
||||
|
||||
let startLocation = BoardLocation(x: 0, y: 1)
|
||||
let targetLocation = BoardLocation(x: 0, y: 2)
|
||||
|
||||
let game = makeGame(board: board, colorToMove: .white)
|
||||
|
||||
guard let whitePlayer = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: startLocation, toLocation: targetLocation)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
guard let piece = game.board.getPiece(at: targetLocation) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertTrue(piece.color == .white)
|
||||
XCTAssertTrue(piece.type == .pawn)
|
||||
XCTAssertTrue(piece.canBeTakenByEnPassant == false)
|
||||
}
|
||||
|
||||
func testPawnEnPassantFlagIsResetAfterSubsequentMove() {
|
||||
|
||||
// White moves pawn
|
||||
let board = Board(state: .newGame)
|
||||
|
||||
let startLocation = BoardLocation(x: 0, y: 1)
|
||||
let targetLocation = BoardLocation(x: 0, y: 2)
|
||||
|
||||
let game = makeGame(board: board, colorToMove: .white)
|
||||
|
||||
guard let whitePlayer = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: startLocation, toLocation: targetLocation)
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Black moves pawn
|
||||
guard let blackPlayer = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
guard blackPlayer.color == .black else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: BoardLocation(x: 0, y: 6), toLocation: BoardLocation(x: 0, y: 5))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Check white pawn flag is false
|
||||
guard let piece = game.board.getPiece(at: targetLocation) else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
XCTAssertTrue(piece.color == .white)
|
||||
XCTAssertTrue(piece.type == .pawn)
|
||||
XCTAssertTrue(piece.canBeTakenByEnPassant == false)
|
||||
}
|
||||
|
||||
func testWhitePawnCanTakeOpponentUsingEnPassant() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"p - - - - - - -" +
|
||||
"+ - - - - - - -" +
|
||||
"* P - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - G" )
|
||||
|
||||
let game = makeGame(board: board.board, colorToMove: .black)
|
||||
let blackPlayer = game.blackPlayer as! Human
|
||||
let whitePlayer = game.whitePlayer as! Human
|
||||
|
||||
// Black move two spaces
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: board.locationOfCharacter("p"), toLocation: board.locationOfCharacter("*"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// White should be able to take the black pawn using the en passant rule
|
||||
let pieceMovement = PieceMovementPawn()
|
||||
XCTAssertTrue(pieceMovement.canPieceMove(fromLocation: board.locationOfCharacter("P"), toLocation: board.locationOfCharacter("+"), board: game.board),
|
||||
"Expected white to be able to make en passant move")
|
||||
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: board.locationOfCharacter("P"), toLocation: board.locationOfCharacter("+"))
|
||||
} catch {
|
||||
XCTFail("Expected white to be able to execute en passant move")
|
||||
}
|
||||
|
||||
XCTAssertTrue(game.board.getPiece(at: board.locationOfCharacter("*")) == nil, "Expected black pawn to be removed from board")
|
||||
|
||||
}
|
||||
|
||||
func testBlackPawnCanTakeOpponentUsingEnPassant() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"* p - - - - - -" +
|
||||
"+ - - - - - - -" +
|
||||
"P - - - - - - -" +
|
||||
"- - - - - - - G" )
|
||||
|
||||
let game = makeGame(board: board.board, colorToMove: .white)
|
||||
let whitePlayer = game.whitePlayer as! Human
|
||||
let blackPlayer = game.blackPlayer as! Human
|
||||
|
||||
// White move two spaces
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: board.locationOfCharacter("P"), toLocation: board.locationOfCharacter("*"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Black should be able to take the white pawn using the en passant rule
|
||||
let pieceMovement = PieceMovementPawn()
|
||||
XCTAssertTrue(pieceMovement.canPieceMove(fromLocation: board.locationOfCharacter("p"), toLocation: board.locationOfCharacter("+"), board: game.board),
|
||||
"Expected black to be able to make en passant move")
|
||||
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: board.locationOfCharacter("p"), toLocation: board.locationOfCharacter("+"))
|
||||
} catch {
|
||||
XCTFail("Expected black to be able to execute en passant move")
|
||||
}
|
||||
|
||||
XCTAssertTrue(game.board.getPiece(at: board.locationOfCharacter("*")) == nil, "Expected white pawn to be removed from board")
|
||||
|
||||
}
|
||||
|
||||
func testWhitePawnCannotTakeOpponentUsingEnPassantIfMoveNotMadeImmediately() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"p - - - - - - %" +
|
||||
"+ - - - - - - -" +
|
||||
"* P - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - &" +
|
||||
"- - - - - - - G" )
|
||||
|
||||
let game = makeGame(board: board.board, colorToMove: .black)
|
||||
let whitePlayer = game.whitePlayer as! Human
|
||||
let blackPlayer = game.blackPlayer as! Human
|
||||
|
||||
// Black move two spaces
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: board.locationOfCharacter("p"), toLocation: board.locationOfCharacter("*"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// White moves king
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: board.locationOfCharacter("G"), toLocation: board.locationOfCharacter("&"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Black moves king
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: board.locationOfCharacter("g"), toLocation: board.locationOfCharacter("%"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// White should not be able to take the black pawn using the en passant rule
|
||||
let pieceMovement = PieceMovementPawn()
|
||||
XCTAssertFalse(pieceMovement.canPieceMove(fromLocation: board.locationOfCharacter("P"), toLocation: board.locationOfCharacter("+"), board: game.board))
|
||||
|
||||
}
|
||||
|
||||
func testBlackPawnCannotTakeOpponentUsingEnPassantIfMoveNotMadeImmediately() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - g" +
|
||||
"- - - - - - - %" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"* p - - - - - -" +
|
||||
"+ - - - - - - -" +
|
||||
"P - - - - - - &" +
|
||||
"- - - - - - - G" )
|
||||
|
||||
let game = makeGame(board: board.board, colorToMove: .white)
|
||||
let whitePlayer = game.whitePlayer as! Human
|
||||
let blackPlayer = game.blackPlayer as! Human
|
||||
|
||||
// White moves pawn two spaces
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: board.locationOfCharacter("P"), toLocation: board.locationOfCharacter("*"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Black moves king
|
||||
do {
|
||||
try blackPlayer.movePiece(fromLocation: board.locationOfCharacter("g"), toLocation: board.locationOfCharacter("%"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// White moves king
|
||||
do {
|
||||
try whitePlayer.movePiece(fromLocation: board.locationOfCharacter("G"), toLocation: board.locationOfCharacter("&"))
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Black should not be able to take the white pawn using the en passant rule
|
||||
let pieceMovement = PieceMovementPawn()
|
||||
XCTAssertFalse(pieceMovement.canPieceMove(fromLocation: board.locationOfCharacter("p"), toLocation: board.locationOfCharacter("+"), board: game.board))
|
||||
}
|
||||
|
||||
// MARK: - Queen movement
|
||||
|
||||
func testQueenCannotMoveToInvalidPositionFromCentre(){
|
||||
@@ -1274,6 +1543,8 @@ class PieceMovementTests: XCTestCase {
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Rook Movement
|
||||
|
||||
func testRookCannotMoveToInvalidPositionFromCentre(){
|
||||
@@ -1400,7 +1671,8 @@ class PieceMovementTests: XCTestCase {
|
||||
let movement = PieceMovementRook()
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// MARK: - Bishop movement
|
||||
@@ -1529,6 +1801,8 @@ class PieceMovementTests: XCTestCase {
|
||||
let movement = PieceMovementBishop()
|
||||
XCTAssertFalse(movement.canPieceMove(fromLocation: BoardLocation(index: pieceIndex), toLocation: BoardLocation(index: kingIndex), board: board.board))
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -55,6 +55,160 @@ class PlayerTests: XCTestCase {
|
||||
XCTAssert(game.whitePlayer.canMovePiece(fromLocation: location, toLocation: location) == false)
|
||||
}
|
||||
|
||||
// TODO: Add tests for move error throw values
|
||||
// MARK: - Move Errors
|
||||
|
||||
func gameForTestingCallbacks(board: Board, color: Color) -> Game {
|
||||
|
||||
let whitePlayer = Human(color: .white)
|
||||
let blackPlayer = Human(color: .black)
|
||||
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer, board: board, colorToMove: color)
|
||||
|
||||
return game
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingQueen() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - * - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - - Q - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let queenLocation = board.locationOfCharacter("Q")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: queenLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingPawn() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - * - - -" +
|
||||
"G - - - P - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let pieceLocation = board.locationOfCharacter("P")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: pieceLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingKnight() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - * - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - - K - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let pieceLocation = board.locationOfCharacter("K")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: pieceLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingBishop() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - * - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - - B - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let pieceLocation = board.locationOfCharacter("B")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: pieceLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingRook() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - * - - -" +
|
||||
"- - - - - - - -" +
|
||||
"G - - - R - - r" +
|
||||
"- - - - - - - -" )
|
||||
|
||||
let pieceLocation = board.locationOfCharacter("R")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: pieceLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
func testMoveInToCheckErrorIsThrownByMovingKing() {
|
||||
|
||||
let board = ASCIIBoard(pieces: "- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"- - - - - - - -" +
|
||||
"* - - - - - - r" +
|
||||
"G - - - - - - -" )
|
||||
|
||||
let pieceLocation = board.locationOfCharacter("G")
|
||||
let targetLocation = board.locationOfCharacter("*")
|
||||
|
||||
let game = gameForTestingCallbacks(board: board.board, color: .white)
|
||||
|
||||
guard let player = game.currentPlayer as? Human else {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// Assert that the correct error is thrown
|
||||
XCTAssertTrue(player.canMovePieceWithError(fromLocation: pieceLocation, toLocation: targetLocation).error == .cannotMoveInToCheck)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,28 +1,126 @@
|
||||
# SwiftChess
|
||||
|
||||
[](https://travis-ci.org/Steve Barnegren/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
[](http://cocoapods.org/pods/SwiftChess)
|
||||
|
||||
SwiftChess is a chess engine written in Swift.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Move validation
|
||||
- AI with three difficulty levels
|
||||
- Callbacks for check, checkmate and stalemate
|
||||
- Supports castling
|
||||
- Supports En Passent
|
||||
- Supports pawn promotion
|
||||
- Asyncronous AI move calculation
|
||||
|
||||
|
||||
SwiftChess doesn't provide any UI, just all of the logic required to create a chess game. The example project contains a complete UIKit UI with touch handling that you start from if you like.
|
||||
|
||||
## Example
|
||||
|
||||
To run the example project, clone the repo, and run `pod install` from the Example directory first.
|
||||
The example application contains a complete implementation of swift chess.
|
||||
|
||||
## Requirements
|
||||
Run `Example/Example.xcodeproj`
|
||||
|
||||
## Installation
|
||||
## Basic Use
|
||||
|
||||
SwiftChess is available through [CocoaPods](http://cocoapods.org). To install
|
||||
it, simply add the following line to your Podfile:
|
||||
##### Start a game
|
||||
|
||||
```ruby
|
||||
pod "SwiftChess"
|
||||
```
|
||||
// Make a human player
|
||||
let whitePlayer = Human(color: .white)
|
||||
|
||||
// ... or an AI Player
|
||||
let blackPlayer = AIPlayer(color: .black, configuration: AIConfiguration(difficulty: .hard))
|
||||
|
||||
// Create a game
|
||||
let game = Game(firstPlayer: whitePlayer, secondPlayer: blackPlayer)
|
||||
```
|
||||
|
||||
##### Make a move
|
||||
|
||||
```
|
||||
if let player = game.currentPlayer as? Human {
|
||||
|
||||
let currentLocation = BoardLocation(x: 4, y: 1)
|
||||
let newLocation = BoardLocation(x: 4, y: 2)
|
||||
|
||||
try! player.movePiece(fromLocation: currentLocation,
|
||||
toLocation: newLocation)
|
||||
}
|
||||
```
|
||||
|
||||
##### Tell the AI to make a move
|
||||
|
||||
```
|
||||
if let player = game.currentPlayer as? AIPlayer {
|
||||
player.makeMoveAsync()
|
||||
}
|
||||
```
|
||||
##### Then just wait for the callbacks!
|
||||
|
||||
```
|
||||
extension GameViewController: GameDelegate {
|
||||
|
||||
func gameDidMovePiece(game: Game, piece: Piece, toLocation: BoardLocation) {
|
||||
// Move piece on board
|
||||
}
|
||||
|
||||
func gameDidRemovePiece(game: Game, piece: Piece, location: BoardLocation) {
|
||||
// Remove piece from board
|
||||
}
|
||||
|
||||
func gameDidTransformPiece(game: Game, piece: Piece, location: BoardLocation) {
|
||||
// A pawn was promoted!
|
||||
}
|
||||
|
||||
func gameWonByPlayer(game: Game, player: Player) {
|
||||
ShowAlert("Checkmate!")
|
||||
}
|
||||
|
||||
func gameEndedInStaleMate(game: Game) {
|
||||
ShowAlert("Stalemate!")
|
||||
}
|
||||
|
||||
func gameDidChangeCurrentPlayer(game: Game) {
|
||||
// Make another move
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Other stuff
|
||||
|
||||
##### Make a castling move
|
||||
|
||||
```
|
||||
if game.board.canColorCastle(color: .white, side: .kingSide) {
|
||||
player.performCastleMove(side: .kingSide)
|
||||
}
|
||||
```
|
||||
|
||||
##### Support pawn promotion
|
||||
|
||||
```
|
||||
func promotedTypeForPawn(location: BoardLocation,
|
||||
player: Human,
|
||||
possiblePromotions: [Piece.PieceType],
|
||||
callback: @escaping (Piece.PieceType) -> Void) {
|
||||
|
||||
// Show UI for the user to select one of the possible promotion types
|
||||
// then call the handler
|
||||
|
||||
// ...or some games just promote to a queen
|
||||
callback(.queen)
|
||||
```
|
||||
|
||||
## Author
|
||||
|
||||
Steve Barnegren, steve.barnegren@gmail.com
|
||||
Follow me on twitter [@SteveBarnegren](https://twitter.com/stevebarnegren)
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+6
-14
@@ -9,7 +9,7 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'SwiftChess'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'A short description of SwiftChess.'
|
||||
s.summary = 'Chess engine written in Swift'
|
||||
|
||||
# This description is used to generate tags and improve search results.
|
||||
# * Think: What does it do? Why did you write it? What is the focus?
|
||||
@@ -18,25 +18,17 @@ Pod::Spec.new do |s|
|
||||
# * Finally, don't worry about the indent, CocoaPods strips it!
|
||||
|
||||
s.description = <<-DESC
|
||||
TODO: Add long description of the pod here.
|
||||
Chess engine written in Swift
|
||||
DESC
|
||||
|
||||
s.homepage = 'https://github.com/<GITHUB_USERNAME>/SwiftChess'
|
||||
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
|
||||
s.homepage = 'https://github.com/SteveBarnegren/SwiftChess'
|
||||
s.license = { :type => 'MIT', :file => 'LICENSE' }
|
||||
s.author = { 'Steve Barnegren' => 'steve.barnegren@gmail.com' }
|
||||
s.source = { :git => 'https://github.com/<GITHUB_USERNAME>/SwiftChess.git', :tag => s.version.to_s }
|
||||
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
|
||||
s.source = { :git => 'https://github.com/SteveBarnegren/SwiftChess.git', :tag => s.version.to_s }
|
||||
s.social_media_url = 'https://twitter.com/stevebarnegren'
|
||||
|
||||
s.ios.deployment_target = '8.0'
|
||||
|
||||
s.source_files = 'SwiftChess/Classes/**/*'
|
||||
|
||||
# s.resource_bundles = {
|
||||
# 'SwiftChess' => ['SwiftChess/Assets/*.png']
|
||||
# }
|
||||
s.source_files = 'SwiftChess/Source/**/*'
|
||||
|
||||
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||
# s.frameworks = 'UIKit', 'MapKit'
|
||||
# s.dependency 'AFNetworking', '~> 2.3'
|
||||
end
|
||||
|
||||
@@ -8,51 +8,62 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct AIConfiguration {
|
||||
|
||||
// BoardRater - Count Pieces
|
||||
var boardRaterCountPiecesWeighting: Double!
|
||||
public struct AIConfiguration {
|
||||
|
||||
// BoardRater - Board Dominance
|
||||
var boardRaterBoardDominanceWeighting: Double!
|
||||
|
||||
// BoardRater - Center Ownership
|
||||
var boardRaterCenterOwnershipWeighting: Double!
|
||||
|
||||
// BoardRater - Center Dominance
|
||||
var boardRaterCenterDominanceWeighting: Double!
|
||||
|
||||
// BoardRater - Threatened Pieces
|
||||
var boardRaterThreatenedPiecesWeighting: Double!
|
||||
var boardRaterThreatenedPiecesOwnPiecesMultiplier: Double!
|
||||
|
||||
// BoardRater - Pawn Progression
|
||||
var boardRaterPawnProgressionWeighting: Double!
|
||||
|
||||
// BoardRater - King Surrounding Possession
|
||||
var boardRaterKingSurroundingPossessionWeighting: Double!
|
||||
|
||||
// BoardRater - Check Mate Opportunity
|
||||
var boardRaterCheckMateOpportunityWeighting: Double!
|
||||
|
||||
// BoardRater - Center Four Occupation
|
||||
var boardRaterCenterFourOccupationWeighting: Double!
|
||||
|
||||
init() {
|
||||
setDefualtValues()
|
||||
public enum Difficulty: Int {
|
||||
case easy
|
||||
case medium
|
||||
case hard
|
||||
}
|
||||
|
||||
mutating func setDefualtValues() {
|
||||
boardRaterCountPiecesWeighting = 1
|
||||
boardRaterBoardDominanceWeighting = 1
|
||||
boardRaterCenterOwnershipWeighting = 1
|
||||
boardRaterCenterDominanceWeighting = 1
|
||||
boardRaterThreatenedPiecesWeighting = 1
|
||||
boardRaterThreatenedPiecesOwnPiecesMultiplier = 2
|
||||
boardRaterPawnProgressionWeighting = 1
|
||||
boardRaterKingSurroundingPossessionWeighting = 1
|
||||
boardRaterCheckMateOpportunityWeighting = 2
|
||||
boardRaterCenterFourOccupationWeighting = 1
|
||||
struct ConfigurationValue {
|
||||
let easyValue: Double
|
||||
let difficultValue: Double
|
||||
let multiplier: Double
|
||||
|
||||
var value: Double {
|
||||
return easyValue + ((difficultValue - easyValue) * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
public let difficulty: Difficulty!
|
||||
var suicideMultipler: ConfigurationValue!
|
||||
var boardRaterCountPiecesWeighting: ConfigurationValue!
|
||||
var boardRaterBoardDominanceWeighting: ConfigurationValue!
|
||||
var boardRaterCenterOwnershipWeighting: ConfigurationValue!
|
||||
var boardRaterCenterDominanceWeighting: ConfigurationValue!
|
||||
var boardRaterThreatenedPiecesWeighting: ConfigurationValue!
|
||||
var boardRaterPawnProgressionWeighting: ConfigurationValue!
|
||||
var boardRaterKingSurroundingPossessionWeighting: ConfigurationValue!
|
||||
var boardRaterCheckMateOpportunityWeighting: ConfigurationValue!
|
||||
var boardRaterCenterFourOccupationWeighting: ConfigurationValue!
|
||||
|
||||
public init(difficulty: Difficulty) {
|
||||
|
||||
self.difficulty = difficulty
|
||||
|
||||
let multiplier: Double
|
||||
|
||||
switch difficulty {
|
||||
case .easy: multiplier = 0
|
||||
case .medium: multiplier = 0.5
|
||||
case .hard: multiplier = 1
|
||||
}
|
||||
|
||||
func makeValue(_ easyValue: Double, _ hardValue: Double) -> ConfigurationValue {
|
||||
return ConfigurationValue(easyValue: easyValue, difficultValue: hardValue, multiplier: multiplier)
|
||||
}
|
||||
|
||||
suicideMultipler = makeValue(0, 0)
|
||||
boardRaterCountPiecesWeighting = makeValue(3, 3)
|
||||
boardRaterBoardDominanceWeighting = makeValue(0, 0.1)
|
||||
boardRaterCenterOwnershipWeighting = makeValue(0.1, 0.3)
|
||||
boardRaterCenterDominanceWeighting = makeValue(0, 0.3)
|
||||
boardRaterThreatenedPiecesWeighting = makeValue(0, 1.5)
|
||||
boardRaterPawnProgressionWeighting = makeValue(0.1, 1)
|
||||
boardRaterKingSurroundingPossessionWeighting = makeValue(0, 0.3)
|
||||
boardRaterCheckMateOpportunityWeighting = makeValue(0, 2)
|
||||
boardRaterCenterFourOccupationWeighting = makeValue(0.1, 0.3)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,13 @@ import Foundation
|
||||
|
||||
open class AIPlayer : Player {
|
||||
|
||||
let boardRaters : [BoardRater]!
|
||||
let configuration = AIConfiguration() // <-- We should pass this in eventually
|
||||
let boardRaters: [BoardRater]!
|
||||
public var configuration: AIConfiguration!
|
||||
var openingMoves = [OpeningMove]()
|
||||
|
||||
public init(color: Color){
|
||||
public init(color: Color, configuration: AIConfiguration){
|
||||
|
||||
self.configuration = configuration
|
||||
|
||||
self.boardRaters = [
|
||||
BoardRaterCountPieces(configuration: configuration),
|
||||
@@ -24,18 +27,97 @@ open class AIPlayer : Player {
|
||||
BoardRaterThreatenedPieces(configuration: configuration),
|
||||
BoardRaterPawnProgression(configuration: configuration),
|
||||
BoardRaterKingSurroundingPossession(configuration: configuration),
|
||||
BoardRaterCheckMateOpportunity(configuration: configuration)
|
||||
BoardRaterCheckMateOpportunity(configuration: configuration),
|
||||
BoardRaterCenterFourOccupation(configuration: configuration)
|
||||
]
|
||||
|
||||
openingMoves = Opening.allOpeningMoves(forColor: color)
|
||||
|
||||
super.init()
|
||||
self.color = color
|
||||
}
|
||||
|
||||
public func makeMove() {
|
||||
public func makeMoveAsync() {
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
self.makeMoveSync()
|
||||
}
|
||||
}
|
||||
|
||||
public func makeMoveSync() {
|
||||
|
||||
//print("\n\n****** Make Move ******");
|
||||
|
||||
// Check that the game is in progress
|
||||
guard game.state == .inProgress else {
|
||||
return
|
||||
}
|
||||
|
||||
let board = game.board
|
||||
|
||||
var move: Move!
|
||||
|
||||
// Build list of possible moves with ratings
|
||||
// Get an opening move
|
||||
if let openingMove = openingMoveForBoard(board){
|
||||
//print("Playing opening move")
|
||||
move = openingMove
|
||||
}
|
||||
// Or, get the Highest rated move
|
||||
else{
|
||||
move = highestRatedMoveOnBoard(board)
|
||||
}
|
||||
|
||||
// Make move
|
||||
var operations = [BoardOperation]()
|
||||
|
||||
switch move.type {
|
||||
case .singlePiece(let sourceLocation, let targetLocation):
|
||||
operations = game.board.movePiece(fromLocation: sourceLocation, toLocation: targetLocation)
|
||||
//print("Chose move (\(sourceLocation.x),\(sourceLocation.y)) -> (\(targetLocation.x),\(targetLocation.y))");
|
||||
case .castle(let color, let side):
|
||||
operations = game.board.performCastle(color: color, side: side)
|
||||
//print("Chose Castling move");
|
||||
}
|
||||
|
||||
// Promote pawns
|
||||
let pawnsToPromoteLocations = game.board.getLocationsOfPromotablePawns(color: color)
|
||||
assert(pawnsToPromoteLocations.count < 2, "There should only ever be one pawn to promote at any time")
|
||||
if pawnsToPromoteLocations.count > 0 {
|
||||
game.board = promotePawnsOnBoard(game.board)
|
||||
|
||||
let location = pawnsToPromoteLocations.first!
|
||||
let transformOperation = BoardOperation(type: .transformPiece, piece: game.board.getPiece(at: location)!, location: location)
|
||||
operations.append(transformOperation)
|
||||
}
|
||||
|
||||
|
||||
let strongGame = self.game!
|
||||
DispatchQueue.main.async {
|
||||
strongGame.playerDidMakeMove(player: self, boardOperations: operations)
|
||||
}
|
||||
}
|
||||
|
||||
func openingMoveForBoard(_ board: Board) -> Move? {
|
||||
|
||||
let possibleMoves = openingMoves.filter{
|
||||
$0.board == board
|
||||
}
|
||||
|
||||
//print("Num opening moves`; \(possibleMoves.count)")
|
||||
|
||||
guard possibleMoves.count > 0 else{
|
||||
return nil
|
||||
}
|
||||
|
||||
let index = Int(arc4random_uniform(UInt32(possibleMoves.count)))
|
||||
let openingMove = possibleMoves[index]
|
||||
|
||||
return Move(type: .singlePiece(sourceLocation: openingMove.fromLocation,
|
||||
targetLocation: openingMove.toLocation),
|
||||
rating: 0)
|
||||
}
|
||||
|
||||
func highestRatedMoveOnBoard(_ board: Board) -> Move {
|
||||
|
||||
var possibleMoves = [Move]()
|
||||
|
||||
@@ -51,7 +133,7 @@ open class AIPlayer : Player {
|
||||
|
||||
for targetLocation in BoardLocation.all {
|
||||
|
||||
guard canMovePiece(fromLocation: sourceLocation, toLocation: targetLocation) else {
|
||||
guard canAIMovePiece(fromLocation: sourceLocation, toLocation: targetLocation) else {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -67,10 +149,17 @@ open class AIPlayer : Player {
|
||||
}
|
||||
|
||||
// Rate
|
||||
let rating = ratingForBoard(resultBoard)
|
||||
var rating = ratingForBoard(resultBoard)
|
||||
|
||||
// reduce rating if suicide
|
||||
if resultBoard.canColorMoveAnyPieceToLocation(color: color.opposite(), location: targetLocation) {
|
||||
rating -= (abs(rating) * configuration.suicideMultipler.value);
|
||||
}
|
||||
|
||||
let move = Move(type: .singlePiece(sourceLocation: sourceLocation, targetLocation: targetLocation),
|
||||
rating: rating)
|
||||
possibleMoves.append(move)
|
||||
// print("Rating: \(rating)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +170,7 @@ open class AIPlayer : Player {
|
||||
guard game.board.canColorCastle(color: color, side: side) else {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// Perform the castling move
|
||||
var resultBoard = board
|
||||
resultBoard.performCastle(color: color, side: side)
|
||||
@@ -92,13 +181,13 @@ open class AIPlayer : Player {
|
||||
possibleMoves.append(move)
|
||||
}
|
||||
|
||||
print("Found \(possibleMoves.count) possible moves")
|
||||
//print("Found \(possibleMoves.count) possible moves")
|
||||
|
||||
// If there are no possible moves, we must be in stale mate
|
||||
if possibleMoves.count == 0 {
|
||||
print("There are no possible moves!!!!");
|
||||
}
|
||||
|
||||
|
||||
// Choose move with highest rating
|
||||
var highestRating = possibleMoves.first!.rating
|
||||
var highestRatedMove = possibleMoves.first!
|
||||
@@ -110,41 +199,36 @@ open class AIPlayer : Player {
|
||||
highestRatedMove = move;
|
||||
}
|
||||
|
||||
print("rating: \(move.rating)")
|
||||
//print("rating: \(move.rating)")
|
||||
}
|
||||
|
||||
print("HIGHEST MOVE RATING: \(highestRating)")
|
||||
|
||||
// Make move
|
||||
var operations = [BoardOperation]()
|
||||
|
||||
switch highestRatedMove.type {
|
||||
case .singlePiece(let sourceLocation, let targetLocation):
|
||||
operations = game.board.movePiece(fromLocation: sourceLocation, toLocation: targetLocation)
|
||||
case .castle(let color, let side):
|
||||
operations = game.board.performCastle(color: color, side: side)
|
||||
}
|
||||
|
||||
// Promote pawns
|
||||
let pawnsToPromoteLocations = game.board.getLocationsOfPromotablePawns(color: color)
|
||||
assert(pawnsToPromoteLocations.count < 2, "There should only ever be one pawn to promote at any time")
|
||||
if pawnsToPromoteLocations.count > 0 {
|
||||
game.board = promotePawnsOnBoard(game.board)
|
||||
|
||||
let location = pawnsToPromoteLocations.first!
|
||||
let transformOperation = BoardOperation(type: .transformPiece, piece: game.board.getPiece(at: location)!, location: location)
|
||||
operations.append(transformOperation)
|
||||
}
|
||||
|
||||
self.game.playerDidMakeMove(player: self, boardOperations: operations)
|
||||
return highestRatedMove;
|
||||
}
|
||||
|
||||
func canAIMovePiece(fromLocation: BoardLocation, toLocation: BoardLocation) -> Bool {
|
||||
|
||||
// This is a stricter version of the canMove function, used by the AI, that returns false for errors
|
||||
|
||||
let canMove = canMovePieceWithError(fromLocation: fromLocation, toLocation: toLocation)
|
||||
if canMove.error != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return canMove.result
|
||||
}
|
||||
|
||||
|
||||
func ratingForBoard(_ board: Board) -> Double {
|
||||
|
||||
var rating: Double = 0;
|
||||
|
||||
for boardRater in boardRaters {
|
||||
rating += boardRater.ratingfor(board: board, color: color)
|
||||
|
||||
let result = boardRater.ratingfor(board: board, color: color)
|
||||
|
||||
//let className = "\(boardRater)"
|
||||
//print("\t\(className): \(result)")
|
||||
rating += result
|
||||
}
|
||||
|
||||
// If opponent is in check mate, set the maximum rating
|
||||
@@ -182,12 +266,13 @@ open class AIPlayer : Player {
|
||||
var newBoard = board
|
||||
|
||||
guard let piece = newBoard.getPiece(at: location) else {
|
||||
|
||||
return board
|
||||
}
|
||||
|
||||
let newPiece = newBoard.getPiece(at: location)?.byChangingType(newType: pieceType)
|
||||
newBoard.squares[location.index].piece = newPiece
|
||||
|
||||
newBoard.setPiece(newPiece!, at: location)
|
||||
|
||||
let rating = ratingForBoard(newBoard)
|
||||
|
||||
if rating > bestRating {
|
||||
@@ -200,8 +285,6 @@ open class AIPlayer : Player {
|
||||
|
||||
return promotedBoard
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
struct Move {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
import SwiftChess
|
||||
|
||||
func transformASCIIBoardInput(_ input: String) -> String{
|
||||
@@ -33,7 +32,7 @@ public struct ASCIIBoard {
|
||||
let artString: String
|
||||
var stringContainsColors: Bool!
|
||||
|
||||
init(pieces artString: String) {
|
||||
public init(pieces artString: String) {
|
||||
|
||||
var artString = artString
|
||||
|
||||
@@ -41,20 +40,20 @@ public struct ASCIIBoard {
|
||||
artString = transformASCIIBoardInput(artString)
|
||||
|
||||
// Check string format
|
||||
XCTAssertEqual(artString.characters.count, 64, "ASCII board art must be 128 characters long")
|
||||
assert(artString.characters.count == 64, "ASCII board art must be 128 characters long")
|
||||
|
||||
self.artString = artString
|
||||
self.stringContainsColors = false
|
||||
|
||||
}
|
||||
|
||||
init(colors artString: String) {
|
||||
public init(colors artString: String) {
|
||||
|
||||
self.init(pieces: artString)
|
||||
self.stringContainsColors = true
|
||||
}
|
||||
|
||||
var board: Board {
|
||||
public var board: Board {
|
||||
|
||||
var boardArt = artString
|
||||
|
||||
@@ -67,15 +66,17 @@ public struct ASCIIBoard {
|
||||
var board = Board(state: .empty)
|
||||
|
||||
// Clear all pieces on the board
|
||||
for i in 0..<64 {
|
||||
board.squares[i].piece = nil;
|
||||
BoardLocation.all.forEach{
|
||||
board.removePiece(atLocation: $0)
|
||||
}
|
||||
|
||||
// Setup pieces from ascii art
|
||||
for i in 0..<64 {
|
||||
(0..<64).forEach{
|
||||
let character = boardArt[boardArt.characters.index(boardArt.startIndex, offsetBy: $0)]
|
||||
|
||||
let character = boardArt[boardArt.characters.index(boardArt.startIndex, offsetBy: i)]
|
||||
board.squares[i].piece = pieceFromCharacter(character)
|
||||
if let piece = pieceFromCharacter(character) {
|
||||
board.setPiece(piece, at: BoardLocation(index: $0))
|
||||
}
|
||||
}
|
||||
|
||||
return board
|
||||
@@ -126,7 +127,7 @@ public struct ASCIIBoard {
|
||||
index = artString.characters.distance(from: artString.startIndex, to: idx)
|
||||
}
|
||||
|
||||
XCTAssert(index != nil, "Unable to find index of character: \(character)")
|
||||
assert(index != nil, "Unable to find index of character: \(character)")
|
||||
return index!
|
||||
}
|
||||
|
||||
@@ -140,11 +141,10 @@ public struct ASCIIBoard {
|
||||
|
||||
var indexes = [Int]()
|
||||
|
||||
for i in 0..<64 {
|
||||
|
||||
let aCharacter = artString[artString.characters.index(artString.startIndex, offsetBy: i)]
|
||||
(0..<64).forEach{
|
||||
let aCharacter = artString[artString.characters.index(artString.startIndex, offsetBy: $0)]
|
||||
if character == aCharacter {
|
||||
indexes.append(i)
|
||||
indexes.append($0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,9 +157,8 @@ public struct ASCIIBoard {
|
||||
|
||||
var locations = [BoardLocation]()
|
||||
|
||||
for index in indexes {
|
||||
|
||||
let location = BoardLocation(index: index)
|
||||
indexes.forEach{
|
||||
let location = BoardLocation(index: $0)
|
||||
locations.append(location)
|
||||
}
|
||||
|
||||
+145
-150
@@ -13,135 +13,44 @@ public enum CastleSide {
|
||||
case queenSide
|
||||
}
|
||||
|
||||
// MARK: - ****** BoardStride ******
|
||||
|
||||
public struct BoardStride {
|
||||
|
||||
public var x: Int;
|
||||
public var y: Int;
|
||||
|
||||
public init(x: Int, y: Int){
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - ****** BoardLocation ******
|
||||
|
||||
public struct BoardLocation : Equatable {
|
||||
|
||||
public var index: Int
|
||||
|
||||
public static var all: [BoardLocation] {
|
||||
|
||||
// TODO: using a computed property could be expensive, can we store this so it doesn't need to be computed each time?
|
||||
var locations = [BoardLocation]()
|
||||
|
||||
for i in 0..<64 {
|
||||
locations.append(BoardLocation(index: i))
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
public var x: Int {
|
||||
return index % 8
|
||||
}
|
||||
|
||||
public var y: Int {
|
||||
return index / 8
|
||||
}
|
||||
|
||||
public init(index: Int) {
|
||||
self.index = index
|
||||
}
|
||||
|
||||
public init(x: Int, y: Int) {
|
||||
self.index = x + (y*8)
|
||||
}
|
||||
|
||||
func isInBounds() -> Bool {
|
||||
return (index < 64 && index >= 0)
|
||||
}
|
||||
|
||||
func incremented(by offset: Int) -> BoardLocation {
|
||||
return BoardLocation(index: index + offset)
|
||||
}
|
||||
|
||||
func incrementedBy(x: Int, y: Int) -> BoardLocation {
|
||||
return self + BoardLocation(x: x, y: y)
|
||||
}
|
||||
|
||||
func incrementedBy(stride: BoardStride) -> BoardLocation {
|
||||
|
||||
// TODO: for performance, we should probably only check this if we're in debug mode
|
||||
if !canIncrementBy(stride: stride) {
|
||||
print("WARNING! BoardLocation is being incremented by a stride that will result in wrapping! call canIncrementBy(stride: BoardStride) first")
|
||||
}
|
||||
|
||||
return BoardLocation(x: x + stride.x,
|
||||
y: y + stride.y)
|
||||
}
|
||||
|
||||
func canIncrementBy(stride: BoardStride) -> Bool {
|
||||
|
||||
// Check will not wrap to right
|
||||
if x + stride.x > 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap to left
|
||||
if x + stride.x < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap top
|
||||
if y + stride.y > 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap bottom
|
||||
if y + stride.y < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: BoardLocation, rhs: BoardLocation) -> Bool {
|
||||
return lhs.index == rhs.index
|
||||
}
|
||||
|
||||
public func +(left: BoardLocation, right: BoardLocation) -> BoardLocation {
|
||||
return BoardLocation(index: left.index + right.index)
|
||||
}
|
||||
|
||||
// MARK: - ****** Square ******
|
||||
|
||||
public struct Square {
|
||||
public struct Square : Equatable {
|
||||
|
||||
public var piece: Piece?
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: Square, rhs: Square) -> Bool {
|
||||
|
||||
switch (lhs.piece, rhs.piece) {
|
||||
case (.none, .none):
|
||||
return true
|
||||
case (.some, .none):
|
||||
return false
|
||||
case (.none, .some):
|
||||
return false
|
||||
case (.some(let rp), .some(let lp)):
|
||||
return rp == lp
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - ****** Board ******
|
||||
|
||||
public struct Board {
|
||||
public struct Board : Equatable {
|
||||
|
||||
public enum InitialState {
|
||||
case empty
|
||||
case newGame
|
||||
}
|
||||
|
||||
public var squares = [Square]()
|
||||
public private(set) var squares = [Square]()
|
||||
|
||||
// MARK: - Init
|
||||
public init(state: InitialState) {
|
||||
|
||||
// Setup squares
|
||||
for i in 0..<64 {
|
||||
for _ in 0..<64 {
|
||||
squares.append(Square())
|
||||
}
|
||||
|
||||
@@ -154,48 +63,56 @@ public struct Board {
|
||||
|
||||
mutating func setupForNewGame() {
|
||||
|
||||
// Setup white bottom row
|
||||
squares[0].piece = Piece(type: .rook, color: .white)
|
||||
squares[1].piece = Piece(type: .knight, color: .white)
|
||||
squares[2].piece = Piece(type: .bishop, color: .white)
|
||||
squares[3].piece = Piece(type: .queen, color: .white)
|
||||
squares[4].piece = Piece(type: .king, color: .white)
|
||||
squares[5].piece = Piece(type: .bishop, color: .white)
|
||||
squares[6].piece = Piece(type: .knight, color: .white)
|
||||
squares[7].piece = Piece(type: .rook, color: .white)
|
||||
func setPieceAtIndex(_ piece: Piece, _ index: Int){
|
||||
setPiece(piece, at: BoardLocation(index: index))
|
||||
}
|
||||
|
||||
// Setup white bottom row
|
||||
setPieceAtIndex(Piece(type: .rook, color: .white), 0)
|
||||
setPieceAtIndex(Piece(type: .knight, color: .white), 1)
|
||||
setPieceAtIndex(Piece(type: .bishop, color: .white), 2)
|
||||
setPieceAtIndex(Piece(type: .queen, color: .white), 3)
|
||||
setPieceAtIndex(Piece(type: .king, color: .white), 4)
|
||||
setPieceAtIndex(Piece(type: .bishop, color: .white), 5)
|
||||
setPieceAtIndex(Piece(type: .knight, color: .white), 6)
|
||||
setPieceAtIndex(Piece(type: .rook, color: .white), 7)
|
||||
|
||||
// Setup white pawn row
|
||||
for i in 8...15 {
|
||||
squares[i].piece = Piece(type: .pawn, color: .white)
|
||||
setPieceAtIndex(Piece(type: .pawn, color: .white), i)
|
||||
}
|
||||
|
||||
// Setup black bottom row
|
||||
squares[63].piece = Piece(type: .rook, color: .black)
|
||||
squares[62].piece = Piece(type: .knight, color: .black)
|
||||
squares[61].piece = Piece(type: .bishop, color: .black)
|
||||
squares[60].piece = Piece(type: .queen, color: .black)
|
||||
squares[59].piece = Piece(type: .king, color: .black)
|
||||
squares[58].piece = Piece(type: .bishop, color: .black)
|
||||
squares[57].piece = Piece(type: .knight, color: .black)
|
||||
squares[56].piece = Piece(type: .rook, color: .black)
|
||||
setPieceAtIndex(Piece(type: .rook, color: .black), 63)
|
||||
setPieceAtIndex(Piece(type: .knight, color: .black), 62)
|
||||
setPieceAtIndex(Piece(type: .bishop, color: .black), 61)
|
||||
setPieceAtIndex(Piece(type: .king, color: .black), 60)
|
||||
setPieceAtIndex(Piece(type: .queen, color: .black), 59)
|
||||
setPieceAtIndex(Piece(type: .bishop, color: .black), 58)
|
||||
setPieceAtIndex(Piece(type: .knight, color: .black), 57)
|
||||
setPieceAtIndex(Piece(type: .rook, color: .black), 56)
|
||||
|
||||
// Setup black pawn row
|
||||
for i in 48...55 {
|
||||
squares[i].piece = Piece(type: .pawn, color: .black)
|
||||
setPieceAtIndex(Piece(type: .pawn, color: .black), i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Manipulate Pieces
|
||||
|
||||
public mutating func setPiece(_ piece: Piece, at location: BoardLocation) {
|
||||
squares[location.index].piece = piece
|
||||
squares[location.index].piece?.location = location
|
||||
}
|
||||
|
||||
public func getPiece(at location: BoardLocation) -> Piece? {
|
||||
return squares[location.index].piece
|
||||
}
|
||||
|
||||
public mutating func removePiece(atLocation location: BoardLocation) {
|
||||
squares[location.index].piece = nil
|
||||
}
|
||||
|
||||
@discardableResult internal mutating func movePiece(fromLocation: BoardLocation, toLocation: BoardLocation) -> [BoardOperation] {
|
||||
|
||||
if toLocation == fromLocation {
|
||||
@@ -204,23 +121,63 @@ public struct Board {
|
||||
|
||||
var operations = [BoardOperation]()
|
||||
|
||||
if let piece = getPiece(at: fromLocation) {
|
||||
let operation = BoardOperation(type: .movePiece, piece: piece, location: toLocation)
|
||||
operations.append(operation)
|
||||
guard let movingPiece = getPiece(at: fromLocation) else {
|
||||
fatalError("There is no piece on at (\(fromLocation.x),\(fromLocation.y))")
|
||||
}
|
||||
|
||||
let operation = BoardOperation(type: .movePiece, piece: movingPiece, location: toLocation)
|
||||
operations.append(operation)
|
||||
|
||||
if let piece = getPiece(at: toLocation) {
|
||||
let operation = BoardOperation(type: .removePiece, piece: piece, location: toLocation)
|
||||
if let targetPiece = getPiece(at: toLocation) {
|
||||
let operation = BoardOperation(type: .removePiece, piece: targetPiece, location: toLocation)
|
||||
operations.append(operation)
|
||||
}
|
||||
|
||||
squares[toLocation.index].piece = self.squares[fromLocation.index].piece
|
||||
squares[toLocation.index].piece?.location = toLocation;
|
||||
squares[toLocation.index].piece?.hasMoved = true
|
||||
squares[fromLocation.index].piece = nil
|
||||
|
||||
|
||||
// If the moving piece is a pawn, check whether it just made an en passent move, and remove the passed piece
|
||||
IF_EN_PASSANT: if movingPiece.type == .pawn {
|
||||
|
||||
let stride = fromLocation.strideTo(location: toLocation)
|
||||
let enPassentStride = BoardStride(x: stride.x, y: 0)
|
||||
let enPassentLocation = fromLocation.incrementedBy(stride: enPassentStride)
|
||||
|
||||
guard let enPassentPiece = getPiece(at: enPassentLocation) else {
|
||||
break IF_EN_PASSANT
|
||||
}
|
||||
|
||||
if enPassentPiece.canBeTakenByEnPassant && enPassentPiece.color == movingPiece.color.opposite() {
|
||||
squares[enPassentLocation.index].piece = nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset en passant flags
|
||||
resetEnPassantFlags()
|
||||
|
||||
// If pawn has moved two squares, then need to update the en passant flag
|
||||
if movingPiece.type == .pawn {
|
||||
|
||||
let startingRow = (movingPiece.color == .white ? 1 : 6)
|
||||
let twoAheadRow = (movingPiece.color == .white ? 3 : 4)
|
||||
|
||||
if fromLocation.y == startingRow && toLocation.y == twoAheadRow {
|
||||
squares[toLocation.index].piece?.canBeTakenByEnPassant = true
|
||||
}
|
||||
}
|
||||
|
||||
return operations
|
||||
}
|
||||
|
||||
mutating func resetEnPassantFlags() {
|
||||
|
||||
for i in 0..<squares.count {
|
||||
squares[i].piece?.canBeTakenByEnPassant = false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Get Specific pieces
|
||||
|
||||
func getKing(color: Color) -> Piece {
|
||||
@@ -322,20 +279,39 @@ public struct Board {
|
||||
|
||||
public func isColorInCheck(color: Color) -> Bool {
|
||||
|
||||
let kingLocation = getKingLocation(color: color)
|
||||
// Get the King location
|
||||
var kingLocation: BoardLocation?
|
||||
for location in BoardLocation.all {
|
||||
|
||||
guard let piece = getPiece(at: location) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if piece.color == color && piece.type == .king {
|
||||
kingLocation = location
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no king, then return false (some tests will be run without a king)
|
||||
if kingLocation == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Work out if we're in check
|
||||
let oppositionLocations = getLocationsOfColor( color.opposite() )
|
||||
|
||||
// Pieces will not move to take the king, so remove it
|
||||
// Pieces will not move to take the king, so change it for a pawn of the same color
|
||||
var noKingBoard = self
|
||||
noKingBoard.squares[kingLocation.index].piece = nil
|
||||
noKingBoard.squares[kingLocation!.index].piece = Piece(type: .pawn, color: color)
|
||||
|
||||
for location in oppositionLocations {
|
||||
|
||||
guard let piece = getPiece(at: location) else {
|
||||
continue
|
||||
}
|
||||
|
||||
if piece.movement.canPieceMove(fromLocation: location, toLocation: kingLocation, board: noKingBoard) {
|
||||
|
||||
if piece.movement.canPieceMove(fromLocation: location, toLocation: kingLocation!, board: noKingBoard) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -444,6 +420,23 @@ public struct Board {
|
||||
return (piece.color == color ? true : false)
|
||||
}
|
||||
|
||||
public func possibleMoveLocationsForPiece(atLocation location: BoardLocation) -> [BoardLocation] {
|
||||
|
||||
guard let piece = squares[location.index].piece else{
|
||||
return []
|
||||
}
|
||||
|
||||
var locations = [BoardLocation]()
|
||||
|
||||
BoardLocation.all.forEach{
|
||||
if piece.movement.canPieceMove(fromLocation: location, toLocation: $0, board: self){
|
||||
locations.append($0)
|
||||
}
|
||||
}
|
||||
|
||||
return locations
|
||||
}
|
||||
|
||||
// MARK: - Castling
|
||||
|
||||
struct CastleMove{
|
||||
@@ -486,16 +479,16 @@ public struct Board {
|
||||
rookEndXPos = 3
|
||||
case (.black, .kingSide):
|
||||
yPos = 7
|
||||
kingStartXPos = 3
|
||||
rookStartXPos = 0
|
||||
kingEndXPos = 1
|
||||
rookEndXPos = 2
|
||||
kingStartXPos = 4
|
||||
rookStartXPos = 7
|
||||
kingEndXPos = 6
|
||||
rookEndXPos = 5
|
||||
case (.black, .queenSide):
|
||||
yPos = 7
|
||||
kingStartXPos = 3
|
||||
rookStartXPos = 7
|
||||
kingEndXPos = 5
|
||||
rookEndXPos = 4
|
||||
kingStartXPos = 4
|
||||
rookStartXPos = 0
|
||||
kingEndXPos = 2
|
||||
rookEndXPos = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -663,6 +656,8 @@ public struct Board {
|
||||
|
||||
print(printString)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public func ==(lhs: Board, rhs: Board) -> Bool {
|
||||
return lhs.squares == rhs.squares
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// BoardLocation.swift
|
||||
// SwiftChess
|
||||
//
|
||||
// Created by Steve Barnegren on 31/01/2017.
|
||||
// Copyright © 2017 Steve Barnegren. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct BoardLocation : Equatable {
|
||||
|
||||
public enum GridPosition : Int{
|
||||
case a1; case b1; case c1; case d1; case e1; case f1; case g1; case h1;
|
||||
case a2; case b2; case c2; case d2; case e2; case f2; case g2; case h2;
|
||||
case a3; case b3; case c3; case d3; case e3; case f3; case g3; case h3;
|
||||
case a4; case b4; case c4; case d4; case e4; case f4; case g4; case h4;
|
||||
case a5; case b5; case c5; case d5; case e5; case f5; case g5; case h5;
|
||||
case a6; case b6; case c6; case d6; case e6; case f6; case g6; case h6;
|
||||
case a7; case b7; case c7; case d7; case e7; case f7; case g7; case h7;
|
||||
case a8; case b8; case c8; case d8; case e8; case f8; case g8; case h8;
|
||||
}
|
||||
|
||||
public var index: Int
|
||||
|
||||
private static var allLocationsBacking: [BoardLocation]?
|
||||
public static var all: [BoardLocation] {
|
||||
|
||||
if let all = allLocationsBacking {
|
||||
return all
|
||||
}
|
||||
else{
|
||||
var locations = [BoardLocation]()
|
||||
|
||||
(0..<64).forEach{
|
||||
locations.append(BoardLocation(index: $0))
|
||||
}
|
||||
|
||||
allLocationsBacking = locations
|
||||
return allLocationsBacking!
|
||||
}
|
||||
}
|
||||
|
||||
public var isDarkSquare: Bool {
|
||||
return (index + y) % 2 == 0
|
||||
}
|
||||
|
||||
public var x: Int {
|
||||
return index % 8
|
||||
}
|
||||
|
||||
public var y: Int {
|
||||
return index / 8
|
||||
}
|
||||
|
||||
public var gridPosition: GridPosition {
|
||||
return GridPosition(rawValue: index)!
|
||||
}
|
||||
|
||||
public init(index: Int) {
|
||||
self.index = index
|
||||
}
|
||||
|
||||
public init(x: Int, y: Int) {
|
||||
self.index = x + (y*8)
|
||||
}
|
||||
|
||||
public init(gridPosition: GridPosition){
|
||||
self.index = gridPosition.rawValue
|
||||
}
|
||||
|
||||
func isInBounds() -> Bool {
|
||||
return (index < 64 && index >= 0)
|
||||
}
|
||||
|
||||
func incremented(by offset: Int) -> BoardLocation {
|
||||
return BoardLocation(index: index + offset)
|
||||
}
|
||||
|
||||
func incrementedBy(x: Int, y: Int) -> BoardLocation {
|
||||
return self + BoardLocation(x: x, y: y)
|
||||
}
|
||||
|
||||
func incrementedBy(stride: BoardStride) -> BoardLocation {
|
||||
|
||||
// TODO: for performance, we should probably only check this if we're in debug mode
|
||||
if !canIncrementBy(stride: stride) {
|
||||
print("WARNING! BoardLocation is being incremented by a stride that will result in wrapping! call canIncrementBy(stride: BoardStride) first")
|
||||
}
|
||||
|
||||
return BoardLocation(x: x + stride.x,
|
||||
y: y + stride.y)
|
||||
}
|
||||
|
||||
func canIncrementBy(stride: BoardStride) -> Bool {
|
||||
|
||||
// Check will not wrap to right
|
||||
if x + stride.x > 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap to left
|
||||
if x + stride.x < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap top
|
||||
if y + stride.y > 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check will not wrap bottom
|
||||
if y + stride.y < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func strideTo(location: BoardLocation) -> BoardStride {
|
||||
|
||||
return BoardStride(x: location.x - x,
|
||||
y: location.y - y)
|
||||
}
|
||||
|
||||
func strideFrom(location: BoardLocation) -> BoardStride {
|
||||
|
||||
return BoardStride(x: x - location.x,
|
||||
y: y - location.y)
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: BoardLocation, rhs: BoardLocation) -> Bool {
|
||||
return lhs.index == rhs.index
|
||||
}
|
||||
|
||||
public func +(left: BoardLocation, right: BoardLocation) -> BoardLocation {
|
||||
return BoardLocation(index: left.index + right.index)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class BoardRaterBoardDominance : BoardRater {
|
||||
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterBoardDominanceWeighting;
|
||||
return rating * configuration.boardRaterBoardDominanceWeighting.value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class BoardRaterCenterDominance : BoardRater {
|
||||
}
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterCenterDominanceWeighting
|
||||
return rating * configuration.boardRaterCenterDominanceWeighting.value
|
||||
}
|
||||
|
||||
func dominanceValueFor(location: BoardLocation) -> Double {
|
||||
|
||||
@@ -32,6 +32,6 @@ class BoardRaterCenterFourOccupation: BoardRater {
|
||||
rating += piece.color == color ? value : -value
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterCenterFourOccupationWeighting
|
||||
return rating * configuration.boardRaterCenterFourOccupationWeighting.value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class BoardRaterCenterOwnership : BoardRater {
|
||||
rating += (piece.color == color) ? distance : -distance
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterCenterOwnershipWeighting
|
||||
return rating * configuration.boardRaterCenterOwnershipWeighting.value
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class BoardRaterCheckMateOpportunity : BoardRater {
|
||||
}
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterCheckMateOpportunityWeighting
|
||||
return rating * configuration.boardRaterCheckMateOpportunityWeighting.value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class BoardRaterCountPieces : BoardRater {
|
||||
rating += piece.color == color ? piece.value() : -piece.value()
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterCountPiecesWeighting
|
||||
return rating * configuration.boardRaterCountPiecesWeighting.value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,8 +20,11 @@ class BoardRaterKingSurroundingPossession : BoardRater {
|
||||
|
||||
// The kings will be able to move to their surrounding locations, so remove them from the board
|
||||
var noKingsBoard = board
|
||||
noKingsBoard.squares[noKingsBoard.getKingLocation(color: .white).index].piece = nil
|
||||
noKingsBoard.squares[noKingsBoard.getKingLocation(color: .black).index].piece = nil
|
||||
noKingsBoard.removePiece(atLocation: noKingsBoard.getKingLocation(color: .white))
|
||||
noKingsBoard.removePiece(atLocation: noKingsBoard.getKingLocation(color: .black))
|
||||
|
||||
// we don't want to encourage the king to move out in to the open
|
||||
rating += Double(8 - ownKingLocations.count) * squareValue * 3
|
||||
|
||||
for location in ownKingLocations {
|
||||
|
||||
@@ -66,7 +69,7 @@ class BoardRaterKingSurroundingPossession : BoardRater {
|
||||
}
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterKingSurroundingPossessionWeighting
|
||||
return rating * configuration.boardRaterKingSurroundingPossessionWeighting.value
|
||||
}
|
||||
|
||||
func locationsSurroundingKing(color: Color, board: Board) -> [BoardLocation] {
|
||||
|
||||
@@ -52,7 +52,7 @@ class BoardRaterPawnProgression : BoardRater {
|
||||
squaresAdvanced = 7 - (location.y + 2)
|
||||
}
|
||||
|
||||
return Double(squaresAdvanced) * configuration.boardRaterPawnProgressionWeighting // <- should probably add some sort of curve
|
||||
return Double(squaresAdvanced) * configuration.boardRaterPawnProgressionWeighting.value // <- should probably add some sort of curve
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,126 +12,243 @@ import Foundation
|
||||
|
||||
class BoardRaterThreatenedPieces : BoardRater {
|
||||
|
||||
/*
|
||||
override func ratingfor(board: Board, color: Color) -> Double {
|
||||
|
||||
var rating = Double(0)
|
||||
|
||||
for location in BoardLocation.all {
|
||||
|
||||
let allPieces = board.squares.flatMap{
|
||||
$0.piece
|
||||
}
|
||||
|
||||
for piece in allPieces {
|
||||
|
||||
guard let piece = board.getPiece(at: location) else {
|
||||
continue;
|
||||
var value = Double(0)
|
||||
|
||||
let threatenedByPieces = getPieces(threatening: piece, onBoard: board)
|
||||
let protectedByPieces = getPieces(protecting: piece, onBoard: board)
|
||||
let isThreatened = threatenedByPieces.count > 0
|
||||
let isProtected = protectedByPieces.count > 0
|
||||
|
||||
switch (isThreatened, isProtected) {
|
||||
case (false, true):
|
||||
value = piece.value()
|
||||
case (true, false):
|
||||
value = -piece.value()
|
||||
case (true, true):
|
||||
let lowestValueThreat = threatenedByPieces.dropFirst().reduce(threatenedByPieces[0].value(), { (value: Double, piece) -> Double in
|
||||
return min(value, piece.value())
|
||||
})
|
||||
|
||||
if lowestValueThreat < piece.value() {
|
||||
value = -piece.value()
|
||||
}
|
||||
case (false, false):
|
||||
break
|
||||
}
|
||||
|
||||
let threatRating = threatRatingForPiece(at: location, board: board, color: color)
|
||||
|
||||
// For a same color, subtract the threat rating (less preferrable move)
|
||||
if piece.color == color {
|
||||
rating -= threatRating * configuration.boardRaterThreatenedPiecesOwnPiecesMultiplier
|
||||
}
|
||||
// For opposite color, add the treat rating (more preferable move)
|
||||
else{
|
||||
rating += threatRating
|
||||
if piece.color == color.opposite() {
|
||||
value = -value
|
||||
}
|
||||
|
||||
rating += (piece.color == color) ? -threatRating : threatRating;
|
||||
rating += value
|
||||
}
|
||||
|
||||
return rating * configuration.boardRaterThreatenedPiecesWeighting
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// Returns a more positive rating the more the piece is threatened
|
||||
func threatRatingForPiece(at location: BoardLocation, board: Board, color: Color) -> Double {
|
||||
override func ratingfor(board: Board, color: Color) -> Double {
|
||||
|
||||
guard let piece = board.getPiece(at: location) else{
|
||||
fatalError()
|
||||
}
|
||||
|
||||
var rating = Double(0)
|
||||
|
||||
let threateningPieceLocations = threateningPiecesLocationsforPiece(at: location, on: board)
|
||||
let isBeingThreatened = threateningPieceLocations.isEmpty ? false : true
|
||||
|
||||
let protectingPieceLocations = protectingPiecesLocationsforPiece(at: location, on: board)
|
||||
let isBeingProtected = protectingPieceLocations.isEmpty ? false : true
|
||||
|
||||
for threateningPieceLocation in threateningPieceLocations {
|
||||
|
||||
guard let threateningPiece = board.getPiece(at: threateningPieceLocation) else {
|
||||
continue
|
||||
}
|
||||
|
||||
let pieceIsProtected = (isBeingProtected && piece.value() < threateningPiece.value())
|
||||
|
||||
if !pieceIsProtected {
|
||||
let rating = board.getPieces(color: color)
|
||||
.map{ threatValue(forPiece: $0, onBoard: board) }
|
||||
.reduce(0,+)
|
||||
* configuration.boardRaterThreatenedPiecesWeighting.value
|
||||
|
||||
rating += piece.value()
|
||||
}
|
||||
}
|
||||
|
||||
return rating
|
||||
}
|
||||
|
||||
// MARK - Get protecting / Threatening pieces
|
||||
|
||||
func protectingPiecesLocationsforPiece(at location: BoardLocation, on board: Board) -> [BoardLocation] {
|
||||
func threatValue(forPiece piece: Piece, onBoard board: Board) -> Double {
|
||||
|
||||
var newBoard = board
|
||||
let threatenedByPieces = getPieces(threatening: piece, onBoard: board)
|
||||
let protectedByPieces = getPieces(protecting: piece, onBoard: board)
|
||||
let isThreatened = threatenedByPieces.count > 0
|
||||
let isProtected = protectedByPieces.count > 0
|
||||
|
||||
guard let protectedPiece = board.getPiece(at: location) else {
|
||||
fatalError("Expected board location to contain piece")
|
||||
// Threatened but not protected
|
||||
if isThreatened && !isProtected {
|
||||
return -piece.value() * 3
|
||||
}
|
||||
|
||||
// Change the piece color to be opposite, to simulate if the piece was taken
|
||||
let oppositeColorPiece = Piece(type: protectedPiece.type, color: protectedPiece.color.opposite())
|
||||
newBoard.setPiece(oppositeColorPiece, at: location)
|
||||
|
||||
var pieces = [BoardLocation]()
|
||||
|
||||
for sourceLocation in BoardLocation.all {
|
||||
// Threatened, but protected (only return if the trade is not preferable)
|
||||
if isThreatened && isProtected {
|
||||
|
||||
guard let protectingPiece = newBoard.getPiece(at: sourceLocation) else{
|
||||
continue
|
||||
let lowestValueThreat = threatenedByPieces.lowestPieceValue()
|
||||
|
||||
if lowestValueThreat < piece.value() {
|
||||
return -piece.value()
|
||||
}
|
||||
|
||||
guard protectingPiece.color == protectedPiece.color else{
|
||||
continue
|
||||
}
|
||||
// Here we could bump the value to encourage a good trade?
|
||||
}
|
||||
|
||||
let targetPieces = getPieces(threatenedBy: piece, onBoard: board)
|
||||
for targetPiece in targetPieces {
|
||||
|
||||
if protectingPiece.movement.canPieceMove(fromLocation: sourceLocation, toLocation: location, board: newBoard) {
|
||||
pieces.append(sourceLocation)
|
||||
let isTargetProtected = isPieceProtected(targetPiece, onBoard: board)
|
||||
|
||||
// If it's protected, is it a good trade
|
||||
if isTargetProtected && targetPiece.value() < piece.value() {
|
||||
return 0
|
||||
}
|
||||
else{
|
||||
return targetPiece.value()
|
||||
}
|
||||
}
|
||||
|
||||
return pieces
|
||||
// Nothing much interesting
|
||||
return 0
|
||||
}
|
||||
|
||||
func threateningPiecesLocationsforPiece(at location: BoardLocation, on board: Board) -> [BoardLocation] {
|
||||
// MARK: - Helpers
|
||||
|
||||
func getPieces(protecting piece: Piece, onBoard board: Board) -> [Piece] {
|
||||
|
||||
guard let threatenedPiece = board.getPiece(at: location) else {
|
||||
fatalError("Expected board location to contain piece")
|
||||
var alteredBoard = board
|
||||
alteredBoard.setPiece(piece.withOppositeColor(), at: piece.location)
|
||||
|
||||
return alteredBoard.getPieces(color: piece.color).filter{
|
||||
$0.movement.canPieceMove(fromLocation: $0.location, toLocation: piece.location, board: alteredBoard, accountForCheckState: true)
|
||||
}
|
||||
|
||||
let threatenedColor = threatenedPiece.color
|
||||
|
||||
var pieces = [BoardLocation]()
|
||||
|
||||
for sourceLocation in BoardLocation.all {
|
||||
|
||||
guard let threateningPiece = board.getPiece(at: sourceLocation) else{
|
||||
continue
|
||||
}
|
||||
|
||||
guard threateningPiece.color == threatenedColor.opposite() else{
|
||||
continue
|
||||
}
|
||||
|
||||
if threateningPiece.movement.canPieceMove(fromLocation: sourceLocation, toLocation: location, board: board) {
|
||||
pieces.append(sourceLocation)
|
||||
}
|
||||
}
|
||||
|
||||
return pieces
|
||||
}
|
||||
|
||||
func getPieces(protectedBy piece: Piece, onBoard board: Board) -> [Piece] {
|
||||
|
||||
return board.getPieces(color: piece.color).filter{
|
||||
piece.movement.canPieceMove(fromLocation: piece.location, toLocation: $0.location, board: board, accountForCheckState: true)
|
||||
}
|
||||
}
|
||||
|
||||
func isPieceProtected(_ piece: Piece, onBoard board: Board) -> Bool {
|
||||
|
||||
var alteredBoard = board
|
||||
alteredBoard.setPiece(piece.withOppositeColor(), at: piece.location)
|
||||
|
||||
for square in alteredBoard.squares {
|
||||
|
||||
guard let squarePiece = square.piece else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard squarePiece.color == piece.color else {
|
||||
continue
|
||||
}
|
||||
|
||||
if squarePiece.movement.canPieceMove(fromLocation: squarePiece.location,
|
||||
toLocation: piece.location,
|
||||
board: alteredBoard,
|
||||
accountForCheckState: true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func isPieceThreatened(_ piece: Piece, onBoard board: Board) -> Bool {
|
||||
|
||||
for square in board.squares {
|
||||
|
||||
guard let squarePiece = square.piece else {
|
||||
continue
|
||||
}
|
||||
|
||||
guard squarePiece.color == piece.color.opposite() else {
|
||||
continue
|
||||
}
|
||||
|
||||
if squarePiece.movement.canPieceMove(fromLocation: squarePiece.location, toLocation: piece.location, board: board, accountForCheckState: true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
func getPieces(threatening piece: Piece, onBoard board: Board) -> [Piece] {
|
||||
|
||||
return board.getPieces(color: piece.color.opposite()).filter{
|
||||
$0.movement.canPieceMove(fromLocation: $0.location, toLocation: piece.location, board: board, accountForCheckState: true)
|
||||
}
|
||||
}
|
||||
|
||||
func getPieces(threatenedBy piece: Piece, onBoard board: Board) -> [Piece] {
|
||||
|
||||
return board.getPieces(color: piece.color.opposite()).filter{
|
||||
piece.movement.canPieceMove(fromLocation: piece.location, toLocation: $0.location, board: board, accountForCheckState: true)
|
||||
}
|
||||
}
|
||||
|
||||
func canPieceMoveToSafety(_ piece: Piece, onBoard board: Board) -> Bool {
|
||||
|
||||
for location in BoardLocation.all {
|
||||
|
||||
if piece.movement.canPieceMove(fromLocation: piece.location, toLocation: location, board: board, accountForCheckState: true) {
|
||||
|
||||
var boardCopy = board
|
||||
boardCopy.movePiece(fromLocation: piece.location, toLocation: location)
|
||||
let movedPiece = boardCopy.getPiece(at: location)!
|
||||
if !isPieceThreatened(movedPiece, onBoard: boardCopy) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
extension Collection where Iterator.Element == Piece{
|
||||
|
||||
func lowestPieceValue() -> Double {
|
||||
|
||||
if self.count == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var result = self.first!.value()
|
||||
|
||||
for piece in self {
|
||||
|
||||
let pieceValue = piece.value()
|
||||
|
||||
if pieceValue < result {
|
||||
result = pieceValue
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func highestPieceValue() -> Double {
|
||||
|
||||
if self.count == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
var result = self.first!.value()
|
||||
|
||||
for piece in self {
|
||||
|
||||
let pieceValue = piece.value()
|
||||
|
||||
if pieceValue > result {
|
||||
result = pieceValue
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// BoardStride.swift
|
||||
// SwiftChess
|
||||
//
|
||||
// Created by Steve Barnegren on 31/01/2017.
|
||||
// Copyright © 2017 Steve Barnegren. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct BoardStride {
|
||||
|
||||
public var x: Int;
|
||||
public var y: Int;
|
||||
|
||||
public init(x: Int, y: Int){
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,56 @@ import Foundation
|
||||
|
||||
open class Game {
|
||||
|
||||
open var board = Board(state: .newGame)
|
||||
// MARK: Types
|
||||
public enum State: Equatable {
|
||||
case inProgress
|
||||
case staleMate(color: Color)
|
||||
case won(color: Color)
|
||||
|
||||
public static func ==(lhs: State, rhs: State) -> Bool {
|
||||
switch (lhs, rhs) {
|
||||
case (.inProgress, .inProgress):
|
||||
return true
|
||||
case (let .staleMate(color1), let staleMate(color2)):
|
||||
return color1 == color2
|
||||
case (let .won(color1), let won(color2)):
|
||||
return color1 == color2
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum GameType {
|
||||
case humanVsHuman
|
||||
case humanVsComputer
|
||||
case computerVsComputer
|
||||
}
|
||||
|
||||
// MARK: Properties
|
||||
open var board = Board(state: .newGame)
|
||||
open var whitePlayer: Player!
|
||||
open var blackPlayer: Player!
|
||||
open var currentPlayer: Player!
|
||||
|
||||
open var state = Game.State.inProgress
|
||||
open weak var delegate: GameDelegate?
|
||||
|
||||
public init(firstPlayer: Player, secondPlayer: Player){
|
||||
|
||||
public var gameType: GameType {
|
||||
|
||||
switch (self.whitePlayer, self.blackPlayer) {
|
||||
case (is Human, is Human):
|
||||
return .humanVsHuman
|
||||
case (is AIPlayer, is AIPlayer):
|
||||
return .computerVsComputer
|
||||
default:
|
||||
return .humanVsComputer
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Init
|
||||
public init(firstPlayer: Player, secondPlayer: Player, board: Board = Board(state: .newGame), colorToMove: Color = .white){
|
||||
|
||||
self.board = board
|
||||
|
||||
// Assign to correct colors
|
||||
if firstPlayer.color == secondPlayer.color {
|
||||
@@ -33,10 +74,13 @@ open class Game {
|
||||
self.blackPlayer.delegate = self
|
||||
self.whitePlayer.game = self
|
||||
self.blackPlayer.game = self
|
||||
self.currentPlayer = self.whitePlayer
|
||||
self.currentPlayer = (colorToMove == .white ? self.whitePlayer : self.blackPlayer)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Game : PlayerDelegate
|
||||
extension Game : PlayerDelegate {
|
||||
|
||||
func playerDidMakeMove(player: Player, boardOperations: [BoardOperation]) {
|
||||
@@ -46,21 +90,29 @@ extension Game : PlayerDelegate {
|
||||
print("Warning - Wrong player took turn")
|
||||
}
|
||||
|
||||
// Tell delegate we will begin updates
|
||||
delegate?.gameWillBeginUpdates(game: self)
|
||||
|
||||
// Process board operations
|
||||
processBoardOperations(boardOperations: boardOperations)
|
||||
|
||||
// Check for game ended
|
||||
if board.isColorInCheckMate(color: currentPlayer.color.opposite()) {
|
||||
state = .won(color: currentPlayer.color)
|
||||
delegate?.gameWonByPlayer(game: self, player: currentPlayer)
|
||||
return
|
||||
}
|
||||
|
||||
// Check for stalemate
|
||||
if board.isColorInStalemate(color: currentPlayer.color.opposite()) {
|
||||
state = .staleMate(color: currentPlayer.color.opposite())
|
||||
delegate?.gameEndedInStaleMate(game: self)
|
||||
return
|
||||
}
|
||||
|
||||
// Tell the delegate that we've ended updates
|
||||
delegate?.gameDidEndUpdates(game: self)
|
||||
|
||||
// Switch to the other player
|
||||
if player === whitePlayer {
|
||||
currentPlayer = blackPlayer
|
||||
@@ -70,6 +122,7 @@ extension Game : PlayerDelegate {
|
||||
}
|
||||
|
||||
self.delegate?.gameDidChangeCurrentPlayer(game: self)
|
||||
|
||||
}
|
||||
|
||||
func processBoardOperations(boardOperations: [BoardOperation]) {
|
||||
@@ -91,6 +144,7 @@ extension Game : PlayerDelegate {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - GameDelegate
|
||||
public protocol GameDelegate: class {
|
||||
|
||||
// State changes
|
||||
|
||||
@@ -19,6 +19,11 @@ open class Human : Player {
|
||||
|
||||
public func movePiece(fromLocation: BoardLocation, toLocation: BoardLocation) throws {
|
||||
|
||||
// Check that the game is in progress
|
||||
guard game.state == .inProgress else {
|
||||
throw MoveError.gameIsNotInProgress
|
||||
}
|
||||
|
||||
// Check that we're the current player
|
||||
guard game.currentPlayer === self else {
|
||||
throw MoveError.notThisPlayersTurn
|
||||
@@ -47,7 +52,7 @@ open class Human : Player {
|
||||
|
||||
// Change the piece
|
||||
let newPiece = self.game.board.squares[pawnLocation.index].piece?.byChangingType(newType: $0)
|
||||
self.game.board.squares[pawnLocation.index].piece = newPiece
|
||||
self.game.board.setPiece(newPiece!, at: pawnLocation)
|
||||
|
||||
// Add a transform piece operation
|
||||
let modifyOperation = BoardOperation(type: .transformPiece, piece: newPiece!, location: pawnLocation)
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
//
|
||||
// Opening.swift
|
||||
// SwiftChess
|
||||
//
|
||||
// Created by Steve Barnegren on 31/01/2017.
|
||||
// Copyright © 2017 Steve Barnegren. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct OpeningMove {
|
||||
let board: Board
|
||||
let fromLocation: BoardLocation
|
||||
let toLocation: BoardLocation
|
||||
}
|
||||
|
||||
class Opening {
|
||||
|
||||
static func allOpenings() -> [Opening] {
|
||||
return [
|
||||
RuyLopez(),
|
||||
ItalianGame(),
|
||||
SicilianDefense(),
|
||||
QueensGambit(),
|
||||
KingsGambit(),
|
||||
]
|
||||
}
|
||||
|
||||
static func allOpeningMoves(forColor color: Color) -> [OpeningMove] {
|
||||
|
||||
var openingMoves = [OpeningMove]()
|
||||
|
||||
allOpenings().forEach{
|
||||
openingMoves += $0.moves(forColor: color)
|
||||
}
|
||||
|
||||
return openingMoves
|
||||
}
|
||||
|
||||
public func moves(forColor color: Color) -> [OpeningMove] {
|
||||
|
||||
var moves = [OpeningMove]()
|
||||
|
||||
var board = Board(state: .newGame)
|
||||
for locations in moveLocations(){
|
||||
|
||||
let move = OpeningMove(board: board,
|
||||
fromLocation: locations.fromLocation,
|
||||
toLocation: locations.toLocation)
|
||||
moves.append(move)
|
||||
|
||||
board.movePiece(fromLocation: locations.fromLocation,
|
||||
toLocation: locations.toLocation)
|
||||
|
||||
//move.board.printBoardPieces()
|
||||
|
||||
}
|
||||
|
||||
// Filter for color
|
||||
return moves.enumerated().flatMap{ (index, value) in
|
||||
index % 2 == (color == .white ? 0 : 1) ? value : nil
|
||||
}
|
||||
}
|
||||
/*
|
||||
public func moveLocations(forColor color: Color) -> [(fromLocation: BoardLocation, toLocation: BoardLocation)] {
|
||||
|
||||
return moveLocations().enumerated().flatMap{ (index, value) in
|
||||
index % 2 == (color == .white ? 0 : 1) ? value : nil
|
||||
}
|
||||
}
|
||||
*/
|
||||
func moveLocations() -> [(fromLocation: BoardLocation, toLocation: BoardLocation)] {
|
||||
|
||||
return moveGridPositions().map{
|
||||
(BoardLocation(gridPosition: $0), BoardLocation(gridPosition: $1))
|
||||
}
|
||||
}
|
||||
|
||||
func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
fatalError("Must override")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Ruy Lopez
|
||||
|
||||
class RuyLopez: Opening {
|
||||
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.e2, .e4), // white moves pawn to e4
|
||||
(.e7, .e5), // black moves pawn to e5
|
||||
(.g1, .f3), // white moves knight to f3
|
||||
(.b8, .c6), // black moves knight to c6
|
||||
(.f1, .b5), // white moves bishop to b5
|
||||
]
|
||||
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Italian Game
|
||||
|
||||
class ItalianGame: Opening {
|
||||
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.e2, .e4), // white moves pawn to e4
|
||||
(.e7, .e5), // black moves pawn to e5
|
||||
(.g1, .f3), // white moves knight to f3
|
||||
(.b8, .c6), // black moves knight to c6
|
||||
(.f1, .c4), // white moves bishop to c4
|
||||
]
|
||||
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sicilian Defense
|
||||
|
||||
class SicilianDefense: Opening {
|
||||
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.e2, .e4), // white moves pawn to e4
|
||||
(.c7, .c5), // black moves pawn to c5
|
||||
]
|
||||
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Queens Gambit
|
||||
|
||||
class QueensGambit: Opening {
|
||||
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.d2, .d4), // white moves pawn to d4
|
||||
(.d7, .d5), // black moves pawn to d5
|
||||
(.c2, .c4), // white moves pawn to c4
|
||||
]
|
||||
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - King's Gambit
|
||||
|
||||
class KingsGambit: Opening {
|
||||
|
||||
override func moveGridPositions() -> [(fromPosition: BoardLocation.GridPosition, toPosition: BoardLocation.GridPosition)] {
|
||||
|
||||
let moves: [(BoardLocation.GridPosition, BoardLocation.GridPosition)] = [
|
||||
(.e2, .e4), // white moves pawn to e4
|
||||
(.e7, .e5), // black moves pawn to e5
|
||||
(.f2, .f4), // white moves pawn to f4
|
||||
]
|
||||
|
||||
return moves
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum Color {
|
||||
public enum Color: Int {
|
||||
case white
|
||||
case black
|
||||
|
||||
@@ -20,13 +20,16 @@ public enum Color {
|
||||
return (self == .white) ? "white" : "black"
|
||||
}
|
||||
|
||||
public func toStringWithCapital() -> String {
|
||||
return (self == .white) ? "White" : "Black"
|
||||
}
|
||||
}
|
||||
|
||||
public struct Piece {
|
||||
public struct Piece : Equatable {
|
||||
|
||||
static private var lastAssignedTag = 0
|
||||
|
||||
public enum PieceType {
|
||||
public enum PieceType: Int {
|
||||
case pawn
|
||||
case rook
|
||||
case knight
|
||||
@@ -43,8 +46,10 @@ public struct Piece {
|
||||
public let color: Color
|
||||
public var tag: Int!
|
||||
public var hasMoved = false
|
||||
public var canBeTakenByEnPassant = false
|
||||
public internal(set) var location = BoardLocation(index: 0)
|
||||
|
||||
var movement : PieceMovement {
|
||||
var movement : PieceMovement! {
|
||||
return PieceMovement.pieceMovementForPieceType(pieceType: self.type)
|
||||
}
|
||||
|
||||
@@ -80,6 +85,10 @@ public struct Piece {
|
||||
let piece = Piece(type: newType, color: color, tag: tag)
|
||||
return piece
|
||||
}
|
||||
|
||||
func withOppositeColor() -> Piece {
|
||||
return Piece(type: type, color: color.opposite())
|
||||
}
|
||||
}
|
||||
|
||||
public func == (left: Piece, right: Piece) -> Bool {
|
||||
|
||||
@@ -10,23 +10,30 @@ import Foundation
|
||||
|
||||
// MARK: - PieceMovement (Base Class)
|
||||
|
||||
let pawnMovement = PieceMovementPawn()
|
||||
let rookMovement = PieceMovementRook()
|
||||
let knightMovement = PieceMovementKnight()
|
||||
let bishopMovement = PieceMovementBishop()
|
||||
let queenMovement = PieceMovementQueen()
|
||||
let kingMovement = PieceMovementKing()
|
||||
|
||||
open class PieceMovement {
|
||||
|
||||
public class func pieceMovementForPieceType(pieceType: Piece.PieceType) -> PieceMovement {
|
||||
|
||||
switch pieceType {
|
||||
case .pawn:
|
||||
return PieceMovementPawn()
|
||||
return pawnMovement
|
||||
case .rook:
|
||||
return PieceMovementRook()
|
||||
return rookMovement
|
||||
case .knight:
|
||||
return PieceMovementKnight()
|
||||
return knightMovement
|
||||
case .bishop:
|
||||
return PieceMovementBishop()
|
||||
return bishopMovement
|
||||
case .queen:
|
||||
return PieceMovementQueen()
|
||||
return queenMovement
|
||||
case .king:
|
||||
return PieceMovementKing()
|
||||
return kingMovement
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +41,63 @@ open class PieceMovement {
|
||||
|
||||
}
|
||||
|
||||
open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board, accountForCheckState: Bool = false) -> Bool {
|
||||
|
||||
if fromLocation == toLocation {
|
||||
return false
|
||||
}
|
||||
|
||||
let canMove = isMovementPossible(fromLocation: fromLocation, toLocation: toLocation, board: board)
|
||||
|
||||
if canMove && accountForCheckState {
|
||||
|
||||
let color = board.getPiece(at: fromLocation)!.color
|
||||
|
||||
var boardCopy = board
|
||||
boardCopy.movePiece(fromLocation: fromLocation, toLocation: toLocation)
|
||||
return boardCopy.isColorInCheck(color: color) ? false : true
|
||||
}
|
||||
else{
|
||||
return canMove
|
||||
}
|
||||
}
|
||||
|
||||
func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board, stride: BoardStride) -> Bool {
|
||||
|
||||
enum Direction: Int{
|
||||
case increasing
|
||||
case decresing
|
||||
case none
|
||||
}
|
||||
|
||||
var strideDirectionX = Direction.none
|
||||
if stride.x < 0 { strideDirectionX = .decresing }
|
||||
if stride.x > 0 { strideDirectionX = .increasing }
|
||||
|
||||
var locationDirectionX = Direction.none
|
||||
if toLocation.x - fromLocation.x < 0 { locationDirectionX = .decresing }
|
||||
if toLocation.x - fromLocation.x > 0 { locationDirectionX = .increasing }
|
||||
|
||||
if strideDirectionX != locationDirectionX {
|
||||
return false
|
||||
}
|
||||
|
||||
var strideDirectionY = Direction.none
|
||||
if stride.y < 0 { strideDirectionY = .decresing }
|
||||
if stride.y > 0 { strideDirectionY = .increasing }
|
||||
|
||||
var locationDirectionY = Direction.none
|
||||
if toLocation.y - fromLocation.y < 0 { locationDirectionY = .decresing }
|
||||
if toLocation.y - fromLocation.y > 0 { locationDirectionY = .increasing }
|
||||
|
||||
if strideDirectionY != locationDirectionY {
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure cannot take king
|
||||
if let piece = board.getPiece(at: toLocation) {
|
||||
if piece.type == .king {
|
||||
@@ -48,9 +106,7 @@ open class PieceMovement {
|
||||
}
|
||||
|
||||
// Get the moving piece
|
||||
var movingPiece = board.getPiece(at: fromLocation)
|
||||
|
||||
if movingPiece == nil {
|
||||
guard let movingPiece = board.getPiece(at: fromLocation) else {
|
||||
print("Cannot from an index that does not contain a piece")
|
||||
return false
|
||||
}
|
||||
@@ -66,15 +122,15 @@ open class PieceMovement {
|
||||
// If there is a piece on the square
|
||||
if let piece = board.getPiece(at: testLocation) {
|
||||
|
||||
if piece.color == movingPiece!.color {
|
||||
if piece.color == movingPiece.color {
|
||||
return false
|
||||
}
|
||||
|
||||
if piece.color == movingPiece!.color.opposite() && testLocation == toLocation {
|
||||
if piece.color == movingPiece.color.opposite() && testLocation == toLocation {
|
||||
return true
|
||||
}
|
||||
|
||||
if piece.color == movingPiece!.color.opposite() && testLocation != toLocation {
|
||||
if piece.color == movingPiece.color.opposite() && testLocation != toLocation {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -109,16 +165,14 @@ open class PieceMovement {
|
||||
}
|
||||
|
||||
// Check if space is occupied
|
||||
var movingPiece = board.getPiece(at: pieceLocation)
|
||||
|
||||
if movingPiece == nil {
|
||||
print("Cannot from an index that does not contain a piece")
|
||||
guard let movingPiece = board.getPiece(at: pieceLocation) else {
|
||||
print("Cannot move from an index that does not contain a piece")
|
||||
return false
|
||||
}
|
||||
|
||||
if let otherPiece = board.getPiece(at: targetLocation) {
|
||||
|
||||
if otherPiece.color == movingPiece!.color {
|
||||
if otherPiece.color == movingPiece.color {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -132,14 +186,21 @@ open class PieceMovement {
|
||||
|
||||
open class PieceMovementStraightLine: PieceMovement {
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
let strides = [
|
||||
BoardStride(x: 0, y: -1 ), // Down
|
||||
BoardStride(x: 0, y: 1 ), // Up
|
||||
BoardStride(x: -1, y: 0 ), // Left
|
||||
BoardStride(x: 1, y: 0 ) // Right
|
||||
]
|
||||
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
let strides = [
|
||||
BoardStride(x: 0, y: -1 ), // Down
|
||||
BoardStride(x: 0, y: 1 ), // Up
|
||||
BoardStride(x: -1, y: 0 ), // Left
|
||||
BoardStride(x: 1, y: 0 ) // Right
|
||||
]
|
||||
let sameX = fromLocation.x == toLocation.x
|
||||
let sameY = fromLocation.y == toLocation.y
|
||||
|
||||
if !(sameX || sameY) {
|
||||
return false
|
||||
}
|
||||
|
||||
for stride in strides {
|
||||
if canPieceMove(fromLocation: fromLocation, toLocation: toLocation, board: board, stride: stride) {
|
||||
@@ -156,14 +217,18 @@ open class PieceMovementStraightLine: PieceMovement {
|
||||
|
||||
open class PieceMovementDiagonal: PieceMovement {
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
let strides = [
|
||||
BoardStride(x: 1, y: -1 ), // South East
|
||||
BoardStride(x: -1, y: -1 ), // South West
|
||||
BoardStride(x: 1, y: 1 ), // North East
|
||||
BoardStride(x: -1, y: 1 ) // North West
|
||||
]
|
||||
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
let strides = [
|
||||
BoardStride(x: 1, y: -1 ), // South East
|
||||
BoardStride(x: -1, y: -1 ), // South West
|
||||
BoardStride(x: 1, y: 1 ), // North East
|
||||
BoardStride(x: -1, y: 1 ) // North West
|
||||
]
|
||||
if fromLocation.isDarkSquare != toLocation.isDarkSquare {
|
||||
return false
|
||||
}
|
||||
|
||||
for stride in strides {
|
||||
if canPieceMove(fromLocation: fromLocation, toLocation: toLocation, board: board, stride: stride) {
|
||||
@@ -183,7 +248,7 @@ open class PieceMovementQueen: PieceMovement {
|
||||
|
||||
let movements : [PieceMovement] = [PieceMovementStraightLine(), PieceMovementDiagonal()]
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
for pieceMovement in movements {
|
||||
|
||||
@@ -203,12 +268,9 @@ open class PieceMovementRook: PieceMovement {
|
||||
|
||||
let straightLineMovement = PieceMovementStraightLine()
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
return straightLineMovement.canPieceMove(fromLocation: fromLocation, toLocation: toLocation, board: board)
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,12 +280,9 @@ open class PieceMovementBishop: PieceMovement {
|
||||
|
||||
let diagonalMovement = PieceMovementDiagonal()
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
return diagonalMovement.canPieceMove(fromLocation: fromLocation, toLocation: toLocation, board: board)
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +290,18 @@ open class PieceMovementBishop: PieceMovement {
|
||||
|
||||
open class PieceMovementKnight: PieceMovement {
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
let offsets: [(x: Int, y: Int)] = [
|
||||
(1,2),
|
||||
(2,1),
|
||||
(2,-1),
|
||||
(-2,1),
|
||||
(-1,-2),
|
||||
(-2,-1),
|
||||
(1,-2),
|
||||
(-1,2)
|
||||
]
|
||||
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
// Make sure cannot take king
|
||||
if let piece = board.getPiece(at: toLocation) {
|
||||
@@ -240,22 +310,12 @@ open class PieceMovementKnight: PieceMovement {
|
||||
}
|
||||
}
|
||||
|
||||
let offsets: [(x: Int, y: Int)] = [
|
||||
(1,2),
|
||||
(2,1),
|
||||
(2,-1),
|
||||
(-2,1),
|
||||
(-1,-2),
|
||||
(-2,-1),
|
||||
(1,-2),
|
||||
(-1,2)
|
||||
]
|
||||
|
||||
for offset in offsets {
|
||||
|
||||
let offsetLocation = fromLocation.incrementedBy(x: offset.x, y: offset.y)
|
||||
|
||||
if toLocation == offsetLocation && canPieceOccupySquare(pieceLocation: fromLocation, xOffset: offset.x, yOffset: offset.y, board: board) {
|
||||
if toLocation == offsetLocation
|
||||
&& canPieceOccupySquare(pieceLocation: fromLocation, xOffset: offset.x, yOffset: offset.y, board: board) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -269,7 +329,20 @@ open class PieceMovementKnight: PieceMovement {
|
||||
|
||||
open class PieceMovementPawn: PieceMovement {
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
// Get the moving piece
|
||||
guard let movingPiece = board.getPiece(at: fromLocation) else{
|
||||
return false
|
||||
}
|
||||
|
||||
if movingPiece.color == .white && toLocation.y == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if movingPiece.color == .black && toLocation.y == 7 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure cannot take king
|
||||
if let piece = board.getPiece(at: toLocation) {
|
||||
@@ -278,11 +351,6 @@ open class PieceMovementPawn: PieceMovement {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the moving piece
|
||||
guard let movingPiece = board.getPiece(at: fromLocation) else{
|
||||
return false
|
||||
}
|
||||
|
||||
let color = movingPiece.color
|
||||
|
||||
// ****** Test forward locations ******
|
||||
@@ -290,12 +358,14 @@ open class PieceMovementPawn: PieceMovement {
|
||||
|
||||
// Test one ahead offset
|
||||
let oneAheadStride = (color == .white ? BoardStride(x: 0, y: 1) : BoardStride(x: 0, y: -1))
|
||||
var canMoveOneAhead = true
|
||||
|
||||
ONE_AHEAD: if fromLocation.canIncrementBy(stride: oneAheadStride) {
|
||||
|
||||
let location = fromLocation.incrementedBy(stride: oneAheadStride)
|
||||
|
||||
if let _ = board.getPiece(at: toLocation) {
|
||||
if let _ = board.getPiece(at: location) {
|
||||
canMoveOneAhead = false
|
||||
break ONE_AHEAD
|
||||
}
|
||||
|
||||
@@ -306,31 +376,31 @@ open class PieceMovementPawn: PieceMovement {
|
||||
|
||||
|
||||
// Test two ahead offset
|
||||
var twoAheadStride: BoardStride?
|
||||
|
||||
if color == .white && fromLocation.y == 1 {
|
||||
twoAheadStride = BoardStride(x: 0, y: 2)
|
||||
}
|
||||
else if color == .black && fromLocation.y == 6 {
|
||||
twoAheadStride = BoardStride(x: 0, y: -2)
|
||||
}
|
||||
|
||||
TWO_AHEAD: if let twoAheadStride = twoAheadStride {
|
||||
if canMoveOneAhead {
|
||||
|
||||
let twoAheadLocation = fromLocation.incrementedBy(stride: twoAheadStride)
|
||||
|
||||
if toLocation != twoAheadLocation {
|
||||
break TWO_AHEAD
|
||||
var twoAheadStride: BoardStride?
|
||||
|
||||
if color == .white && fromLocation.y == 1 {
|
||||
twoAheadStride = BoardStride(x: 0, y: 2)
|
||||
}
|
||||
else if color == .black && fromLocation.y == 6 {
|
||||
twoAheadStride = BoardStride(x: 0, y: -2)
|
||||
}
|
||||
|
||||
let oneAheadLocation = fromLocation.incrementedBy(stride: oneAheadStride)
|
||||
|
||||
if board.getPiece(at: oneAheadLocation) == nil && board.getPiece(at: twoAheadLocation) == nil {
|
||||
return true
|
||||
TWO_AHEAD: if let twoAheadStride = twoAheadStride {
|
||||
|
||||
let twoAheadLocation = fromLocation.incrementedBy(stride: twoAheadStride)
|
||||
|
||||
if toLocation != twoAheadLocation {
|
||||
break TWO_AHEAD
|
||||
}
|
||||
|
||||
if board.getPiece(at: twoAheadLocation) == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ****** Test Diagonal locations ******
|
||||
var diagonalStrides = [BoardStride]()
|
||||
|
||||
@@ -355,11 +425,30 @@ open class PieceMovementPawn: PieceMovement {
|
||||
continue
|
||||
}
|
||||
|
||||
// If the target square has an opponent piece
|
||||
if let piece = board.getPiece(at: location) {
|
||||
if piece.color == color.opposite() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// If can make en passent move
|
||||
let enPassentStride = BoardStride(x: stride.x, y: 0)
|
||||
|
||||
guard fromLocation.canIncrementBy(stride: enPassentStride) else {
|
||||
break
|
||||
}
|
||||
|
||||
let enPassentLocation = fromLocation.incrementedBy(stride: enPassentStride)
|
||||
|
||||
guard let passingPiece = board.getPiece(at: enPassentLocation) else {
|
||||
break
|
||||
}
|
||||
|
||||
if passingPiece.canBeTakenByEnPassant && passingPiece.color == color.opposite() {
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -372,7 +461,18 @@ open class PieceMovementPawn: PieceMovement {
|
||||
|
||||
open class PieceMovementKing: PieceMovement {
|
||||
|
||||
override open func canPieceMove(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
let offsets: [(x: Int, y: Int)] = [
|
||||
(0,1), // North
|
||||
(1,1), // North-East
|
||||
(1,0), // East
|
||||
(1,-1), // South-East
|
||||
(0,-1), // South
|
||||
(-1,-1), // South-West
|
||||
(-1,0), // West
|
||||
(-1,1) // North- West
|
||||
]
|
||||
|
||||
override func isMovementPossible(fromLocation: BoardLocation, toLocation: BoardLocation, board: Board) -> Bool {
|
||||
|
||||
// Make sure cannot take king
|
||||
if let piece = board.getPiece(at: toLocation) {
|
||||
@@ -381,17 +481,6 @@ open class PieceMovementKing: PieceMovement {
|
||||
}
|
||||
}
|
||||
|
||||
let offsets: [(x: Int, y: Int)] = [
|
||||
(0,1), // North
|
||||
(1,1), // North-East
|
||||
(1,0), // East
|
||||
(1,-1), // South-East
|
||||
(0,-1), // South
|
||||
(-1,-1), // South-West
|
||||
(-1,0), // West
|
||||
(-1,1) // North- West
|
||||
]
|
||||
|
||||
for offset in offsets {
|
||||
|
||||
let offsetLocation = fromLocation.incrementedBy(x: offset.x, y: offset.y)
|
||||
|
||||
@@ -41,6 +41,7 @@ open class Player {
|
||||
case pieceUnableToMoveToLocation
|
||||
case playerMustMoveOutOfCheck
|
||||
case cannotMoveInToCheck
|
||||
case gameIsNotInProgress
|
||||
}
|
||||
|
||||
public func canMovePieceWithError(fromLocation: BoardLocation, toLocation: BoardLocation) -> (result: Bool, error: MoveError?) {
|
||||
@@ -69,7 +70,7 @@ open class Player {
|
||||
let inCheckBeforeMove = self.game.board.isColorInCheck(color: self.color)
|
||||
var board = self.game.board
|
||||
board.movePiece(fromLocation: fromLocation, toLocation: toLocation)
|
||||
var inCheckAfterMove = board.isColorInCheck(color: self.color)
|
||||
let inCheckAfterMove = board.isColorInCheck(color: self.color)
|
||||
|
||||
// Return
|
||||
if inCheckBeforeMove && inCheckAfterMove {
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
67F779241E1C326D00885B89 /* BoardRaterCenterFourOccupation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F779231E1C326D00885B89 /* BoardRaterCenterFourOccupation.swift */; };
|
||||
67F9DB6C1E1AC59600C7EC5A /* BoardRaterCheckMateOpportunity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F9DB6B1E1AC59600C7EC5A /* BoardRaterCheckMateOpportunity.swift */; };
|
||||
67F9DB771E1AD46000C7EC5A /* AIConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F9DB761E1AD46000C7EC5A /* AIConfiguration.swift */; };
|
||||
67F9DB7A1E2438EC00C7EC5A /* ASCIIBoard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67F9DB791E2438EC00C7EC5A /* ASCIIBoard.swift */; };
|
||||
67FD86871E41021E0023335C /* Opening.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FD86861E41021E0023335C /* Opening.swift */; };
|
||||
67FD86891E4105B80023335C /* BoardStride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FD86881E4105B80023335C /* BoardStride.swift */; };
|
||||
67FD868B1E4105D40023335C /* BoardLocation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67FD868A1E4105D40023335C /* BoardLocation.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -63,6 +67,10 @@
|
||||
67F779231E1C326D00885B89 /* BoardRaterCenterFourOccupation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCenterFourOccupation.swift; sourceTree = "<group>"; };
|
||||
67F9DB6B1E1AC59600C7EC5A /* BoardRaterCheckMateOpportunity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardRaterCheckMateOpportunity.swift; sourceTree = "<group>"; };
|
||||
67F9DB761E1AD46000C7EC5A /* AIConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AIConfiguration.swift; sourceTree = "<group>"; };
|
||||
67F9DB791E2438EC00C7EC5A /* ASCIIBoard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASCIIBoard.swift; sourceTree = "<group>"; };
|
||||
67FD86861E41021E0023335C /* Opening.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Opening.swift; sourceTree = "<group>"; };
|
||||
67FD86881E4105B80023335C /* BoardStride.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardStride.swift; sourceTree = "<group>"; };
|
||||
67FD868A1E4105D40023335C /* BoardLocation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoardLocation.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -123,6 +131,9 @@
|
||||
67A9C9F81DE64D2500510FB8 /* Source */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
67F9DB791E2438EC00C7EC5A /* ASCIIBoard.swift */,
|
||||
67FD86881E4105B80023335C /* BoardStride.swift */,
|
||||
67FD868A1E4105D40023335C /* BoardLocation.swift */,
|
||||
67A9C9FB1DE64D2500510FB8 /* Board.swift */,
|
||||
67A9C9FD1DE64D2500510FB8 /* Piece.swift */,
|
||||
67A9C9FE1DE64D2500510FB8 /* PieceMovement.swift */,
|
||||
@@ -148,6 +159,7 @@
|
||||
children = (
|
||||
67A9C9FA1DE64D2500510FB8 /* AIPlayer.swift */,
|
||||
67F9DB761E1AD46000C7EC5A /* AIConfiguration.swift */,
|
||||
67FD86851E4101DD0023335C /* Openings */,
|
||||
67D54A4B1DE6D8D200C12258 /* BoardRaters */,
|
||||
);
|
||||
name = AIPlayer;
|
||||
@@ -169,6 +181,14 @@
|
||||
name = BoardRaters;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
67FD86851E4101DD0023335C /* Openings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
67FD86861E41021E0023335C /* Opening.swift */,
|
||||
);
|
||||
name = Openings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -286,11 +306,15 @@
|
||||
67A9CA051DE64D2500510FB8 /* PieceMovement.swift in Sources */,
|
||||
6719898B1DFFE0D80053EA3D /* BoardRaterBoardDominance.swift in Sources */,
|
||||
67D54A481DE6D34100C12258 /* Player.swift in Sources */,
|
||||
67F9DB7A1E2438EC00C7EC5A /* ASCIIBoard.swift in Sources */,
|
||||
67F9DB6C1E1AC59600C7EC5A /* BoardRaterCheckMateOpportunity.swift in Sources */,
|
||||
67FD86891E4105B80023335C /* BoardStride.swift in Sources */,
|
||||
671989831DEB11900053EA3D /* BoardRaterCenterOwnership.swift in Sources */,
|
||||
67A9CA061DE64D2500510FB8 /* Human.swift in Sources */,
|
||||
676EF7C31E15A8A500E275B4 /* BoardRaterKingSurroundingPossession.swift in Sources */,
|
||||
67FD86871E41021E0023335C /* Opening.swift in Sources */,
|
||||
67F9DB771E1AD46000C7EC5A /* AIConfiguration.swift in Sources */,
|
||||
67FD868B1E4105D40023335C /* BoardLocation.swift in Sources */,
|
||||
67D54A4D1DE6DE1400C12258 /* BoardRaterCountPieces.swift in Sources */,
|
||||
09A4C0271E013950000CFBF4 /* BoardRaterThreatenedPieces.swift in Sources */,
|
||||
67D54A6B1DEAF16200C12258 /* BoardOperation.swift in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user