From 9049f25521fd2b71ef8faf848878c8e73720105a Mon Sep 17 00:00:00 2001 From: Calcitem Date: Sat, 17 Jun 2023 12:25:06 +0800 Subject: [PATCH 1/3] Update the target to .net framework 4.8 --- Controller/Controller.csproj | 4 +- Controller/Properties/Resources.Designer.cs | 4 +- Controller/Properties/Settings.Designer.cs | 4 +- Controller/app.config | 2 +- Malom3/App.config | 4 +- Malom3/Malom3.vbproj | 4 +- Malom3/My Project/Application.Designer.vb | 2 +- Malom3/My Project/Resources.Designer.vb | 4 +- Malom3/My Project/Settings.Designer.vb | 6 +-- MalomAPI/MalomAPI.vbproj | 3 +- MalomAPI/My Project/Resources.Designer.vb | 2 +- MalomAPI/My Project/Settings.Designer.vb | 50 ++++++++++----------- 12 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Controller/Controller.csproj b/Controller/Controller.csproj index 2f760dd..724f663 100644 --- a/Controller/Controller.csproj +++ b/Controller/Controller.csproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -10,7 +10,7 @@ Properties Controller Controller - v4.5 + v4.8 512 diff --git a/Controller/Properties/Resources.Designer.cs b/Controller/Properties/Resources.Designer.cs index 0b5c1a4..4d5fcc7 100644 --- a/Controller/Properties/Resources.Designer.cs +++ b/Controller/Properties/Resources.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18408 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -19,7 +19,7 @@ namespace Controller.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/Controller/Properties/Settings.Designer.cs b/Controller/Properties/Settings.Designer.cs index 20c5764..6d8f528 100644 --- a/Controller/Properties/Settings.Designer.cs +++ b/Controller/Properties/Settings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18408 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,7 +12,7 @@ namespace Controller.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/Controller/app.config b/Controller/app.config index 51278a4..3e0e37c 100644 --- a/Controller/app.config +++ b/Controller/app.config @@ -1,3 +1,3 @@ - + diff --git a/Malom3/App.config b/Malom3/App.config index 95f68a6..06a53ef 100644 --- a/Malom3/App.config +++ b/Malom3/App.config @@ -1,9 +1,9 @@ - + - + diff --git a/Malom3/Malom3.vbproj b/Malom3/Malom3.vbproj index f1bc824..cdf7b41 100644 --- a/Malom3/Malom3.vbproj +++ b/Malom3/Malom3.vbproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -13,7 +13,7 @@ Malom 512 WindowsForms - v4.5 + v4.8 publish\ diff --git a/Malom3/My Project/Application.Designer.vb b/Malom3/My Project/Application.Designer.vb index e3ae1f4..e5c99f6 100644 --- a/Malom3/My Project/Application.Designer.vb +++ b/Malom3/My Project/Application.Designer.vb @@ -1,7 +1,7 @@ '------------------------------------------------------------------------------ ' ' This code was generated by a tool. -' Runtime Version:4.0.30319.18408 +' Runtime Version:4.0.30319.42000 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. diff --git a/Malom3/My Project/Resources.Designer.vb b/Malom3/My Project/Resources.Designer.vb index 27a1994..7096896 100644 --- a/Malom3/My Project/Resources.Designer.vb +++ b/Malom3/My Project/Resources.Designer.vb @@ -1,7 +1,7 @@ '------------------------------------------------------------------------------ ' ' This code was generated by a tool. -' Runtime Version:4.0.30319.18444 +' Runtime Version:4.0.30319.42000 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. @@ -22,7 +22,7 @@ Namespace My.Resources ''' ''' A strongly-typed resource class, for looking up localized strings, etc. ''' - _ diff --git a/Malom3/My Project/Settings.Designer.vb b/Malom3/My Project/Settings.Designer.vb index a4a3428..19ce599 100644 --- a/Malom3/My Project/Settings.Designer.vb +++ b/Malom3/My Project/Settings.Designer.vb @@ -1,7 +1,7 @@ '------------------------------------------------------------------------------ ' ' This code was generated by a tool. -' Runtime Version:4.0.30319.18408 +' Runtime Version:4.0.30319.42000 ' ' Changes to this file may cause incorrect behavior and will be lost if ' the code is regenerated. @@ -15,7 +15,7 @@ Option Explicit On Namespace My _ Partial Friend NotInheritable Class MySettings Inherits Global.System.Configuration.ApplicationSettingsBase @@ -29,7 +29,7 @@ Namespace My Private Shared addedHandlerLockObject As New Object _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) + Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) If My.Application.SaveMySettingsOnExit Then My.Settings.Save() End If diff --git a/MalomAPI/MalomAPI.vbproj b/MalomAPI/MalomAPI.vbproj index 8908837..b69879d 100644 --- a/MalomAPI/MalomAPI.vbproj +++ b/MalomAPI/MalomAPI.vbproj @@ -10,8 +10,9 @@ MalomAPI 512 Windows - v4.5 + v4.8 true + true diff --git a/MalomAPI/My Project/Resources.Designer.vb b/MalomAPI/My Project/Resources.Designer.vb index 29b113f..b85fc0a 100644 --- a/MalomAPI/My Project/Resources.Designer.vb +++ b/MalomAPI/My Project/Resources.Designer.vb @@ -22,7 +22,7 @@ Namespace My.Resources ''' ''' A strongly-typed resource class, for looking up localized strings, etc. ''' - _ diff --git a/MalomAPI/My Project/Settings.Designer.vb b/MalomAPI/My Project/Settings.Designer.vb index dc51f4a..9622d2d 100644 --- a/MalomAPI/My Project/Settings.Designer.vb +++ b/MalomAPI/My Project/Settings.Designer.vb @@ -13,42 +13,42 @@ Option Explicit On Namespace My - - _ + + _ Partial Friend NotInheritable Class MySettings Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings), MySettings) - + + Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) + #Region "My.Settings Auto-Save Functionality" #If _MyType = "WindowsForms" Then - Private Shared addedHandler As Boolean + Private Shared addedHandler As Boolean - Private Shared addedHandlerLockObject As New Object + Private Shared addedHandlerLockObject As New Object - _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub + _ + Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) + If My.Application.SaveMySettingsOnExit Then + My.Settings.Save() + End If + End Sub #End If #End Region - + Public Shared ReadOnly Property [Default]() As MySettings Get - + #If _MyType = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If + If Not addedHandler Then + SyncLock addedHandlerLockObject + If Not addedHandler Then + AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings + addedHandler = True + End If + End SyncLock + End If #End If Return defaultInstance End Get From fe7d7acdccaa306d2ead11626187ba3e14615939 Mon Sep 17 00:00:00 2001 From: Calcitem Date: Sat, 17 Jun 2023 12:27:50 +0800 Subject: [PATCH 2/3] Upgrade projects to target the latest Microsoft toolset and Windows SDK version --- Analyzer/Analyzer.vcxproj | 6 +++--- Malom3/Malom3.vbproj | 1 + MalomAPI/MalomAPI.vbproj | 1 + Malom_megoldas/Malom_megoldas.vcxproj | 8 ++++---- Wrappers/Hash.vcxproj | 8 ++++---- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/Analyzer/Analyzer.vcxproj b/Analyzer/Analyzer.vcxproj index 217151a..5350ad0 100644 --- a/Analyzer/Analyzer.vcxproj +++ b/Analyzer/Analyzer.vcxproj @@ -14,21 +14,21 @@ {953104A5-7106-4468-9888-72D37CAE07CD} Win32Proj Analyzer - 10.0.17763.0 + 10.0 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/Malom3/Malom3.vbproj b/Malom3/Malom3.vbproj index cdf7b41..ae80f80 100644 --- a/Malom3/Malom3.vbproj +++ b/Malom3/Malom3.vbproj @@ -140,6 +140,7 @@ True Application.myapp + True True diff --git a/MalomAPI/MalomAPI.vbproj b/MalomAPI/MalomAPI.vbproj index b69879d..7627b8c 100644 --- a/MalomAPI/MalomAPI.vbproj +++ b/MalomAPI/MalomAPI.vbproj @@ -121,6 +121,7 @@ True Application.myapp + True True diff --git a/Malom_megoldas/Malom_megoldas.vcxproj b/Malom_megoldas/Malom_megoldas.vcxproj index 3c580cb..544c982 100644 --- a/Malom_megoldas/Malom_megoldas.vcxproj +++ b/Malom_megoldas/Malom_megoldas.vcxproj @@ -18,27 +18,27 @@ {F77A30F9-3EB8-4748-9B06-4B2F8D694847} Win32Proj Malom_megoldas - 10.0.17763.0 + 10.0 Application true Unicode - v141 + v143 Application true Unicode - v141 + v143 Application false true Unicode - v141 + v143 diff --git a/Wrappers/Hash.vcxproj b/Wrappers/Hash.vcxproj index bc0d627..ab854c0 100644 --- a/Wrappers/Hash.vcxproj +++ b/Wrappers/Hash.vcxproj @@ -12,11 +12,11 @@ {D8FEE320-54D4-4779-A9C0-F97521ACE759} - v4.5 + v4.7.2 ManagedCProj Wrappers Wrappers - 10.0.17763.0 + 10.0 @@ -24,14 +24,14 @@ true true Unicode - v141 + v143 DynamicLibrary false true Unicode - v141 + v143 From 15b89eefd563c729f9410bc838a56bc6401f4216 Mon Sep 17 00:00:00 2001 From: Calcitem Date: Sat, 17 Jun 2023 12:34:12 +0800 Subject: [PATCH 3/3] Add MalomAPI_cpp Add MalomAPI_cpp following C++17 standards with no external dependencies. Examples of API usage can be found in perfect_test.cpp file. sec_val_path can be passed via argv[1]. --- MalomAPI_cpp/.clang-format | 160 +++++++ MalomAPI_cpp/.editorconfig | 22 + MalomAPI_cpp/.gitattributes | 34 ++ MalomAPI_cpp/.gitignore | 398 ++++++++++++++++++ MalomAPI_cpp/AUTHORS | 8 + MalomAPI_cpp/CPPLINT.cfg | 44 ++ MalomAPI_cpp/MalomAPI_cpp.vcxproj | 176 ++++++++ MalomAPI_cpp/MalomAPI_cpp.vcxproj.filters | 129 ++++++ MalomAPI_cpp/perfect_api.cpp | 219 ++++++++++ MalomAPI_cpp/perfect_api.h | 56 +++ MalomAPI_cpp/perfect_common.cpp | 35 ++ MalomAPI_cpp/perfect_common.h | 346 +++++++++++++++ MalomAPI_cpp/perfect_debug.cpp | 99 +++++ MalomAPI_cpp/perfect_debug.h | 37 ++ MalomAPI_cpp/perfect_eval_elem.cpp | 241 +++++++++++ MalomAPI_cpp/perfect_eval_elem.h | 123 ++++++ MalomAPI_cpp/perfect_game.cpp | 110 +++++ MalomAPI_cpp/perfect_game.h | 64 +++ MalomAPI_cpp/perfect_game_state.cpp | 339 +++++++++++++++ MalomAPI_cpp/perfect_game_state.h | 85 ++++ MalomAPI_cpp/perfect_hash.cpp | 293 +++++++++++++ MalomAPI_cpp/perfect_hash.h | 67 +++ MalomAPI_cpp/perfect_log.cpp | 67 +++ MalomAPI_cpp/perfect_log.h | 65 +++ MalomAPI_cpp/perfect_move.cpp | 54 +++ MalomAPI_cpp/perfect_move.h | 75 ++++ MalomAPI_cpp/perfect_platform.h | 47 +++ MalomAPI_cpp/perfect_player.cpp | 489 ++++++++++++++++++++++ MalomAPI_cpp/perfect_player.h | 206 +++++++++ MalomAPI_cpp/perfect_rules.cpp | 253 +++++++++++ MalomAPI_cpp/perfect_rules.h | 82 ++++ MalomAPI_cpp/perfect_sec_val.cpp | 131 ++++++ MalomAPI_cpp/perfect_sec_val.h | 40 ++ MalomAPI_cpp/perfect_sector.cpp | 316 ++++++++++++++ MalomAPI_cpp/perfect_sector.h | 93 ++++ MalomAPI_cpp/perfect_sector_graph.cpp | 205 +++++++++ MalomAPI_cpp/perfect_sector_graph.h | 66 +++ MalomAPI_cpp/perfect_symmetries.cpp | 99 +++++ MalomAPI_cpp/perfect_symmetries.h | 36 ++ MalomAPI_cpp/perfect_symmetries_slow.cpp | 254 +++++++++++ MalomAPI_cpp/perfect_symmetries_slow.h | 44 ++ MalomAPI_cpp/perfect_test.cpp | 63 +++ MalomAPI_cpp/perfect_wrappers.cpp | 83 ++++ MalomAPI_cpp/perfect_wrappers.h | 362 ++++++++++++++++ Malom_megoldas.sln | 36 +- 45 files changed, 6249 insertions(+), 2 deletions(-) create mode 100644 MalomAPI_cpp/.clang-format create mode 100644 MalomAPI_cpp/.editorconfig create mode 100644 MalomAPI_cpp/.gitattributes create mode 100644 MalomAPI_cpp/.gitignore create mode 100644 MalomAPI_cpp/AUTHORS create mode 100644 MalomAPI_cpp/CPPLINT.cfg create mode 100644 MalomAPI_cpp/MalomAPI_cpp.vcxproj create mode 100644 MalomAPI_cpp/MalomAPI_cpp.vcxproj.filters create mode 100644 MalomAPI_cpp/perfect_api.cpp create mode 100644 MalomAPI_cpp/perfect_api.h create mode 100644 MalomAPI_cpp/perfect_common.cpp create mode 100644 MalomAPI_cpp/perfect_common.h create mode 100644 MalomAPI_cpp/perfect_debug.cpp create mode 100644 MalomAPI_cpp/perfect_debug.h create mode 100644 MalomAPI_cpp/perfect_eval_elem.cpp create mode 100644 MalomAPI_cpp/perfect_eval_elem.h create mode 100644 MalomAPI_cpp/perfect_game.cpp create mode 100644 MalomAPI_cpp/perfect_game.h create mode 100644 MalomAPI_cpp/perfect_game_state.cpp create mode 100644 MalomAPI_cpp/perfect_game_state.h create mode 100644 MalomAPI_cpp/perfect_hash.cpp create mode 100644 MalomAPI_cpp/perfect_hash.h create mode 100644 MalomAPI_cpp/perfect_log.cpp create mode 100644 MalomAPI_cpp/perfect_log.h create mode 100644 MalomAPI_cpp/perfect_move.cpp create mode 100644 MalomAPI_cpp/perfect_move.h create mode 100644 MalomAPI_cpp/perfect_platform.h create mode 100644 MalomAPI_cpp/perfect_player.cpp create mode 100644 MalomAPI_cpp/perfect_player.h create mode 100644 MalomAPI_cpp/perfect_rules.cpp create mode 100644 MalomAPI_cpp/perfect_rules.h create mode 100644 MalomAPI_cpp/perfect_sec_val.cpp create mode 100644 MalomAPI_cpp/perfect_sec_val.h create mode 100644 MalomAPI_cpp/perfect_sector.cpp create mode 100644 MalomAPI_cpp/perfect_sector.h create mode 100644 MalomAPI_cpp/perfect_sector_graph.cpp create mode 100644 MalomAPI_cpp/perfect_sector_graph.h create mode 100644 MalomAPI_cpp/perfect_symmetries.cpp create mode 100644 MalomAPI_cpp/perfect_symmetries.h create mode 100644 MalomAPI_cpp/perfect_symmetries_slow.cpp create mode 100644 MalomAPI_cpp/perfect_symmetries_slow.h create mode 100644 MalomAPI_cpp/perfect_test.cpp create mode 100644 MalomAPI_cpp/perfect_wrappers.cpp create mode 100644 MalomAPI_cpp/perfect_wrappers.h diff --git a/MalomAPI_cpp/.clang-format b/MalomAPI_cpp/.clang-format new file mode 100644 index 0000000..b797a99 --- /dev/null +++ b/MalomAPI_cpp/.clang-format @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: GPL-3.0 +# +# clang-format configuration file. Intended for clang-format >= 12. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +#AlignConsecutiveBitFields: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: false +AlignEscapedNewlines: DontAlign +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortLambdasOnASingleLine: All +#AllowShortEnumsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +#BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false + #BeforeLambdaBody: false + #BeforeWhile: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +#BreakBeforeConceptDeclarations: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerIndentWidth : 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +#EmptyLineBeforeAccessModifier: Never +FixNamespaceComments: true +ForEachMacros: + - forever + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"config\.h"' + Priority: -1 + - Regex: '^<.*>' + Priority: 3 + - Regex: '^. +# +# +# Don't search for additional CPPLINT.cfg in parent directories. +set noparent +# Use 'ART_' as the cpp header guard prefix (e.g. #ifndef ART_PATH_TO_FILE_H_). +root=.. +# Limit line length. +linelength=1024 +# Ignore the following categories of errors, as specified by the filter: +# (the filter settings are concatenated together) +filter=-build/c++11 +filter=-build/header_guard +filter=-build/include_subdir +filter=-build/include_order +filter=-build/include +filter=-build/namespaces +filter=-readability/casting +filter=-readability/fn_size +filter=-runtime/arrays +filter=-runtime/int +filter=-runtime/references +filter=-runtime/string +filter=-runtime/threadsafe_fn +filter=-whitespace/comma +filter=-whitespace/braces +filter=-whitespace/comments +filter=-whitespace/indent +filter=-whitespace/parens diff --git a/MalomAPI_cpp/MalomAPI_cpp.vcxproj b/MalomAPI_cpp/MalomAPI_cpp.vcxproj new file mode 100644 index 0000000..f47f6fd --- /dev/null +++ b/MalomAPI_cpp/MalomAPI_cpp.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {3d1bed67-7f9e-495c-b4ad-fd473286ec3f} + MalomAPIcpp + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + true + + + Console + true + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MalomAPI_cpp/MalomAPI_cpp.vcxproj.filters b/MalomAPI_cpp/MalomAPI_cpp.vcxproj.filters new file mode 100644 index 0000000..ffbd19e --- /dev/null +++ b/MalomAPI_cpp/MalomAPI_cpp.vcxproj.filters @@ -0,0 +1,129 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/MalomAPI_cpp/perfect_api.cpp b/MalomAPI_cpp/perfect_api.cpp new file mode 100644 index 0000000..ca89c28 --- /dev/null +++ b/MalomAPI_cpp/perfect_api.cpp @@ -0,0 +1,219 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_api.h" +#include "perfect_player.h" +#include "perfect_game_state.h" + +#include + +PerfectPlayer *MalomSolutionAccess::pp = nullptr; +std::exception *MalomSolutionAccess::lastError = nullptr; + +int MalomSolutionAccess::getBestMove(int whiteBitboard, int blackBitboard, + int whiteStonesToPlace, + int blackStonesToPlace, int playerToMove, + bool onlyStoneTaking) +{ + initializeIfNeeded(); + + GameState s; + + const int W = 0; + const int B = 1; + + if ((whiteBitboard & blackBitboard) != 0) { + throw std::invalid_argument("whiteBitboard and blackBitboard shouldn't " + "have any overlap"); + } + + for (int i = 0; i < 24; i++) { + if ((whiteBitboard & (1 << i)) != 0) { + s.T[i] = W; + s.stoneCount[W] += 1; + } + if ((blackBitboard & (1 << i)) != 0) { + s.T[i] = B; + s.stoneCount[B] += 1; + } + } + + s.phase = ((whiteStonesToPlace == 0 && blackStonesToPlace == 0) ? 2 : 1); + mustBeBetween("whiteStonesToPlace", whiteStonesToPlace, 0, Rules::maxKSZ); + mustBeBetween("blackStonesToPlace", blackStonesToPlace, 0, Rules::maxKSZ); + s.setStoneCount[W] = Rules::maxKSZ - whiteStonesToPlace; + s.setStoneCount[B] = Rules::maxKSZ - blackStonesToPlace; + s.kle = onlyStoneTaking; + mustBeBetween("playerToMove", playerToMove, 0, 1); + s.sideToMove = playerToMove; + s.moveCount = 10; + + if (s.futureStoneCount(W) > Rules::maxKSZ) { + throw std::invalid_argument("Number of stones in whiteBitboard + " + "whiteStonesToPlace > " + + std::to_string(Rules::maxKSZ)); + } + if (s.futureStoneCount(B) > Rules::maxKSZ) { + throw std::invalid_argument("Number of stones in blackBitboard + " + "blackStonesToPlace > " + + std::to_string(Rules::maxKSZ)); + } + + std::string errorMsg = s.setOverAndCheckValidSetup(); + if (errorMsg != "") { + throw std::invalid_argument(errorMsg); + } + if (s.over) { + throw std::invalid_argument("Game is already over."); + } + + s.lastIrrev = 0; + + int ret = 0; + + try { + ret = pp->chooseRandom(pp->goodMoves(s)).toBitBoard(); + } catch (std::out_of_range &) { + throw std::runtime_error("We don't have a database entry for this " + "position. This can happen either if the " + "database is corrupted (missing files), or " + "sometimes when the position is not reachable " + "from the starting position."); + } + + deinitializeIfNeeded(); + + return ret; +} + +int MalomSolutionAccess::getBestMoveNoException( + int whiteBitboard, int blackBitboard, int whiteStonesToPlace, + int blackStonesToPlace, int playerToMove, bool onlyStoneTaking) +{ + try { + lastError = nullptr; + return getBestMove(whiteBitboard, blackBitboard, whiteStonesToPlace, + blackStonesToPlace, playerToMove, onlyStoneTaking); + } catch (std::exception &e) { + lastError = &e; + return 0; + } +} + +std::string MalomSolutionAccess::getLastError() +{ + if (lastError == nullptr) { + return "No error"; + } + return lastError->what(); +} + +void MalomSolutionAccess::initializeIfNeeded() +{ + if (pp != nullptr) { + return; + } + + if (sec_val_path == "") { + sec_val_path = "."; + } + + Rules::initRules(); + setVariantStripped(); + if (!Sectors::hasDatabase()) { + throw std::runtime_error("Database files not found in the current " + "working directory (" + + std::filesystem::current_path().string() + + ")"); + } + pp = new PerfectPlayer(); +} + +void MalomSolutionAccess::deinitializeIfNeeded() +{ + Rules::cleanup(); + + if (pp == nullptr) { + return; + } + + delete pp; + + pp = nullptr; +} + +void MalomSolutionAccess::mustBeBetween(std::string paramName, int value, + int min, int max) +{ + if (value < min || value > max) { + throw std::out_of_range(paramName + " must be between " + + std::to_string(min) + " and " + + std::to_string(max)); + } +} + +void MalomSolutionAccess::setVariantStripped() +{ + // copy-paste from Rules.cpp, but references to Main stripped + + switch (Wrappers::Constants::variant) { + case (int)Wrappers::Constants::Variants::std: + std::memcpy(Rules::millPos, Rules::stdLaskerMillPos, + sizeof(Rules::stdLaskerMillPos)); + std::memcpy(Rules::invMillPos, Rules::stdLaskerInvMillPos, + sizeof(Rules::stdLaskerInvMillPos)); + std::memcpy(Rules::boardGraph, Rules::stdLaskerBoardGraph, + sizeof(Rules::stdLaskerBoardGraph)); + std::memcpy(Rules::aLBoardGraph, Rules::stdLaskerALBoardGraph, + sizeof(Rules::stdLaskerALBoardGraph)); + Rules::maxKSZ = 9; + Rules::variantName = "std"; + break; + case (int)Wrappers::Constants::Variants::lask: + std::memcpy(Rules::millPos, Rules::stdLaskerMillPos, + sizeof(Rules::stdLaskerMillPos)); + std::memcpy(Rules::invMillPos, Rules::stdLaskerInvMillPos, + sizeof(Rules::stdLaskerInvMillPos)); + std::memcpy(Rules::boardGraph, Rules::stdLaskerBoardGraph, + sizeof(Rules::stdLaskerBoardGraph)); + std::memcpy(Rules::aLBoardGraph, Rules::stdLaskerALBoardGraph, + sizeof(Rules::stdLaskerALBoardGraph)); + Rules::maxKSZ = 10; + Rules::variantName = "lask"; + break; + case (int)Wrappers::Constants::Variants::mora: + std::memcpy(Rules::millPos, Rules::moraMillPos, + sizeof(Rules::moraMillPos)); + std::memcpy(Rules::invMillPos, Rules::moraInvMillPos, + sizeof(Rules::moraInvMillPos)); + std::memcpy(Rules::boardGraph, Rules::moraBoardGraph, + sizeof(Rules::moraBoardGraph)); + std::memcpy(Rules::aLBoardGraph, Rules::moraALBoardGraph, + sizeof(Rules::moraALBoardGraph)); + Rules::maxKSZ = 12; + Rules::variantName = "mora"; + break; + } + + if (Wrappers::Constants::extended) { + Rules::maxKSZ = 12; + } +} diff --git a/MalomAPI_cpp/perfect_api.h b/MalomAPI_cpp/perfect_api.h new file mode 100644 index 0000000..3f85ceb --- /dev/null +++ b/MalomAPI_cpp/perfect_api.h @@ -0,0 +1,56 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_MALOM_SOLUTION_H_INCLUDED +#define PERFECT_MALOM_SOLUTION_H_INCLUDED + +#include "perfect_player.h" + +class MalomSolutionAccess +{ +private: + static PerfectPlayer *pp; + static std::exception *lastError; + +public: + static int getBestMove(int whiteBitboard, int blackBitboard, + int whiteStonesToPlace, int blackStonesToPlace, + int playerToMove, bool onlyStoneTaking); + + static int getBestMoveNoException(int whiteBitboard, int blackBitboard, + int whiteStonesToPlace, + int blackStonesToPlace, int playerToMove, + bool onlyStoneTaking); + + static std::string getLastError(); + + static int getBestMoveStr(std::string args); + + static void initializeIfNeeded(); + static void deinitializeIfNeeded(); + + static void mustBeBetween(std::string paramName, int value, int min, + int max); + + static void setVariantStripped(); +}; + +#endif // PERFECT_MALOM_SOLUTION_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_common.cpp b/MalomAPI_cpp/perfect_common.cpp new file mode 100644 index 0000000..417f0dc --- /dev/null +++ b/MalomAPI_cpp/perfect_common.cpp @@ -0,0 +1,35 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" + +#include + +std::string sec_val_path = "."; +std::string sec_val_fname = ""; +FILE *f = nullptr; + +void failwith(std::string s) +{ + throw std::runtime_error(std::string(VARIANT_NAME) + ": " + s); +} diff --git a/MalomAPI_cpp/perfect_common.h b/MalomAPI_cpp/perfect_common.h new file mode 100644 index 0000000..69d581d --- /dev/null +++ b/MalomAPI_cpp/perfect_common.h @@ -0,0 +1,346 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_COMMON_H_INCLUDED +#define PERFECT_COMMON_H_INCLUDED + +#include "perfect_platform.h" + +#include +#include +#include + +#define STANDARD 1 +#define MORABARABA 2 +#define LASKER 3 + +//------------------------------------- +// Settings: + +#define RULE_VARIANT STANDARD // STANDARD, MORABARABA, or LASKER + +#define FULL_BOARD_IS_DRAW 1 // 0 or 1 + +//#define FULL_SECTOR_GRAPH //extended solution //comment or uncomment + +#define DD // distinguish draws (ultra) //comment or uncomment + +//#define STONE_DIFF //value of sectors is the stone difference (otherwise, get +// values from the .secval file) //comment or uncomment + +//------------------------------------- + +#ifdef STONE_DIFF +#ifndef DD +static_assert(false, ""); +#endif +#endif + +#ifdef DD +#ifdef FULL_SECTOR_GRAPH +static_assert(false, "sec_val range"); +#endif +#endif + +#ifdef DD +#ifndef STONE_DIFF +const int eval_struct_size = 3; // byte +#if RULE_VARIANT == STANDARD +const int field2_offset = 12; // bit +#else +const int field2_offset = 14; // bit +#endif +#else +const int eval_struct_size = 2; // byte +const int field2_offset = 6; // bit +#endif +const int field1_size = field2_offset; +const int field2_size = 8 * eval_struct_size - field2_offset; +using field2_t = int16_t; +#endif + +#ifdef STONE_DIFF +const char stone_diff_flag = 1; +#else +const char stone_diff_flag = 0; +#endif + +#ifdef DD +#define FNAME_SUFFIX "2" +#else +#define FNAME_SUFFIX "" +#endif + +using sec_val = int16_t; + +#ifdef DD +const sec_val sec_val_min_value = -(1 << (field1_size - 1)); +#endif + +#ifndef DD +// val, spec, sym, count (forditva) +// a 256 ertekbol lefoglalunk 1-et a specnek, 1-et a val0-nak, 1-et a count0-nak +// (ezert 253-...) +#define MAX_VAL 178 +#define MAX_COUNT (253 - MAX_VAL - 15) /*60*/ +#define SPEC (MAX_VAL + 1) +#endif + +const int version = 2; + +#if RULE_VARIANT == STANDARD +#define VARIANT_NAME "std" +#define GRAPH_FUNC_NOTNEG std_mora_graph_func +#define MILL_POS_CNT 16 +#ifndef FULL_SECTOR_GRAPH +const int max_ksz = 9; +#endif +#endif + +#if RULE_VARIANT == LASKER +#define VARIANT_NAME "lask" +#define GRAPH_FUNC_NOTNEG lask_graph_func +#define MILL_POS_CNT 16 +#ifndef FULL_SECTOR_GRAPH +const int max_ksz = 10; +#endif +#endif + +#if RULE_VARIANT == MORABARABA +#define VARIANT_NAME "mora" +#define GRAPH_FUNC_NOTNEG std_mora_graph_func +#define MILL_POS_CNT 20 +#ifndef FULL_SECTOR_GRAPH +const int max_ksz = 12; +#endif +#endif + +#ifdef FULL_SECTOR_GRAPH +const int max_ksz = 12; +//#pragma message ("Warning: max_ksz leveve") +#endif + +extern std::string sec_val_path; +extern std::string sec_val_fname; + +// This file is created by the solver +// with the -writemovegenlookups switch. +// The Controller automatically makes +// this, if the file doesn't exist. +const std::string movegen_file = (std::string) "C:\\malom_data_aux\\" + + VARIANT_NAME + + ".movegen"; + +typedef int64_t board; + +// azert nem lehet int, mert van olyan +// hasznalata, hogy pl. mask24<> i); + return r; + } +}; +#endif + +#if DANNER +#pragma message("Warning : Compiled in Danner mode! (conversion warnings)") +#endif + +#if __cplusplus +#define WRAPPER +#endif + +//#define STATISTICS + +#ifdef STATISTICS +#ifdef DD +#pragma message("Warning : STATISTICS and DD are both defined!") +#endif +#endif + +// You must not store count in it +struct val +{ + sec_val key1 {0}; + int key2 {0}; + + val() { } + val(sec_val key1, int key2) + : key1(key1) + , key2(key2) + { + assert(key1); /* nem count */ + } + + bool operator==(const val &o) const + { + return key1 == o.key1 && key2 == o.key2; + } + + // We correct towards directions depending on the sign of key2. + std::tuple tr() const + { + return std::make_tuple(static_cast(-abs(key1)), key2); + } + + bool operator<(const val &o) const + { + return tr() < o.tr(); + } + bool operator<=(const val &o) const { return tr() <= o.tr(); } + bool operator>(const val &o) const { return tr() > o.tr(); } + bool operator>=(const val &o) const { return tr() >= o.tr(); } + + val undo_negate() { return val(-key1, key2 + 1); } +}; + +struct id +{ + int W {0}; + int B {0}; + int WF {0}; + int BF {0}; + + id(int W, int B, int WF, int BF) + : W(W) + , B(B) + , WF(WF) + , BF(BF) + { } + + id() { } + + static id null() { return id {-1, -1, -1, -1}; } + + void negate() + { + std::swap(W, B); + std::swap(WF, BF); + } + + id operator-() const + { + id r = *this; + r.negate(); + return r; + } + + bool eks() const { return *this == -*this; } + + bool transient() const + { +#if RULE_VARIANT == STANDARD || RULE_VARIANT == MORABARABA + return !(WF == 0 && BF == 0); +#else + return !(W != 0 && B != 0); +#endif + } + + bool twine() const + { + return !eks() && !transient(); + } + + std::string file_name() + { + char b[255]; + SPRINTF(b, sizeof(b), "%s_%d_%d_%d_%d.sec%s", VARIANT_NAME, W, B, WF, + BF, FNAME_SUFFIX); + std::string r = std::string(b); + return r; + } + + bool operator<(const id &o) const + { + return std::make_pair(std::make_pair(W, B), std::make_pair(WF, BF)) < + std::make_pair(std::make_pair(o.W, o.B), std::make_pair(o.WF, o.BF)); + } + bool operator>(const id &o) const + { + return std::make_pair(std::make_pair(W, B), std::make_pair(WF, BF)) > + std::make_pair(std::make_pair(o.W, o.B), std::make_pair(o.WF, o.BF)); + } + + bool operator==(const id &o) const + { + return W == o.W && B == o.B && WF == o.WF && BF == o.BF; + } + bool operator!=(const id &o) const + { + return !(*this == o); + } + + std::string to_string() + { + char buf[255]; + SPRINTF(buf, sizeof(buf), "%s_%d_%d_%d_%d", VARIANT_NAME, W, B, WF, BF); + return std::string(buf); + } +}; + +template <> +struct std::hash +{ + size_t operator()(const id &k) const + { + return static_cast(k.W) | (static_cast(k.B) << 4) | + (static_cast(k.WF) << 8) | + (static_cast(k.BF) << 12); + } +}; + +// this must be in the define path of WRAPPER +#include "perfect_log.h" + +template +std::string tostring(T x) +{ + std::stringstream ss; + ss << x; + return ss.str(); +} + +template +int sign(T x) +{ + return x < 0 ? -1 : (x > 0 ? 1 : 0); +} + +void failwith(std::string s); + +#endif // PERFECT_COMMON_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_debug.cpp b/MalomAPI_cpp/perfect_debug.cpp new file mode 100644 index 0000000..851eeb5 --- /dev/null +++ b/MalomAPI_cpp/perfect_debug.cpp @@ -0,0 +1,99 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_debug.h" +#include "perfect_common.h" + +#include +#include +#include + +const char *toclp(board b) +{ + board mask = 1; + std::vector kit(24, -1); + for (int i = 0; i < 24; i++) { + if ((mask << i) & b) + kit[i] = 0; + } + for (int i = 24; i < 48; i++) { + if ((mask << i) & b) + kit[i - 24] = 1; + } + + std::stringstream ss; + for (int i = 0; i < 24; i++) + ss << kit[i] << ","; + ss << "0,0,0,2,9,9," << POPCNT((unsigned int)(b & mask24)) << "," + << POPCNT((unsigned int)((b & (mask24 << 24)) >> 24)) + << ",False,60,-1000,0,3,malom2"; + + char *ret = new char[1024]; + STRCPY(ret, 1024, ss.str().c_str()); + return ret; +} + +std::string toclp2(board b) +{ + board mask = 1; + std::vector kit(24, -1); + for (int i = 0; i < 24; i++) { + if ((mask << i) & b) + kit[i] = 0; + } + for (int i = 24; i < 48; i++) { + if ((mask << i) & b) + kit[i - 24] = 1; + } + + std::stringstream ss; + for (int i = 0; i < 24; i++) + ss << kit[i] << ","; + ss << "0,0,0,2,9,9,3,3,False,60,-1000,0,3,malom2"; + + return ss.str(); +} + +std::string toclp3(board b, id id) +{ + board mask = 1; + std::vector kit(24, -1); + for (int i = 0; i < 24; i++) { + if ((mask << i) & b) + kit[i] = 0; + } + for (int i = 24; i < 48; i++) { + if ((mask << i) & b) + kit[i - 24] = 1; + } + + std::stringstream ss; + for (int i = 0; i < 24; i++) + ss << kit[i] << ","; + + ss << "0,0,0," << (id.WF ? 1 : 2) << "," << max_ksz - id.WF << "," + << max_ksz - id.BF << "," << id.W << "," << id.B + << ",False,60,-1000,0,3,malom2"; + + return ss.str(); +} diff --git a/MalomAPI_cpp/perfect_debug.h b/MalomAPI_cpp/perfect_debug.h new file mode 100644 index 0000000..fac69d4 --- /dev/null +++ b/MalomAPI_cpp/perfect_debug.h @@ -0,0 +1,37 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_DEBUG_H_INCLUDED +#define PERFECT_DEBUG_H_INCLUDED + +#include "perfect_common.h" + +#include + +const char *toclp(board b); + +std::string toclp2(board b); + +std::string toclp3(board b, id id); + +#endif // PERFECT_DEBUG_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_eval_elem.cpp b/MalomAPI_cpp/perfect_eval_elem.cpp new file mode 100644 index 0000000..b4a136c --- /dev/null +++ b/MalomAPI_cpp/perfect_eval_elem.cpp @@ -0,0 +1,241 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" +#include "perfect_eval_elem.h" + +#include + +eval_elem_sym::eval_elem_sym(cas c, int x) + : c(c) + , x(x) +{ } +bool eval_elem_sym::operator==(const eval_elem_sym &o) const +{ + return c == o.c && x == o.x; +} +eval_elem_sym::eval_elem_sym(const eval_elem &o) + : c((cas)o.c) + , x(o.x) +{ } + +eval_elem::eval_elem(cas c, int x) + : c(c) + , x(x) +{ } +bool eval_elem::operator==(const eval_elem &o) const +{ + return c == o.c && x == o.x; +} +eval_elem::eval_elem(const eval_elem_sym &o) + : c((cas)o.c) + , x(o.x) +{ + assert(o.c != eval_elem_sym::sym); +} + +eval_elem_sym::eval_elem_sym(const eval_elem_sym2 &o) +{ + if (o.cas() == eval_elem_sym2::Val) { + assert(abs(o.value().key1) == 1); + assert(o.value().key2 >= 0); + assert((o.value().key2 & 1) == (o.value().key1 < 0 ? 0 : 1)); + c = val; + x = o.value().key2; + } else if (o.cas() == eval_elem_sym2::Count) { + c = count; + x = o.count(); + } else { // sym + c = sym; + x = o.sym(); + } +} + +eval_elem::eval_elem(const eval_elem2 &o) +{ + if (o.cas() == eval_elem2::Val) { + assert(abs(o.value().key1) == 1); + assert(o.value().key2 >= 0); + assert((o.value().key2 & 1) == (o.value().key1 < 0 ? 0 : 1)); + c = val; + x = o.value().key2; + } else { // count + c = count; + x = o.count(); + } +} + +eval_elem2::eval_elem2(val v) + : key1(v.key1) + , key2(v.key2) +{ } +eval_elem2::eval_elem2(int c) + : key1(0) + , key2(c) +{ } + +val eval_elem2::value() const +{ + assert(cas() == Val); + return val {key1, key2}; +} + +int eval_elem2::count() const +{ + assert(cas() == Count); + return key2; +} + +eval_elem2::Cas eval_elem2::cas() const +{ + return key1 ? Val : Count; +} + +eval_elem2::eval_elem2(eval_elem ee) +{ + if (ee.c == eval_elem::val) { + key1 = ee.x & 1 ? 1 : -1; + key2 = ee.x; + } else { + key1 = 0; + key2 = ee.x; + } +} + +bool eval_elem2::operator==(const eval_elem2 &o) const +{ + return *this <= o && *this >= o; +} +bool eval_elem2::operator!=(const eval_elem2 &o) const +{ + return !(*this == o); +} + +eval_elem_sym2::eval_elem_sym2(val v) + : key1(v.key1) + , key2(v.key2) +{ } +eval_elem_sym2::eval_elem_sym2(int c) + : key1(0) + , key2(c) +{ } +eval_elem_sym2::eval_elem_sym2(sec_val key1, int key2) + : key1 {key1} + , key2 {key2} +{ } + +eval_elem_sym2 eval_elem_sym2::make_sym(int s) +{ + return eval_elem_sym2(-s - 1); +} + +val eval_elem_sym2::value() const +{ + assert(cas() == Val); + return val {key1, key2}; +} + +int eval_elem_sym2::count() const +{ + assert(cas() == Count); + return key2; +} + +int eval_elem_sym2::sym() const +{ + assert(cas() == Sym); + return -(key2 + 1); +} + +eval_elem_sym2::Cas eval_elem_sym2::cas() const +{ + return key1 ? Val : (key2 >= 0 ? Count : Sym); +} + +eval_elem_sym2::eval_elem_sym2(eval_elem_sym ees) +{ + if (ees.c == eval_elem_sym::val) { + key1 = ees.x & 1 ? 1 : -1; + key2 = ees.x; + } else if (ees.c == eval_elem_sym::count) { + key1 = 0; + key2 = ees.x; + } else { + key1 = 0; + key2 = -ees.x - 1; + } +} + +bool eval_elem_sym2::operator==(const eval_elem_sym2 &o) const +{ + return key1 == o.key1 && key2 == o.key2; +} + +eval_elem2::eval_elem2(const eval_elem_sym2 &o) + : key1(o.key1) + , key2(o.key2) +{ + assert(o.cas() != eval_elem_sym2::Sym); +} + +eval_elem_sym2::eval_elem_sym2(const eval_elem2 &o) + : key1(o.key1) + , key2(o.key2) +{ } + +eval_elem2 eval_elem2::corr(int corr) +{ + sec_val new_key1 = static_cast(key1 + corr); + + // magic, don't touch! + return eval_elem2 {new_key1, + sign((long long)new_key1 * key1) * key2}; +} + +bool eval_elem2::operator<(const eval_elem2 &b) const +{ + auto &a = *this; + if (a.key1 != b.key1) + return a.key1 < b.key1; + else if (a.key1 < 0) + return a.key2 < b.key2; + else if (a.key1 > 0) + return a.key2 > b.key2; + else + return false; +} +bool eval_elem2::operator>(const eval_elem2 &b) const +{ + auto &a = *this; + return b < a; +} +bool eval_elem2::operator<=(const eval_elem2 &b) const +{ + auto &a = *this; + return !(a > b); +} +bool eval_elem2::operator>=(const eval_elem2 &b) const +{ + auto &a = *this; + return !(a < b); +} diff --git a/MalomAPI_cpp/perfect_eval_elem.h b/MalomAPI_cpp/perfect_eval_elem.h new file mode 100644 index 0000000..ba42166 --- /dev/null +++ b/MalomAPI_cpp/perfect_eval_elem.h @@ -0,0 +1,123 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_EVAL_ELEM_H_INCLUDED +#define PERFECT_EVAL_ELEM_H_INCLUDED + +#include "perfect_common.h" + +struct eval_elem; +struct eval_elem_sym2; +struct eval_elem2; + +struct eval_elem_sym +{ + enum cas { val, count, sym }; + cas c; + int x; + eval_elem_sym(cas c, int x); + bool operator==(const eval_elem_sym &o) const; + eval_elem_sym(const eval_elem &o); + eval_elem_sym(const eval_elem_sym2 &o); +}; + +struct eval_elem +{ + enum cas { val, count }; + cas c; + int x; + eval_elem(cas c, int x); + bool operator==(const eval_elem &o) const; + eval_elem(const eval_elem_sym &o); + eval_elem(const eval_elem2 &o); +}; + +class Sector; + +struct eval_elem2 +{ + // azert cannot be valid, because it cannot contain a count (as asserted by + // the ctor) + sec_val key1; + int key2; + + enum Cas { Val, Count }; + + eval_elem2(val v); + eval_elem2(int c); + eval_elem2(eval_elem ee); + eval_elem2(sec_val key1, int key2) + : key1 {key1} + , key2 {key2} + { } + + val value() const; + int count() const; + Cas cas() const; + +private: + bool operator==(const eval_elem2 &o) const; + +public: + bool operator!=(const eval_elem2 &o) const; + eval_elem2(const eval_elem_sym2 &o); + bool operator<(const eval_elem2 &b) const; + bool operator>(const eval_elem2 &b) const; + bool operator<=(const eval_elem2 &b) const; + bool operator>=(const eval_elem2 &b) const; + + eval_elem2 corr(int corr); + + // static eval_elem2 min_value(); +}; + +struct eval_elem_sym2 +{ + // azert cannot be valid, because it cannot contain a count (as asserted by + // the ctor) + sec_val key1; + int key2; + + enum Cas { Val, Count, Sym }; + + eval_elem_sym2(val v); + eval_elem_sym2(int c); + static eval_elem_sym2 make_sym(int s); + eval_elem_sym2(eval_elem_sym ee); + eval_elem_sym2(sec_val key1, int key2); + + val value() const; + int count() const; + int sym() const; + Cas cas() const; + + bool operator==(const eval_elem_sym2 &o) const; + eval_elem_sym2(const eval_elem2 &o); + +#ifdef DD + static const field2_t spec_field2 = -(1 << (field2_size - 1)); + static const field2_t max_field2 = -(spec_field2 + 1); +#endif +}; + +#endif // PERFECT_EVAL_ELEM_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_game.cpp b/MalomAPI_cpp/perfect_game.cpp new file mode 100644 index 0000000..9c63245 --- /dev/null +++ b/MalomAPI_cpp/perfect_game.cpp @@ -0,0 +1,110 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_player.h" +#include "perfect_game.h" +#include "perfect_game_state.h" + +class Player; +class GameState; +class CMove; + +GameState &Game::s() const +{ + // wrapper of current.value + return *current; +} + +Game::Game(Player *p1, Player *p2) +{ + history.push_back(GameState()); + current = std::prev(history.end()); + _ply[0] = p1; + _ply[1] = p2; +} + +Player **Game::plys() +{ + return _ply; +} + +Player *Game::ply(int i) const +{ + // get players in the game + return _ply[i]; +} + +void Game::set_ply(int i, Player *p) +{ + // set players in the game + if (p == nullptr) { + _ply[i] = nullptr; + return; + } + + // we exit p to see if it was in a game (e.g. NewGame in the + // previous one) + p->quit(); + + if (_ply[i] != nullptr) + _ply[i]->quit(); // the player replaced by p is kicked out + _ply[i] = p; + p->enter(this); +} + +void Game::makeMove(CMove *M) +{ // called by player objects when they want to move + try { + ply(1 - s().sideToMove)->followMove(M); + + history.insert(std::next(current), GameState(s())); + current++; + + s().makeMove(M); + } catch (std::exception &ex) { + // If TypeOf ex Is KeyNotFoundException Then Throw + std::cerr << "Exception in makeMove\n" << ex.what() << std::endl; + } +} + +void Game::applySetup(GameState toSet) +{ + history.insert(std::next(current), toSet); + current++; +} + +void Game::cancelThinking() +{ + for (int i = 0; i < 2; ++i) { + ply(i)->cancelThinking(); + } +} + +bool Game::playertypeChangingCmdAllowed() +{ + // Return TypeOf ply(s.sideToMove) Is HumanPlayer + return true; +} + +void Game::copyMoveList() +{ + throw std::runtime_error("NotImplementedException"); +} diff --git a/MalomAPI_cpp/perfect_game.h b/MalomAPI_cpp/perfect_game.h new file mode 100644 index 0000000..5d7c67e --- /dev/null +++ b/MalomAPI_cpp/perfect_game.h @@ -0,0 +1,64 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_GAME_H_INCLUDED +#define PERFECT_GAME_H_INCLUDED + +#include "perfect_player.h" +#include "perfect_rules.h" + +#include + +class Player; +class CMove; + +class Game +{ +private: + Player *_ply[2]; // players in the game + std::list history; // GameStates in this (and previous) games + + // the node of the current GameState in history + std::list::iterator current; + +public: + GameState &s() const; + + Game(Player *p1, Player *p2); + + Player **plys(); + + Player *ply(int i) const; + + void set_ply(int i, Player *p); + + void makeMove(CMove *M); + + void applySetup(GameState toSet); + + void cancelThinking(); + + bool playertypeChangingCmdAllowed(); + + void copyMoveList(); +}; + +#endif // PERFECT_MAIN_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_game_state.cpp b/MalomAPI_cpp/perfect_game_state.cpp new file mode 100644 index 0000000..9f55d28 --- /dev/null +++ b/MalomAPI_cpp/perfect_game_state.cpp @@ -0,0 +1,339 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_api.h" +#include "perfect_game_state.h" + +class Player; +class GameState; +class CMove; + +GameState::GameState(const GameState &s) +{ + T = s.T; + phase = s.phase; + setStoneCount = s.setStoneCount; + stoneCount = s.stoneCount; + kle = s.kle; + sideToMove = s.sideToMove; + moveCount = s.moveCount; + over = s.over; + winner = s.winner; + block = s.block; + lastIrrev = s.lastIrrev; +} + +int GameState::futureStoneCount(int p) +{ + return stoneCount[p] + Rules::maxKSZ - setStoneCount[p]; +} + +// Sets the state for Setup Mode: the placed stones are unchanged, but we switch +// to phase 2. +void GameState::initSetup() +{ + moveCount = 10; // Nearly all the same, just don't be too small, see other + // comments + over = false; + // Winner can be undefined, as over = False + block = false; + lastIrrev = 0; +} + +void GameState::makeMove(CMove *M) +{ + if (M == nullptr) { + throw std::invalid_argument("M is null"); + } + + checkInvariants(); + checkValidMove(M); + + moveCount++; + + SetKorong *sk = dynamic_cast(M); + MoveKorong *mk = dynamic_cast(M); + LeveszKorong *lk = dynamic_cast(M); + + if (sk != nullptr) { + T[sk->hov] = sideToMove; + setStoneCount[sideToMove]++; + stoneCount[sideToMove]++; + lastIrrev = 0; + } else if (mk != nullptr) { + T[mk->hon] = -1; + T[mk->hov] = sideToMove; + lastIrrev++; + if (lastIrrev >= Rules::lastIrrevLimit) { + over = true; + winner = -1; // draw + } + } else if (lk != nullptr) { + T[lk->hon] = -1; + stoneCount[1 - sideToMove]--; + kle = false; + if (stoneCount[1 - sideToMove] + Rules::maxKSZ - + setStoneCount[1 - sideToMove] < + 3) { + over = true; + winner = sideToMove; + } + lastIrrev = 0; + } + + if ((sk != nullptr && Rules::malome(sk->hov, *this) > -1 && + stoneCount[1 - sideToMove] > 0) || + (mk != nullptr && Rules::malome(mk->hov, *this) > -1 && + stoneCount[1 - sideToMove] > 0)) { + kle = true; + } else { + sideToMove = 1 - sideToMove; + if (setStoneCount[0] == Rules::maxKSZ && + setStoneCount[1] == Rules::maxKSZ && phase == 1) + phase = 2; + if (!Rules::youCanMove(*this)) { + over = true; + block = true; + winner = 1 - sideToMove; + if (Wrappers::Constants::FBD && stoneCount[0] == 12 && + stoneCount[1] == 12) { + winner = -1; + } + } + } + + delete M; + + checkInvariants(); +} + +void GameState::checkValidMove(CMove *M) +{ + // Hard to ensure that the 'over and winner = -1' case never occurs. For + // example, the WithTaking case of PerfectPlayer.MakeMoveInState is tricky, + // because the previous makeMove may have already made it a draw. + assert(!over || winner == -1); + + SetKorong *sk = dynamic_cast(M); + MoveKorong *mk = dynamic_cast(M); + LeveszKorong *lk = dynamic_cast(M); + + if (sk != nullptr) { + assert(phase == 1); + assert(T[sk->hov] == -1); + } + if (mk != nullptr) { + assert(T[mk->hon] == sideToMove); + assert(T[mk->hov] == -1); + } + if (lk != nullptr) { + assert(kle); + assert(T[lk->hon] == 1 - sideToMove); + } +} + +void GameState::checkInvariants() +{ + assert(setStoneCount[0] >= 0); + assert(setStoneCount[0] <= Rules::maxKSZ); + assert(setStoneCount[1] >= 0); + assert(setStoneCount[1] <= Rules::maxKSZ); + assert(phase == 1 || (phase == 2 && setStoneCount[0] == Rules::maxKSZ && + setStoneCount[1] == Rules::maxKSZ)); +} + +#if defined(_WIN32) +#pragma warning(push) +#pragma warning(disable : 4127) +#endif +// Called when applying a free setup. It sets over and checks whether the +// position is valid. Returns "" if valid, reason str otherwise. Also called +// when pasting a position. +std::string GameState::setOverAndCheckValidSetup() +{ + assert(!over && !block); + + // Validity checks: + // Note: this should be before setting over, because we will deny applying + // the setup if the state is not valid, and we want to maintain the 'Not + // over and Not block' invariants. + + int toBePlaced0 = Rules::maxKSZ - setStoneCount[0]; + if (stoneCount[0] + toBePlaced0 > Rules::maxKSZ) { + return "Too many white stones (on the board + to be placed). Please " + "remove some white stones from the board and/or decrease the " + "number of white stones to be placed."; + } + int toBePlaced1 = Rules::maxKSZ - setStoneCount[1]; + if (stoneCount[1] + toBePlaced1 > Rules::maxKSZ) { + return "Too many black stones (on the board + to be placed). Please " + "remove some black stones from the board and/or decrease the " + "number of black stones to be placed."; + } + + assert(!(phase == 1 && toBePlaced0 == 0 && toBePlaced1 == 0)); + assert(!(phase == 2 && (toBePlaced0 > 0 || toBePlaced1 > 0))); + + if (Wrappers::Constants::variant != + (int)Wrappers::Constants::Variants::lask && + !Wrappers::Constants::extended) { + if (phase == 1) { + if (toBePlaced0 != + toBePlaced1 - (((sideToMove == 0) ^ kle) ? 0 : 1)) { + return "If Black is to move in the placement phase, then the " + "number of black stones to be placed should be one more " + "than the number of white stones to placed. If White is " + "to move in the placement phase, then the number of " + "white and black stones to be placed should be equal. " + "(Except in a stone taking position, where these " + "conditions are reversed.)\n\nNote: The Lasker variant " + "(and the extended solutions) doesn't have these " + "constraints.\n\nNote: You can switch the side to move " + "by the \"Switch STM\" button in position setup mode."; + } + } else { + if (phase != 2) { + throw std::runtime_error("Phase is not 2"); + } + if (toBePlaced0 != 0 || toBePlaced1 != 0) { + throw std::runtime_error("toBePlaced0 or toBePlaced1 is not 0"); + } + } + } + + if (kle && stoneCount[1 - sideToMove] == 0) { + return "A position where the opponent doesn't have any stones cannot " + "be a stone taking position."; + } + + // Set over if needed: + bool whiteLose = false, blackLose = false; + if (stoneCount[0] + Rules::maxKSZ - setStoneCount[0] < 3) { + whiteLose = true; + } + if (stoneCount[1] + Rules::maxKSZ - setStoneCount[1] < 3) { + blackLose = true; + } + if (whiteLose || blackLose) { + over = true; + if (whiteLose && blackLose) { + winner = -1; // draw + } else { + if (whiteLose) { + winner = 1; + } else { + assert(blackLose); + winner = 0; + } + } + } + if (!kle && !Rules::youCanMove(*this)) { // youCanMove doesn't handle the + // kle case. However, we should + // always have a move in kle, see + // the validity check above. + over = true; + block = true; + winner = 1 - sideToMove; + if (Wrappers::Constants::FBD && stoneCount[0] == 12 && + stoneCount[1] == 12) { + winner = -1; + } + } + + // Even though lastIrrev is always 0 while in free setup mode, it can be + // non-0 when pasting + if (lastIrrev >= Rules::lastIrrevLimit) { + over = true; + winner = -1; + } + + return ""; +} +#if defined(_WIN32) +#pragma warning(pop) +#endif + +// to paste from clipboard +GameState::GameState(const std::string &s) +{ + std::vector ss; + std::string temp; + std::stringstream strStream(s); + + // split by commas + while (std::getline(strStream, temp, ',')) { + ss.push_back(temp); + } + + try { + if (ss[33] == "malom" || ss[34] == "malom" || ss[35] == "malom" || + ss[37] == "malom2") { // you need to be able to interpret older + // formats as well + for (int i = 0; i < 24; i++) { + T[i] = std::stoi(ss[i]); + } + sideToMove = std::stoi(ss[24]); + phase = std::stoi(ss[27]); + setStoneCount[0] = std::stoi(ss[28]); + setStoneCount[1] = std::stoi(ss[29]); + stoneCount[0] = std::stoi(ss[30]); + stoneCount[1] = std::stoi(ss[31]); + kle = (ss[32] == "True" || ss[32] == "true"); + moveCount = (ss[33] != "malom") ? std::stoi(ss[33]) : 10; + lastIrrev = ((ss[33] != "malom") && (ss[34] != "malom")) ? + std::stoi(ss[34]) : + 0; + + // ensure correct count of stones + ptrdiff_t count0 = std::count(T.begin(), T.end(), 0); + ptrdiff_t count1 = std::count(T.begin(), T.end(), 1); + if (stoneCount[0] != count0 || stoneCount[1] != count1) { + throw InvalidGameStateException("Number of stones is " + "incorrect."); + } + } else { + throw std::invalid_argument("Invalid Format"); + } + } catch (InvalidGameStateException &ex) { + throw ex; + } catch (std::exception &) { + throw std::invalid_argument("Invalid Format"); + } +} + +// for clipboard +std::string GameState::toString() +{ + std::stringstream s; + for (int i = 0; i < 24; i++) { + s << T[i] << ","; + } + s << sideToMove << "," << 0 << "," << 0 << "," << phase << "," + << setStoneCount[0] << "," << setStoneCount[1] << "," << stoneCount[0] + << "," << stoneCount[1] << "," << (kle ? "True" : "False") << "," + << moveCount << "," << lastIrrev; + return s.str(); +} + +const char *InvalidGameStateException::what() const noexcept +{ + return mymsg.c_str(); +} diff --git a/MalomAPI_cpp/perfect_game_state.h b/MalomAPI_cpp/perfect_game_state.h new file mode 100644 index 0000000..b4f6b44 --- /dev/null +++ b/MalomAPI_cpp/perfect_game_state.h @@ -0,0 +1,85 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_GAME_STATE_H_INCLUDED +#define PERFECT_GAME_STATE_H_INCLUDED + +#include + +class CMove; // forward declaration, implement this + +class GameState +{ +public: + // The board (-1: empty, 0: white piece, 1: black piece) + std::vector T = std::vector(24, -1); + int phase = 1; + // How many stones the players have set + std::vector setStoneCount = std::vector(2, 0); + std::vector stoneCount = std::vector(2, 0); + bool kle = false; // Is there a puck removal coming? + int sideToMove = 0; + int moveCount = 0; + bool over = false; + int winner = 0; // (-1, if a draw) + bool block = false; + int lastIrrev = 0; + + GameState() { } // start of game + + GameState(const GameState &s); + + int futureStoneCount(int p); + + // Sets the state for Setup Mode: the placed stones are unchanged, but we + // switch to phase 2. + void initSetup(); + + void makeMove(CMove *M); + + void checkValidMove(CMove *M); + + void checkInvariants(); + + // Called when applying a free setup. It sets over and checks whether the + // position is valid. Returns "" if valid, reason str otherwise. Also called + // when pasting a position. + std::string setOverAndCheckValidSetup(); + + // to paste from clipboard + GameState(const std::string &s); + + // for clipboard + std::string toString(); +}; + +class InvalidGameStateException : public std::exception +{ +public: + std::string mymsg; + InvalidGameStateException(const std::string &msg) + : mymsg(msg) + { } + + virtual const char *what() const noexcept override; +}; + +#endif // PERFECT_GAME_STATE_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_hash.cpp b/MalomAPI_cpp/perfect_hash.cpp new file mode 100644 index 0000000..e01a628 --- /dev/null +++ b/MalomAPI_cpp/perfect_hash.cpp @@ -0,0 +1,293 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" +#include "perfect_hash.h" +#include "perfect_symmetries.h" + +#include +#include + +const int binom[25][25] = { + {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 4, 6, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 5, 10, 10, 5, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 6, 15, 20, 15, 6, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 7, 21, 35, 35, 21, 7, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 8, 28, 56, 70, 56, 28, 8, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 9, 36, 84, 126, 126, 84, 36, 9, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, + 14, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, + 105, 15, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {1, 16, 120, 560, 1820, 4368, 8008, 11440, 12870, + 11440, 8008, 4368, 1820, 560, 120, 16, 1, 0, + 0, 0, 0, 0, 0, 0, 0}, + {1, 17, 136, 680, 2380, 6188, 12376, 19448, 24310, + 24310, 19448, 12376, 6188, 2380, 680, 136, 17, 1, + 0, 0, 0, 0, 0, 0, 0}, + {1, 18, 153, 816, 3060, 8568, 18564, 31824, 43758, + 48620, 43758, 31824, 18564, 8568, 3060, 816, 153, 18, + 1, 0, 0, 0, 0, 0, 0}, + {1, 19, 171, 969, 3876, 11628, 27132, 50388, 75582, + 92378, 92378, 75582, 50388, 27132, 11628, 3876, 969, 171, + 19, 1, 0, 0, 0, 0, 0}, + {1, 20, 190, 1140, 4845, 15504, 38760, 77520, 125970, + 167960, 184756, 167960, 125970, 77520, 38760, 15504, 4845, 1140, + 190, 20, 1, 0, 0, 0, 0}, + {1, 21, 210, 1330, 5985, 20349, 54264, 116280, 203490, + 293930, 352716, 352716, 293930, 203490, 116280, 54264, 20349, 5985, + 1330, 210, 21, 1, 0, 0, 0}, + {1, 22, 231, 1540, 7315, 26334, 74613, 170544, 319770, + 497420, 646646, 705432, 646646, 497420, 319770, 170544, 74613, 26334, + 7315, 1540, 231, 22, 1, 0, 0}, + {1, 23, 253, 1771, 8855, 33649, 100947, 245157, 490314, + 817190, 1144066, 1352078, 1352078, 1144066, 817190, 490314, 245157, 100947, + 33649, 8855, 1771, 253, 23, 1, 0}, + {1, 24, 276, 2024, 10626, 42504, 134596, + 346104, 735471, 1307504, 1961256, 2496144, 2704156, 2496144, + 1961256, 1307504, 735471, 346104, 134596, 42504, 10626, + 2024, 276, 24, 1}}; + +int next_choose(int x) +{ + if (x == 0) + return 1 << 24; + + int c = x & -x, r = x + c; + return (((r ^ x) >> 2) / c) | r; +} + +void init_collapse_lookup(); + +Hash::Hash(int W, int B, Sector *s) + : W(W) + , B(B) + , s(s) +{ + g_lookup = new int[1LL << (24 - W)]; + + memset(f_lookup, -1, sizeof(f_lookup)); + memset(f_sym_lookup2, 0, sizeof(f_sym_lookup2)); + int c = 0; + for (int w = (1 << W) - 1; w < 1 << 24; w = next_choose(w)) + if (f_lookup[w] == -1) { + for (int i = 0; i < 16; i++) { + // for(int i=15; i>=0; i--){ + auto sw = sym24(i, w); + f_lookup[sw] = c; + f_sym_lookup[sw] = inv[i]; + f_sym_lookup2[sw] |= 1 << inv[i]; + } + /* + We call a state canonical that can be hashed (i.e., the inv_hash may + return it). A partition is one that has a matching hash. The + previous loop sometimes writes to the same place in f_sym_lookup + more than once. This corresponds to a table that can be symmetrized + into several canonical states (that is, they match on the white + part, but may vary on the black part) (several symmetry operations + (that overwrite the same) bring the whites into the same state, but + not necessarily the blacks). The point is that f_sym_lookup should + always lead to a canonical form. It usually doesn't matter which + one, except when we are already in a canonical form, because then it + is only allowed to lead into itself. (because partitions can only + look like they have one canonical member, and each points to the + canonical state) The line below is needed because if a symmetry + operation led the whites into itself, then the overwrite can lead + the canonical into another canonical with f_sym_lookup. The line + below corrects the f_sym value associated with the canonical. It + doesn't need to be corrected for the others, because they definitely + lead to a canonical (which is enough), since the inv[i] in the third + line of the loop body necessarily leads into w, which will be + canonical. (We call a position canonical if it is half of a proper + canonical (then it also fulfills that it is only half of + canonicals)) Thus, the first reached of a partition will certainly + be canonical, because thinking through a forward-backward hashing, + we get back such positions. (this is what the line below is for) + + We call the sets obtained based on the above loop orbits. (we only + look at the white part of the positions) Below, the ws flip is + needed because the different elements of the set we get by + supplementing an orbit with blacks (everywhere in the same way (in a + collapsed sense)), may be in different partitions. The reason for + this is that the hash value at the g-s tag may differ because the + value of f_sym_lookup can be different at that tag. + + New solution: + We replace the line below by putting the identical into the end of + the array of symmetry operations. Because then if the identical + collides with something, 0 will get into f_sym_lookup. // I mean, + this is written, right? So it's not "0 gets in", but identical gets + in. + */ + // f_sym_lookup[w]=0; + c++; + } + + f_count = c; + f_inv_lookup = new int[f_count]; + + std::vector ws; + for (int w = (1 << W) - 1; w < 1 << 24; w = next_choose(w)) + ws.push_back(w); + reverse(ws.begin(), ws.end()); + for (auto it = ws.begin(); it != ws.end(); ++it) { + auto w = *it; + f_inv_lookup[f_lookup[w]] = w; + } + + g_inv_lookup = new int[binom[24 - W][B]]; + c = 0; + for (int b = (1 << B) - 1; b < 1 << (24 - W); b = next_choose(b)) { + if (c >= binom[24 - W][B]) { + assert(false); + break; + } + g_lookup[b] = c; + g_inv_lookup[c] = b; + c++; + } + + hash_count = f_count * binom[24 - W][B]; + + init_collapse_lookup(); + +#ifdef _DEBUG +#ifndef WRAPPER // The Wrapper uses the manual popcnt, which makes this + // noticeably slow when playing + check_hash_init_consistency(); +#endif +#endif +} + +void Hash::check_hash_init_consistency() +{ + for (int i = 0; i < 1 << 24; i++) + if (static_cast(POPCNT(i)) == W) + assert(f_sym_lookup[i] >= 0 && f_sym_lookup[i] < 16); +} + +Hash::~Hash() +{ + delete [] f_inv_lookup; + delete [] g_lookup; + delete [] g_inv_lookup; +} + +std::pair Hash::hash(board a) +{ + a = sym48(f_sym_lookup[a & mask24], a); + int h1 = f_lookup[a & mask24] * binom[24 - W][B] + g_lookup[collapse(a)]; + eval_elem_sym2 e = s->get_eval_inner(h1); + if (e.cas() != eval_elem_sym2::Sym) + return std::make_pair(h1, e); + else { + a = sym48(e.sym(), a); + int h2 = f_lookup[a & mask24] * binom[24 - W][B] + + g_lookup[collapse(a)]; + assert(s->get_eval_inner(h2).cas() != eval_elem_sym2::Sym); + return std::make_pair(h2, s->get_eval(h2)); + } +} + +board Hash::inv_hash(int h) +{ + int m = binom[24 - W][B]; + int f = h / m, g = h % m; + return uncollapse(f_inv_lookup[f] | ((board)g_inv_lookup[g] << 24)); +} + +board uncollapse(board a) +{ + int w = (int)(a & mask24), b = (int)(a >> 24), r = 0; + for (int i = 1; i < 1 << 24; i <<= 1) + if (w & i) + b <<= 1; + else + r |= b & i; + return ((board)r << 24) | w; +} + +// Original version +// ~83 clock cycles if we increment the hash one by one (probably the branch +// prediction is good at this time, since the positions are similar to each +// other) +// __declspec(noinline) +#ifdef WRAPPER +int collapse(board a) +{ + int w = (int)(a & mask24), b = (int)(a >> 24); + int i = 1, j = 1, r = 0; + for (; i < 1 << 24; i <<= 1) { + if (!(w & i)) { + r |= b & j; + j <<= 1; + } else + b >>= 1; + } + return r; +} +#endif + +// 8: 1:24 +// 6: 1:29 +// 4: 1:32 +const int sl = 8, psl = 1 << sl; +unsigned char collapse_lookup[psl][psl]; + +void init_collapse_lookup() +{ + // LOG("init_collapse_lookup"); + + for (int w = 0; w < psl; w++) + for (int bl = 0; bl < psl; bl++) { + int b = bl; + int i = 1, j = 1, r = 0; + for (; i < psl; i <<= 1) { + if (!(w & i)) { + r |= b & j; + j <<= 1; + } else + b >>= 1; + } + collapse_lookup[w][bl] = static_cast(r); + } + + // LOG(".\n"); +} diff --git a/MalomAPI_cpp/perfect_hash.h b/MalomAPI_cpp/perfect_hash.h new file mode 100644 index 0000000..9673212 --- /dev/null +++ b/MalomAPI_cpp/perfect_hash.h @@ -0,0 +1,67 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_HASH_H_INCLUDED +#define PERFECT_HASH_H_INCLUDED + +#include "perfect_sector.h" + +#include + +// void init_hash_lookuptables(); + +class Hash +{ + int W, B; // It might be worth to put these after the large arrays for cache + // locality reasons + + int f_lookup[1 << 24] {0}; + char f_sym_lookup[1 << 24] {0}; // Converted from int to char + int *f_inv_lookup {nullptr}; + int *g_lookup {nullptr}; + int *g_inv_lookup {nullptr}; + + int f_count {0}; + + Sector *s {nullptr}; + +public: + Hash(int W, int B, Sector *s); + + std::pair hash(board a); + board inv_hash(int h); + + int hash_count {0}; + + unsigned short f_sym_lookup2[1 << 24]; + + void check_hash_init_consistency(); + + ~Hash(); +}; + +int collapse(board a); +board uncollapse(board a); +int next_choose(int x); + +#endif // PERFECT_HASH_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_log.cpp b/MalomAPI_cpp/perfect_log.cpp new file mode 100644 index 0000000..b6c5cb3 --- /dev/null +++ b/MalomAPI_cpp/perfect_log.cpp @@ -0,0 +1,67 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" +#include "perfect_log.h" + +#include + +bool Log::log_to_file = false; +FILE *Log::logfile = stdout; +std::string Log::fname, Log::fnamelogging, Log::donefname; + +void Log::setup_logfile(std::string filename, std::string extension) +{ + Log::fname = filename; + log_to_file = true; + fnamelogging = filename + ".logging" + FNAME_SUFFIX; + + donefname = filename + "." + extension + FNAME_SUFFIX; + + remove(donefname.c_str()); + if (FOPEN(&logfile, fnamelogging.c_str(), "w") == -1) { + std::string errMsg = "Fatal error: Unable to open log file. (Another " + "instance is " + "probably running with the same parameters.)"; + std::cerr << errMsg << std::endl; +#if defined(_WIN32) || defined(_WIN64) + system("pause"); +#endif + throw std::runtime_error(errMsg); + } + if (logfile == nullptr) { + throw std::runtime_error("Failed to set buffer for the log file " + "because it is null."); + } + setvbuf(logfile, 0, _IONBF, 0); +} + +void Log::close() +{ + if (log_to_file) { + fclose(logfile); + if (rename(fnamelogging.c_str(), donefname.c_str())) + std::cout << "Renaming logfile failed; errno: " << errno + << std::endl; + } +} diff --git a/MalomAPI_cpp/perfect_log.h b/MalomAPI_cpp/perfect_log.h new file mode 100644 index 0000000..78cab6a --- /dev/null +++ b/MalomAPI_cpp/perfect_log.h @@ -0,0 +1,65 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_LOG_H_INCLUDED +#define PERFECT_LOG_H_INCLUDED + +#include +#include +#include + +struct Log +{ + // This is not in the other branch because log.cpp is not included in the + // wrapper project (but there would be no obstacle to adding it) + static bool log_to_file; + static FILE *logfile; + static void setup_logfile(std::string fname, std::string extension); + static std::string fname, fnamelogging, donefname; + static void close(); +}; + +template +void LOG(const char *format, Args... args) +{ +#if defined(_WIN32) + printf_s(format, args...); + fflush(stdout); + if (Log::log_to_file) { + fprintf_s(Log::logfile, format, args...); + fflush(Log::logfile); + } +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat-security" + printf(format, args...); + fflush(stdout); + if (Log::log_to_file) { + fprintf(Log::logfile, format, args...); + fflush(Log::logfile); + } +#pragma GCC diagnostic pop +#endif +} + +#endif // PERFECT_LOG_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_move.cpp b/MalomAPI_cpp/perfect_move.cpp new file mode 100644 index 0000000..7edb527 --- /dev/null +++ b/MalomAPI_cpp/perfect_move.cpp @@ -0,0 +1,54 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_move.h" +#include "perfect_api.h" +#include "perfect_player.h" +#include "perfect_rules.h" + +std::vector SetKorong::getMezok() +{ + return {hov}; +} + +std::string SetKorong::toString() +{ + return mezoToString[hov]; +} + +std::vector MoveKorong::getMezok() +{ + return {hon, hov}; +} + +std::string MoveKorong::toString() +{ + return mezoToString[hon] + "-" + mezoToString[hov]; +} + +std::vector LeveszKorong::getMezok() +{ + return {hon}; +} +std::string LeveszKorong::toString() +{ + return "x" + mezoToString[hon]; +} diff --git a/MalomAPI_cpp/perfect_move.h b/MalomAPI_cpp/perfect_move.h new file mode 100644 index 0000000..7740c2e --- /dev/null +++ b/MalomAPI_cpp/perfect_move.h @@ -0,0 +1,75 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_MOVE_H_INCLUDED +#define PERFECT_MOVE_H_INCLUDED + +#include "perfect_player.h" +#include "perfect_rules.h" + +class CMove +{ +public: + virtual std::vector getMezok() = 0; // Returns the fields included in + // the step + virtual ~CMove() = default; +protected: + std::string mezoToString[24] = {"a4", "a7", "d7", "g7", "g4", "g1", + "d1", "a1", "b4", "b6", "d6", "f6", + "f4", "f2", "d2", "b2", "c4", "c5", + "d5", "e5", "e4", "e3", "d3", "c3"}; +}; + +class SetKorong : public CMove +{ +public: + int hov; + SetKorong(int m) + : hov(m) + { } + std::vector getMezok() override; + std::string toString(); +}; + +class MoveKorong : public CMove +{ +public: + int hon, hov; // from, to + MoveKorong(int m1, int m2) + : hon(m1) + , hov(m2) + { } + std::vector getMezok() override; + std::string toString(); +}; + +class LeveszKorong : public CMove +{ +public: + int hon; + LeveszKorong(int m) + : hon(m) + { } + std::vector getMezok() override; + std::string toString(); +}; + +#endif // PERFECT_MOVE_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_platform.h b/MalomAPI_cpp/perfect_platform.h new file mode 100644 index 0000000..02221eb --- /dev/null +++ b/MalomAPI_cpp/perfect_platform.h @@ -0,0 +1,47 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_PLATFORM_H_INCLUDED +#define PERFECT_PLATFORM_H_INCLUDED + +#include +#include + +#if defined(_WIN32) +#define SPRINTF(buffer, buffer_size, format, ...) \ + sprintf_s(buffer, buffer_size, format, ##__VA_ARGS__) +#define FOPEN(file, filename, mode) fopen_s(file, filename, mode) +#define FSCANF(file, format, ...) fscanf_s(file, format, ##__VA_ARGS__) +#define STRCPY(destination, destination_size, source) \ + strcpy_s(destination, destination_size, source) +#define POPCNT(x) __popcnt(x) +#else // _WIN32 +#define SPRINTF(buffer, buffer_size, format, ...) \ + snprintf(buffer, buffer_size, format, ##__VA_ARGS__) +#define FOPEN(file, filename, mode) \ + ((*file = fopen(filename, mode)) != NULL ? 0 : -1) +#define FSCANF(file, format, ...) fscanf(file, format, ##__VA_ARGS__) +#define STRCPY(destination, destination_size, source) \ + strncpy(destination, source, destination_size) +#define POPCNT(x) __builtin_popcount(x) +#endif // _WIN32 + +#endif // PERFECT_PLATFORM_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_player.cpp b/MalomAPI_cpp/perfect_player.cpp new file mode 100644 index 0000000..0300dd7 --- /dev/null +++ b/MalomAPI_cpp/perfect_player.cpp @@ -0,0 +1,489 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_api.h" +#include "perfect_player.h" +#include "perfect_game_state.h" +#include "perfect_move.h" +#include "perfect_rules.h" + +#include "perfect_wrappers.h" + +#include +#include // for assert +#include // for int64_t +#include // for std::exit +#include // for std::exception +#include +#include +#include // for std::cerr +#include +#include // for std::mutex and std::lock_guard +#include +#include +#include // for std::out_of_range +#include +#include + +class GameState; + +std::map Sectors::sectors; +bool Sectors::created = false; + +std::map Sectors::getSectors() +{ + try { + if (!created) { + Wrappers::Init::init_sym_lookuptables(); + Wrappers::Init::init_sec_vals(); + + for (int w = 0; w <= Rules::maxKSZ; ++w) { + for (int b = 0; b <= Rules::maxKSZ; ++b) { + for (int wf = 0; wf <= Rules::maxKSZ; ++wf) { + for (int bf = 0; bf <= Rules::maxKSZ; ++bf) { + std::string fname = + Rules::variantName + "_" + std::to_string(w) + + "_" + std::to_string(b) + "_" + + std::to_string(wf) + "_" + std::to_string(bf) + + ".sec" + Wrappers::Constants::fname_suffix; + // std::cout << "Looking for database file " << + // fname << std::endl; + Wrappers::WID _id(w, b, wf, bf); +#ifdef _WIN32 + std::ifstream file(sec_val_path + "\\" + fname); +#else + std::ifstream file(sec_val_path + "/" + fname); +#endif + if (file.good()) { + sectors.emplace(_id, Wrappers::WSector(_id)); + } + } + } + } + } + created = true; + } + return sectors; + } catch (std::exception &ex) { + if (dynamic_cast(&ex)) { + throw; + } + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } +} + +bool Sectors::hasDatabase() +{ + return getSectors().size() > 0; +} + +// The object is informed to enter the specified game +void Player::enter(Game *_g) +{ + g = _g; +} + +// The object is informed to exit from the game +void Player::quit() +{ + if (g == nullptr) + return; + g = nullptr; +} + + +PerfectPlayer::PerfectPlayer() +{ + assert(Sectors::hasDatabase()); + secs = Sectors::getSectors(); +} + +void PerfectPlayer::enter(Game *_g) +{ + Player::enter(_g); +} + +Wrappers::WSector *PerfectPlayer::getSec(GameState s) +{ + try { + if (s.kle) + return nullptr; + + Wrappers::WID id_val(s.stoneCount[0], s.stoneCount[1], + Rules::maxKSZ - s.setStoneCount[0], + Rules::maxKSZ - s.setStoneCount[1]); + + if (s.sideToMove == 1) { + id_val.negate(); + } + + auto iter = secs.find(id_val); + if (iter == secs.end()) { + throw std::runtime_error("Key not found in secs"); + } + return &(iter->second); + + } catch (std::exception &ex) { + if (typeid(ex) == typeid(std::out_of_range)) + throw; + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } + return nullptr; +} + +std::string PerfectPlayer::toHumanReadableEval(Wrappers::gui_eval_elem2 e) +{ + try { + return e.toString(); + } catch (std::exception &ex) { + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } +} + +int PerfectPlayer::futureKorongCount(const GameState &s) +{ + return s.stoneCount[s.sideToMove] + Rules::maxKSZ - + s.setStoneCount[s.sideToMove]; // TODO: refactor to call to + // futureStoneCount +} + +bool PerfectPlayer::makesMill(const GameState &s, int hon, int hov) +{ + GameState s2 = s; + if (hon != -1) + s2.T[hon] = -1; + s2.T[hov] = s.sideToMove; + return -1 != Rules::malome(hov, s2); +} + +bool PerfectPlayer::isMill(const GameState &s, int m) +{ + return -1 != Rules::malome(m, s); +} + +std::vector PerfectPlayer::setMoves(const GameState &s) +{ + std::vector r; + for (int i = 0; i < 24; ++i) { + if (s.T[i] == -1) { + r.push_back(ExtMove {i, i, CMoveType::SetMove, makesMill(s, -1, i), + false, 0}); + } + } + return r; +} + +std::vector PerfectPlayer::slideMoves(const GameState &s) +{ + std::vector r; + for (int i = 0; i < 24; ++i) { + for (int j = 0; j < 24; ++j) { + if (s.T[i] == s.sideToMove && s.T[j] == -1 && + (futureKorongCount(s) == 3 || Rules::boardGraph[i][j])) { + r.push_back(ExtMove {i, j, CMoveType::SlideMove, + makesMill(s, i, j), false, 0}); + } + } + } + return r; +} + +// m has a withTaking step, where takeHon is not filled out. This function +// creates a list, the elements of which are copies of m supplemented with one +// possible removal each. +std::vector PerfectPlayer::withTakingMoves(const GameState &s, + ExtMove &m) +{ + std::vector r; + bool everythingInMill = true; + for (int i = 0; i < 24; ++i) { + if (s.T[i] == 1 - s.sideToMove && !isMill(s, i)) { + everythingInMill = false; + } + } + + for (int i = 0; i < 24; ++i) { + if (s.T[i] == 1 - s.sideToMove && (!isMill(s, i) || everythingInMill)) { + ExtMove m2 = m; + m2.takeHon = i; + r.push_back(m2); + } + } + return r; +} + +std::vector PerfectPlayer::onlyTakingMoves(const GameState &s) +{ + // there's some copy-paste code here + std::vector r; + bool everythingInMill = true; + for (int i = 0; i < 24; ++i) { + if (s.T[i] == 1 - s.sideToMove && !isMill(s, i)) { + everythingInMill = false; + } + } + + for (int i = 0; i < 24; ++i) { + if (s.T[i] == 1 - s.sideToMove && (!isMill(s, i) || everythingInMill)) { + r.push_back(ExtMove {0, 0, CMoveType::SlideMove, false, true, + i}); // Assuming default values for hon and hov + } + } + return r; +} + +#pragma warning(push) +#pragma warning(disable : 4127) +#pragma warning(push) +#pragma warning(disable : 6285) +std::vector PerfectPlayer::getMoveList(const GameState &s) +{ + std::vector ms0, ms; + if (!s.kle) { + if (Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::std || + Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::mora) { + if (s.setStoneCount[s.sideToMove] < Rules::maxKSZ) { + ms0 = setMoves(s); + } else { + ms0 = slideMoves(s); + } + } else { // Lasker + ms0 = slideMoves(s); + if (s.setStoneCount[s.sideToMove] < Rules::maxKSZ) { + std::vector setMovesResult = setMoves(s); + ms0.insert(ms0.end(), setMovesResult.begin(), + setMovesResult.end()); + } + } + + for (size_t i = 0; i < ms0.size(); ++i) { + if (!ms0[i].withTaking) { + ms.push_back(ms0[i]); + } else { + std::vector withTakingMovesResult = withTakingMoves( + s, ms0[i]); + ms.insert(ms.end(), withTakingMovesResult.begin(), + withTakingMovesResult.end()); + } + } + } else { // kle + ms = onlyTakingMoves(s); + } + return ms; +} +#pragma warning(pop) +#pragma warning(pop) + +GameState PerfectPlayer::makeMoveInState(const GameState &s, ExtMove &m) +{ + GameState s2(s); + if (!m.onlyTaking) { + if (m.moveType == CMoveType::SetMove) { + s2.makeMove(new SetKorong(m.hov)); + } else { + s2.makeMove(new MoveKorong(m.hon, m.hov)); + } + if (m.withTaking) + s2.makeMove(new LeveszKorong(m.takeHon)); + } else { + s2.makeMove(new LeveszKorong(m.takeHon)); + } + return s2; +} + +// Assuming gui_eval_elem2 and getSec functions are defined somewhere +Wrappers::gui_eval_elem2 PerfectPlayer::moveValue(const GameState &s, + ExtMove &m) +{ + try { + return eval(makeMoveInState(s, m)).undo_negate(getSec(s)); + } catch (const std::exception &ex) { + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } +} + +template +std::vector PerfectPlayer::allMaxBy(std::function f, + const std::vector &l, K minValue) +{ + std::vector r; + K ma = minValue; + for (auto &m : l) { + K e = f(m); + if (e > ma) { + ma = e; + r.clear(); + r.push_back(m); + } else if (e == ma) { + r.push_back(m); + } + } + return r; +} + +// Assuming the definition of gui_eval_elem2::min_value function +std::vector PerfectPlayer::goodMoves(const GameState &s) +{ + return allMaxBy(std::function( + [this, &s](ExtMove m) { return moveValue(s, m); }), + getMoveList(s), + Wrappers::gui_eval_elem2::min_value(getSec(s))); +} + +int PerfectPlayer::NGMAfterMove(const GameState &s, ExtMove &m) +{ + return numGoodMoves(makeMoveInState(s, m)); +} + +template +T PerfectPlayer::chooseRandom(const std::vector &l) +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, static_cast(l.size() - 1)); + return l[dis(gen)]; +} + +void PerfectPlayer::sendMoveToGUI(ExtMove m) +{ + if (!m.onlyTaking) { + if (m.moveType == CMoveType::SetMove) { + g->makeMove(new SetKorong(m.hov)); + } else { + g->makeMove(new MoveKorong(m.hon, m.hov)); + } + } else { + g->makeMove(new LeveszKorong(m.takeHon)); + } +} + +void PerfectPlayer::toMove(const GameState &s) +{ + try { + ExtMove mh = chooseRandom(goodMoves(s)); + sendMoveToGUI(mh); + } catch (const std::out_of_range &) { + sendMoveToGUI(chooseRandom(getMoveList(s))); + } catch (const std::exception &ex) { + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } +} + +int PerfectPlayer::numGoodMoves(const GameState &s) +{ + if (futureKorongCount(s) < 3) + return 0; // Assuming futureKorongCount function is defined + auto ma = Wrappers::gui_eval_elem2::min_value(getSec(s)); // Assuming getSec + // function is + // defined + ExtMove mh; + int c = 0; + for (auto &m : getMoveList(s)) { + auto e = moveValue(s, m); + if (e > ma) { + ma = e; + mh = m; + c = 1; + } else if (e == ma) { + c++; + } + } + return c; +} + +int cp; + +struct MoveValuePair +{ + ExtMove m; + double val; +}; + +// const double WRGMInf = 2; // Is this good? + +std::mutex evalLock; + +Wrappers::gui_eval_elem2 PerfectPlayer::eval(GameState s) +{ + try { + std::lock_guard lock(evalLock); + assert(!s.kle); // Assuming s has a boolean member kle + + Wrappers::WID id(s.stoneCount[0], s.stoneCount[1], + Rules::maxKSZ - s.setStoneCount[0], + Rules::maxKSZ - s.setStoneCount[1]); + + if (futureKorongCount(s) < 3) + return Wrappers::gui_eval_elem2::virt_loss_val(); + + int64_t a = 0; + for (int i = 0; i < 24; ++i) { + if (s.T[i] == 0) { + a |= (1ll << i); + } else if (s.T[i] == 1) { + a |= (1ll << (i + 24)); + } + } + + if (s.sideToMove == 1) { + a = boardNegate(a); + id.negate(); + } + + auto it = secs.find(id); + if (it == secs.end()) { + throw std::runtime_error("Key not found in map"); + } + + Wrappers::WSector &sec = it->second; + + return sec.hash(a).second; + } catch (const std::exception &ex) { + if (typeid(ex) == typeid(std::out_of_range)) + throw; + std::cerr << "An error happened in " << __func__ << "\n" + << ex.what() << std::endl; + throw std::runtime_error(std::string("An error happened in ") + + __func__ + ": " + ex.what()); + } +} + +int64_t PerfectPlayer::boardNegate(int64_t a) +{ + return ((a & mask24) << 24) | ((a & (mask24 << 24)) >> 24); +} diff --git a/MalomAPI_cpp/perfect_player.h b/MalomAPI_cpp/perfect_player.h new file mode 100644 index 0000000..a32a231 --- /dev/null +++ b/MalomAPI_cpp/perfect_player.h @@ -0,0 +1,206 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_PERFECT_PLAYER_H_INCLUDED +#define PERFECT_PERFECT_PLAYER_H_INCLUDED + +#include "perfect_common.h" +#include "perfect_game.h" +#include "perfect_move.h" +#include "perfect_rules.h" +#include "perfect_sector.h" +#include "perfect_wrappers.h" + +#include +#include // for assert +#include // for int64_t +#include // for std::exit +#include // for std::exception +#include +#include +#include +#include // for std::cerr +#include +#include // for std::mutex and std::lock_guard +#include +#include // for std::out_of_range +#include +#include + +enum class CMoveType { + SetMove, + SlideMove // should be renamed to SlideOrJumpMove +}; + +struct ExtMove +{ + int hon, hov; + CMoveType moveType; + bool withTaking, onlyTaking; // withTaking includes the steps in mill + // closure, onlyTaking only includes removal + int takeHon; + + int toBitBoard() + { + if (onlyTaking) { + return 1 << takeHon; + } + int ret = 1 << hov; + if (moveType == CMoveType::SlideMove) { + ret += 1 << hon; + } + if (withTaking) { + ret += 1 << takeHon; + } + return ret; + } +}; + +class Sectors +{ +public: + static std::map sectors; + static bool created; + + static std::map getSectors(); + + static bool hasDatabase(); +}; + + +class Player +{ +protected: + Game *g {nullptr}; // Assuming Game is a pre-defined class + +public: + Player() + : g(nullptr) + { } + + // The object is informed to enter the specified game + virtual void enter(Game *_g); + + // The object is informed to exit from the game + virtual void quit(); + + // The object is informed that it is its turn to move + virtual void toMove(const GameState &s) = 0; // Assuming GameState is a + // pre-defined class + + // Notifies about the opponent's move + virtual void followMove(CMove *) { } // Assuming Object is a pre-defined + // class or built-in type + + // The object is informed that it is the opponent's turn to move + virtual void oppToMove(const GameState &) { } + + // Game is over + virtual void over(const GameState &) { } + + // Cancel thinking + virtual void cancelThinking() { } + + // Determine the opponent player +protected: + Player *opponent() + { + return (g->ply(0) == this) ? g->ply(1) : g->ply(0); // Assuming Game has + // a ply function + } +}; + +class PerfectPlayer : public Player +{ +public: + std::map secs; + + PerfectPlayer(); + virtual ~PerfectPlayer() {} + + void enter(Game *_g) override; + + void quit() override { Player::quit(); } + + Wrappers::WSector *getSec(const GameState s); + + std::string toHumanReadableEval(Wrappers::gui_eval_elem2 e); + + int futureKorongCount(const GameState &s); + + bool makesMill(const GameState &s, int hon, int hov); + + bool isMill(const GameState &s, int m); + + std::vector setMoves(const GameState &s); + + std::vector slideMoves(const GameState &s); + + // m has a withTaking step, where takeHon is not filled out. This function + // creates a list, the elements of which are copies of m supplemented with + // one possible removal each. + std::vector withTakingMoves(const GameState &s, ExtMove &m); + + std::vector onlyTakingMoves(const GameState &s); + + std::vector getMoveList(const GameState &s); + + GameState makeMoveInState(const GameState &s, ExtMove &m); + + // Assuming gui_eval_elem2 and getSec functions are defined somewhere + Wrappers::gui_eval_elem2 moveValue(const GameState &s, ExtMove &m); + + template + std::vector allMaxBy(std::function f, const std::vector &l, + K minValue); + + // Assuming the definition of gui_eval_elem2::min_value function + std::vector goodMoves(const GameState &s); + + int NGMAfterMove(const GameState &s, ExtMove &m); + + template + T chooseRandom(const std::vector &l); + + void sendMoveToGUI(ExtMove m); + + void toMove(const GameState &s) override; + + int numGoodMoves(const GameState &s); + + int cp; + + struct MoveValuePair + { + ExtMove m; + double val; + }; + + // const double WRGMInf = 2; // Is this good? + + std::mutex evalLock; + + Wrappers::gui_eval_elem2 eval(GameState s); + + int64_t boardNegate(int64_t a); +}; + +#endif // PERFECT_PLAYER_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_rules.cpp b/MalomAPI_cpp/perfect_rules.cpp new file mode 100644 index 0000000..a015adc --- /dev/null +++ b/MalomAPI_cpp/perfect_rules.cpp @@ -0,0 +1,253 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "perfect_api.h" +#include "perfect_player.h" +#include "perfect_game_state.h" +#include "perfect_move.h" +#include "perfect_rules.h" + +#include +#include +#include +#include + +uint8_t Rules::millPos[20][3]; +uint8_t Rules::stdLaskerMillPos[16][3]; +int *Rules::stdLaskerInvMillPos[24] = {nullptr}; +bool Rules::stdLaskerBoardGraph[24][24] = {{false}}; +uint8_t Rules::stdLaskerALBoardGraph[24][5] = {{0}}; +uint8_t Rules::moraMillPos[20][3]; +int *Rules::moraInvMillPos[24]; +bool Rules::moraBoardGraph[24][24]; +uint8_t Rules::moraALBoardGraph[24][5]; +int *Rules::invMillPos[24]; +size_t Rules::invMillPosLengths[24]; +bool Rules::boardGraph[24][24]; +uint8_t Rules::aLBoardGraph[24][5]; +std::string Rules::variantName; +int Rules::maxKSZ = 0; + +void Rules::initRules() +{ + stdLaskerMillPos[0][0] = 1; + stdLaskerMillPos[0][1] = 2; + stdLaskerMillPos[0][2] = 3; + stdLaskerMillPos[1][0] = 3; + stdLaskerMillPos[1][1] = 4; + stdLaskerMillPos[1][2] = 5; + stdLaskerMillPos[2][0] = 5; + stdLaskerMillPos[2][1] = 6; + stdLaskerMillPos[2][2] = 7; + stdLaskerMillPos[3][0] = 7; + stdLaskerMillPos[3][1] = 0; + stdLaskerMillPos[3][2] = 1; + for (int i = 4; i <= 11; i++) { + stdLaskerMillPos[i][0] = stdLaskerMillPos[i - 4][0] + 8; + stdLaskerMillPos[i][1] = stdLaskerMillPos[i - 4][1] + 8; + stdLaskerMillPos[i][2] = stdLaskerMillPos[i - 4][2] + 8; + } + stdLaskerMillPos[12][0] = 0; + stdLaskerMillPos[13][0] = 2; + stdLaskerMillPos[14][0] = 4; + stdLaskerMillPos[15][0] = 6; + for (int i = 12; i <= 15; i++) { + stdLaskerMillPos[i][1] = stdLaskerMillPos[i][0] + 8; + stdLaskerMillPos[i][2] = stdLaskerMillPos[i][0] + 16; + } + // Since C++ arrays cannot be resized dynamically, we'll need to allocate + // memory for stdLaskerInvMillPos beforehand, and then populate it in this + // function. + bool kell; + for (int i = 0; i <= 23; i++) { + std::vector l; + for (int j = 0; j <= 15; j++) { + kell = false; + for (int k = 0; k <= 2; k++) { + if (stdLaskerMillPos[j][k] == i) + kell = true; + } + if (kell) { + l.push_back(j); + } + } + // Store the length + invMillPosLengths[i] = l.size(); + // Convert the vector into an array and store it in stdLaskerInvMillPos + stdLaskerInvMillPos[i] = new int[l.size()]; + for (size_t j = 0; j < l.size(); j++) { + stdLaskerInvMillPos[i][j] = l[j]; + } + } + // Initialize stdLaskerBoardGraph with false + for (int i = 0; i <= 23; i++) { + for (int j = 0; j <= 23; j++) { + stdLaskerBoardGraph[i][j] = false; + } + } + // Fill the board + for (int i = 0; i <= 6; i++) { + stdLaskerBoardGraph[i][i + 1] = true; + } + stdLaskerBoardGraph[7][0] = true; + for (int i = 8; i <= 14; i++) { + stdLaskerBoardGraph[i][i + 1] = true; + } + stdLaskerBoardGraph[15][8] = true; + for (int i = 16; i <= 22; i++) { + stdLaskerBoardGraph[i][i + 1] = true; + } + stdLaskerBoardGraph[23][16] = true; + for (int j = 0; j <= 6; j += 2) { + for (int i = 0; i <= 8; i += 8) { + stdLaskerBoardGraph[j + i][j + i + 8] = true; + } + } + // Fill the rest of the graph + for (int i = 0; i <= 23; i++) { + for (int j = 0; j <= 23; j++) { + if (stdLaskerBoardGraph[i][j] == true) { + stdLaskerBoardGraph[j][i] = true; + } + } + } + // Initialize stdLaskerALBoardGraph with 0 + for (int i = 0; i <= 23; i++) { + stdLaskerALBoardGraph[i][0] = 0; + } + // Fill the rest of the graph + for (int i = 0; i <= 23; i++) { + for (uint8_t j = 0; j <= 23; j++) { + if (stdLaskerBoardGraph[i][j] == true) { + stdLaskerALBoardGraph[i][stdLaskerALBoardGraph[i][0] + 1] = j; + stdLaskerALBoardGraph[i][0] += 1; + } + } + } +} + +void Rules::cleanup() +{ + for (int i = 0; i < 24; ++i) { + delete[] stdLaskerInvMillPos[i]; + } +} + +// Returns -1 if there is no mill on the given field, otherwise returns the +// sequence number in StdLaskerMalomPoz +int Rules::malome(int m, GameState s) +{ + int result = -1; + // Use the stored length instead of sizeof + size_t length = invMillPosLengths[m]; // TODO: Right? + for (size_t i = 0; i < length; i++) { + if (s.T[millPos[invMillPos[m][i]][0]] == s.T[m] && + s.T[millPos[invMillPos[m][i]][1]] == s.T[m] && + s.T[millPos[invMillPos[m][i]][2]] == s.T[m]) { + result = invMillPos[m][i]; + } + } + return result; +} + +// Tells whether the next player can move '(doesn't handle the kle case) +bool Rules::youCanMove(const GameState &s) +{ + assert(!s.kle); + if (s.setStoneCount[s.sideToMove] == maxKSZ && + s.stoneCount[s.sideToMove] > 3) { + for (int i = 0; i <= 23; i++) { + if (s.T[i] == s.sideToMove) { + for (int j = 1; j <= aLBoardGraph[i][0]; j++) { + if (s.T[aLBoardGraph[i][j]] == -1) + return true; + } + } + } + } else { + return true; + } + return false; +} + +bool Rules::mindenEllensegesKorongMalomban(GameState s) +{ + for (int i = 0; i <= 23; i++) { + if (s.T[i] == 1 - s.sideToMove && malome(i, s) == -1) + return false; + } + return true; +} + +// Checking if AlphaBeta is available +bool Rules::alphaBetaAvailable() +{ + return Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::std && + !Wrappers::Constants::extended; +} + +#pragma warning(push) +#pragma warning(disable : 4127) +void Rules::setVariant() +{ + // Part of this is copy-pasted in MalomAPI + if (Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::std) { + std::memcpy(millPos, stdLaskerMillPos, sizeof(stdLaskerMillPos)); + for (int i = 0; i < 24; ++i) { + invMillPos[i] = stdLaskerInvMillPos[i]; + } + std::memcpy(boardGraph, stdLaskerBoardGraph, + sizeof(stdLaskerBoardGraph)); + std::memcpy(aLBoardGraph, stdLaskerALBoardGraph, + sizeof(stdLaskerALBoardGraph)); + maxKSZ = 9; + variantName = "std"; + } else if (Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::lask) { + std::memcpy(millPos, stdLaskerMillPos, sizeof(stdLaskerMillPos)); + for (int i = 0; i < 24; ++i) { + invMillPos[i] = stdLaskerInvMillPos[i]; + } + std::memcpy(boardGraph, stdLaskerBoardGraph, + sizeof(stdLaskerBoardGraph)); + std::memcpy(aLBoardGraph, stdLaskerALBoardGraph, + sizeof(stdLaskerALBoardGraph)); + maxKSZ = 10; + variantName = "lask"; + } else if (Wrappers::Constants::variant == + (int)Wrappers::Constants::Variants::mora) { + std::memcpy(millPos, moraMillPos, sizeof(moraMillPos)); + for (int i = 0; i < 24; ++i) { + invMillPos[i] = moraInvMillPos[i]; + } + std::memcpy(boardGraph, moraBoardGraph, sizeof(moraBoardGraph)); + std::memcpy(aLBoardGraph, moraALBoardGraph, sizeof(moraALBoardGraph)); + maxKSZ = 12; + variantName = "mora"; + } + + if (Wrappers::Constants::extended) { + maxKSZ = 12; + } +} +#pragma warning(pop) diff --git a/MalomAPI_cpp/perfect_rules.h b/MalomAPI_cpp/perfect_rules.h new file mode 100644 index 0000000..6314852 --- /dev/null +++ b/MalomAPI_cpp/perfect_rules.h @@ -0,0 +1,82 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef PERFECT_RULES_H_INCLUDED +#define PERFECT_RULES_H_INCLUDED + +#include "perfect_wrappers.h" + +#include +#include +#include +#include + +class GameState; + +class Rules +{ +public: + // Define your byte arrays + static uint8_t millPos[20][3]; // TODO: Initial: [16][3]; + static uint8_t stdLaskerMillPos[16][3]; + static uint8_t moraMillPos[20][3]; + + // Define your integer arrays + static int *invMillPos[24]; + static size_t invMillPosLengths[24]; + static int *stdLaskerInvMillPos[24]; + static int *moraInvMillPos[24]; + + // Define your boolean arrays + static bool boardGraph[24][24]; + static bool stdLaskerBoardGraph[24][24]; + static bool moraBoardGraph[24][24]; + + // Define your adjacency list byte arrays + static uint8_t aLBoardGraph[24][5]; + static uint8_t stdLaskerALBoardGraph[24][5]; + static uint8_t moraALBoardGraph[24][5]; + + // Define other variables + static std::string variantName; + static int maxKSZ; + static const int lastIrrevLimit = 50; + +public: + static void initRules(); + static void cleanup(); + + // Returns -1 if there is no mill on the given field, otherwise returns the + // sequence number in StdLaskerMalomPoz + static int malome(int m, GameState s); + + // Tells whether the next player can move '(doesn't handle the kle case) + static bool youCanMove(const GameState &s); + + static bool mindenEllensegesKorongMalomban(GameState s); + + // Checking if AlphaBeta is available + static bool alphaBetaAvailable(); + + static void setVariant(); +}; + +#endif // PERFECT_RULES_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_sec_val.cpp b/MalomAPI_cpp/perfect_sec_val.cpp new file mode 100644 index 0000000..4e3faf9 --- /dev/null +++ b/MalomAPI_cpp/perfect_sec_val.cpp @@ -0,0 +1,131 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_sec_val.h" + +#include + +// Be careful: In the case of STONE_DIFF, +// there are also sectors that do not exist at all. +std::map sec_vals; + +#ifndef STONE_DIFF +std::map inv_sec_vals; +#endif +sec_val virt_loss_val = 0, virt_win_val = 0; + +void init_sec_vals() +{ +#ifdef DD +#ifndef STONE_DIFF + FILE *f = nullptr; +#ifdef _WIN32 + sec_val_fname = sec_val_path + "\\" + (std::string)VARIANT_NAME + ".secval"; +#else + sec_val_fname = sec_val_path + "/" + (std::string)VARIANT_NAME + ".secval"; +#endif + if (FOPEN(&f, sec_val_fname.c_str(), "rt") != 0) { + failwith(VARIANT_NAME ".secval file not found."); + return; + } + FSCANF(f, "virt_loss_val: %hd\nvirt_win_val: %hd\n", &virt_loss_val, + &virt_win_val); + assert(virt_win_val == -virt_loss_val); + int n; + FSCANF(f, "%d\n", &n); + for (int i = 0; i < n; i++) { + int w, b, wf, bf; + int16_t v; + FSCANF(f, "%d %d %d %d %hd\n", &w, &b, &wf, &bf, &v); + sec_vals[id(w, b, wf, bf)] = v; + } + fclose(f); +#else + for (int W = 0; W <= max_ksz; W++) { + for (int WF = 0; WF <= max_ksz; WF++) { + for (int B = 0; B <= max_ksz; B++) { + for (int BF = 0; BF <= max_ksz; BF++) { + id s = id {W, WF, B, BF}; + sec_vals[s] = s.W + s.WF - s.B - s.BF; + } + } + } + } + virt_win_val = max_ksz + 1; + virt_loss_val = -max_ksz - 1; +#endif + // It is needed for two reasons: one is for correction, and the other is to + // subtract one from it at the value of the kle sectors in gui_eval_elem2 + // (the -5 is just for safety, maybe -1 would be enough) + assert(2 * virt_loss_val - 5 > sec_val_min_value); +#else + virt_loss_val = -1; + virt_win_val = 1; +#endif + +#ifndef STONE_DIFF + for (const auto &sv : sec_vals) { + if (sv.second) { // not NTREKS if DD (if not DD, then only the virt + // sectors (which btw don't get here) are non-0) + assert(!inv_sec_vals.count(sv.second)); // non-NTREKS sec_vals + // should be unique + inv_sec_vals[sv.second] = id(sv.first); + } + } +#endif + +#ifdef HAS_SECTOR_GRAPH + for (auto s : sector_list) { + assert(sec_vals.count(s)); // every sector has a value + auto xx = sec_vals[s]; + assert(s.transient() || sec_vals[s] == -sec_vals[-s]); // wus are + // zero-sum + } +#endif +} + +std::string sec_val_to_sec_name(sec_val v) +{ + if (v == 0) +#ifdef DD +#ifdef STONE_DIFF + return "0"; +#else + return "NTESC"; +#endif +#else + return "D"; +#endif + else if (v == virt_loss_val) + return "L"; + else if (v == virt_win_val) + return "W"; + else { +#ifdef STONE_DIFF + return to_string(v); +#else + assert(inv_sec_vals.count(v)); + return std::to_string(v) + " (" + inv_sec_vals[v].to_string() + ")"; +#endif + } +} diff --git a/MalomAPI_cpp/perfect_sec_val.h b/MalomAPI_cpp/perfect_sec_val.h new file mode 100644 index 0000000..0454f70 --- /dev/null +++ b/MalomAPI_cpp/perfect_sec_val.h @@ -0,0 +1,40 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_SEV_VAL_H_INCLUDED +#define PERFECT_SEV_VAL_H_INCLUDED + +#include "perfect_common.h" + +#include +#include + +extern std::map sec_vals; +extern std::map inv_sec_vals; +extern sec_val virt_loss_val, virt_win_val; + +std::string sec_val_to_sec_name(sec_val v); + +void init_sec_vals(); + +#endif // PERFECT_SEV_VAL_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_sector.cpp b/MalomAPI_cpp/perfect_sector.cpp new file mode 100644 index 0000000..7a8ebd2 --- /dev/null +++ b/MalomAPI_cpp/perfect_sector.cpp @@ -0,0 +1,316 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" +#include "perfect_hash.h" +#include "perfect_sector.h" +#include "perfect_symmetries.h" + +#include +#include +#include + +Sector *sectors[max_ksz + 1][max_ksz + 1][max_ksz + 1][max_ksz + 1]; + +std::vector sector_objs; + +const int sbufsize = 1024 * 1024; +char sbuf[sbufsize]; // Caution + +Sector::Sector(::id id) + : W(id.W) + , B(id.B) + , WF(id.WF) + , BF(id.BF) + , id(id) + , max_val(-1) + , max_count(-1) + , hash(nullptr) +#ifdef WRAPPER + , f(nullptr) + , sval( +#else + , sval( +#endif +#ifdef DD + (assert(sec_vals.count(id)), sec_vals[id]) +#else + 0 +#endif + ) +{ + sector_objs.push_back(this); + + STRCPY(fname, sizeof(fname), id.file_name().c_str()); + LOG("Creating sector object for %s\n", fname); + +#ifndef WRAPPER + allocate_hash(); +#endif +} + +template +size_t fread1(T &x, FILE *file) +{ + return fread(&x, sizeof(x), 1, file); +} +template +size_t fwrite1(T &x, FILE *file) +{ + return fwrite(&x, sizeof(x), 1, file); +} +void Sector::read_header(FILE *file) +{ +#ifdef DD + int _version, _eval_struct_size, _field2_offset; + char _stone_diff_flag; + fread1(_version, file); + fread1(_eval_struct_size, file); + fread1(_field2_offset, file); + fread1(_stone_diff_flag, file); + assert(_version == version); + assert(_eval_struct_size == eval_struct_size); + assert(_field2_offset == field2_offset); + assert(_stone_diff_flag == stone_diff_flag); + fseek(f, header_size, SEEK_SET); +#endif +} +void Sector::write_header(FILE *file) +{ +#ifdef DD + fwrite1(version, file); + fwrite1(eval_struct_size, file); + fwrite1(field2_offset, file); + fwrite1(stone_diff_flag, file); + int ffu_size = header_size - ftell(file); + char *dummy = new char[ffu_size]; + memset(dummy, 0, ffu_size); + fwrite(dummy, 1, ffu_size, file); + delete[] dummy; +#endif +} + +void Sector::read_em_set(FILE *file) +{ + auto start = std::chrono::steady_clock::now(); + auto last_update = std::chrono::steady_clock::now(); + + int em_set_size = 0; + fread(&em_set_size, 4, 1, file); + for (int i = 0; i < em_set_size; i++) { + int e[2]; + fread(e, 4, 2, file); + em_set[e[0]] = e[1]; + + auto now = std::chrono::steady_clock::now(); + auto time_since_last_update = + std::chrono::duration_cast(now - last_update) + .count(); + + // Only update the console every second + if (time_since_last_update >= 1) { + // Calculate memory usage + float memoryUsageMB = ((i + 1) * 8.0f) / (1024 * 1024); // MB + + // Calculate elapsed time + auto elapsed_seconds = + std::chrono::duration_cast(now - start) + .count(); + auto hours = elapsed_seconds / 3600; + int minutes = (elapsed_seconds % 3600) / 60; + int seconds = elapsed_seconds % 60; + + // Calculate remaining time + int remaining_iterations = em_set_size - (i + 1); + auto avg_seconds_per_iteration = elapsed_seconds / (float)(i + 1); + auto remaining_seconds = remaining_iterations * + avg_seconds_per_iteration; + auto remaining_hours = remaining_seconds / 3600; + int remaining_minutes = ((unsigned int)remaining_seconds % 3600) / + 60; + int remaining_secs = (unsigned int)remaining_seconds % 60; + + if (memoryUsageMB < 1024) { + printf("\rProgress: %.2f%%, Memory Usage: %.2fMB, Elapsed " + "time: %02d:%02d:%02d, Remaining time: %02d:%02d:%02d", + ((float)(i + 1) / em_set_size) * 100, memoryUsageMB, + static_cast(hours), static_cast(minutes), + static_cast(seconds), + static_cast(remaining_hours), + static_cast(remaining_minutes), + static_cast(remaining_secs)); + } else { + printf("\rProgress: %.2f%%, Memory Usage: %.2fGB, Elapsed " + "time: %02d:%02d:%02d, Remaining time: %02d:%02d:%02d", + ((float)(i + 1) / em_set_size) * 100, + memoryUsageMB / 1024.0, static_cast(hours), + static_cast(minutes), static_cast(seconds), + static_cast(remaining_hours), + static_cast(remaining_minutes), + static_cast(remaining_secs)); + } + + // Flush the output buffer to immediately update the output + fflush(stdout); + + last_update = now; + } + } + + // Print a new line after the loop ends to avoid subsequent outputs + // on the same line + printf("\n"); +} + +#ifdef DD +eval_elem2 Sector::get_eval(int i) +{ + return (eval_elem2)(get_eval_inner(i)); +} + +eval_elem_sym2 Sector::get_eval_inner(int i) +{ + std::pair resi = extract(i); + if (resi.second == eval_elem_sym2::spec_field2) { + assert(em_set.count(i)); + return eval_elem_sym2 {resi.first, em_set[i]}; + } else { + return eval_elem_sym2 {resi.first, resi.second}; + } +} +#else +eval_elem2 Sector::get_eval(int i) +{ + return (eval_elem2)(get_eval_inner(i)); +} + +eval_elem_sym2 Sector::get_eval_inner(int i) +{ +#ifndef WRAPPER + int resi = eval[i]; +#else + fseek(f, i, SEEK_SET); + unsigned char read; + fread(&read, 1, 1, f); + int resi = read; +#endif + if (resi == SPEC) { + assert(em_set.count(i)); + int x = em_set[i]; + return x >= 0 ? eval_elem_sym(eval_elem_sym::val, x) : + eval_elem_sym(eval_elem_sym::count, -x); + } else + return resi <= MAX_VAL ? + eval_elem_sym(eval_elem_sym::val, resi) : + (resi <= MAX_VAL + 16 ? + eval_elem_sym(eval_elem_sym::sym, resi - SPEC - 1) : + eval_elem_sym(eval_elem_sym::count, 255 - resi)); +} +#endif + +#ifdef DD + +template +T sign_extend(T x) +{ + if ((1 << (b - 1)) & x) + return x | ((-1) ^ ((1 << b) - 1)); + else + return x; +} + +std::pair Sector::extract(int i) +{ + unsigned int a = 0; + static_assert(sizeof(a) >= eval_struct_size, "Increase the size of 'a'! " + "(Also consider in 'intract') " + "(And " + "check the types of literal " + "1s " + "(in 'extend' as well))) " + "(And also check all int type " + "casts)"); +#ifndef WRAPPER + for (int j = 0; j < eval_struct_size; j++) + a |= (int)eval[eval_struct_size * i + j] << 8 * j; +#else + fseek(f, header_size + eval_struct_size * i, SEEK_SET); + unsigned char read[eval_struct_size]; + fread(&read, 1, eval_struct_size, f); + for (int j = 0; j < eval_struct_size; j++) + a |= (int)read[j] << 8 * j; +#endif + + auto r = std::make_pair(sign_extend( + static_cast(a & ((1 << field1_size) - 1))), + sign_extend( + static_cast(a >> field2_offset))); + + return r; +} + +#endif + +void Sector::allocate_hash() +{ + // and read em_set (should be renamed) + hash = new Hash(W, B, this); +#ifdef DD + eval_size = hash->hash_count * eval_struct_size; +#else + eval_size = hash->hash_count; +#endif + +#ifdef WRAPPER + if (!f) { + std::string filename = std::string(fname); +#ifdef _WIN32 + filename = sec_val_path + "\\" + filename; +#else + filename = sec_val_path + "/" + filename; +#endif + + if (FOPEN(&f, filename.c_str(), "rb") == -1) { + std::cerr << "Failed to open file " << filename << '\n'; + return; + } + read_header(f); + } + fseek(f, header_size + eval_size, SEEK_SET); + read_em_set(f); +#endif +} + +void Sector::release_hash() +{ + // and clear em_set (should be renamed) + delete hash; + hash = nullptr; + + em_set.clear(); + +#ifdef WRAPPER + fclose(f); + f = nullptr; +#endif +} diff --git a/MalomAPI_cpp/perfect_sector.h b/MalomAPI_cpp/perfect_sector.h new file mode 100644 index 0000000..bff0018 --- /dev/null +++ b/MalomAPI_cpp/perfect_sector.h @@ -0,0 +1,93 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_SECTOR_H_INCLUDED +#define PERFECT_SECTOR_H_INCLUDED + +#include "perfect_common.h" +#include "perfect_eval_elem.h" +#include "perfect_sec_val.h" +#include "perfect_sector_graph.h" + +#ifndef WRAPPER +#include "movegen.h" +#endif + +class Hash; +class Sector; + +class Sector +{ + char fname[255] {0}; + + int eval_size; + + std::map em_set; + + FILE* f { nullptr }; + + +#ifdef DD + static const int header_size = 64; +#else + static const int header_size = 0; +#endif + void read_header(FILE *file); + void write_header(FILE *file); + void read_em_set(FILE *file); + +public: + int W {0}; + int B {0}; + int WF {0}; + int BF {0}; + id id; + + Sector(::id id); + + eval_elem2 get_eval(int i); + eval_elem_sym2 get_eval_inner(int i); + +#ifdef DD + std::pair extract(int i); + void intract(int i, std::pair x); +#endif + + // Statistics: + int max_val, max_count; + + Hash *hash {nullptr}; + + void allocate_hash(); + void release_hash(); + +public: + sec_val sval; +}; + +extern Sector *sectors[max_ksz + 1][max_ksz + 1][max_ksz + 1][max_ksz + 1]; +#define sectors(id) (sectors[(id).W][(id).B][(id).WF][(id).BF]) + +extern std::vector sector_objs; + +#endif // PERFECT_SECTOR_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_sector_graph.cpp b/MalomAPI_cpp/perfect_sector_graph.cpp new file mode 100644 index 0000000..2e7c746 --- /dev/null +++ b/MalomAPI_cpp/perfect_sector_graph.cpp @@ -0,0 +1,205 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" + +#include "perfect_sector_graph.h" + +#include +#include + +std::vector std_mora_graph_func(id u) +{ + std::vector v; + v.push_back(u); + v.push_back(u); + + if (u.WF) { + v[0].WF--; + v[0].W++; + + v[1].WF--; + v[1].W++; + v[1].B--; + } else { + v[1].B--; + } + + std::vector r; + for (auto it = v.begin(); it != v.end(); it++) + // this actually only handles the initial part, cf. doc + if (it->B + it->BF >= 3 && it->B >= 0) + r.push_back(*it); + + return r; +} + +std::vector lask_graph_func(id u) +{ + std::vector v; + + if (u.WF != 0) { + id a = u; + id b = u; + + a.WF--; + a.W++; + + b.WF--; + b.W++; + b.B--; + + v.push_back(a); + v.push_back(b); + } + if (u.W != 0) { + id a = u; + id b = u; + + b.B--; + + v.push_back(a); + v.push_back(b); + } + + std::vector r; + for (auto it = v.begin(); it != v.end(); it++) + // This actually only handles the initial part, cf. doc + if (it->B + it->BF >= 3 && it->B >= 0) + r.push_back(*it); + + return r; +} + +std::vector graph_func(id u, bool elim_loops) +{ + std::vector r0 = GRAPH_FUNC_NOTNEG(u); + + for (auto it = r0.begin(); it != r0.end(); it++) + it->negate(); + + std::set sr(r0.begin(), r0.end()); // parallel electric discharge + if (elim_loops) + sr.erase(u); // kizurese of hurokel + + return std::vector(sr.begin(), sr.end()); +} + +std::unordered_map> sector_graph; +std::unordered_map> sector_graph_t; + +void init_wu_graph(); + +std::vector sector_list; + +void init_sector_graph() +{ + LOG("init_sector_graph %s", VARIANT_NAME); + + std::queue q; + std::set volt; +#ifndef FULL_SECTOR_GRAPH + q.push(id(0, 0, max_ksz, max_ksz)); + volt.insert(q.front()); +#else + for (int i = 3; i <= max_ksz; i++) { + for (int j = 3; j <= max_ksz; j++) { + id s = id {0, 0, i, j}; + q.push(s); + volt.insert(s); + } + } +#endif + while (!q.empty()) { + id u = q.front(); + q.pop(); + std::vector v = graph_func(u); + for (auto it = v.begin(); it != v.end(); it++) { + if (!volt.count(*it)) { + q.push(*it); + volt.insert(*it); + } + sector_graph[u].push_back(*it); + sector_graph_t[*it].push_back(u); + } + } + + sector_list = std::vector(volt.begin(), volt.end()); + + init_wu_graph(); + + LOG(".\n"); +} + +std::unordered_map wus; + +// manages the addition of neighbors of a sector of wu to wu.adj +void add_adj(wu &wu, id id) +{ + auto &e = sector_graph_t[id]; + for (auto it = e.begin(); it != e.end(); ++it) + // small size of loops (make sure that we only count the wu's according + // to the pointer!) + if (wus[*it] != &wu) + // the parallel elements are squeezed out + if (wu.parents.insert(wus[*it]).second) + wus[*it]->child_count++; +} + +std::set wu_ids; + +void init_wu_graph() +{ + // the order in the sector_list determines which of the wu's sectors is + // primary, it can always have the smaller ID + for (auto it = sector_list.begin(); it != sector_list.end(); ++it) + wus[*it] = new wu(*it); + + int n = (int)sector_list.size(); + for (int i = 0; i < n - 1; i++) { + // (it's okay to hit the wu's twice) + id s1 = sector_list[i]; + for (id s2 : sector_graph[s1]) { + std::vector &e2 = sector_graph[s2]; + if (std::find(e2.begin(), e2.end(), s1) != e2.end()) { + assert(s1 == -s2); + wus[s1]->twine = true; + wus[s2] = wus[s1]; + } + } + // + } + + for (auto it = wus.begin(); it != wus.end(); ++it) { + // (it's okay to go on the twines twice) + auto &wu = *(it->second); + + add_adj(wu, wu.id); + + if (wu.twine) + add_adj(wu, -wu.id); + } + + for (auto it = wus.begin(); it != wus.end(); ++it) + wu_ids.insert(it->second->id); +} diff --git a/MalomAPI_cpp/perfect_sector_graph.h b/MalomAPI_cpp/perfect_sector_graph.h new file mode 100644 index 0000000..49bd8e7 --- /dev/null +++ b/MalomAPI_cpp/perfect_sector_graph.h @@ -0,0 +1,66 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_SECTOR_GRAPH_H_INCLUDED +#define PERFECT_SECTOR_GRAPH_H_INCLUDED + +#include "perfect_common.h" + +#include +#include +#include + +// (the Analyzer doesn't have the sector graph +// (init_sec_vals() has an ifdef for this)) +#define HAS_SECTOR_GRAPH + +extern std::unordered_map> sector_graph_t; +std::vector graph_func(id u, bool elim_loops = true); + +void init_sector_graph(); + +struct wu +{ + id id; + bool twine; + std::set parents; + int child_count; + + wu(::id id) + : id(id) + , twine(false) + , child_count(0) {}; + + // forbid copying + wu(const wu &o) = delete; + wu &operator=(const wu &o) = delete; +}; + +extern std::unordered_map wus; + +extern std::vector sector_list; + +// the ids for which there is a wu with this id +extern std::set wu_ids; + +#endif // PERFECT_SECTOR_GRAPH_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_symmetries.cpp b/MalomAPI_cpp/perfect_symmetries.cpp new file mode 100644 index 0000000..d833963 --- /dev/null +++ b/MalomAPI_cpp/perfect_symmetries.cpp @@ -0,0 +1,99 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_common.h" +#include "perfect_symmetries.h" +#include "perfect_symmetries_slow.h" + +int (*slow[16])(int) = {rot90, + rot180, + rot270, + tt_fuggoleges, + tt_vizszintes, + tt_bslash, + tt_slash, + swap, + swap_rot90, + swap_rot180, + swap_rot270, + swap_tt_fuggoleges, + swap_tt_vizszintes, + swap_tt_bslash, + swap_tt_slash, + id}; + +const int patsize = 8, patc = 1 << patsize; +static_assert(24 % patsize == 0, ""); + +int table1[16][patc], table2[16][patc], table3[16][patc]; // 64 KB in total + +void init_sym_lookuptables() +{ + static bool called = false; + if (called) + return; + called = true; + + LOG("init_sym_lookuptables\n"); + + for (int pat = 0; pat < patc; pat++) { + /*for(int i=0; i<16; i++) + table1[i][pat] = slow[i](pat << 0); + for(int i=0; i<16; i++) + table2[i][pat] = slow[i](pat << 6); + for(int i=0; i<16; i++) + table3[i][pat] = slow[i](pat << 12); + for(int i=0; i<16; i++) + table4[i][pat] = slow[i](pat << 18);*/ + + /*for(int k = 0; k < patn; k++) + for(int i = 0; i<16; i++) + table[k][i][pat] = slow[i](pat << k*patsize);*/ + + for (int i = 0; i < 16; i++) + table1[i][pat] = slow[i](pat << 0); + for (int i = 0; i < 16; i++) + table2[i][pat] = slow[i](pat << 8); + for (int i = 0; i < 16; i++) + table3[i][pat] = slow[i](pat << 16); + } +} + +board sym24(int op, board a) +{ + int mask = (1 << patsize) - 1; + board b = 0; + + b |= table1[op][(a >> 0) & mask]; + b |= table2[op][(a >> 8) & mask]; + b |= table3[op][(a >> 16) & mask]; + + return b; +} + +board sym48(int op, board a) +{ + return sym24(op, a & mask24) | (sym24(op, a >> 24) << 24); +} + +int8_t inv[] = {2, 1, 0, 3, 4, 5, 6, 7, 10, 9, 8, 11, 12, 13, 14, 15}; diff --git a/MalomAPI_cpp/perfect_symmetries.h b/MalomAPI_cpp/perfect_symmetries.h new file mode 100644 index 0000000..486374b --- /dev/null +++ b/MalomAPI_cpp/perfect_symmetries.h @@ -0,0 +1,36 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_SYMMETRIES_H_INCLUDED +#define PERFECT_SYMMETRIES_H_INCLUDED + +#include "perfect_common.h" + +void init_sym_lookuptables(); + +board sym24(int op, board a); +board sym48(int op, board a); + +extern int8_t inv[]; + +#endif // PERFECT_SYMMETRIES_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_symmetries_slow.cpp b/MalomAPI_cpp/perfect_symmetries_slow.cpp new file mode 100644 index 0000000..5be4a8b --- /dev/null +++ b/MalomAPI_cpp/perfect_symmetries_slow.cpp @@ -0,0 +1,254 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_symmetries_slow.h" + +int id(int a) +{ + return a; +} + +int rot90(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 2; + b |= (((1 << 1) & a) >> 1) << 3; + b |= (((1 << 2) & a) >> 2) << 4; + b |= (((1 << 3) & a) >> 3) << 5; + b |= (((1 << 4) & a) >> 4) << 6; + b |= (((1 << 5) & a) >> 5) << 7; + b |= (((1 << 6) & a) >> 6) << 0; + b |= (((1 << 7) & a) >> 7) << 1; + b |= (((1 << 8) & a) >> 8) << 10; + b |= (((1 << 9) & a) >> 9) << 11; + b |= (((1 << 10) & a) >> 10) << 12; + b |= (((1 << 11) & a) >> 11) << 13; + b |= (((1 << 12) & a) >> 12) << 14; + b |= (((1 << 13) & a) >> 13) << 15; + b |= (((1 << 14) & a) >> 14) << 8; + b |= (((1 << 15) & a) >> 15) << 9; + b |= (((1 << 16) & a) >> 16) << 18; + b |= (((1 << 17) & a) >> 17) << 19; + b |= (((1 << 18) & a) >> 18) << 20; + b |= (((1 << 19) & a) >> 19) << 21; + b |= (((1 << 20) & a) >> 20) << 22; + b |= (((1 << 21) & a) >> 21) << 23; + b |= (((1 << 22) & a) >> 22) << 16; + b |= (((1 << 23) & a) >> 23) << 17; + return b; +} + +int rot180(int a) +{ + return rot90(rot90(a)); +} + +int rot270(int a) +{ + return rot180(rot90(a)); +} + +int tt_fuggoleges(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 4; + b |= (((1 << 1) & a) >> 1) << 3; + b |= (((1 << 2) & a) >> 2) << 2; + b |= (((1 << 3) & a) >> 3) << 1; + b |= (((1 << 4) & a) >> 4) << 0; + b |= (((1 << 5) & a) >> 5) << 7; + b |= (((1 << 6) & a) >> 6) << 6; + b |= (((1 << 7) & a) >> 7) << 5; + b |= (((1 << 8) & a) >> 8) << 12; + b |= (((1 << 9) & a) >> 9) << 11; + b |= (((1 << 10) & a) >> 10) << 10; + b |= (((1 << 11) & a) >> 11) << 9; + b |= (((1 << 12) & a) >> 12) << 8; + b |= (((1 << 13) & a) >> 13) << 15; + b |= (((1 << 14) & a) >> 14) << 14; + b |= (((1 << 15) & a) >> 15) << 13; + b |= (((1 << 16) & a) >> 16) << 20; + b |= (((1 << 17) & a) >> 17) << 19; + b |= (((1 << 18) & a) >> 18) << 18; + b |= (((1 << 19) & a) >> 19) << 17; + b |= (((1 << 20) & a) >> 20) << 16; + b |= (((1 << 21) & a) >> 21) << 23; + b |= (((1 << 22) & a) >> 22) << 22; + b |= (((1 << 23) & a) >> 23) << 21; + return b; +} + +int tt_vizszintes(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 0; + b |= (((1 << 1) & a) >> 1) << 7; + b |= (((1 << 2) & a) >> 2) << 6; + b |= (((1 << 3) & a) >> 3) << 5; + b |= (((1 << 4) & a) >> 4) << 4; + b |= (((1 << 5) & a) >> 5) << 3; + b |= (((1 << 6) & a) >> 6) << 2; + b |= (((1 << 7) & a) >> 7) << 1; + b |= (((1 << 8) & a) >> 8) << 8; + b |= (((1 << 9) & a) >> 9) << 15; + b |= (((1 << 10) & a) >> 10) << 14; + b |= (((1 << 11) & a) >> 11) << 13; + b |= (((1 << 12) & a) >> 12) << 12; + b |= (((1 << 13) & a) >> 13) << 11; + b |= (((1 << 14) & a) >> 14) << 10; + b |= (((1 << 15) & a) >> 15) << 9; + b |= (((1 << 16) & a) >> 16) << 16; + b |= (((1 << 17) & a) >> 17) << 23; + b |= (((1 << 18) & a) >> 18) << 22; + b |= (((1 << 19) & a) >> 19) << 21; + b |= (((1 << 20) & a) >> 20) << 20; + b |= (((1 << 21) & a) >> 21) << 19; + b |= (((1 << 22) & a) >> 22) << 18; + b |= (((1 << 23) & a) >> 23) << 17; + return b; +} + +int tt_bslash(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 2; + b |= (((1 << 1) & a) >> 1) << 1; + b |= (((1 << 2) & a) >> 2) << 0; + b |= (((1 << 3) & a) >> 3) << 7; + b |= (((1 << 4) & a) >> 4) << 6; + b |= (((1 << 5) & a) >> 5) << 5; + b |= (((1 << 6) & a) >> 6) << 4; + b |= (((1 << 7) & a) >> 7) << 3; + b |= (((1 << 8) & a) >> 8) << 10; + b |= (((1 << 9) & a) >> 9) << 9; + b |= (((1 << 10) & a) >> 10) << 8; + b |= (((1 << 11) & a) >> 11) << 15; + b |= (((1 << 12) & a) >> 12) << 14; + b |= (((1 << 13) & a) >> 13) << 13; + b |= (((1 << 14) & a) >> 14) << 12; + b |= (((1 << 15) & a) >> 15) << 11; + b |= (((1 << 16) & a) >> 16) << 18; + b |= (((1 << 17) & a) >> 17) << 17; + b |= (((1 << 18) & a) >> 18) << 16; + b |= (((1 << 19) & a) >> 19) << 23; + b |= (((1 << 20) & a) >> 20) << 22; + b |= (((1 << 21) & a) >> 21) << 21; + b |= (((1 << 22) & a) >> 22) << 20; + b |= (((1 << 23) & a) >> 23) << 19; + return b; +} + +int tt_slash(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 6; + b |= (((1 << 1) & a) >> 1) << 5; + b |= (((1 << 2) & a) >> 2) << 4; + b |= (((1 << 3) & a) >> 3) << 3; + b |= (((1 << 4) & a) >> 4) << 2; + b |= (((1 << 5) & a) >> 5) << 1; + b |= (((1 << 6) & a) >> 6) << 0; + b |= (((1 << 7) & a) >> 7) << 7; + b |= (((1 << 8) & a) >> 8) << 14; + b |= (((1 << 9) & a) >> 9) << 13; + b |= (((1 << 10) & a) >> 10) << 12; + b |= (((1 << 11) & a) >> 11) << 11; + b |= (((1 << 12) & a) >> 12) << 10; + b |= (((1 << 13) & a) >> 13) << 9; + b |= (((1 << 14) & a) >> 14) << 8; + b |= (((1 << 15) & a) >> 15) << 15; + b |= (((1 << 16) & a) >> 16) << 22; + b |= (((1 << 17) & a) >> 17) << 21; + b |= (((1 << 18) & a) >> 18) << 20; + b |= (((1 << 19) & a) >> 19) << 19; + b |= (((1 << 20) & a) >> 20) << 18; + b |= (((1 << 21) & a) >> 21) << 17; + b |= (((1 << 22) & a) >> 22) << 16; + b |= (((1 << 23) & a) >> 23) << 23; + return b; +} + +int swap(int a) +{ + int b = 0; + b |= (((1 << 0) & a) >> 0) << 16; + b |= (((1 << 1) & a) >> 1) << 17; + b |= (((1 << 2) & a) >> 2) << 18; + b |= (((1 << 3) & a) >> 3) << 19; + b |= (((1 << 4) & a) >> 4) << 20; + b |= (((1 << 5) & a) >> 5) << 21; + b |= (((1 << 6) & a) >> 6) << 22; + b |= (((1 << 7) & a) >> 7) << 23; + b |= (((1 << 8) & a) >> 8) << 8; + b |= (((1 << 9) & a) >> 9) << 9; + b |= (((1 << 10) & a) >> 10) << 10; + b |= (((1 << 11) & a) >> 11) << 11; + b |= (((1 << 12) & a) >> 12) << 12; + b |= (((1 << 13) & a) >> 13) << 13; + b |= (((1 << 14) & a) >> 14) << 14; + b |= (((1 << 15) & a) >> 15) << 15; + b |= (((1 << 16) & a) >> 16) << 0; + b |= (((1 << 17) & a) >> 17) << 1; + b |= (((1 << 18) & a) >> 18) << 2; + b |= (((1 << 19) & a) >> 19) << 3; + b |= (((1 << 20) & a) >> 20) << 4; + b |= (((1 << 21) & a) >> 21) << 5; + b |= (((1 << 22) & a) >> 22) << 6; + b |= (((1 << 23) & a) >> 23) << 7; + return b; +} + +int swap_rot90(int a) +{ + return swap(rot90(a)); +} + +int swap_rot180(int a) +{ + return swap(rot180(a)); +} + +int swap_rot270(int a) +{ + return swap(rot270(a)); +} + +int swap_tt_fuggoleges(int a) +{ + return swap(tt_fuggoleges(a)); +} + +int swap_tt_vizszintes(int a) +{ + return swap(tt_vizszintes(a)); +} + +int swap_tt_bslash(int a) +{ + return swap(tt_bslash(a)); +} + +int swap_tt_slash(int a) +{ + return swap(tt_slash(a)); +} diff --git a/MalomAPI_cpp/perfect_symmetries_slow.h b/MalomAPI_cpp/perfect_symmetries_slow.h new file mode 100644 index 0000000..5e06707 --- /dev/null +++ b/MalomAPI_cpp/perfect_symmetries_slow.h @@ -0,0 +1,44 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_SYMMETRIES_SLOW_H_INCLUDED +#define PERFECT_SYMMETRIES_SLOW_H_INCLUDED + +int id(int a); +int rot90(int a); +int rot180(int a); +int rot270(int a); +int tt_fuggoleges(int a); +int tt_vizszintes(int a); +int tt_bslash(int a); +int tt_slash(int a); +int swap(int a); +int swap_rot90(int a); +int swap_rot180(int a); +int swap_rot270(int a); +int swap_tt_fuggoleges(int a); +int swap_tt_vizszintes(int a); +int swap_tt_bslash(int a); +int swap_tt_slash(int a); + +#endif // PERFECT_SYMMETRIES_SLOW_H_INCLUDED diff --git a/MalomAPI_cpp/perfect_test.cpp b/MalomAPI_cpp/perfect_test.cpp new file mode 100644 index 0000000..e59dfb3 --- /dev/null +++ b/MalomAPI_cpp/perfect_test.cpp @@ -0,0 +1,63 @@ +// Malom, a Nine Men's Morris (and variants) player and solver program. +// Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +// Copyright (C) 2023 The Sanmill developers (see AUTHORS file) +// +// See our webpage (and the paper linked from there): +// http://compalg.inf.elte.hu/~ggevay/mills/index.php +// +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// You have to set the working directory to the directory of the database. + +#define USE_DEPRECATED_CLR_API_WITHOUT_WARNING + +#include +#include +#include + +#include "perfect_api.h" +#include "perfect_common.h" + +int perfect_test(int argc, char *argv[]) +{ + if (argc == 2) { + sec_val_path = argv[1]; + } + + //int res = MalomSolutionAccess::getBestMove(0, 0, 9, 9, 0, false); + // Correct output: 16384 + int res = MalomSolutionAccess::getBestMove(1, 2, 8, 8, 0, false); + // int res = MalomSolutionAccess::getBestMove(1 + 2 + 4, 8 + 16 + 32, 100, + // 0, 0, false); // tests exception + // int res = MalomSolutionAccess::getBestMove(1 + 2 + 4, 1 + 8 + 16 + 32, + // 0, 0, 0, false); // tests exception int res = + // MalomSolutionAccess::getBestMove(1 + 2 + 4, 8 + 16 + 32, 0, 0, 0, true); + // // Correct output: any of 8, 16, 32 + + printf("GetBestMove result: %d\n", res); + +#ifdef _WIN32 + system("pause"); +#endif + + return 0; +} + +int main(int argc, char *argv[]) +{ + perfect_test(argc, argv); + + return 0; +} diff --git a/MalomAPI_cpp/perfect_wrappers.cpp b/MalomAPI_cpp/perfect_wrappers.cpp new file mode 100644 index 0000000..054e616 --- /dev/null +++ b/MalomAPI_cpp/perfect_wrappers.cpp @@ -0,0 +1,83 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "perfect_wrappers.h" + +std::unordered_map sector_sizes; + +// This manages the lookup tables of the hash function: it keeps them in memory +// for a few most recently accessed sectors. +std::pair Wrappers::WSector::hash(board a) +{ + static std::set> loaded_hashes; + static std::map<::Sector *, int> loaded_hashes_inv; + static int timestamp = 0; + + ::Sector *tmp = s; + + if (s->hash == nullptr) { + // hash object is not present + + if (loaded_hashes.size() == 8) { + // release one if there are too many + ::Sector *to_release = loaded_hashes.begin()->second; + LOG("Releasing hash: %s\n", to_release->id.to_string().c_str()); + to_release->release_hash(); + loaded_hashes.erase(loaded_hashes.begin()); + loaded_hashes_inv.erase(to_release); + } + + // load new one + LOG("Loading hash: %s\n", s->id.to_string().c_str()); + s->allocate_hash(); + } else { + // update access time + loaded_hashes.erase(std::make_pair(loaded_hashes_inv[tmp], tmp)); + } + loaded_hashes.insert(std::make_pair(timestamp, tmp)); + // s doesn't work here, which is probably a compiler bug! + loaded_hashes_inv[tmp] = timestamp; + + timestamp++; + + auto e = s->hash->hash(a); + return std::make_pair(e.first, Wrappers::gui_eval_elem2(e.second, s)); +} + +void Wrappers::WID::negate() +{ + int t = W; + W = B; + B = t; + + t = WF; + WF = BF; + BF = t; +} + +Wrappers::WID operator-(Wrappers::WID s) +{ + id r = s.tonat(); + r.negate(); + return Wrappers::WID(r); +} diff --git a/MalomAPI_cpp/perfect_wrappers.h b/MalomAPI_cpp/perfect_wrappers.h new file mode 100644 index 0000000..7858c3c --- /dev/null +++ b/MalomAPI_cpp/perfect_wrappers.h @@ -0,0 +1,362 @@ +/* +Malom, a Nine Men's Morris (and variants) player and solver program. +Copyright(C) 2007-2016 Gabor E. Gevay, Gabor Danner +Copyright (C) 2023 The Sanmill developers (see AUTHORS file) + +See our webpage (and the paper linked from there): +http://compalg.inf.elte.hu/~ggevay/mills/index.php + + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef PERFECT_WRAPPER_H_INCLUDED +#define PERFECT_WRAPPER_H_INCLUDED + +#include "perfect_common.h" +#include "perfect_hash.h" +#include "perfect_symmetries.h" +#include "perfect_debug.h" +#include "perfect_sector.h" +#include "perfect_sector_graph.h" + +#include +#include // for factorial function +#include +#include +#include +#include +#include +#include +#include + +namespace Wrappers { + +class WSector; + +extern std::unordered_map sector_sizes; +static int f_inv_count[] {1, 4, 30, 158, 757, 2830, 8774, + 22188, 46879, 82880, 124124, 157668, 170854}; + +struct WID +{ + int W, B, WF, BF; + WID(int W, int B, int WF, int BF) + : W(W) + , B(B) + , WF(WF) + , BF(BF) + { } + WID(id id) + : W(id.W) + , B(id.B) + , WF(id.WF) + , BF(id.BF) + { } + ::id tonat() { return ::id(W, B, WF, BF); } + void negate(); + WID operator-(WID s); + + std::string ToString() { return this->tonat().to_string(); } + + int GetHashCode() { return (W << 0) | (B << 4) | (WF << 8) | (BF << 12); } + +private: + static int64_t factorial(int n) + { + if (n == 0) + return 1; + else + return n * factorial(n - 1); + } + + static int64_t nCr(int n, int r) + { + return factorial(n) / (factorial(r) * factorial(n - r)); + } + +public: + int size() + { + auto tn = tonat(); + if (sector_sizes.count(tn) == 0) { + sector_sizes[tn] = static_cast(nCr(24 - W, B)) * + f_inv_count[W]; + } + return sector_sizes[tn]; + } + + bool operator==(const WID &other) const + { + return W == other.W && B == other.B && WF == other.WF && BF == other.BF; + } + + bool operator<(const WID &other) const + { + return std::tie(W, B, WF, BF) < + std::tie(other.W, other.B, other.WF, other.BF); + } +}; + +struct eval_elem +{ + enum class cas { val, count, sym }; + cas c; + int x; + + eval_elem(cas c, int x) + : c(c) + , x(x) + { } + eval_elem(::eval_elem e) + : c(static_cast(e.c)) + , x(e.x) + { } +}; + +struct gui_eval_elem2; + +class WSector +{ +public: + ::Sector *s; + WSector(WID id) + : s(new ::Sector(id.tonat())) + { } + + std::pair hash(board a); + + sec_val sval() { return s->sval; } +}; + +struct gui_eval_elem2 +{ +private: + // azert cannot be valid, because it cannot contain a count (as asserted by + // the ctor) + sec_val key1; + int key2; + ::Sector *s; // this is zero if there is a virtual win/loss or KLE + + enum class Cas { Val, Count }; + + eval_elem2 to_eval_elem2() const { return eval_elem2 {key1, key2}; } + +public: + // The nezo point of key1 is s. However, if s is null, then + // virt_unique_sec_val. + gui_eval_elem2(sec_val key1, int key2, Sector *s) + : key1 {key1} + , key2 {key2} + , s {s} + { } + gui_eval_elem2(::eval_elem2 e, ::Sector *s) + : gui_eval_elem2 {e.key1, e.key2, s} + { } + + gui_eval_elem2 undo_negate(WSector *sector) + { + auto a = this->to_eval_elem2().corr( + (sector ? sector->sval() : virt_unique_sec_val()) + + (this->s ? this->s->sval : virt_unique_sec_val())); + a.key1 *= -1; + if (sector) // if sector is null, we go to KLE + a.key2++; + return gui_eval_elem2(a, sector ? sector->s : nullptr); + } + + inline static const bool ignore_DD = false; + +private: + static sec_val abs_min_value() + { + assert(::virt_loss_val != 0); + return ::virt_loss_val - 2; + } + static void drop_DD(eval_elem2 &e) + { + // absolute viewpoint + assert(e.key1 >= abs_min_value()); + assert(e.key1 <= ::virt_win_val); + assert(e.key1 != ::virt_loss_val - 1); // You can take it out + if (e.key1 != virt_win_val && e.key1 != ::virt_loss_val && + e.key1 != abs_min_value()) + e.key1 = 0; + } + +public: + int compare(const gui_eval_elem2 &o) const + { + assert(s == o.s); + if (!ignore_DD) { + if (key1 != o.key1) + return key1 < o.key1 ? -1 : 1; + else if (key1 < 0) + return key2 < o.key2 ? -1 : 1; + else if (key1 > 0) + return key2 > o.key2 ? -1 : 1; + else + return 0; + } else { + auto a1 = to_eval_elem2().corr(s ? s->sval : virt_unique_sec_val()); + auto a2 = o.to_eval_elem2().corr(o.s ? o.s->sval : + virt_unique_sec_val()); + drop_DD(a1); + drop_DD(a2); + if (a1.key1 != a2.key1) + return a1.key1 < a2.key1 ? -1 : 1; + else if (a1.key1 < 0) + return a1.key2 < a2.key2 ? -1 : 1; + else if (a1.key1 > 0) + return a2.key2 < a1.key2 ? -1 : 1; + else + return 0; + } + } + + bool operator<(const gui_eval_elem2 &b) const + { + return this->compare(b) < 0; + } + bool operator>(const gui_eval_elem2 &b) const + { + return this->compare(b) > 0; + } + bool operator==(const gui_eval_elem2 &b) const + { + return this->compare(b) == 0; + } + + static gui_eval_elem2 min_value(WSector *s) + { + return gui_eval_elem2 { + static_cast(abs_min_value() - + (s ? s->sval() : virt_unique_sec_val())), + 0, s ? s->s : nullptr}; + } + + static gui_eval_elem2 virt_loss_val() + { + // Attention: it works well only in KLE because, in order to work + // correctly, something meaningful should be subtracted, but we always + // subtract virt_unique_sec_val from it. + assert(::virt_loss_val); + return gui_eval_elem2 { + static_cast(::virt_loss_val - virt_unique_sec_val()), 0, + nullptr}; + } + + static sec_val virt_unique_sec_val() + { + // It is necessary so that the distance is not reset in KLE positions. + assert(::virt_loss_val); +#ifdef DD + return ::virt_loss_val - 1; +#else + return 0; +#endif + } + + sec_val akey1() + { + return key1 + (s ? s->sval : virt_unique_sec_val()); + } + + std::string toString() + { + assert(::virt_loss_val); + assert(::virt_win_val); + std::string s1, s2; + + sec_val akey1 = this->akey1(); + s1 = sec_val_to_sec_name(akey1); + + if (key1 == 0) +#ifdef DD + s2 = "C"; // The value of akey2 is always 0 here. +#else + s2 = ""; +#endif + else + s2 = std::to_string(key2); + +#ifdef DD + return s1 + ", (" + std::to_string(key1) + ", " + s2 + ")"; +#else + return s1 + s2; +#endif + } +}; + +class Nwu +{ +public: + static std::vector WuIds; + static void initWuGraph() + { + init_sector_graph(); + WuIds = std::vector(); + for (auto it = wu_ids.begin(); it != wu_ids.end(); ++it) + WuIds.push_back(WID(*it)); + } + static std::vector wuGraphT(WID u) + { + auto r = std::vector(); + wu *w = wus[u.tonat()]; + for (auto it = w->parents.begin(); it != w->parents.end(); ++it) + r.push_back(WID((*it)->id)); + return r; + } + static bool twine(WID w) { return wus[w.tonat()]->twine; } +}; + +class Init +{ +public: + static void init_sym_lookuptables() { ::init_sym_lookuptables(); } + static void init_sec_vals() { ::init_sec_vals(); } +}; + +class Constants +{ +public: + static const int variant = RULE_VARIANT; + inline static const std::string fname_suffix = FNAME_SUFFIX; + const std::string movegenFname = movegen_file; + + enum class Variants { std = STANDARD, mora = MORABARABA, lask = LASKER }; + +#ifdef DD + static const bool dd = true; +#else + static const bool dd = false; +#endif + + static const bool FBD = FULL_BOARD_IS_DRAW; + +#ifdef FULL_SECTOR_GRAPH + static const bool extended = true; +#else + static const bool extended = false; +#endif +}; + +class Helpers +{ +public: + static std::string toclp(board a) { return ::toclp(a); } +}; +} // namespace Wrappers + +#endif // PERFECT_WRAPPER_H_INCLUDED diff --git a/Malom_megoldas.sln b/Malom_megoldas.sln index 25b15ff..76e6361 100644 --- a/Malom_megoldas.sln +++ b/Malom_megoldas.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.1500 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33723.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Malom_megoldas", "Malom_megoldas\Malom_megoldas.vcxproj", "{F77A30F9-3EB8-4748-9B06-4B2F8D694847}" EndProject @@ -17,6 +17,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Analyzer", "Analyzer\Analyz EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "MalomAPI", "MalomAPI\MalomAPI.vbproj", "{0F512A78-B6AF-40F9-93A6-AB72659A98DF}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MalomAPI_cpp", "MalomAPI_cpp\MalomAPI_cpp.vcxproj", "{3D1BED67-7F9E-495C-B4AD-FD473286EC3F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -184,6 +186,36 @@ Global {0F512A78-B6AF-40F9-93A6-AB72659A98DF}.Release|x64.Build.0 = Release|Any CPU {0F512A78-B6AF-40F9-93A6-AB72659A98DF}.Release|x86.ActiveCfg = Release|Any CPU {0F512A78-B6AF-40F9-93A6-AB72659A98DF}.Release|x86.Build.0 = Release|Any CPU + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Any CPU.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Any CPU.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Mixed Platforms.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Win32.ActiveCfg = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|Win32.Build.0 = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|x64.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|x64.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|x86.ActiveCfg = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Debug|x86.Build.0 = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Any CPU.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Any CPU.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Mixed Platforms.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Mixed Platforms.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Win32.ActiveCfg = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|Win32.Build.0 = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|x64.ActiveCfg = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|x64.Build.0 = Debug|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|x86.ActiveCfg = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Opt_Debug|x86.Build.0 = Debug|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Any CPU.ActiveCfg = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Any CPU.Build.0 = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Mixed Platforms.ActiveCfg = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Mixed Platforms.Build.0 = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Win32.ActiveCfg = Release|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|Win32.Build.0 = Release|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|x64.ActiveCfg = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|x64.Build.0 = Release|x64 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|x86.ActiveCfg = Release|Win32 + {3D1BED67-7F9E-495C-B4AD-FD473286EC3F}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE