From c181fc2d8307ddd6c27c987b86a7922a9eb2da3c Mon Sep 17 00:00:00 2001 From: Jean Bovet Date: Sun, 16 May 2021 22:00:58 -0700 Subject: [PATCH] Added preliminary support to show variations when replaying a game --- BChess.xcodeproj/project.pbxproj | 42 +++++++---- Shared/Actions.swift | 25 ++++++- Shared/Bridge/FEngine.h | 6 +- Shared/Bridge/FEngine.mm | 9 ++- Shared/Bridge/FEngineMoveNode+Private.h | 4 ++ Shared/Bridge/FEngineMoveNode.h | 4 ++ Shared/Bridge/FEngineMoveNode.mm | 4 ++ Shared/ChessDocument.swift | 19 +++-- Shared/Engine/Engine/ChessGame.cpp | 25 ++++++- Shared/Engine/Engine/ChessGame.hpp | 17 ++++- Shared/{MoveNodes.swift => FullMove.swift} | 52 ++++---------- Shared/Game.swift | 63 ++++++++++++++++ Shared/Model/Square.swift | 3 +- Shared/Model/Variation.swift | 19 +++++ Shared/Views/ContentView.swift | 1 + ...lMoveItemView.swift => FullMoveView.swift} | 10 +-- Shared/Views/InformationView.swift | 4 +- Shared/Views/PiecesView.swift | 8 +-- Shared/Views/VariationSelectionView.swift | 72 +++++++++++++++++++ 19 files changed, 310 insertions(+), 77 deletions(-) rename Shared/{MoveNodes.swift => FullMove.swift} (60%) create mode 100644 Shared/Game.swift create mode 100644 Shared/Model/Variation.swift rename Shared/Views/{FullMoveItemView.swift => FullMoveView.swift} (85%) create mode 100644 Shared/Views/VariationSelectionView.swift diff --git a/BChess.xcodeproj/project.pbxproj b/BChess.xcodeproj/project.pbxproj index 7f41d18..b9ecc25 100644 --- a/BChess.xcodeproj/project.pbxproj +++ b/BChess.xcodeproj/project.pbxproj @@ -19,9 +19,15 @@ A70A61DB1FD4D49500AFDF0E /* ChessEvaluater.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A70A61C81FD46E3C00AFDF0E /* ChessEvaluater.cpp */; }; A70A61DD1FD4D49500AFDF0E /* FFEN.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A70A61B91FD132D200AFDF0E /* FFEN.cpp */; }; A70A61DE1FD4D49F00AFDF0E /* magicmoves.c in Sources */ = {isa = PBXBuildFile; fileRef = A7712D411FCB97A900E7E802 /* magicmoves.c */; }; - A70B41A3264A527900A56E29 /* FullMoveItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70B41A2264A527900A56E29 /* FullMoveItemView.swift */; }; - A70B41A4264A527900A56E29 /* FullMoveItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70B41A2264A527900A56E29 /* FullMoveItemView.swift */; }; + A70B41A3264A527900A56E29 /* FullMoveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70B41A2264A527900A56E29 /* FullMoveView.swift */; }; + A70B41A4264A527900A56E29 /* FullMoveView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A70B41A2264A527900A56E29 /* FullMoveView.swift */; }; + A712C25326522F6000E05408 /* FullMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712C25226522F6000E05408 /* FullMove.swift */; }; + A712C25426522F6000E05408 /* FullMove.swift in Sources */ = {isa = PBXBuildFile; fileRef = A712C25226522F6000E05408 /* FullMove.swift */; }; A716976D262BFF8C00156BD6 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A716976C262BFF8C00156BD6 /* SettingsView.swift */; }; + A72C3E022650D78100CB9DB7 /* VariationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72C3E012650D78100CB9DB7 /* VariationSelectionView.swift */; }; + A72C3E032650D78100CB9DB7 /* VariationSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72C3E012650D78100CB9DB7 /* VariationSelectionView.swift */; }; + A72C3E052650D82B00CB9DB7 /* Variation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72C3E042650D82B00CB9DB7 /* Variation.swift */; }; + A72C3E062650D82C00CB9DB7 /* Variation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A72C3E042650D82B00CB9DB7 /* Variation.swift */; }; A72E2B57200496CD006CBB1C /* BoardHashTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A72E2B56200496CC006CBB1C /* BoardHashTests.cpp */; }; A731BD3720009856004C13EF /* Tournament.swift in Sources */ = {isa = PBXBuildFile; fileRef = A731BD3620009856004C13EF /* Tournament.swift */; }; A731BD392000A513004C13EF /* TournamentEngines.swift in Sources */ = {isa = PBXBuildFile; fileRef = A731BD382000A513004C13EF /* TournamentEngines.swift */; }; @@ -35,8 +41,8 @@ A75359851FDC994E008D1DEE /* FENgineInfo+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75359831FDC51B2008D1DEE /* FENgineInfo+Extension.swift */; }; A75683291FCD28A000CF1408 /* FEngine.mm in Sources */ = {isa = PBXBuildFile; fileRef = A75683281FCD28A000CF1408 /* FEngine.mm */; }; A756832D1FD0B3FE00CF1408 /* magicmoves.c in Sources */ = {isa = PBXBuildFile; fileRef = A7712D411FCB97A900E7E802 /* magicmoves.c */; }; - A757614B264A504D006242F9 /* MoveNodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A757614A264A504D006242F9 /* MoveNodes.swift */; }; - A757614C264A504D006242F9 /* MoveNodes.swift in Sources */ = {isa = PBXBuildFile; fileRef = A757614A264A504D006242F9 /* MoveNodes.swift */; }; + A757614B264A504D006242F9 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = A757614A264A504D006242F9 /* Game.swift */; }; + A757614C264A504D006242F9 /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = A757614A264A504D006242F9 /* Game.swift */; }; A757614F264A50E8006242F9 /* FEngineMoveNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = A757614E264A50E8006242F9 /* FEngineMoveNode.mm */; }; A7576150264A50E8006242F9 /* FEngineMoveNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = A757614E264A50E8006242F9 /* FEngineMoveNode.mm */; }; A7576152264A51BC006242F9 /* FEngineMoveNode+Private.h in Sources */ = {isa = PBXBuildFile; fileRef = A7576151264A51BC006242F9 /* FEngineMoveNode+Private.h */; }; @@ -212,8 +218,11 @@ A70A61C91FD46E3C00AFDF0E /* ChessEvaluater.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ChessEvaluater.hpp; sourceTree = ""; }; A70A61CF1FD4AA9600AFDF0E /* FEngineInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FEngineInfo.h; sourceTree = ""; }; A70A61D01FD4AA9600AFDF0E /* FEngineInfo.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FEngineInfo.mm; sourceTree = ""; }; - A70B41A2264A527900A56E29 /* FullMoveItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullMoveItemView.swift; sourceTree = ""; }; + A70B41A2264A527900A56E29 /* FullMoveView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullMoveView.swift; sourceTree = ""; }; + A712C25226522F6000E05408 /* FullMove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullMove.swift; sourceTree = ""; }; A716976C262BFF8C00156BD6 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; + A72C3E012650D78100CB9DB7 /* VariationSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariationSelectionView.swift; sourceTree = ""; }; + A72C3E042650D82B00CB9DB7 /* Variation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Variation.swift; sourceTree = ""; }; A72E2B56200496CC006CBB1C /* BoardHashTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BoardHashTests.cpp; sourceTree = ""; }; A731BD3620009856004C13EF /* Tournament.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tournament.swift; sourceTree = ""; }; A731BD382000A513004C13EF /* TournamentEngines.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TournamentEngines.swift; sourceTree = ""; }; @@ -228,7 +237,7 @@ A753598A1FDCF95E008D1DEE /* Coordinate.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Coordinate.hpp; sourceTree = ""; }; A75683271FCD28A000CF1408 /* FEngine.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FEngine.h; sourceTree = ""; }; A75683281FCD28A000CF1408 /* FEngine.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FEngine.mm; sourceTree = ""; }; - A757614A264A504D006242F9 /* MoveNodes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveNodes.swift; sourceTree = ""; }; + A757614A264A504D006242F9 /* Game.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Game.swift; sourceTree = ""; }; A757614D264A50E8006242F9 /* FEngineMoveNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FEngineMoveNode.h; sourceTree = ""; }; A757614E264A50E8006242F9 /* FEngineMoveNode.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FEngineMoveNode.mm; sourceTree = ""; }; A7576151264A51BC006242F9 /* FEngineMoveNode+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FEngineMoveNode+Private.h"; sourceTree = ""; }; @@ -472,6 +481,7 @@ A79515EA25ABE41000AEA95F /* Position.swift */, A795160B25ABE55E00AEA95F /* Square.swift */, A795162125ABE59900AEA95F /* PlayAgainst.swift */, + A72C3E042650D82B00CB9DB7 /* Variation.swift */, ); path = Model; sourceTree = ""; @@ -482,7 +492,8 @@ A77F66AC25A832230030E61D /* BChessUIApp.swift */, A716976C262BFF8C00156BD6 /* SettingsView.swift */, A77F66AD25A832240030E61D /* ChessDocument.swift */, - A757614A264A504D006242F9 /* MoveNodes.swift */, + A757614A264A504D006242F9 /* Game.swift */, + A712C25226522F6000E05408 /* FullMove.swift */, A795159D25ABE1D700AEA95F /* PiecesFactory.swift */, A758BB0525AC119C0092714E /* Actions.swift */, A7A30EC125AEB28D00729432 /* ActionsToolbar.swift */, @@ -569,10 +580,11 @@ A79515A825ABE31100AEA95F /* SquareView.swift */, A79515B325ABE34300AEA95F /* TopInformationView.swift */, A79515BE25ABE36700AEA95F /* InformationView.swift */, - A70B41A2264A527900A56E29 /* FullMoveItemView.swift */, + A70B41A2264A527900A56E29 /* FullMoveView.swift */, A795163C25ABE66600AEA95F /* BoardView.swift */, A79515F525ABE44000AEA95F /* PiecesView.swift */, A7A30EDC25AEBD7C00729432 /* LabelsView.swift */, + A72C3E012650D78100CB9DB7 /* VariationSelectionView.swift */, A77F66AE25A832240030E61D /* ContentView.swift */, A7A30F2E25B2BAFC00729432 /* PromotionView.swift */, A746997B2637CCF7007E0058 /* NavigationView.swift */, @@ -901,19 +913,20 @@ A7FE322A25AA96D100A75936 /* ChessBoardHash.cpp in Sources */, A77F66D725A832270030E61D /* ChessDocument.swift in Sources */, A791117C264B923600F97FA7 /* FEngineGame.mm in Sources */, + A712C25326522F6000E05408 /* FullMove.swift in Sources */, A791117F264B92E500F97FA7 /* FEngineGame+Private.h in Sources */, A795162225ABE59900AEA95F /* PlayAgainst.swift in Sources */, A746997C2637CCF7007E0058 /* NavigationView.swift in Sources */, A7FE324E25AAD61200A75936 /* FEngineUtility.mm in Sources */, A79515F625ABE44000AEA95F /* PiecesView.swift in Sources */, A7BC72E42636003F008FBBB4 /* NewGameView.swift in Sources */, - A757614B264A504D006242F9 /* MoveNodes.swift in Sources */, + A757614B264A504D006242F9 /* Game.swift in Sources */, A758BB0625AC119C0092714E /* Actions.swift in Sources */, A79515E025ABE3D300AEA95F /* Selection.swift in Sources */, A7FE320A25AA96C500A75936 /* ChessGame.cpp in Sources */, A7A30EDD25AEBD7C00729432 /* LabelsView.swift in Sources */, A7FE324F25AAD61200A75936 /* FEngineInfo.mm in Sources */, - A70B41A3264A527900A56E29 /* FullMoveItemView.swift in Sources */, + A70B41A3264A527900A56E29 /* FullMoveView.swift in Sources */, A7FE31EA25AA96B900A75936 /* ChessBoard.cpp in Sources */, A795163D25ABE66600AEA95F /* BoardView.swift in Sources */, A7FE322B25AA96D100A75936 /* FPGN.cpp in Sources */, @@ -924,9 +937,11 @@ A79515BF25ABE36700AEA95F /* InformationView.swift in Sources */, A7FE31EB25AA96B900A75936 /* MoveList.cpp in Sources */, A7FE324D25AAD61200A75936 /* FEngineMove.mm in Sources */, + A72C3E052650D82B00CB9DB7 /* Variation.swift in Sources */, A77F66D525A832270030E61D /* BChessUIApp.swift in Sources */, A79515A925ABE31100AEA95F /* SquareView.swift in Sources */, A7FE320C25AA96C500A75936 /* ChessState.cpp in Sources */, + A72C3E022650D78100CB9DB7 /* VariationSelectionView.swift in Sources */, A7FE326925AAD64500A75936 /* FEngine.mm in Sources */, A79515B425ABE34300AEA95F /* TopInformationView.swift in Sources */, A7FE320B25AA96C500A75936 /* ChessOpenings.cpp in Sources */, @@ -955,19 +970,20 @@ A7FE31DA25AA96B800A75936 /* MoveList.cpp in Sources */, A795162325ABE59900AEA95F /* PlayAgainst.swift in Sources */, A791117D264B923600F97FA7 /* FEngineGame.mm in Sources */, + A712C25426522F6000E05408 /* FullMove.swift in Sources */, A7911180264B92E500F97FA7 /* FEngineGame+Private.h in Sources */, A7FE31CB25AA96A800A75936 /* FEngineUtility.mm in Sources */, A746997D2637CCF7007E0058 /* NavigationView.swift in Sources */, A79515F725ABE44000AEA95F /* PiecesView.swift in Sources */, A758BB0725AC119C0092714E /* Actions.swift in Sources */, A716976D262BFF8C00156BD6 /* SettingsView.swift in Sources */, - A757614C264A504D006242F9 /* MoveNodes.swift in Sources */, + A757614C264A504D006242F9 /* Game.swift in Sources */, A79515E125ABE3D300AEA95F /* Selection.swift in Sources */, A7FE31DB25AA96B800A75936 /* GameHistory.cpp in Sources */, A7A30EDE25AEBD7C00729432 /* LabelsView.swift in Sources */, A7FE321C25AA96D000A75936 /* magicmoves.c in Sources */, A77F66D825A832270030E61D /* ChessDocument.swift in Sources */, - A70B41A4264A527900A56E29 /* FullMoveItemView.swift in Sources */, + A70B41A4264A527900A56E29 /* FullMoveView.swift in Sources */, A795163E25ABE66600AEA95F /* BoardView.swift in Sources */, A7FE31DC25AA96B800A75936 /* ChessEvaluater.cpp in Sources */, A79515CB25ABE38F00AEA95F /* LastMoveModifier.swift in Sources */, @@ -978,9 +994,11 @@ A7FE31FD25AA96C500A75936 /* ChessState.cpp in Sources */, A7FE321925AA96D000A75936 /* FFEN.cpp in Sources */, A7BC72E52636003F008FBBB4 /* NewGameView.swift in Sources */, + A72C3E062650D82C00CB9DB7 /* Variation.swift in Sources */, A77F66D625A832270030E61D /* BChessUIApp.swift in Sources */, A79515AA25ABE31100AEA95F /* SquareView.swift in Sources */, A7FE31D925AA96B800A75936 /* ChessBoard.cpp in Sources */, + A72C3E032650D78100CB9DB7 /* VariationSelectionView.swift in Sources */, A7FE31A525AA963F00A75936 /* FEngine.mm in Sources */, A79515B525ABE34300AEA95F /* TopInformationView.swift in Sources */, A77F66DA25A832270030E61D /* ContentView.swift in Sources */, diff --git a/Shared/Actions.swift b/Shared/Actions.swift index acc588b..907226c 100644 --- a/Shared/Actions.swift +++ b/Shared/Actions.swift @@ -73,8 +73,29 @@ struct Actions { engine.cancel() } - engine.move(to: to) - document.pgn = document.engine.pgnAllGames() + if document.variations.show { + // If the variations are being show, then move using the selected variation index + engine.move(to: to, variation: UInt(document.variations.selectedVariationIndex)) + document.pgn = document.engine.pgnAllGames() + + // Hide the variations + document.variations.show = false + document.variations.selectedVariationIndex = 0 + } else { + // Get the next move UUID + let nextMoveUUID = UInt(engine.moveUUID(to)) + if document.game.hasVariations(moveUUID: nextMoveUUID) { + // If that move has variations - that is, more than one move possible, + // the show these variations to the user who can select one of them. + document.variations.show = true + document.variations.variations = document.game.variations(moveUUID: nextMoveUUID) + } else { + // That move has no variations + document.variations.show = false + engine.move(to: to, variation: 0) + document.pgn = document.engine.pgnAllGames() + } + } } } diff --git a/Shared/Bridge/FEngine.h b/Shared/Bridge/FEngine.h index 2912f97..3d2cd83 100644 --- a/Shared/Bridge/FEngine.h +++ b/Shared/Bridge/FEngine.h @@ -62,8 +62,12 @@ typedef NS_ENUM(NSInteger, Direction){ - (NSArray* _Nonnull)movesAt:(NSUInteger)rank file:(NSUInteger)file; - (void)move:(NSUInteger)move; +// Returns YES if a move in the specified direction can be performed - (BOOL)canMoveTo:(Direction)direction; -- (void)moveTo:(Direction)direction; +// Perform the next move given the direction and variation index +- (void)moveTo:(Direction)direction variation:(NSUInteger)variation; +// Returns the next move UUID given the direction +- (UInt)moveUUID:(Direction)direction; - (void)move:(NSString* _Nonnull)from to:(NSString* _Nonnull)to; diff --git a/Shared/Bridge/FEngine.mm b/Shared/Bridge/FEngine.mm index 1b8839e..52c657a 100644 --- a/Shared/Bridge/FEngine.mm +++ b/Shared/Bridge/FEngine.mm @@ -223,13 +223,18 @@ - (BOOL)canMoveTo:(Direction)direction { return engine.game().canMoveTo([self gameDirection:direction]); } -- (void)moveTo:(Direction)direction { +- (void)moveTo:(Direction)direction variation:(NSUInteger)variation { // TODO: handle the cancel with a callback when the cancel actually really happened [self cancel]; - engine.game().moveTo([self gameDirection:direction]); + engine.game().moveTo([self gameDirection:direction], (unsigned int)variation); [self fireUpdate:self.stateIndex]; } +- (UInt)moveUUID:(Direction)direction { + auto nextIndexes = engine.game().moveIndexesTo([self gameDirection:direction]); + return engine.game().getMoveUUID(nextIndexes); +} + #pragma mark - - (void)stop { diff --git a/Shared/Bridge/FEngineMoveNode+Private.h b/Shared/Bridge/FEngineMoveNode+Private.h index d85fb3a..fc3b2f0 100644 --- a/Shared/Bridge/FEngineMoveNode+Private.h +++ b/Shared/Bridge/FEngineMoveNode+Private.h @@ -16,6 +16,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readwrite) NSUInteger moveNumber; @property (nonatomic, readwrite) BOOL whiteMove; @property (nonatomic, readwrite) NSUInteger uuid; +@property (nonatomic, readwrite) NSUInteger fromRank; +@property (nonatomic, readwrite) NSUInteger toRank; +@property (nonatomic, readwrite) NSUInteger fromFile; +@property (nonatomic, readwrite) NSUInteger toFile; @property (nonatomic, strong, readwrite) NSString * _Nonnull name; @property (nonatomic, strong, readwrite) NSString * _Nonnull comment; @property (nonatomic, strong, readwrite) NSMutableArray * _Nonnull variations; diff --git a/Shared/Bridge/FEngineMoveNode.h b/Shared/Bridge/FEngineMoveNode.h index 1623e10..7e59f66 100644 --- a/Shared/Bridge/FEngineMoveNode.h +++ b/Shared/Bridge/FEngineMoveNode.h @@ -14,6 +14,10 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, readonly) NSUInteger moveNumber; @property (nonatomic, readonly) BOOL whiteMove; @property (nonatomic, readonly) NSUInteger uuid; +@property (nonatomic, readonly) NSUInteger fromFile; +@property (nonatomic, readonly) NSUInteger fromRank; +@property (nonatomic, readonly) NSUInteger toFile; +@property (nonatomic, readonly) NSUInteger toRank; @property (nonatomic, strong, readonly) NSString * _Nonnull name; @property (nonatomic, strong, readonly) NSString * _Nonnull comment; @property (nonatomic, strong, readonly) NSArray * _Nonnull variations; diff --git a/Shared/Bridge/FEngineMoveNode.mm b/Shared/Bridge/FEngineMoveNode.mm index ca58e66..921c99b 100644 --- a/Shared/Bridge/FEngineMoveNode.mm +++ b/Shared/Bridge/FEngineMoveNode.mm @@ -19,6 +19,10 @@ - (instancetype)initWithNode:(ChessGame::MoveNode)node { if (self = [super init]) { self.moveNumber = node.moveNumber; self.whiteMove = MOVE_COLOR(node.move) == WHITE; + self.fromFile = FileFrom(MOVE_FROM(node.move)); + self.fromRank = RankFrom(MOVE_FROM(node.move)); + self.toFile = FileFrom(MOVE_TO(node.move)); + self.toRank = RankFrom(MOVE_TO(node.move)); self.uuid = node.uuid; self.name = NSStringFromString(FPGN::to_string(node.move, FPGN::SANType::tight)); self.comment = NSStringFromString(node.comment); diff --git a/Shared/ChessDocument.swift b/Shared/ChessDocument.swift index 545b77c..f33c109 100644 --- a/Shared/ChessDocument.swift +++ b/Shared/ChessDocument.swift @@ -48,6 +48,16 @@ struct GameMode { var value: Value = .play } +// Structure used to hold information about the current variations +// that the user can choose a move from. This is used when the user +// move forward in a game and a choice must be made because more than +// one move is available as the next move. +struct Variations { + var show = false + var selectedVariationIndex = 0 + var variations = [FEngineMoveNode]() +} + struct ChessDocument: FileDocument { static let startPosPGN = "*" @@ -56,14 +66,14 @@ struct ChessDocument: FileDocument { var pgn: String { didSet { - moveNodes.rebuild(engine: engine) + game.rebuild(engine: engine) } } var currentGameIndex: UInt = 0 { didSet { engine.currentGameIndex = currentGameIndex - moveNodes.rebuild(engine: engine) + game.rebuild(engine: engine) } } @@ -75,7 +85,8 @@ struct ChessDocument: FileDocument { var selection = Selection.empty() var lastMove: FEngineMove? = nil var info: FEngineInfo? = nil - var moveNodes = MoveNodes() + var game = Game() + var variations = Variations() var mode: GameMode @@ -96,7 +107,7 @@ struct ChessDocument: FileDocument { self.rotated = rotated self.mode = mode - moveNodes.rebuild(engine: engine) + game.rebuild(engine: engine) loadOpenings() } diff --git a/Shared/Engine/Engine/ChessGame.cpp b/Shared/Engine/Engine/ChessGame.cpp index 48f03a4..f417d85 100644 --- a/Shared/Engine/Engine/ChessGame.cpp +++ b/Shared/Engine/Engine/ChessGame.cpp @@ -143,25 +143,48 @@ bool ChessGame::canMoveTo(Direction direction) { } } -void ChessGame::moveTo(Direction direction) { +void ChessGame::moveTo(Direction direction, unsigned variationIndex) { assert(canMoveTo(direction)); switch (direction) { case Direction::start: moveIndexes.moveCursor = 0; + moveIndexes.resetToMainVariation(); break; case Direction::end: moveIndexes.moveCursor = (int)moveIndexes.moves.size(); break; case Direction::backward: moveIndexes.moveCursor--; + moveIndexes.resetToMainVariation(); break; case Direction::forward: + moveIndexes.moves[moveIndexes.moveCursor] = variationIndex; moveIndexes.moveCursor++; break; } replayMoves(); } +ChessGame::MoveIndexes ChessGame::moveIndexesTo(Direction direction) { + assert(canMoveTo(direction)); + MoveIndexes newIndexes = moveIndexes; + switch (direction) { + case Direction::start: + newIndexes.moveCursor = 0; + break; + case Direction::end: + newIndexes.moveCursor = (int)newIndexes.moves.size(); + break; + case Direction::backward: + newIndexes.moveCursor--; + break; + case Direction::forward: + newIndexes.moveCursor++; + break; + } + return newIndexes; +} + void ChessGame::replayMoves() { board.reset(); auto result = FFEN::setFEN(initialFEN, board); diff --git a/Shared/Engine/Engine/ChessGame.hpp b/Shared/Engine/Engine/ChessGame.hpp index fc95998..e2f5040 100644 --- a/Shared/Engine/Engine/ChessGame.hpp +++ b/Shared/Engine/Engine/ChessGame.hpp @@ -41,6 +41,14 @@ class ChessGame { moveCursor++; } + // Reset the move path identifiers to only use the main variation + void resetToMainVariation() { + for (int index=0; index String { let nc = comment.components(separatedBy: .newlines) .joined(separator: " ") @@ -50,7 +54,7 @@ final class FullMoveItem: Identifiable { } } - func canMergeWithNext(item: FullMoveItem) -> Bool { + func canMergeWithNext(item: FullMove) -> Bool { guard let wm = whiteMove else { return false } @@ -76,9 +80,9 @@ final class FullMoveItem: Identifiable { } } -extension Array where Element == FullMoveItem { +extension Array where Element == FullMove { - mutating func add(element: FullMoveItem) { + mutating func add(element: FullMove) { if let lastElement = last { if lastElement.hasChildren { append(element) @@ -93,35 +97,3 @@ extension Array where Element == FullMoveItem { } } } - -class MoveNodes: ObservableObject { - - @Published var moveNodes = [FullMoveItem]() - - func rebuild(engine: FEngine) { - moveNodes.removeAll() - moveNodes = moveItems(engine: engine) - } - - func moveItems(engine: FEngine) -> [FullMoveItem] { - var items = [FullMoveItem]() - for node in engine.moveNodesTree() { - items.add(element: moveItems(from: node, engine: engine)) - } - return items - } - - func moveItems(from: FEngineMoveNode, engine: FEngine) -> FullMoveItem { - var children = [FullMoveItem]() - for childNode in from.variations { - let child = moveItems(from: childNode, engine: engine) - children.add(element: child) - } - let item = FullMoveItem() - item.whiteMove = from.whiteMove ? from : nil - item.blackMove = from.whiteMove ? nil : from - item.children = children.isEmpty ? nil : children - return item - } - -} diff --git a/Shared/Game.swift b/Shared/Game.swift new file mode 100644 index 0000000..8a84d54 --- /dev/null +++ b/Shared/Game.swift @@ -0,0 +1,63 @@ +// +// FullMove.swift +// BChess +// +// Created by Jean Bovet on 5/10/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +import SwiftUI + +final class Game: ObservableObject { + + @Published var moves = [FullMove]() + + func rebuild(engine: FEngine) { + moves.removeAll() + moves = moveItems(engine: engine) + } + + func moveItems(engine: FEngine) -> [FullMove] { + var items = [FullMove]() + for node in engine.moveNodesTree() { + items.add(element: moveItems(from: node, engine: engine)) + } + return items + } + + func moveItems(from: FEngineMoveNode, engine: FEngine) -> FullMove { + var children = [FullMove]() + for childNode in from.variations { + let child = moveItems(from: childNode, engine: engine) + children.add(element: child) + } + let item = FullMove(id: "\(from.uuid)") + item.whiteMove = from.whiteMove ? from : nil + item.blackMove = from.whiteMove ? nil : from + item.children = children.isEmpty ? nil : children + return item + } + + func hasVariations(moveUUID: UInt) -> Bool { + return variations(moveUUID: moveUUID).count > 1 + } + + func variations(moveUUID: UInt) -> [FEngineMoveNode] { + let nodes = moves.compactMap { node -> FEngineMoveNode? in + if let wm = node.whiteMove, wm.uuid == moveUUID { + return wm + } + if let bm = node.blackMove, bm.uuid == moveUUID { + return bm + } + return nil + } + + if let node = nodes.first { + return [node] + node.variations + } else { + return [] + } + } + +} diff --git a/Shared/Model/Square.swift b/Shared/Model/Square.swift index 735f9ee..4e06d68 100644 --- a/Shared/Model/Square.swift +++ b/Shared/Model/Square.swift @@ -12,7 +12,6 @@ struct Square: Identifiable { var id: String { return piece?.name ?? "\(UUID())" } - let rank: Int - let file: Int + let position: Position let piece: Piece? } diff --git a/Shared/Model/Variation.swift b/Shared/Model/Variation.swift new file mode 100644 index 0000000..83462d4 --- /dev/null +++ b/Shared/Model/Variation.swift @@ -0,0 +1,19 @@ +// +// Variation.swift +// BChess +// +// Created by Jean Bovet on 5/15/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +import Foundation + +struct Variation: Identifiable { + var id: String { + return "\(index)" + } + let index: Int + let from: Position + let to: Position + let label: String +} diff --git a/Shared/Views/ContentView.swift b/Shared/Views/ContentView.swift index 3963645..1fc558d 100644 --- a/Shared/Views/ContentView.swift +++ b/Shared/Views/ContentView.swift @@ -30,6 +30,7 @@ struct ContentView: View { } LabelsView(document: $document) PiecesView(document: $document) + VariationSelectionView(document: $document) } .padding() .padding(.bottom, 20) // Because the labels are "leaking" a bit below the board space itself diff --git a/Shared/Views/FullMoveItemView.swift b/Shared/Views/FullMoveView.swift similarity index 85% rename from Shared/Views/FullMoveItemView.swift rename to Shared/Views/FullMoveView.swift index 61d69ce..67abf0c 100644 --- a/Shared/Views/FullMoveItemView.swift +++ b/Shared/Views/FullMoveView.swift @@ -1,5 +1,5 @@ // -// FullMoveItemView.swift +// FullMoveView.swift // BChess // // Created by Jean Bovet on 5/10/21. @@ -8,9 +8,9 @@ import SwiftUI -struct FullMoveItemView: View { +struct FullMoveView: View { - var item: FullMoveItem + var item: FullMove var currentMoveUUID: UInt func moveNameText(node: FEngineMoveNode) -> Text { @@ -48,8 +48,8 @@ struct FullMoveItemView: View { } } -struct FullMoveItemView_Previews: PreviewProvider { +struct FullMoveView_Previews: PreviewProvider { static var previews: some View { - FullMoveItemView(item: FullMoveItem(), currentMoveUUID: 0) + FullMoveView(item: FullMove(id: "foo"), currentMoveUUID: 0) } } diff --git a/Shared/Views/InformationView.swift b/Shared/Views/InformationView.swift index ad16db2..9497c6d 100644 --- a/Shared/Views/InformationView.swift +++ b/Shared/Views/InformationView.swift @@ -74,8 +74,8 @@ struct InformationView: View { } } - List(document.moveNodes.moveNodes, children: \FullMoveItem.children) { item in - FullMoveItemView(item: item, currentMoveUUID: document.engine.currentMoveNodeUUID()) + List(document.game.moves, children: \FullMove.children) { item in + FullMoveView(item: item, currentMoveUUID: document.engine.currentMoveNodeUUID()) } Spacer() diff --git a/Shared/Views/PiecesView.swift b/Shared/Views/PiecesView.swift index ba08fe5..d596703 100644 --- a/Shared/Views/PiecesView.swift +++ b/Shared/Views/PiecesView.swift @@ -56,7 +56,7 @@ struct PiecesView: View { for rank in 0...7 { for file in 0...7 { let piece = pieces.piece(atRank: rank, file: file) - let square = Square(rank: rank, file: file, piece: piece) + let square = Square(position: Position(rank: rank, file: file), piece: piece) squares.append(square) } } @@ -128,14 +128,14 @@ struct PiecesView: View { let yOffset: CGFloat = (geometry.size.height - minSize) / 2 let b: [Square] = board(withPieces: document.pieces) ForEach(b) { square in - let x = CGFloat(square.file.actual(rotated: document.rotated)) * squareSize + xOffset - let y = CGFloat(7 - square.rank.actual(rotated: document.rotated)) * squareSize + yOffset + let x = CGFloat(square.position.file.actual(rotated: document.rotated)) * squareSize + xOffset + let y = CGFloat(7 - square.position.rank.actual(rotated: document.rotated)) * squareSize + yOffset SquareView(piece: square.piece) .frame(width: squareSize, height: squareSize) .offset(x: x, y: y) .onTapGesture { - processTap(square.rank, square.file) + processTap(square.position.rank, square.position.file) } } } diff --git a/Shared/Views/VariationSelectionView.swift b/Shared/Views/VariationSelectionView.swift new file mode 100644 index 0000000..21fb710 --- /dev/null +++ b/Shared/Views/VariationSelectionView.swift @@ -0,0 +1,72 @@ +// +// VariationSelectionView.swift +// BChess +// +// Created by Jean Bovet on 5/15/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +import SwiftUI + +struct VariationSelectionView: View { + + @Binding var document: ChessDocument + + func variations(from: [FEngineMoveNode]) -> [Variation] { + var variations = [Variation]() + for (i, v) in from.enumerated() { + variations.append(Variation(index: i, + from: Position(rank: Int(v.fromRank), file: Int(v.fromFile)), + to: Position(rank: Int(v.toRank), file: Int(v.toFile)), + label: "Nxe2")) + } + return variations + } + + var body: some View { + if document.variations.show { + GeometryReader { geometry in + let minSize: CGFloat = min(geometry.size.width, geometry.size.height) + let squareSize: CGFloat = minSize / CGFloat(numberOfSquares) + let xOffset: CGFloat = (geometry.size.width - minSize) / 2 + squareSize / 2 + let yOffset: CGFloat = (geometry.size.height - minSize) / 2 + squareSize / 2 + + ForEach(variations(from: document.variations.variations)) { v in + let x1 = CGFloat(v.from.file.actual(rotated: document.rotated)) * squareSize + xOffset + let y1 = CGFloat(7 - v.from.rank.actual(rotated: document.rotated)) * squareSize + yOffset + let x2 = CGFloat(v.to.file.actual(rotated: document.rotated)) * squareSize + xOffset + let y2 = CGFloat(7 - v.to.rank.actual(rotated: document.rotated)) * squareSize + yOffset + + Rectangle() + .background(Color.blue) + .opacity(0.2) + .frame(width: squareSize, height: squareSize, alignment: .center) + .offset(x: x2 - squareSize/2, y: y2 - squareSize/2) + .onTapGesture { + document.variations.selectedVariationIndex = v.index + Actions(document: $document).move(to: .forward) + } + + Path { path in + path.move(to: CGPoint(x: x1, y: y1)) + path.addLine(to: CGPoint(x: x2, y: y2)) + } + .stroke(Color.blue, lineWidth: 10) + } + } + } + } +} + +struct VariationSelectionView_Previews: PreviewProvider { + static var previews: some View { + Group { + let doc = ChessDocument() + ZStack { + BoardView(document: .constant(doc)) + PiecesView(document: .constant(doc)) + VariationSelectionView(document: .constant(doc)) + } + } + } +}