From fa84584d14552a38f9fc091e0b118a87ee458322 Mon Sep 17 00:00:00 2001 From: Jean Bovet Date: Tue, 11 May 2021 22:08:28 -0700 Subject: [PATCH] Added support for multiple games for each PGN file --- BChess.xcodeproj/project.pbxproj | 26 +++++++++++- BChess/BChess-Bridging-Header.h | 1 + BChess/UCI/Tournament.swift | 4 +- BChessTests/FEngineTests.swift | 2 +- Shared/Actions.swift | 10 ++--- Shared/ActionsToolbar.swift | 14 +++++++ Shared/Bridge/FEngine.h | 8 +++- Shared/Bridge/FEngine.mm | 49 ++++++++++++++++++---- Shared/Bridge/FEngineGame+Private.h | 21 ++++++++++ Shared/Bridge/FEngineGame.h | 20 +++++++++ Shared/Bridge/FEngineGame.mm | 23 +++++++++++ Shared/ChessDocument.swift | 15 +++++-- Shared/Engine/Engine/ChessEngine.hpp | 61 +++++++++++++++++++--------- Shared/Engine/Helpers/FPGN.cpp | 34 +++++++++++----- Shared/Engine/Helpers/FPGN.hpp | 2 + Shared/Views/ContentView.swift | 5 +-- Shared/Views/InformationView.swift | 17 ++++++-- Shared/Views/PiecesView.swift | 4 +- 18 files changed, 254 insertions(+), 62 deletions(-) create mode 100644 Shared/Bridge/FEngineGame+Private.h create mode 100644 Shared/Bridge/FEngineGame.h create mode 100644 Shared/Bridge/FEngineGame.mm diff --git a/BChess.xcodeproj/project.pbxproj b/BChess.xcodeproj/project.pbxproj index 0f7d579..7f41d18 100644 --- a/BChess.xcodeproj/project.pbxproj +++ b/BChess.xcodeproj/project.pbxproj @@ -75,6 +75,15 @@ A77F66DC25A832270030E61D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A77F66AF25A832270030E61D /* Assets.xcassets */; }; A78E754C202C080E00445360 /* MovesTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A78E754B202C080E00445360 /* MovesTests.cpp */; }; A78E7551202C110200445360 /* UnitTestHelper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A78E754F202C110200445360 /* UnitTestHelper.cpp */; }; + A791117C264B923600F97FA7 /* FEngineGame.mm in Sources */ = {isa = PBXBuildFile; fileRef = A791117B264B923600F97FA7 /* FEngineGame.mm */; }; + A791117D264B923600F97FA7 /* FEngineGame.mm in Sources */ = {isa = PBXBuildFile; fileRef = A791117B264B923600F97FA7 /* FEngineGame.mm */; }; + A791117F264B92E500F97FA7 /* FEngineGame+Private.h in Sources */ = {isa = PBXBuildFile; fileRef = A791117E264B92E500F97FA7 /* FEngineGame+Private.h */; }; + A7911180264B92E500F97FA7 /* FEngineGame+Private.h in Sources */ = {isa = PBXBuildFile; fileRef = A791117E264B92E500F97FA7 /* FEngineGame+Private.h */; }; + A7911181264B98DC00F97FA7 /* FEngineGame.mm in Sources */ = {isa = PBXBuildFile; fileRef = A791117B264B923600F97FA7 /* FEngineGame.mm */; }; + A7911183264B98E600F97FA7 /* FEngineMoveNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = A757614E264A50E8006242F9 /* FEngineMoveNode.mm */; }; + A7911184264B998900F97FA7 /* FEngineMoveNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = A757614E264A50E8006242F9 /* FEngineMoveNode.mm */; }; + A7911185264B998900F97FA7 /* FEngineGame.mm in Sources */ = {isa = PBXBuildFile; fileRef = A791117B264B923600F97FA7 /* FEngineGame.mm */; }; + A7911186264B998900F97FA7 /* FEngineMove.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7C92AB31FF86BF200160D2E /* FEngineMove.mm */; }; A79514EC25AAE2C400AEA95F /* FENgineInfo+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75359831FDC51B2008D1DEE /* FENgineInfo+Extension.swift */; }; A79514F925AAE2C500AEA95F /* FENgineInfo+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A75359831FDC51B2008D1DEE /* FENgineInfo+Extension.swift */; }; A795159E25ABE1D700AEA95F /* PiecesFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A795159D25ABE1D700AEA95F /* PiecesFactory.swift */; }; @@ -113,7 +122,6 @@ A7BC72E42636003F008FBBB4 /* NewGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7BC72E32636003F008FBBB4 /* NewGameView.swift */; }; A7BC72E52636003F008FBBB4 /* NewGameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7BC72E32636003F008FBBB4 /* NewGameView.swift */; }; A7C92AAB1FF6F19100160D2E /* Openings.pgn in Resources */ = {isa = PBXBuildFile; fileRef = A7C92AA91FF6EE5500160D2E /* Openings.pgn */; }; - A7C92AB41FF86BF200160D2E /* FEngineMove.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7C92AB31FF86BF200160D2E /* FEngineMove.mm */; }; A7C92AB51FF86C4300160D2E /* FEngineMove.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7C92AB31FF86BF200160D2E /* FEngineMove.mm */; }; A7C92AB91FF86F6800160D2E /* FEngineUtility.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7C92AB81FF86F6800160D2E /* FEngineUtility.mm */; }; A7C92ABB1FF8712800160D2E /* FEngineUtility.mm in Sources */ = {isa = PBXBuildFile; fileRef = A7C92AB81FF86F6800160D2E /* FEngineUtility.mm */; }; @@ -270,6 +278,9 @@ A78E754B202C080E00445360 /* MovesTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MovesTests.cpp; sourceTree = ""; }; A78E754F202C110200445360 /* UnitTestHelper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = UnitTestHelper.cpp; sourceTree = ""; }; A78E7550202C110200445360 /* UnitTestHelper.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = UnitTestHelper.hpp; sourceTree = ""; }; + A791117A264B923600F97FA7 /* FEngineGame.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FEngineGame.h; sourceTree = ""; }; + A791117B264B923600F97FA7 /* FEngineGame.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FEngineGame.mm; sourceTree = ""; }; + A791117E264B92E500F97FA7 /* FEngineGame+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FEngineGame+Private.h"; sourceTree = ""; }; A795159D25ABE1D700AEA95F /* PiecesFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PiecesFactory.swift; sourceTree = ""; }; A79515A825ABE31100AEA95F /* SquareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SquareView.swift; sourceTree = ""; }; A79515B325ABE34300AEA95F /* TopInformationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopInformationView.swift; sourceTree = ""; }; @@ -362,6 +373,9 @@ A7C92AB31FF86BF200160D2E /* FEngineMove.mm */, A75683271FCD28A000CF1408 /* FEngine.h */, A75683281FCD28A000CF1408 /* FEngine.mm */, + A791117A264B923600F97FA7 /* FEngineGame.h */, + A791117E264B92E500F97FA7 /* FEngineGame+Private.h */, + A791117B264B923600F97FA7 /* FEngineGame.mm */, A757614D264A50E8006242F9 /* FEngineMoveNode.h */, A7576151264A51BC006242F9 /* FEngineMoveNode+Private.h */, A757614E264A50E8006242F9 /* FEngineMoveNode.mm */, @@ -843,6 +857,7 @@ A78E754C202C080E00445360 /* MovesTests.cpp in Sources */, A7712D341FC895D100E7E802 /* UCI.swift in Sources */, A70A61DA1FD4D49500AFDF0E /* ChessBoard.cpp in Sources */, + A7911183264B98E600F97FA7 /* FEngineMoveNode.mm in Sources */, A75CB25D1FED8F82005487BD /* EvaluationTests.cpp in Sources */, A7E490EC1FEA23DD00970EAD /* PGNTests.cpp in Sources */, A7E490ED1FEA29B900970EAD /* GamesTests.swift in Sources */, @@ -854,6 +869,7 @@ A75359851FDC994E008D1DEE /* FENgineInfo+Extension.swift in Sources */, A7C92AB51FF86C4300160D2E /* FEngineMove.mm in Sources */, A70A61D51FD4D42100AFDF0E /* FEngineInfo.mm in Sources */, + A7911181264B98DC00F97FA7 /* FEngineGame.mm in Sources */, A7688F64204B73BF004B1E9E /* StateTests.cpp in Sources */, A7E490F21FEA2E7E00970EAD /* GoogleTests.mm in Sources */, A7EF55C91FF1CF77004CF2DA /* BestMoveTests.cpp in Sources */, @@ -884,6 +900,8 @@ A795160C25ABE55E00AEA95F /* Square.swift in Sources */, A7FE322A25AA96D100A75936 /* ChessBoardHash.cpp in Sources */, A77F66D725A832270030E61D /* ChessDocument.swift in Sources */, + A791117C264B923600F97FA7 /* FEngineGame.mm in Sources */, + A791117F264B92E500F97FA7 /* FEngineGame+Private.h in Sources */, A795162225ABE59900AEA95F /* PlayAgainst.swift in Sources */, A746997C2637CCF7007E0058 /* NavigationView.swift in Sources */, A7FE324E25AAD61200A75936 /* FEngineUtility.mm in Sources */, @@ -936,6 +954,8 @@ A7FE31CA25AA96A800A75936 /* FEngineInfo.mm in Sources */, A7FE31DA25AA96B800A75936 /* MoveList.cpp in Sources */, A795162325ABE59900AEA95F /* PlayAgainst.swift in Sources */, + A791117D264B923600F97FA7 /* FEngineGame.mm in Sources */, + A7911180264B92E500F97FA7 /* FEngineGame+Private.h in Sources */, A7FE31CB25AA96A800A75936 /* FEngineUtility.mm in Sources */, A746997D2637CCF7007E0058 /* NavigationView.swift in Sources */, A79515F725ABE44000AEA95F /* PiecesView.swift in Sources */, @@ -999,13 +1019,14 @@ A7712D2F1FC7C4CD00E7E802 /* UCI.swift in Sources */, A77372011FE320010001A90F /* FPGN.cpp in Sources */, A746C2492001707F001F4437 /* GameHistory.cpp in Sources */, + A7911186264B998900F97FA7 /* FEngineMove.mm in Sources */, A70A61BB1FD132D200AFDF0E /* FFEN.cpp in Sources */, A75359841FDC51B2008D1DEE /* FENgineInfo+Extension.swift in Sources */, A756832D1FD0B3FE00CF1408 /* magicmoves.c in Sources */, A7712D471FCBC13100E7E802 /* ChessBoard.cpp in Sources */, + A7911184264B998900F97FA7 /* FEngineMoveNode.mm in Sources */, A77371FE1FE31FEF0001A90F /* ChessGame.cpp in Sources */, A7688F60204B6E91004B1E9E /* ChessState.cpp in Sources */, - A7C92AB41FF86BF200160D2E /* FEngineMove.mm in Sources */, A70A61CA1FD46E3C00AFDF0E /* ChessEvaluater.cpp in Sources */, A70A61C41FD458DE00AFDF0E /* ChessMoveGenerator.cpp in Sources */, A70A61D11FD4AA9600AFDF0E /* FEngineInfo.mm in Sources */, @@ -1014,6 +1035,7 @@ A7C92AB91FF86F6800160D2E /* FEngineUtility.mm in Sources */, A731BD392000A513004C13EF /* TournamentEngines.swift in Sources */, A731BD3720009856004C13EF /* Tournament.swift in Sources */, + A7911185264B998900F97FA7 /* FEngineGame.mm in Sources */, A746C244200148E9001F4437 /* ChessBoardHash.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/BChess/BChess-Bridging-Header.h b/BChess/BChess-Bridging-Header.h index 01ad5df..3b6f235 100644 --- a/BChess/BChess-Bridging-Header.h +++ b/BChess/BChess-Bridging-Header.h @@ -6,3 +6,4 @@ #import "FEngineMove.h" #import "FEngineInfo.h" #import "FEngineMoveNode.h" +#import "FEngineGame.h" diff --git a/BChess/UCI/Tournament.swift b/BChess/UCI/Tournament.swift index 2a105d8..92ca425 100644 --- a/BChess/UCI/Tournament.swift +++ b/BChess/UCI/Tournament.swift @@ -10,7 +10,7 @@ import Foundation typealias Callback = () -> Void -protocol Player : class { +protocol Player : AnyObject { var state: String { get set } @@ -37,7 +37,7 @@ class PlayerChessEngine: Player { engine.setPGN(newValue) } get { - return engine.pgn() + return engine.getPGNCurrentGame() } } diff --git a/BChessTests/FEngineTests.swift b/BChessTests/FEngineTests.swift index 4f7385f..dd79193 100644 --- a/BChessTests/FEngineTests.swift +++ b/BChessTests/FEngineTests.swift @@ -1,5 +1,5 @@ // -// FEngine.swift +// FEngineTests.swift // BChessTests // // Created by Jean Bovet on 5/10/21. diff --git a/Shared/Actions.swift b/Shared/Actions.swift index 7926992..acc588b 100644 --- a/Shared/Actions.swift +++ b/Shared/Actions.swift @@ -50,7 +50,7 @@ struct Actions { func newGame() { engine.setFEN(StartPosFEN) document.info = nil - document.pgn = engine.pgn() + document.pgn = engine.pgnAllGames() document.selection = Selection.empty() document.lastMove = nil @@ -74,7 +74,7 @@ struct Actions { } engine.move(to: to) - document.pgn = document.engine.pgn() + document.pgn = document.engine.pgnAllGames() } } @@ -102,7 +102,7 @@ struct Actions { #if os(macOS) let pb = NSPasteboard.general pb.declareTypes([.string], owner: nil) - pb.setString(engine.pgn(), forType: .string) + pb.setString(engine.getPGNCurrentGame(), forType: .string) #else UIPasteboard.general.string = engine.pgn() #endif @@ -119,7 +119,7 @@ struct Actions { } #endif if engine.setFEN(content) { - document.pgn = document.engine.pgn() + document.pgn = document.engine.pgnAllGames() return true } else { return false @@ -137,7 +137,7 @@ struct Actions { } #endif if engine.setPGN(content) { - document.pgn = document.engine.pgn() + document.pgn = document.engine.pgnAllGames() return true } else { return false diff --git a/Shared/ActionsToolbar.swift b/Shared/ActionsToolbar.swift index c64fd95..debf1cd 100644 --- a/Shared/ActionsToolbar.swift +++ b/Shared/ActionsToolbar.swift @@ -118,6 +118,18 @@ struct CopyPasteMenu: View { } } +struct GameSelectionMenu: View { + @Binding var document: ChessDocument + + var body: some View { + Picker(selection: $document.currentGameIndex, label: Text("Games")) { + ForEach(document.engine.games, id:\.self) { game in + Text(game.name).tag(game.index) + } + } + } +} + struct ActionsToolbar: ToolbarContent { @Binding var document: ChessDocument @@ -132,6 +144,8 @@ struct ActionsToolbar: ToolbarContent { NewGameButton(document: $document, showNewGameSheet: $showNewGameSheet, newGameSheetEditMode: $newGameSheetEditMode) EditGameButton(document: $document, showNewGameSheet: $showNewGameSheet, newGameSheetEditMode: $newGameSheetEditMode) + GameSelectionMenu(document: $document) + Divider() AnalyzeBoard(document: $document) diff --git a/Shared/Bridge/FEngine.h b/Shared/Bridge/FEngine.h index 5942bfe..2912f97 100644 --- a/Shared/Bridge/FEngine.h +++ b/Shared/Bridge/FEngine.h @@ -11,6 +11,7 @@ @class FEngineMove; @class FEngineInfo; @class FEngineMoveNode; +@class FEngineGame; typedef void(^FEngineSearchCallback)(FEngineInfo * _Nonnull info, BOOL completed); typedef void(^FEngineDidUpdateCallback)(); @@ -36,6 +37,9 @@ typedef NS_ENUM(NSInteger, Direction){ @property (nonatomic, strong, readonly) NSString * _Nonnull state; +@property (nonatomic, strong, readonly) NSArray* _Nonnull games; +@property (nonatomic, assign) NSUInteger currentGameIndex; + - (id _Nonnull)init; - (BOOL)loadOpening:(NSString* _Nonnull)pgn; @@ -45,8 +49,10 @@ typedef NS_ENUM(NSInteger, Direction){ - (BOOL)setFEN:(NSString* _Nonnull)FEN; - (NSString* _Nonnull)FEN; +- (BOOL)loadAllGames:(NSString* _Nonnull)PGN; - (BOOL)setPGN:(NSString* _Nonnull)PGN; -- (NSString* _Nonnull)PGN; +- (NSString* _Nonnull)pgnAllGames; +- (NSString* _Nonnull)getPGNCurrentGame; - (NSString* _Nonnull)PGNFormattedForDisplay; - (NSArray* _Nonnull)moveNodesTree; diff --git a/Shared/Bridge/FEngine.mm b/Shared/Bridge/FEngine.mm index 875d44c..1b8839e 100644 --- a/Shared/Bridge/FEngine.mm +++ b/Shared/Bridge/FEngine.mm @@ -9,6 +9,7 @@ #import "FEngine.h" #import "FEngineInfo+Private.h" #import "FEngineMove.h" +#import "FEngineGame+Private.h" #import "FEngineMoveNode.h" #import "FEngineMoveNode+Private.h" #import "FEngineInfo.h" @@ -34,6 +35,9 @@ @interface FEngine () { @implementation FEngine +@synthesize games; +@synthesize currentGameIndex; + + (void)initialize { if (self == [FEngine class]) { ChessEngine::initialize(); @@ -89,12 +93,43 @@ - (NSString*)FEN { return NSStringFromString(engine.getFEN()); } +- (NSArray*)games { + NSMutableArray *engineGames = [NSMutableArray array]; + auto games = engine.games; + for (int index=0; index* _Nonnull)allMoves { NSMutableArray *moves = [NSMutableArray array]; - for (Move move : engine.game.allMoves()) { + for (Move move : engine.game().allMoves()) { [moves addObject:[self engineMoveFromMove:move]]; } return moves; @@ -185,13 +220,13 @@ - (void)move:(NSString*)from to:(NSString*)to { } } - (BOOL)canMoveTo:(Direction)direction { - return engine.game.canMoveTo([self gameDirection:direction]); + return engine.game().canMoveTo([self gameDirection:direction]); } - (void)moveTo:(Direction)direction { // 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]); [self fireUpdate:self.stateIndex]; } @@ -221,7 +256,7 @@ - (BOOL)canPlay { - (FEngineInfo*)infoFor:(ChessEvaluation)info { FEngineInfo *ei = [[FEngineInfo alloc] init]; ei.info = info; - ei.game = engine.game; + ei.game = engine.game(); return ei; } diff --git a/Shared/Bridge/FEngineGame+Private.h b/Shared/Bridge/FEngineGame+Private.h new file mode 100644 index 0000000..1c43dfd --- /dev/null +++ b/Shared/Bridge/FEngineGame+Private.h @@ -0,0 +1,21 @@ +// +// FEngineGame+Private.m +// BChess +// +// Created by Jean Bovet on 5/11/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +#import "FEngineGame.h" +#import "ChessGame.hpp" + +@interface FEngineGame () { + ChessGame game; +} + +@property (nonatomic, assign, readwrite) NSUInteger index; +@property (nonatomic, strong, readwrite) NSString * _Nonnull name; + +- (instancetype _Nonnull )initWithGame:(ChessGame)game andIndex:(NSUInteger)index; + +@end diff --git a/Shared/Bridge/FEngineGame.h b/Shared/Bridge/FEngineGame.h new file mode 100644 index 0000000..afd4f76 --- /dev/null +++ b/Shared/Bridge/FEngineGame.h @@ -0,0 +1,20 @@ +// +// FEngineGame.h +// BChess +// +// Created by Jean Bovet on 5/11/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface FEngineGame: NSObject + +@property (nonatomic, assign, readonly) NSUInteger index; +@property (nonatomic, strong, readonly) NSString * _Nonnull name; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Shared/Bridge/FEngineGame.mm b/Shared/Bridge/FEngineGame.mm new file mode 100644 index 0000000..7c65476 --- /dev/null +++ b/Shared/Bridge/FEngineGame.mm @@ -0,0 +1,23 @@ +// +// FEngineGame.m +// BChess +// +// Created by Jean Bovet on 5/11/21. +// Copyright © 2021 Jean Bovet. All rights reserved. +// + +#import "FEngineGame+Private.h" +#import "FEngineUtility.h" + +@implementation FEngineGame + +- (instancetype)initWithGame:(ChessGame)game andIndex:(NSUInteger)index { + if (self = [super init]) { + self->game = game; + self.index = index; + self.name = NSStringFromString(game.tags["White"] + " - " + game.tags["Black"]); + } + return self; +} + +@end diff --git a/Shared/ChessDocument.swift b/Shared/ChessDocument.swift index dcd34d5..545b77c 100644 --- a/Shared/ChessDocument.swift +++ b/Shared/ChessDocument.swift @@ -60,6 +60,13 @@ struct ChessDocument: FileDocument { } } + var currentGameIndex: UInt = 0 { + didSet { + engine.currentGameIndex = currentGameIndex + moveNodes.rebuild(engine: engine) + } + } + var whitePlayer: GamePlayer var blackPlayer: GamePlayer @@ -74,7 +81,7 @@ struct ChessDocument: FileDocument { // This variable is used by any action that needs the engine to move // if suitable. For example, after New Game or Edit Game. - // If is observed in the PiecesView view. + // It is observed in the PiecesView view. var engineShouldMove = false var pieces: [Piece] { @@ -83,14 +90,14 @@ struct ChessDocument: FileDocument { init(pgn: String = startPosPGN, white: GamePlayer? = nil, black: GamePlayer? = nil, rotated: Bool = false, mode: GameMode = GameMode(value: .play)) { self.pgn = pgn - self.engine.setPGN(pgn) + self.engine.loadAllGames(pgn) self.whitePlayer = white ?? GamePlayer(name: "", computer: false, level: 0) self.blackPlayer = black ?? GamePlayer(name: "", computer: true, level: 0) self.rotated = rotated self.mode = mode - - moveNodes.rebuild(engine: engine) + moveNodes.rebuild(engine: engine) + loadOpenings() } diff --git a/Shared/Engine/Engine/ChessEngine.hpp b/Shared/Engine/Engine/ChessEngine.hpp index 1405790..e6cc398 100644 --- a/Shared/Engine/Engine/ChessEngine.hpp +++ b/Shared/Engine/Engine/ChessEngine.hpp @@ -28,7 +28,14 @@ typedef MinMaxSearch ChessMinMaxSearch; class ChessEngine { public: ChessOpenings openings; - ChessGame game; + + std::vector games; + unsigned gameIndex = 0; + + ChessGame & game() { + return games[gameIndex]; + } + IterativeDeepening iterativeSearch; bool transpositionTable = true; @@ -36,6 +43,11 @@ class ChessEngine { typedef std::function SearchCallback; public: + + ChessEngine() { + games.push_back(ChessGame()); + } + static void initialize() { ChessMoveGenerator::initialize(); ChessBoardHash::initialize(); @@ -45,32 +57,41 @@ class ChessEngine { return openings.load(pgn); } + bool loadAllGames(std::string pgn) { + games.clear(); + return FPGN::setGames(pgn, games); + } + bool setFEN(std::string fen) { - return game.setFEN(fen); + return game().setFEN(fen); } std::string getFEN() { - return game.getFEN(); + return game().getFEN(); } bool setPGN(std::string pgn) { - return FPGN::setGame(pgn, game); + return FPGN::setGame(pgn, game()); } - std::string getPGN() { - return FPGN::getGame(game); + std::string getPGN(bool currentGame = true) { + if (currentGame) { + return FPGN::getGame(game()); + } else { + return FPGN::getGames(games); + } } std::string getPGNForDisplay() { - return FPGN::getGame(game, FPGN::Formatting::history, 0, game.getNumberOfMoves()); + return FPGN::getGame(game(), FPGN::Formatting::history, 0, game().getNumberOfMoves()); } ChessGame::MoveNode getRootMoveNode() { - return game.getRoot(); + return game().getRoot(); } std::string getPieceAt(File file, Rank rank) { - BoardSquare square = game.getPieceAt(file, rank); + BoardSquare square = game().getPieceAt(file, rank); if (square.empty) { return ""; } else { @@ -80,15 +101,15 @@ class ChessEngine { } std::vector getMovesAt(File file, Rank rank) { - return game.movesAt(file, rank); + return game().movesAt(file, rank); } void move(Move move, std::string comment, bool replace) { - game.move(move, comment, replace); + game().move(move, comment, replace); } void move(std::string from, std::string to) { - game.move(from, to); + game().move(from, to); } void stop() { @@ -104,19 +125,19 @@ class ChessEngine { } bool isWhite() { - return game.board.color == WHITE; + return game().board.color == WHITE; } bool canPlay() { - return game.outcome == ChessGame::Outcome::in_progress; + return game().outcome == ChessGame::Outcome::in_progress; } // Returns true if the current moves are following a valid opening line as defined // by the openings loaded with loadOpening() bool isValidOpeningMoves(std::string &name) { name = ""; - if (game.getNumberOfMoves() > 0) { - bool result = openings.lookup(game.allMoves(), [&](auto opening) { + if (game().getNumberOfMoves() > 0) { + bool result = openings.lookup(game().allMoves(), [&](auto opening) { name = opening.name; }); return result; @@ -126,14 +147,14 @@ class ChessEngine { } bool lookupOpeningMove(ChessEvaluation & evaluation) { - if (game.getNumberOfMoves() == 0 && game.board.fullMoveCount > 1) { + if (game().getNumberOfMoves() == 0 && game().board.fullMoveCount > 1) { // If the game is not at the starting position, that is, // there are no moves recorded yet but the fullMoveCount is greater // than one (meaning the game has one or more move already), don't use // any openings. return false; } - bool result = openings.best(game.allMoves(), [&evaluation](auto opening) { + bool result = openings.best(game().allMoves(), [&evaluation](auto opening) { evaluation.line.push(opening.move); }); return result; @@ -141,7 +162,7 @@ class ChessEngine { void searchBestMove(int maxDepth, SearchCallback callback) { iterativeSearch.minMaxSearch.config.transpositionTable = transpositionTable; - ChessEvaluation info = iterativeSearch.search(game.board, game.history, maxDepth, [&](ChessEvaluation info) { + ChessEvaluation info = iterativeSearch.search(game().board, game().history, maxDepth, [&](ChessEvaluation info) { if (!iterativeSearch.cancelled()) { callback(info, false); } @@ -152,6 +173,6 @@ class ChessEngine { } std::string getState() { - return game.getState(); + return game().getState(); } }; diff --git a/Shared/Engine/Helpers/FPGN.cpp b/Shared/Engine/Helpers/FPGN.cpp index 8817c46..2002c9d 100644 --- a/Shared/Engine/Helpers/FPGN.cpp +++ b/Shared/Engine/Helpers/FPGN.cpp @@ -826,6 +826,9 @@ static void getPGN(ChessBoard board, // The chess board representation which is } } + // Execute the move on the board + board.move(move); + // Output the actual move if (!skip) { if (pgn.size() > 0) { @@ -834,17 +837,6 @@ static void getPGN(ChessBoard board, // The chess board representation which is pgn += FPGN::to_string(move, sanType); } - // Output the comment for the move - if (!skip && !node.comment.empty()) { - if (pgn.size() > 0) { - pgn += " "; - } - pgn += "{"+node.comment+"}"; - } - - // Execute the move on the board - board.move(move); - // Determine if the position is check or mate if (board.isCheck(board.color) && !skip) { ChessMoveGenerator generator; @@ -856,6 +848,14 @@ static void getPGN(ChessBoard board, // The chess board representation which is } } + // Output the comment for the move + if (!skip && !node.comment.empty()) { + if (pgn.size() > 0) { + pgn += " "; + } + pgn += "{"+node.comment+"}"; + } + // If we are not doing any recursive output, return now. if (!recursive) { return; @@ -892,6 +892,18 @@ static void getPGN(ChessBoard board, // The chess board representation which is } } +std::string FPGN::getGames(std::vector & games) { + std::string allGamesPGN; + for (int index=0; index & games); + // fromIndex: the index from which to start building the PGN. Used to display a line from a game during analysis, // which means we only want to display the game after the current move (to show the thinking of the computer). // This parameter takes effect only if the Formatting is set to line diff --git a/Shared/Views/ContentView.swift b/Shared/Views/ContentView.swift index fe80347..3963645 100644 --- a/Shared/Views/ContentView.swift +++ b/Shared/Views/ContentView.swift @@ -37,9 +37,8 @@ struct ContentView: View { } if (showInfo) { - VStack(alignment: .leading) { - NavigationView(document: $document) - + VStack(alignment: .leading, spacing: 10) { + NavigationView(document: $document) InformationView(document: document) } .frame(minWidth: 350, idealWidth: 350, maxWidth: 350, alignment: .leading) diff --git a/Shared/Views/InformationView.swift b/Shared/Views/InformationView.swift index c940426..ad16db2 100644 --- a/Shared/Views/InformationView.swift +++ b/Shared/Views/InformationView.swift @@ -61,21 +61,30 @@ struct InformationView: View { } var body: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 10) { + HStack { + Image(systemName: "checkerboard.rectangle") + Text(document.engine.games[Int(document.currentGameIndex)].name) + } + if let opening = document.engine.openingName() { - Text(opening) - .padding(.bottom) + HStack { + Image(systemName: "book") + Text(opening) + } } + List(document.moveNodes.moveNodes, children: \FullMoveItem.children) { item in FullMoveItemView(item: item, currentMoveUUID: document.engine.currentMoveNodeUUID()) } + Spacer() + if document.mode.value == .play { HStack() { Image(systemName: "cpu") Text(value()) } - .padding(.bottom) HStack { Text(Image(systemName: "speedometer")) Text(speed()) diff --git a/Shared/Views/PiecesView.swift b/Shared/Views/PiecesView.swift index 9528bc1..ba08fe5 100644 --- a/Shared/Views/PiecesView.swift +++ b/Shared/Views/PiecesView.swift @@ -108,7 +108,7 @@ struct PiecesView: View { document.lastMove = move document.applyEngineSettings() document.engine.move(move.rawMoveValue) - document.pgn = document.engine.pgn() + document.pgn = document.engine.pgnAllGames() } func playMove(info: FEngineInfo) { @@ -117,7 +117,7 @@ struct PiecesView: View { document.info = info document.applyEngineSettings() document.engine.move(info.bestMove) - document.pgn = document.engine.pgn() + document.pgn = document.engine.pgnAllGames() } var body: some View {