52 Commits

Author SHA1 Message Date
Steve Barnegren 88dda592b9 Added swift version file for cocoa pods 2017-03-16 22:22:19 +00:00
Steve Barnegren 69458e8a28 Update pod spec 2017-03-16 22:12:29 +00:00
Steve Barnegren ad5f9e9122 Updated readme 2017-03-16 22:03:06 +00:00
Steve Barnegren 7311b9add1 Use hard ai for ai vs ai game example 2017-03-16 21:19:50 +00:00
Steve Barnegren 8f5ae059b1 Updated readme 2017-03-16 20:46:48 +00:00
Steve Barnegren cca6079e56 Fix castling logic 2017-03-16 20:46:30 +00:00
Steve Barnegren 6d8bcf1211 Fixed issue with black king and queen on incorrect spaces 2017-03-07 09:00:19 +00:00
Steve Barnegren cc4b32dad6 Added computed property for game type 2017-03-07 08:48:51 +00:00
Steve Barnegren c50d4de8d7 Make board squares setter private 2017-02-19 19:57:59 +00:00
Steve Barnegren f75b7cbe9d Minor access changes 2017-02-17 21:47:47 +00:00
Steve Barnegren e7b92a6ceb Added difficulty levels 2017-02-09 21:51:13 +00:00
Steve Barnegren 216bbfe0dd Fix bug in threat rater 2017-02-09 21:41:31 +00:00
Steve Barnegren 455b6e8a8b Alter AI Configuration 2017-02-09 20:33:17 +00:00
Steve Barnegren 7c4e85e855 Fix typo 2017-02-09 20:32:56 +00:00
Steve Barnegren 73d3dbbd38 Behaviour tests passing 2017-02-09 19:36:31 +00:00
Steve Barnegren 77925776b8 Stop AI player from trying to develop king 2017-02-09 18:56:41 +00:00
Steve Barnegren 717de7b90c Fix threat rating tests 2017-02-09 18:42:02 +00:00
Steve Barnegren 782269b3d5 New threat rating logic 2017-02-09 17:59:04 +00:00
Steve Barnegren d04cffff36 Pieces store their location 2017-02-05 17:21:07 +00:00
Steve Barnegren 2f2b6e2d39 Update project settings 2017-02-05 16:39:15 +00:00
Steve Barnegren 17221a7f78 Improved piece movement performance 2017-02-05 10:36:58 +00:00
Steve Barnegren 1f64711de9 Piece movement performance improvements 2017-02-05 10:22:28 +00:00
Steve Barnegren 8b92378030 Performance optimisation for diagonal movement 2017-02-05 09:39:55 +00:00
Steve Barnegren 50514237ff Improved pawn movement validation performance 2017-02-03 16:34:45 +00:00
Steve Barnegren 59eebff8ed AI performance improvements 2017-02-03 16:06:24 +00:00
Steve Barnegren 2ab3ec8f98 Added performance tests for move validation 2017-02-02 22:01:34 +00:00
Steve Barnegren 3bf665e7d4 Fixed crash when selecting opening moves 2017-02-02 08:03:33 +00:00
Steve Barnegren 7a5c82b638 Don't allow players to move if game not in progress 2017-02-01 22:19:47 +00:00
Steve Barnegren 702ac1ce2c AI works on background thread 2017-02-01 22:05:58 +00:00
Steve Barnegren 2c5c1e1494 suppress logging to improve test performance 2017-02-01 19:22:20 +00:00
Steve Barnegren 30e274b05f Fixed bug with Square Equatable 2017-02-01 08:47:33 +00:00
Steve Barnegren 55795bf00a Fixed board equatable conformance 2017-02-01 08:44:47 +00:00
Steve Barnegren 2ee384172c Added failing test case for board equality 2017-02-01 08:39:51 +00:00
Steve Barnegren 97b10dfa7b AI can play opening moves (although currently crashing) 2017-01-31 22:04:21 +00:00
Steve Barnegren b3ab7cfbf0 Break out openings in to separate moves and board states 2017-01-31 21:26:13 +00:00
Steve Barnegren 82f68a6875 Added moves for some common openings 2017-01-31 20:19:25 +00:00
Steve Barnegren c0cc240716 Added tests for board location 2017-01-31 18:26:33 +00:00
Steve Barnegren 8c64655149 Added GridPositon to BoardLocation 2017-01-31 18:01:32 +00:00
Steve Barnegren ee073d5edf Added board method to be able to get possible moves locations for a piece 2017-01-30 21:44:20 +00:00
Steve Barnegren 8592eb4928 Use forEach to make a few verbose loops more concise 2017-01-28 10:57:21 +00:00
Steve Barnegren 1594973694 Added game state enum to game 2017-01-26 20:38:57 +00:00
Steve Barnegren 8c1ad9b6e5 Update readme 2017-01-17 18:12:15 +00:00
Steve Barnegren babc1cf062 Updated podspec 2017-01-12 21:22:17 +00:00
Steve Barnegren dde1d3f054 Slightly adjusted AI Configuration values 2017-01-12 20:28:03 +00:00
Steve Barnegren e93f6758fc Added king's gambit opening 2017-01-11 20:08:16 +00:00
Steve Barnegren aaec289f33 Made ASCIIBoard part of SwiftChess module 2017-01-09 21:35:27 +00:00
Steve Barnegren 8fd84bea84 Implemented en passant 2017-01-09 21:12:40 +00:00
Steve Barnegren fa2e34d026 Added logic for whether piece is able to be taken via en passant move 2017-01-08 20:13:10 +00:00
Steve Barnegren 8fa6ca0002 Added failing tests for en passant rule 2017-01-08 19:47:43 +00:00
Steve Barnegren b200acaffb Fix issue with pawns not being able to check king 2017-01-08 18:37:18 +00:00
Steve Barnegren 150e763d87 Added behavioural test 2017-01-08 11:07:11 +00:00
Steve Barnegren 5503a770b6 Altered configuration weightings 2017-01-06 08:36:43 +00:00
50 changed files with 3090 additions and 647 deletions
+1
View File
@@ -0,0 +1 @@
3.0
+54 -8
View File
@@ -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";
@@ -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>
@@ -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>
+21 -3
View File
@@ -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"/>
+8 -3
View File
@@ -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()
}
}
+3 -3
View File
@@ -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)
+139 -2
View File
@@ -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)
}
}
+162 -5
View File
@@ -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))
}
}
+188
View File
@@ -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)
}
+64
View File
@@ -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
View File
@@ -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)
}
}
}
+81 -18
View File
@@ -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))
}
}
+53
View File
@@ -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)")
}
}
}
}
}
+226
View File
@@ -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)
}
}
}
}
+278 -4
View File
@@ -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))
}
+155 -1
View File
@@ -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)
}
}
+110 -12
View File
@@ -1,28 +1,126 @@
# SwiftChess
[![CI Status](http://img.shields.io/travis/Steve Barnegren/SwiftChess.svg?style=flat)](https://travis-ci.org/Steve Barnegren/SwiftChess)
[![Version](https://img.shields.io/cocoapods/v/SwiftChess.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
[![License](https://img.shields.io/cocoapods/l/SwiftChess.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
[![Platform](https://img.shields.io/cocoapods/p/SwiftChess.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
[![Version](https://img.shields.io/cocoapods/v/SBAutoLayout.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
[![License](https://img.shields.io/cocoapods/l/SBAutoLayout.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
[![Platform](https://img.shields.io/cocoapods/p/SBAutoLayout.svg?style=flat)](http://cocoapods.org/pods/SwiftChess)
SwiftChess is a chess engine written in Swift.
![swiftchess](https://cloud.githubusercontent.com/assets/6288713/24018928/d90f9182-0a8d-11e7-808c-a96bcb998462.gif)
## 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
View File
@@ -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
+53 -42
View File
@@ -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)
}
}
+124 -41
View File
@@ -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
View File
@@ -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
}
+139
View File
@@ -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
}
}
+21
View File
@@ -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;
}
}
+59 -5
View File
@@ -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
+6 -1
View File
@@ -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)
+170
View File
@@ -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
}
}
+13 -4
View File
@@ -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 {
+181 -92
View File
@@ -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)
+2 -1
View File
@@ -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 */,