├── PerfectClearNET ├── sfinder-dll │ ├── callback.cpp │ ├── callback.hpp │ ├── finder │ │ ├── frames.hpp │ │ ├── two_lines_pc.hpp │ │ ├── frames.cpp │ │ ├── types.hpp │ │ ├── thread_pool.hpp │ │ ├── spins.hpp │ │ └── perfect_clear.cpp │ ├── resource.h │ ├── core │ │ ├── bits.hpp │ │ ├── types.hpp │ │ ├── srs.hpp │ │ ├── field.hpp │ │ ├── srs.cpp │ │ ├── moves.cpp │ │ ├── piece.hpp │ │ ├── field.cpp │ │ ├── piece.cpp │ │ └── bits.cpp │ ├── app.rc │ ├── sfinder-dll.vcxproj.filters │ ├── main.cpp │ └── sfinder-dll.vcxproj ├── Tester │ ├── packages.config │ ├── App.config │ ├── Properties │ │ ├── Settings.settings │ │ ├── Settings.Designer.cs │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── Program.cs │ ├── Tester.csproj │ ├── MainForm.Designer.cs │ ├── MainForm.resx │ └── MainForm.cs ├── PerfectClearNET │ ├── PerfectClearNET.targets │ ├── Enums.cs │ ├── PerfectClearNET.nuspec │ ├── AbortCoordinator.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Operation.cs │ ├── Interface.cs │ ├── PerfectClearNET.csproj │ └── Main.cs └── PerfectClearNET.sln ├── README.md ├── .gitattributes └── .gitignore /PerfectClearNET/sfinder-dll/callback.cpp: -------------------------------------------------------------------------------- 1 | #include "callback.hpp" 2 | 3 | Callback Abort = 0; -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/callback.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CALLBACK_H 2 | #define CALLBACK_H 3 | 4 | typedef int(__stdcall * Callback)(); 5 | 6 | extern Callback Abort; 7 | 8 | #endif -------------------------------------------------------------------------------- /PerfectClearNET/Tester/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/frames.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FINDER_FRAMES_HPP 2 | #define FINDER_FRAMES_HPP 3 | 4 | #include "types.hpp" 5 | 6 | #include "../core/types.hpp" 7 | 8 | namespace finder { 9 | int getFrames(Operation &operation); 10 | } 11 | 12 | #endif //FINDER_FRAMES_HPP 13 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/two_lines_pc.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FINDER_TWO_LINE_PC_HPP 2 | #define FINDER_TWO_LINE_PC_HPP 3 | 4 | #include 5 | 6 | #include "../core/types.hpp" 7 | 8 | namespace finder { 9 | bool canTake2LinePC(std::vector &pieces); 10 | } 11 | 12 | #endif //FINDER_TWO_LINE_PC_HPP 13 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/PerfectClearNET.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | sfinder-dll.dll 5 | PreserveNewest 6 | 7 | 8 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/Enums.cs: -------------------------------------------------------------------------------- 1 | namespace PerfectClearNET { 2 | public enum PerfectClearGame { 3 | None = 0, 4 | PPT = 1, 5 | TETRIO = 2 6 | } 7 | 8 | public enum SearchType { 9 | Fast, 10 | TSpins, 11 | AllSpins, 12 | AllSpinsNoMini, 13 | TETRIOSeason2 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace Tester { 5 | static class Program { 6 | [STAThread] 7 | static void Main() { 8 | Application.EnableVisualStyles(); 9 | Application.SetCompatibleTextRenderingDefault(false); 10 | Application.Run(new MainForm()); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by app.rc 4 | // 5 | 6 | // Next default values for new objects 7 | // 8 | #ifdef APSTUDIO_INVOKED 9 | #ifndef APSTUDIO_READONLY_SYMBOLS 10 | #define _APS_NEXT_RESOURCE_VALUE 101 11 | #define _APS_NEXT_COMMAND_VALUE 40001 12 | #define _APS_NEXT_CONTROL_VALUE 1001 13 | #define _APS_NEXT_SYMED_VALUE 101 14 | #endif 15 | #endif 16 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/frames.cpp: -------------------------------------------------------------------------------- 1 | #include "frames.hpp" 2 | 3 | namespace finder { 4 | int getFrames(Operation& operation) { 5 | int x = operation.x; 6 | int r = operation.rotateType; 7 | 8 | if (operation.pieceType == core::PieceType::O) { 9 | r = 0; 10 | } 11 | 12 | if (operation.pieceType == core::PieceType::I) { 13 | if (r == 3 && x >= 5) r = 1; 14 | if (r == 1 && x <= 4) r = 3; 15 | 16 | if (operation.rotateType == 1 || operation.rotateType == 2) x--; 17 | } 18 | 19 | r = abs(2 - ((r + 2) % 4)); 20 | int m = abs(4 - x); 21 | 22 | // note: we count the frame we hard drop on 23 | if (m == 0 && r == 0) { 24 | return 1; 25 | 26 | } else if (m >= r) { 27 | return m * 2; 28 | 29 | } else { 30 | return r * 2 - 1; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/PerfectClearNET.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PerfectClearNET 5 | 1.4.0.3 6 | PerfectClearNET 7 | mat1jaczyyy 8 | mat1jaczyyy 9 | https://github.com/mat1jaczyyy/PerfectClearNET 10 | A .NET wrapper for knewjade's solution-finder, a guideline Tetris Perfect Clear Finder 11 | 12 | tetris finder search 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/bits.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CORE_BITS_HPP 2 | #define CORE_BITS_HPP 3 | 4 | #include 5 | 6 | #include "types.hpp" 7 | 8 | namespace core { 9 | Bitboard deleteLine_(Bitboard x, LineKey key); 10 | 11 | Bitboard deleteLine(Bitboard x, LineKey mask); 12 | 13 | Bitboard insertBlackLine_(Bitboard x, LineKey key); 14 | 15 | Bitboard insertBlackLine(Bitboard x, LineKey mask); 16 | 17 | Bitboard insertWhiteLine_(Bitboard x, LineKey key); 18 | 19 | Bitboard insertWhiteLine(Bitboard x, LineKey mask); 20 | 21 | Bitboard getColumnOneLineBelowY(int maxY); 22 | 23 | bool isWallBetweenLeft(int x, int maxY, Bitboard board); 24 | 25 | int bitCount(uint64_t b); 26 | 27 | int mostSignificantDigit(uint64_t b); 28 | 29 | uint64_t fillVertical(uint64_t b); 30 | } 31 | 32 | #endif //CORE_BITS_HPP 33 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CORE_TYPES_HPP 2 | #define CORE_TYPES_HPP 3 | 4 | #include 5 | 6 | namespace core { 7 | using Bitboard = uint64_t; 8 | using LineKey = uint64_t; 9 | 10 | enum PieceType { 11 | Empty = -1, 12 | T = 0, 13 | I = 1, 14 | L = 2, 15 | J = 3, 16 | S = 4, 17 | Z = 5, 18 | O = 6, 19 | }; 20 | 21 | enum RotateType { 22 | Spawn = 0, 23 | Right = 1, 24 | Reverse = 2, 25 | Left = 3, 26 | }; 27 | 28 | const auto dummyRotate = RotateType::Spawn; 29 | const RotateType rotateBitToVal[16] {dummyRotate, 30 | RotateType::Spawn, 31 | RotateType::Right, dummyRotate, 32 | RotateType::Reverse, dummyRotate, dummyRotate, dummyRotate, 33 | RotateType::Left, 34 | }; 35 | 36 | const int FIELD_WIDTH = 10; 37 | const int MAX_FIELD_HEIGHT = 24; 38 | } 39 | 40 | #endif //CORE_TYPES_HPP 41 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/srs.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CORE_SRS_HPP 2 | #define CORE_SRS_HPP 3 | 4 | #include "field.hpp" 5 | #include "types.hpp" 6 | 7 | namespace core::srs { 8 | int right( 9 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 10 | ); 11 | 12 | int right( 13 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 14 | ); 15 | 16 | int left( 17 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 18 | ); 19 | 20 | int left( 21 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 22 | ); 23 | 24 | int rotate180( 25 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 26 | ); 27 | 28 | int rotate180( 29 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 30 | ); 31 | } 32 | 33 | #endif //CORE_SRS_HPP -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Tester.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PerfectClearNET 2 | [![NuGet - Download](https://img.shields.io/badge/nuget-download-orange)](https://www.nuget.org/packages/PerfectClearNET/) 3 | [![Discord - Join Chat](https://img.shields.io/badge/discord-join%20chat-blue)](https://discord.gg/vfrmzUV) 4 | 5 | .NET version of the knewjade Perfect Clear solution finder (via DLL importing). 6 | 7 | ## Installation and Usage Example 8 | 9 | Use PerfectClearNET from [NuGet](https://www.nuget.org/packages/MisaMinoNET/) in your project. 10 | 11 | ```cs 12 | using PerfectClearNET; 13 | 14 | // Listen for search completion 15 | PerfectClear.Finished += ...; 16 | 17 | // Start search in the background 18 | PerfectClear.Find(...); 19 | 20 | // Abort search prematurely 21 | PerfectClear.Abort(); 22 | 23 | // Access results of last search 24 | PerfectClear.LastSolution; 25 | PerfectClear.LastTime; 26 | ``` 27 | 28 | A common need with the Perfect Clear Finder is a pathfinder to tell how to move the piece into position, [MisaMinoNET](https://github.com/mat1jaczyyy/MisaMinoNET)'s pathfinder can be used for this purpose: 29 | 30 | ```cs 31 | using PerfectClearNET; 32 | using MisaMinoNET; 33 | 34 | // Utilize MisaMinoNET pathfinder after search 35 | movements = MisaMino.FindPath( 36 | ..., 37 | PerfectClear.LastSolution[0].Piece, 38 | PerfectClear.LastSolution[0].X, 39 | PerfectClear.LastSolution[0].Y, 40 | PerfectClear.LastSolution[0].R, 41 | current_piece != PerfectClear.LastSolution[0].Piece, 42 | ... 43 | ); 44 | ``` 45 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tester")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tester")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4b48bc83-a776-498d-bf97-ef494575ba24")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/AbortCoordinator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | 6 | namespace PerfectClearNET { 7 | static class AbortCoordinator { 8 | private static readonly object locker = new object(); 9 | private static readonly HashSet waits = new HashSet(); 10 | 11 | public static Action CreateWaiter() { 12 | var wait = new ManualResetEvent(false); 13 | lock (locker) 14 | waits.Add(wait); 15 | 16 | return () => { 17 | try { 18 | while (true) { 19 | if (wait.WaitOne(200)) 20 | return; 21 | 22 | if (!Interface.Running) { 23 | // We're in the fucking stupid ass undebuggable deadlock 24 | lock (locker) 25 | waits.Remove(wait); 26 | return; 27 | } 28 | } 29 | } finally { 30 | wait.Dispose(); 31 | } 32 | }; 33 | } 34 | 35 | public static void WakeWaiters() { 36 | ManualResetEvent[] toSignal; 37 | lock (locker) { 38 | toSignal = waits.ToArray(); 39 | waits.Clear(); 40 | } 41 | 42 | foreach (var ev in toSignal) 43 | ev.Set(); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("PerfectClearNET")] 9 | [assembly: AssemblyDescription("A .NET wrapper for knewjade's solution-finder, a guideline Tetris Perfect Clear Finder")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("mat1jaczyyy")] 12 | [assembly: AssemblyProduct("PerfectClearNET")] 13 | [assembly: AssemblyCopyright("Copyright © 2019 - 2025")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("12ac9064-faf9-4714-8e9f-9ec11d897daf")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.5.0.0")] 36 | [assembly: AssemblyFileVersion("1.5.0.0")] 37 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/Operation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace PerfectClearNET { 6 | /// 7 | /// A performable in-game placement described with the piece's final position. 8 | /// 9 | public class Operation { 10 | #pragma warning disable 0169 11 | /// 12 | /// Gets which piece should be used in the Operation. 13 | /// 14 | public int Piece { get; private set; } 15 | 16 | /// 17 | /// Gets the X position of the piece. 18 | /// 19 | public int X { get; private set; } 20 | 21 | /// 22 | /// Gets the Y position of the piece. 23 | /// 24 | public int Y { get; private set; } 25 | 26 | /// 27 | /// Gets the rotation of the piece. 28 | /// 29 | public int R { get; private set; } 30 | #pragma warning restore 0169 31 | 32 | /// 33 | /// Creates a custom Operation from raw Perfect Clear Finder output. 34 | /// 35 | /// The raw Perfect Clear Finder output string which resulted from the search. 36 | public Operation(string input) { 37 | List parsed = (from i in input.Split(',') select Convert.ToInt32(i)).ToList(); 38 | 39 | Piece = PerfectClear.FromFinder[parsed[0]]; 40 | X = parsed[1]; 41 | R = parsed[3]; 42 | 43 | Y = 23 - parsed[2] - Convert.ToInt32(Piece == 6 && R == 3); 44 | } 45 | 46 | /// 47 | /// Returns a human-readable string representation of the Operation. 48 | /// 49 | public override string ToString() => $"{PerfectClear.ToChar[Piece]}={X},{Y},{R}"; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31515.178 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerfectClearNET", "PerfectClearNET\PerfectClearNET.csproj", "{12AC9064-FAF9-4714-8E9F-9EC11D897DAF}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997} = {F71BF46C-2C69-40AF-A5DF-A73E25A21997} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sfinder-dll", "sfinder-dll\sfinder-dll.vcxproj", "{F71BF46C-2C69-40AF-A5DF-A73E25A21997}" 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tester", "Tester\Tester.csproj", "{4B48BC83-A776-498D-BF97-EF494575BA24}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|x64 = Debug|x64 18 | Release|x64 = Release|x64 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {12AC9064-FAF9-4714-8E9F-9EC11D897DAF}.Debug|x64.ActiveCfg = Debug|x64 22 | {12AC9064-FAF9-4714-8E9F-9EC11D897DAF}.Debug|x64.Build.0 = Debug|x64 23 | {12AC9064-FAF9-4714-8E9F-9EC11D897DAF}.Release|x64.ActiveCfg = Release|x64 24 | {12AC9064-FAF9-4714-8E9F-9EC11D897DAF}.Release|x64.Build.0 = Release|x64 25 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997}.Debug|x64.ActiveCfg = Debug|x64 26 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997}.Debug|x64.Build.0 = Debug|x64 27 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997}.Release|x64.ActiveCfg = Release|x64 28 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997}.Release|x64.Build.0 = Release|x64 29 | {4B48BC83-A776-498D-BF97-EF494575BA24}.Debug|x64.ActiveCfg = Debug|x64 30 | {4B48BC83-A776-498D-BF97-EF494575BA24}.Debug|x64.Build.0 = Debug|x64 31 | {4B48BC83-A776-498D-BF97-EF494575BA24}.Release|x64.ActiveCfg = Release|x64 32 | {4B48BC83-A776-498D-BF97-EF494575BA24}.Release|x64.Build.0 = Release|x64 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | GlobalSection(ExtensibilityGlobals) = postSolution 38 | SolutionGuid = {B4514018-516A-4FFF-9DB3-D27C034F4D72} 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/Interface.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace PerfectClearNET { 6 | static class Interface { 7 | private static bool abort = false; 8 | 9 | private delegate bool Callback(); 10 | private static Callback AbortCallback; 11 | 12 | [DllImport("sfinder-dll.dll")] 13 | private static extern void set_abort(Callback func); 14 | 15 | private static object locker = new object(); 16 | public static bool Running { get; private set; } = false; 17 | 18 | [DllImport("sfinder-dll.dll")] 19 | public static extern bool init_finder(PerfectClearGame game); 20 | 21 | [DllImport("sfinder-dll.dll")] 22 | public static extern void set_threads(uint threads); 23 | 24 | [DllImport("sfinder-dll.dll")] 25 | private static extern void action( 26 | string field, string queue, string hold, int height, 27 | int max_height, bool swap, int searchtype, int combo, bool b2b, bool two_line, 28 | StringBuilder str, int len 29 | ); 30 | 31 | static Interface() { 32 | AbortCallback = new Callback(Abort); 33 | set_abort(AbortCallback); 34 | } 35 | 36 | public static bool Abort() => abort; 37 | public static void SetAbort() { 38 | if (Running) abort = true; 39 | } 40 | 41 | public static string Process( 42 | string field, string queue, string hold, int height, 43 | int max_height, bool swap, int search_type, int combo, bool b2b, bool two_line, 44 | out long time 45 | ) { 46 | 47 | StringBuilder sb = new StringBuilder(500); 48 | 49 | abort = true; 50 | 51 | lock (locker) { 52 | abort = false; 53 | 54 | Stopwatch stopwatch = new Stopwatch(); 55 | stopwatch.Start(); 56 | 57 | Running = true; 58 | 59 | action( 60 | field, queue, hold, height, 61 | max_height, swap, search_type, combo, b2b, two_line, 62 | sb, sb.Capacity 63 | ); 64 | 65 | Running = false; 66 | 67 | stopwatch.Stop(); 68 | time = stopwatch.ElapsedMilliseconds; 69 | } 70 | 71 | return sb.ToString(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/app.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "resource.h" 4 | 5 | #define APSTUDIO_READONLY_SYMBOLS 6 | ///////////////////////////////////////////////////////////////////////////// 7 | // 8 | // Generated from the TEXTINCLUDE 2 resource. 9 | // 10 | #include "winres.h" 11 | 12 | ///////////////////////////////////////////////////////////////////////////// 13 | #undef APSTUDIO_READONLY_SYMBOLS 14 | 15 | ///////////////////////////////////////////////////////////////////////////// 16 | // English (United States) resources 17 | 18 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 19 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 20 | #pragma code_page(1252) 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Version 51 | // 52 | 53 | VS_VERSION_INFO VERSIONINFO 54 | FILEVERSION 1,4,0,3 55 | PRODUCTVERSION 1,4,0,3 56 | FILEFLAGSMASK 0x3fL 57 | #ifdef _DEBUG 58 | FILEFLAGS 0x1L 59 | #else 60 | FILEFLAGS 0x0L 61 | #endif 62 | FILEOS 0x40004L 63 | FILETYPE 0x2L 64 | FILESUBTYPE 0x0L 65 | BEGIN 66 | BLOCK "StringFileInfo" 67 | BEGIN 68 | BLOCK "040904b0" 69 | BEGIN 70 | VALUE "CompanyName", "mat1jaczyyy" 71 | VALUE "FileVersion", "1.4.0.3" 72 | VALUE "InternalName", "sfinder-.dll" 73 | VALUE "LegalCopyright", "Copyright (C) 2019" 74 | VALUE "OriginalFilename", "sfinder-.dll" 75 | VALUE "ProductName", "PerfectClearNET" 76 | VALUE "ProductVersion", "1.4.0.3" 77 | END 78 | END 79 | BLOCK "VarFileInfo" 80 | BEGIN 81 | VALUE "Translation", 0x409, 1200 82 | END 83 | END 84 | 85 | #endif // English (United States) resources 86 | ///////////////////////////////////////////////////////////////////////////// 87 | 88 | 89 | 90 | #ifndef APSTUDIO_INVOKED 91 | ///////////////////////////////////////////////////////////////////////////// 92 | // 93 | // Generated from the TEXTINCLUDE 3 resource. 94 | // 95 | 96 | 97 | ///////////////////////////////////////////////////////////////////////////// 98 | #endif // not APSTUDIO_INVOKED 99 | 100 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/field.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CORE_FIELD_HPP 2 | #define CORE_FIELD_HPP 3 | 4 | #include 5 | 6 | #include "types.hpp" 7 | #include "bits.hpp" 8 | #include "piece.hpp" 9 | 10 | namespace core { 11 | union Field { 12 | public: 13 | Bitboard boards[4]; 14 | struct { 15 | Bitboard xBoardLow; 16 | Bitboard xBoardMidLow; 17 | Bitboard xBoardMidHigh; 18 | Bitboard xBoardHigh; 19 | }; 20 | 21 | Field() : xBoardLow(0), xBoardMidLow(0), xBoardMidHigh(0), xBoardHigh(0) {}; 22 | 23 | Field(Bitboard low, Bitboard midLow, Bitboard midHigh, Bitboard high) 24 | : xBoardLow(low), xBoardMidLow(midLow), xBoardMidHigh(midHigh), xBoardHigh(high) {}; 25 | 26 | void setBlock(int x, int y); 27 | 28 | void removeBlock(int x, int y); 29 | 30 | bool isEmpty(int x, int y) const; 31 | 32 | void put(const Blocks &blocks, int x, int y); 33 | 34 | void putAtMaskIndex(const Blocks &blocks, int leftX, int lowerY); 35 | 36 | void remove(const Blocks &blocks, int x, int y); 37 | 38 | void removeAtMaskIndex(const Blocks &blocks, int leftX, int lowerY); 39 | 40 | bool canPut(const Blocks &blocks, int x, int y) const; 41 | 42 | bool canPutAtMaskIndex(const Blocks &blocks, int leftX, int lowerY) const; 43 | 44 | bool isOnGround(const Blocks &blocks, int x, int y) const; 45 | 46 | int getYOnHarddrop(const Blocks &blocks, int x, int startY) const; 47 | 48 | bool canReachOnHarddrop(const Blocks &blocks, int x, int y) const; 49 | 50 | void clearLine(); 51 | 52 | int clearLineReturnNum(); 53 | 54 | LineKey clearLineReturnKey(); 55 | 56 | int getBlockOnX(int x, int maxY) const; 57 | 58 | bool isWallBetween(int x, int maxY) const; 59 | 60 | std::string toString(int height) const; 61 | 62 | int getNumOfBlocks() const; 63 | 64 | int getNumOfVerticalTransitions() const; 65 | 66 | int getMaxY() const; 67 | 68 | void fillBelowSurface(); 69 | 70 | int getNumOfHoles() const; 71 | 72 | private: 73 | void deleteLine_(LineKey low, LineKey midLow, LineKey midHigh, LineKey high); 74 | }; 75 | 76 | inline bool operator==(const Field &lhs, const Field &rhs) { 77 | return lhs.xBoardLow == rhs.xBoardLow && lhs.xBoardMidLow == rhs.xBoardMidLow 78 | && lhs.xBoardMidHigh == rhs.xBoardMidHigh && lhs.xBoardHigh == rhs.xBoardHigh; 79 | } 80 | 81 | inline bool operator!=(const Field &lhs, const Field &rhs) { 82 | return !(lhs == rhs); 83 | } 84 | 85 | Field createField(std::string marks); 86 | } 87 | 88 | #endif //CORE_FIELD_HPP 89 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Tester.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Tester.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/PerfectClearNET.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {12AC9064-FAF9-4714-8E9F-9EC11D897DAF} 8 | Library 9 | Properties 10 | PerfectClearNET 11 | PerfectClearNET 12 | v4.6.1 13 | 512 14 | true 15 | 16 | 17 | 18 | true 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | full 22 | x64 23 | prompt 24 | MinimumRecommendedRules.ruleset 25 | false 26 | 27 | 28 | bin\Release\ 29 | TRACE 30 | true 31 | pdbonly 32 | x64 33 | prompt 34 | MinimumRecommendedRules.ruleset 35 | false 36 | bin\Release\PerfectClearNET.xml 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {f71bf46c-2c69-40af-a5df-a73e25a21997} 58 | sfinder-dll 59 | false 60 | Content 61 | Always 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/srs.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "srs.hpp" 4 | 5 | namespace core::srs { 6 | int right( 7 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 8 | ) { 9 | assert((fromRotate + 1) % 4 == toRotate); 10 | auto &toBlocks = piece.blocks[toRotate]; 11 | return right(field, piece, fromRotate, toBlocks, fromX, fromY); 12 | } 13 | 14 | int right( 15 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 16 | ) { 17 | int fromLeftX = fromX + toBlocks.minX; 18 | int fromLowerY = fromY + toBlocks.minY; 19 | 20 | auto head = fromRotate * Piece::MaxOffsetRotate90; 21 | int width = FIELD_WIDTH - toBlocks.width; 22 | for (int index = head; index < head + piece.offsetsSize; ++index) { 23 | auto &offset = piece.rightOffsets[index]; 24 | int toX = fromLeftX + offset.x; 25 | int toY = fromLowerY + offset.y; 26 | if (0 <= toX && toX <= width && 0 <= toY && field.canPutAtMaskIndex(toBlocks, toX, toY)) { 27 | return index; 28 | } 29 | } 30 | 31 | return -1; 32 | } 33 | 34 | int left( 35 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 36 | ) { 37 | assert((fromRotate + 3) % 4 == toRotate); 38 | auto &toBlocks = piece.blocks[toRotate]; 39 | return left(field, piece, fromRotate, toBlocks, fromX, fromY); 40 | } 41 | 42 | int left( 43 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 44 | ) { 45 | int fromLeftX = fromX + toBlocks.minX; 46 | int fromLowerY = fromY + toBlocks.minY; 47 | 48 | auto head = fromRotate * Piece::MaxOffsetRotate90; 49 | int width = FIELD_WIDTH - toBlocks.width; 50 | for (int index = head; index < head + piece.offsetsSize; ++index) { 51 | auto &offset = piece.leftOffsets[index]; 52 | int toX = fromLeftX + offset.x; 53 | int toY = fromLowerY + offset.y; 54 | if (0 <= toX && toX <= width && 0 <= toY && field.canPutAtMaskIndex(toBlocks, toX, toY)) { 55 | return index; 56 | } 57 | } 58 | 59 | return -1; 60 | } 61 | 62 | int rotate180( 63 | const Field &field, const Piece &piece, RotateType fromRotate, RotateType toRotate, int fromX, int fromY 64 | ) { 65 | assert((fromRotate + 2) % 4 == toRotate); 66 | auto &toBlocks = piece.blocks[toRotate]; 67 | return rotate180(field, piece, fromRotate, toBlocks, fromX, fromY); 68 | } 69 | 70 | int rotate180( 71 | const Field &field, const Piece &piece, RotateType fromRotate, const Blocks &toBlocks, int fromX, int fromY 72 | ) { 73 | int fromLeftX = fromX + toBlocks.minX; 74 | int fromLowerY = fromY + toBlocks.minY; 75 | 76 | auto head = fromRotate * Piece::MaxOffsetRotate180; 77 | int width = FIELD_WIDTH - toBlocks.width; 78 | for (int index = head; index < head + piece.rotate180OffsetsSize; ++index) { 79 | auto &offset = piece.rotate180Offsets[index]; 80 | int toX = fromLeftX + offset.x; 81 | int toY = fromLowerY + offset.y; 82 | if (0 <= toX && toX <= width && 0 <= toY && field.canPutAtMaskIndex(toBlocks, toX, toY)) { 83 | return index; 84 | } 85 | } 86 | 87 | return -1; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/sfinder-dll.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | 50 | 51 | Header Files 52 | 53 | 54 | Header Files 55 | 56 | 57 | Header Files 58 | 59 | 60 | Header Files 61 | 62 | 63 | Header Files 64 | 65 | 66 | Header Files 67 | 68 | 69 | Header Files 70 | 71 | 72 | Header Files 73 | 74 | 75 | Header Files 76 | 77 | 78 | Header Files 79 | 80 | 81 | Header Files 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | Header Files 91 | 92 | 93 | Header Files 94 | 95 | 96 | 97 | 98 | Resource Files 99 | 100 | 101 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/moves.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "moves.hpp" 4 | 5 | namespace core { 6 | namespace { 7 | constexpr unsigned int kFieldWidthUnsigned = FIELD_WIDTH; 8 | constexpr int kFieldWidth = FIELD_WIDTH; 9 | constexpr int kMaxFieldHeight = MAX_FIELD_HEIGHT; 10 | } 11 | 12 | namespace { 13 | uint64_t getXMask(int x, int y) { 14 | assert(0 <= x && x < kFieldWidth); 15 | assert(0 <= y && y < kMaxFieldHeight); 16 | 17 | return 1LLU << (x + y * kFieldWidthUnsigned); 18 | } 19 | } 20 | 21 | void Cache::visit(int x, int y, RotateType rotateType) { 22 | assert(0 <= x && x < kFieldWidth); 23 | assert(0 <= y && y < kMaxFieldHeight); 24 | 25 | int index = y / 6; 26 | uint64_t mask = getXMask(x, y - 6 * index); 27 | 28 | int boardIndex = index + 4 * rotateType; 29 | visitedBoard[boardIndex] |= mask; 30 | } 31 | 32 | bool Cache::isVisit(int x, int y, core::RotateType rotateType) const { 33 | assert(0 <= x && x < kFieldWidth); 34 | assert(0 <= y && y < kMaxFieldHeight); 35 | 36 | int index = y / 6; 37 | uint64_t mask = getXMask(x, y - 6 * index); 38 | 39 | int boardIndex = index + 4 * rotateType; 40 | return (visitedBoard[boardIndex] & mask) != 0; 41 | } 42 | 43 | void Cache::visitPartially(int x, int y, RotateType rotateType) { 44 | assert(0 <= x && x < kFieldWidth); 45 | assert(0 <= y && y < kMaxFieldHeight); 46 | 47 | int index = y / 6; 48 | uint64_t mask = getXMask(x, y - 6 * index); 49 | 50 | int boardIndex = index + 4 * rotateType; 51 | visitedPartiallyBoard[boardIndex] |= mask; 52 | } 53 | 54 | bool Cache::isVisitPartially(int x, int y, core::RotateType rotateType) const { 55 | assert(0 <= x && x < kFieldWidth); 56 | assert(0 <= y && y < kMaxFieldHeight); 57 | 58 | int index = y / 6; 59 | uint64_t mask = getXMask(x, y - 6 * index); 60 | 61 | int boardIndex = index + 4 * rotateType; 62 | return (visitedPartiallyBoard[boardIndex] & mask) != 0; 63 | } 64 | 65 | void Cache::found(int x, int y, RotateType rotateType) { 66 | assert(0 <= x && x < kFieldWidth); 67 | assert(0 <= y && y < kMaxFieldHeight); 68 | 69 | int index = y / 6; 70 | uint64_t mask = getXMask(x, y - 6 * index); 71 | 72 | int boardIndex = index + 4 * rotateType; 73 | foundBoard[boardIndex] |= mask; 74 | } 75 | 76 | bool Cache::isFound(int x, int y, core::RotateType rotateType) const { 77 | assert(0 <= x && x < kFieldWidth); 78 | assert(0 <= y && y < kMaxFieldHeight); 79 | 80 | int index = y / 6; 81 | auto mask = getXMask(x, y - 6 * index); 82 | 83 | int boardIndex = index + 4 * rotateType; 84 | return (foundBoard[boardIndex] & mask) != 0; 85 | } 86 | 87 | void Cache::push(int x, int y, RotateType rotateType) { 88 | assert(0 <= x && x < kFieldWidth); 89 | assert(0 <= y && y < kMaxFieldHeight); 90 | 91 | int index = y / 6; 92 | uint64_t mask = getXMask(x, y - 6 * index); 93 | 94 | int boardIndex = index + 4 * rotateType; 95 | pushedBoard[boardIndex] |= mask; 96 | } 97 | 98 | bool Cache::isPushed(int x, int y, core::RotateType rotateType) const { 99 | assert(0 <= x && x < kFieldWidth); 100 | assert(0 <= y && y < kMaxFieldHeight); 101 | 102 | int index = y / 6; 103 | uint64_t mask = getXMask(x, y - 6 * index); 104 | 105 | int boardIndex = index + 4 * rotateType; 106 | return (pushedBoard[boardIndex] & mask) != 0; 107 | } 108 | 109 | namespace harddrop { 110 | void MoveGenerator::search( 111 | std::vector &moves, const Field &field, const PieceType pieceType, int validHeight 112 | ) { 113 | auto &piece = factory.get(pieceType); 114 | 115 | auto bit = piece.uniqueRotateBit; 116 | assert(bit != 0); 117 | 118 | do { 119 | auto next = bit & (bit - 1U); 120 | RotateType rotateType = rotateBitToVal[bit & ~next]; 121 | 122 | auto &blocks = factory.get(pieceType, rotateType); 123 | 124 | int y = validHeight - blocks.minY; 125 | int maxY = validHeight - blocks.maxY; 126 | for (int x = -blocks.minX, maxX = kFieldWidth - blocks.maxX; x < maxX; ++x) { 127 | int harddropY = field.getYOnHarddrop(blocks, x, y); 128 | if (harddropY < maxY) { 129 | moves.push_back(Move{rotateType, x, harddropY, true}); 130 | } 131 | } 132 | 133 | bit = next; 134 | } while (bit != 0); 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Tester.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {4B48BC83-A776-498D-BF97-EF494575BA24} 8 | WinExe 9 | Tester 10 | Tester 11 | v4.6.1 12 | 512 13 | true 14 | 15 | 16 | 17 | true 18 | bin\x64\Debug\ 19 | DEBUG;TRACE 20 | full 21 | x64 22 | prompt 23 | MinimumRecommendedRules.ruleset 24 | true 25 | 26 | 27 | bin\x64\Release\ 28 | TRACE 29 | true 30 | pdbonly 31 | x64 32 | prompt 33 | MinimumRecommendedRules.ruleset 34 | true 35 | 36 | 37 | 38 | ..\packages\MisaMinoNET.1.0.2\lib\net45\MisaMino.dll 39 | 40 | 41 | ..\packages\MisaMinoNET.1.0.2\lib\net45\MisaMinoNET.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Form 58 | 59 | 60 | MainForm.cs 61 | 62 | 63 | 64 | 65 | MainForm.cs 66 | 67 | 68 | ResXFileCodeGenerator 69 | Resources.Designer.cs 70 | Designer 71 | 72 | 73 | True 74 | Resources.resx 75 | True 76 | 77 | 78 | 79 | SettingsSingleFileGenerator 80 | Settings.Designer.cs 81 | 82 | 83 | True 84 | Settings.settings 85 | True 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {12ac9064-faf9-4714-8e9f-9ec11d897daf} 94 | PerfectClearNET 95 | 96 | 97 | {f71bf46c-2c69-40af-a5df-a73e25a21997} 98 | sfinder-dll 99 | false 100 | Content 101 | Always 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Windows.h" 2 | #define DLL extern "C" __declspec(dllexport) 3 | 4 | #include 5 | #include 6 | 7 | #include "callback.hpp" 8 | #include "core/field.hpp" 9 | #include "finder/thread_pool.hpp" 10 | #include "finder/concurrent_perfect_clear.hpp" 11 | 12 | static const unsigned char BitsSetTable256[256] = 13 | { 14 | # define B2(n) n, n+1, n+1, n+2 15 | # define B4(n) B2(n), B2(n+1), B2(n+1), B2(n+2) 16 | # define B6(n) B4(n), B4(n+1), B4(n+1), B4(n+2) 17 | B6(0), B6(1), B6(1), B6(2) 18 | }; 19 | 20 | enum Game { 21 | None = 0, 22 | PPT = 1, 23 | TETRIO = 2 24 | }; 25 | 26 | using PPTFinder = finder::ConcurrentPerfectClearFinder; 27 | using TETRIOFinder = finder::ConcurrentPerfectClearFinder; 28 | 29 | auto srs = core::Factory::create(); 30 | auto srsPlus = core::Factory::createForSRSPlus(); 31 | 32 | auto threadPool = finder::ThreadPool(1); 33 | 34 | std::optional pptfinder; 35 | std::optional tetriofinder; 36 | Game game = Game::None; 37 | 38 | DLL void set_abort(Callback handler) { 39 | Abort = handler; 40 | } 41 | 42 | // returns whether PC finder is inited or not 43 | DLL bool init_finder(Game init) { 44 | if (game > Game::None) return true; 45 | 46 | if (init == Game::PPT) { 47 | pptfinder.emplace(srs, threadPool); 48 | } else if (init == Game::TETRIO) { 49 | tetriofinder.emplace(srsPlus, threadPool); 50 | } else { 51 | return false; 52 | } 53 | 54 | game = init; 55 | return true; 56 | } 57 | 58 | DLL void set_threads(unsigned int threads) { 59 | threadPool.changeThreadCount(threads); 60 | } 61 | 62 | core::PieceType charToPiece(char x) { 63 | switch (x) { 64 | case 'S': 65 | return core::PieceType::S; 66 | 67 | case 'Z': 68 | return core::PieceType::Z; 69 | 70 | case 'J': 71 | return core::PieceType::J; 72 | 73 | case 'L': 74 | return core::PieceType::L; 75 | 76 | case 'T': 77 | return core::PieceType::T; 78 | 79 | case 'O': 80 | return core::PieceType::O; 81 | 82 | case 'I': 83 | return core::PieceType::I; 84 | 85 | default: 86 | assert(true); 87 | } 88 | } 89 | 90 | DLL void action( 91 | const char* _field, const char* _queue, const char* _hold, int height, 92 | int max_height, bool swap, int searchtype, int combo, bool b2b, bool twoLine, 93 | char* _str, int _len 94 | ) { 95 | bool solved = false; 96 | std::stringstream out; 97 | 98 | if (game > Game::None) { 99 | auto field = core::createField(_field); 100 | 101 | int minos_placed = 0; 102 | 103 | for (core::Bitboard v : field.boards) 104 | minos_placed += BitsSetTable256[v & 0xff] + 105 | BitsSetTable256[(v >> 8) & 0xff] + 106 | BitsSetTable256[(v >> 16) & 0xff] + 107 | BitsSetTable256[(v >> 24) & 0xff] + 108 | BitsSetTable256[(v >> 32) & 0xff] + 109 | BitsSetTable256[(v >> 40) & 0xff] + 110 | BitsSetTable256[(v >> 48) & 0xff] + 111 | BitsSetTable256[v >> 56]; 112 | 113 | if (minos_placed % 2 == 0) { 114 | if (max_height < 0) max_height = 0; 115 | if (max_height > 20) max_height = 20; 116 | 117 | auto pieces = std::vector(); 118 | 119 | bool holdEmpty = _hold[0] == 'E'; 120 | bool holdAllowed = _hold[0] != 'X'; 121 | 122 | if (!holdEmpty) 123 | pieces.push_back(charToPiece(_hold[0])); 124 | 125 | int max_pieces = (max_height * 10 - minos_placed) / 4 + 1; 126 | 127 | for (int i = 0; i < max_pieces && _queue[i] != '\0'; i++) 128 | pieces.push_back(charToPiece(_queue[i])); 129 | 130 | // sus 131 | //height += minos_placed % 4 == (height % 2)? 0 : 2; 132 | 133 | // need to clear odd number of lines 134 | if (minos_placed % 4 == 2) { 135 | if (height % 2 == 0) { 136 | height += 1; 137 | } 138 | } 139 | // need to clear even number of lines 140 | else { 141 | if (height % 2 == 1) { 142 | height += 1; 143 | } 144 | } 145 | 146 | if (height == 0) { 147 | height = 2; 148 | } 149 | 150 | // for completely skipping two line PC search 151 | //if (!twoLine && height < 3) { 152 | // height += 2; 153 | //} 154 | 155 | for (; height <= max_height; height += 2) { 156 | if ((height * 10 - minos_placed) / 4 + 1 > pieces.size()) break; 157 | 158 | auto result = game == Game::PPT 159 | ? pptfinder->run(field, pieces, height, holdEmpty, holdAllowed, !swap, searchtype, combo, b2b, twoLine, 6) 160 | : (game == Game::TETRIO 161 | ? tetriofinder->run(field, pieces, height, holdEmpty, holdAllowed, !swap, searchtype, combo, b2b, twoLine, 6) 162 | : finder::Solution() // empty solution 163 | ); 164 | 165 | if (!result.empty()) { 166 | solved = true; 167 | 168 | for (const auto& item : result) { 169 | out << item.pieceType << "," 170 | << item.x << "," 171 | << item.y << "," 172 | << item.rotateType << "|"; 173 | } 174 | 175 | break; 176 | } 177 | } 178 | } 179 | } 180 | 181 | if (!solved) out << "-1"; 182 | 183 | std::string a = out.str(); 184 | std::copy(a.c_str(), a.c_str() + a.length() + 1, _str); 185 | } 186 | 187 | // Managed code may not be run under loader lock, 188 | // including the DLL entrypoint and calls reached from the DLL entrypoint 189 | #pragma managed(push, off) 190 | BOOL WINAPI DllMain(HANDLE handle, DWORD reason, LPVOID reserved) { 191 | if (reason == DLL_PROCESS_DETACH) 192 | threadPool.shutdown(); 193 | 194 | return TRUE; 195 | } 196 | #pragma managed(pop) 197 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/types.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FINDER_TYPES_HPP 2 | #define FINDER_TYPES_HPP 3 | 4 | #include "../core/moves.hpp" 5 | #include "../core/types.hpp" 6 | 7 | namespace finder { 8 | struct Configure { 9 | const std::vector &pieces; 10 | std::vector> &movePool; 11 | std::vector> &scoredMovePool; 12 | const int maxDepth; 13 | const int fastSearchStartDepth; 14 | const int pieceSize; 15 | const bool holdAllowed; 16 | const bool leastLineClears; 17 | bool alwaysRegularAttack; 18 | uint8_t lastHoldPriority; // 0bEOZSJLIT // 0b11000000 -> Give high priority to solutions that last hold is Empty,O 19 | }; 20 | 21 | struct Operation { 22 | core::PieceType pieceType; 23 | core::RotateType rotateType; 24 | int x; 25 | int y; 26 | }; 27 | 28 | template 29 | struct PreOperation { 30 | core::Field field; 31 | C candidate; 32 | core::PieceType pieceType; 33 | core::RotateType rotateType; 34 | int x; 35 | int y; 36 | bool harddrop; 37 | int numCleared; 38 | int score; 39 | }; 40 | 41 | using Solution = std::vector; 42 | inline const Solution kNoSolution = std::vector(); 43 | 44 | // For fast search 45 | struct FastCandidate { 46 | int currentIndex; 47 | int holdIndex; 48 | int leftLine; 49 | int depth; 50 | int softdropCount; 51 | int holdCount; 52 | int lineClearCount; 53 | int currentCombo; 54 | int maxCombo; 55 | int frames; 56 | }; 57 | 58 | struct FastRecord { 59 | // new 60 | Solution solution; 61 | core::PieceType hold; 62 | int holdPriority; // Priority is given when the least significant bit is 1 // 0bEOZSJLIT 63 | 64 | // from candidate 65 | int currentIndex; 66 | int holdIndex; 67 | int leftLine; 68 | int depth; 69 | int softdropCount; 70 | int holdCount; 71 | int lineClearCount; 72 | int currentCombo; 73 | int maxCombo; 74 | int frames; 75 | }; 76 | 77 | // For T-Spin search 78 | struct TSpinCandidate { 79 | int currentIndex; 80 | int holdIndex; 81 | int leftLine; 82 | int depth; 83 | int softdropCount; 84 | int holdCount; 85 | int lineClearCount; 86 | int currentCombo; 87 | int maxCombo; 88 | int tSpinAttack; 89 | bool b2b; 90 | int leftNumOfT; 91 | int frames; 92 | }; 93 | 94 | struct TSpinRecord { 95 | // new 96 | Solution solution; 97 | core::PieceType hold; 98 | int holdPriority; // Priority is given when the least significant bit is 1 // 0bEOZSJLIT 99 | 100 | // from candidate 101 | int currentIndex; 102 | int holdIndex; 103 | int leftLine; 104 | int depth; 105 | int softdropCount; 106 | int holdCount; 107 | int lineClearCount; 108 | int currentCombo; 109 | int maxCombo; 110 | int tSpinAttack; 111 | bool b2b; 112 | int leftNumOfT; 113 | int frames; 114 | }; 115 | 116 | // For all spins search 117 | struct AllSpinsCandidate { 118 | int currentIndex; 119 | int holdIndex; 120 | int leftLine; 121 | int depth; 122 | int softdropCount; 123 | int holdCount; 124 | int lineClearCount; 125 | int currentCombo; 126 | int maxCombo; 127 | int spinAttack; 128 | bool b2b; 129 | int frames; 130 | }; 131 | 132 | struct AllSpinsRecord { 133 | // new 134 | Solution solution; 135 | core::PieceType hold; 136 | int holdPriority; // Priority is given when the least significant bit is 1 // 0bEOZSJLIT 137 | 138 | // candidate 139 | int currentIndex; 140 | int holdIndex; 141 | int leftLine; 142 | int depth; 143 | int softdropCount; 144 | int holdCount; 145 | int lineClearCount; 146 | int currentCombo; 147 | int maxCombo; 148 | int spinAttack; 149 | bool b2b; 150 | int frames; 151 | }; 152 | 153 | // For S2 all spins search 154 | struct TETRIOS2Candidate { 155 | int currentIndex; 156 | int holdIndex; 157 | int leftLine; 158 | int depth; 159 | int softdropCount; 160 | int holdCount; 161 | int lineClearCount; 162 | int currentCombo; 163 | int maxCombo; 164 | int spinAttack; 165 | int b2b; 166 | int frames; 167 | bool isClean; 168 | bool isFlatI; 169 | }; 170 | 171 | struct TETRIOS2Record { 172 | // new 173 | Solution solution; 174 | core::PieceType hold; 175 | int holdPriority; // Priority is given when the least significant bit is 1 // 0bEOZSJLIT 176 | 177 | // candidate 178 | int currentIndex; 179 | int holdIndex; 180 | int leftLine; 181 | int depth; 182 | int softdropCount; 183 | int holdCount; 184 | int lineClearCount; 185 | int currentCombo; 186 | int maxCombo; 187 | int spinAttack; 188 | int b2b; 189 | int frames; 190 | bool isClean; 191 | bool isFlatI; 192 | }; 193 | } 194 | 195 | #endif //FINDER_TYPES_HPP 196 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Visual Studio Code 5 | .vscode 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | pack.bat 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | 264 | # Python Tools for Visual Studio (PTVS) 265 | __pycache__/ 266 | *.pyc 267 | 268 | # User defines 269 | .idea 270 | cmake-build-* 271 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Tester { 2 | partial class MainForm { 3 | /// 4 | /// Required designer variable. 5 | /// 6 | private System.ComponentModel.IContainer components = null; 7 | 8 | /// 9 | /// Clean up any resources being used. 10 | /// 11 | /// true if managed resources should be disposed; otherwise, false. 12 | protected override void Dispose(bool disposing) { 13 | if (disposing && (components != null)) { 14 | components.Dispose(); 15 | } 16 | base.Dispose(disposing); 17 | } 18 | 19 | #region Windows Form Designer generated code 20 | 21 | /// 22 | /// Required method for Designer support - do not modify 23 | /// the contents of this method with the code editor. 24 | /// 25 | private void InitializeComponent() { 26 | this.Run1 = new System.Windows.Forms.Button(); 27 | this.Display = new System.Windows.Forms.Label(); 28 | this.Run2 = new System.Windows.Forms.Button(); 29 | this.Run3 = new System.Windows.Forms.Button(); 30 | this.Run4 = new System.Windows.Forms.Button(); 31 | this.SuspendLayout(); 32 | // 33 | // Run1 34 | // 35 | this.Run1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); 36 | this.Run1.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 37 | this.Run1.Location = new System.Drawing.Point(15, 12); 38 | this.Run1.Name = "Run1"; 39 | this.Run1.Size = new System.Drawing.Size(48, 27); 40 | this.Run1.TabIndex = 0; 41 | this.Run1.Text = "Run 1"; 42 | this.Run1.UseVisualStyleBackColor = false; 43 | this.Run1.Click += new System.EventHandler(this.Run1_Click); 44 | // 45 | // Display 46 | // 47 | this.Display.AutoSize = true; 48 | this.Display.Location = new System.Drawing.Point(232, 19); 49 | this.Display.Name = "Display"; 50 | this.Display.Size = new System.Drawing.Size(28, 13); 51 | this.Display.TabIndex = 1; 52 | this.Display.Text = "Text"; 53 | // 54 | // Run2 55 | // 56 | this.Run2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); 57 | this.Run2.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 58 | this.Run2.Location = new System.Drawing.Point(70, 12); 59 | this.Run2.Name = "Run2"; 60 | this.Run2.Size = new System.Drawing.Size(48, 27); 61 | this.Run2.TabIndex = 0; 62 | this.Run2.Text = "Run 2"; 63 | this.Run2.UseVisualStyleBackColor = false; 64 | this.Run2.Click += new System.EventHandler(this.Run2_Click); 65 | // 66 | // Run3 67 | // 68 | this.Run3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); 69 | this.Run3.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 70 | this.Run3.Location = new System.Drawing.Point(124, 12); 71 | this.Run3.Name = "Run3"; 72 | this.Run3.Size = new System.Drawing.Size(48, 27); 73 | this.Run3.TabIndex = 2; 74 | this.Run3.Text = "Run 3"; 75 | this.Run3.UseVisualStyleBackColor = false; 76 | this.Run3.Click += new System.EventHandler(this.Run3_Click); 77 | // 78 | // Run4 79 | // 80 | this.Run4.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(24)))), ((int)(((byte)(24)))), ((int)(((byte)(24))))); 81 | this.Run4.FlatStyle = System.Windows.Forms.FlatStyle.Flat; 82 | this.Run4.Location = new System.Drawing.Point(178, 12); 83 | this.Run4.Name = "Run4"; 84 | this.Run4.Size = new System.Drawing.Size(48, 27); 85 | this.Run4.TabIndex = 3; 86 | this.Run4.Text = "Run 4"; 87 | this.Run4.UseVisualStyleBackColor = false; 88 | this.Run4.Click += new System.EventHandler(this.Run4_Click); 89 | // 90 | // MainForm 91 | // 92 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 93 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 94 | this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(16)))), ((int)(((byte)(16)))), ((int)(((byte)(16))))); 95 | this.ClientSize = new System.Drawing.Size(1038, 50); 96 | this.Controls.Add(this.Run4); 97 | this.Controls.Add(this.Run3); 98 | this.Controls.Add(this.Display); 99 | this.Controls.Add(this.Run2); 100 | this.Controls.Add(this.Run1); 101 | this.ForeColor = System.Drawing.Color.Gainsboro; 102 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 103 | this.MaximizeBox = false; 104 | this.MinimizeBox = false; 105 | this.Name = "MainForm"; 106 | this.Text = "PerfectClear Tester"; 107 | this.Load += new System.EventHandler(this.MainForm_Load); 108 | this.ResumeLayout(false); 109 | this.PerformLayout(); 110 | 111 | } 112 | 113 | #endregion 114 | 115 | private System.Windows.Forms.Button Run1; 116 | private System.Windows.Forms.Label Display; 117 | private System.Windows.Forms.Button Run2; 118 | private System.Windows.Forms.Button Run3; 119 | private System.Windows.Forms.Button Run4; 120 | } 121 | } -------------------------------------------------------------------------------- /PerfectClearNET/Tester/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/MainForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/piece.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CORE_PIECE_HPP 2 | #define CORE_PIECE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "types.hpp" 9 | 10 | namespace core { 11 | using MinMax = std::pair; 12 | 13 | struct Point { 14 | int x; 15 | int y; 16 | }; 17 | 18 | struct Offset { 19 | int x; 20 | int y; 21 | }; 22 | 23 | inline Offset operator+(const Offset &lhs, const Offset &rhs) { 24 | return {lhs.x + rhs.x, lhs.y + rhs.y}; 25 | } 26 | 27 | inline Offset operator-(const Offset &lhs, const Offset &rhs) { 28 | return {lhs.x - rhs.x, lhs.y - rhs.y}; 29 | } 30 | 31 | struct Collider { 32 | Bitboard boards[4]; 33 | }; 34 | 35 | struct BlocksMask { 36 | Bitboard low; 37 | Bitboard high; 38 | }; 39 | 40 | struct Transform { 41 | Offset offset; 42 | RotateType toRotate; 43 | }; 44 | 45 | class Blocks { 46 | public: 47 | static Blocks create(RotateType rotateType, const std::array &points); 48 | 49 | const RotateType rotateType; 50 | const std::array points; 51 | const std::array harddropColliders; 52 | const int minX; 53 | const int maxX; 54 | const int minY; 55 | const int maxY; 56 | const int width; 57 | const int height; 58 | 59 | BlocksMask mask(int leftX, int lowerY) const; 60 | 61 | Collider harddrop(int leftX, int lowerY) const; 62 | 63 | private: 64 | Blocks(const RotateType rotateType, const std::array points, const Bitboard mask, 65 | const std::array harddropColliders, 66 | const MinMax &minMaxX, const MinMax &minMaxY) 67 | : rotateType(rotateType), points(points), harddropColliders(harddropColliders), 68 | minX(minMaxX.first), maxX(minMaxX.second), minY(minMaxY.first), maxY(minMaxY.second), 69 | width(minMaxX.second - minMaxX.first + 1), height(minMaxY.second - minMaxY.first + 1), mask_(mask) { 70 | }; 71 | 72 | const Bitboard mask_; // Left align 73 | }; 74 | 75 | class Piece { 76 | public: 77 | static constexpr int MaxOffsetRotate90 = 5; 78 | static constexpr int MaxOffsetRotate180 = 6; 79 | 80 | template 81 | static Piece create( 82 | PieceType pieceType, 83 | const std::string &name, 84 | const std::array &points, 85 | const std::array, 4> &offsets, 86 | const std::array &transforms 87 | ); 88 | 89 | template 90 | static Piece create( 91 | PieceType pieceType, 92 | const std::string &name, 93 | const std::array &points, 94 | const std::array, 4> &offsets, 95 | const std::array &rotate180Offsets, 96 | const std::array &transforms 97 | ); 98 | 99 | template 100 | static Piece create( 101 | PieceType pieceType, 102 | const std::string &name, 103 | const std::array &points, 104 | const std::array &cwOffsets, 105 | const std::array &ccwOffsets, 106 | const std::array &rotate180Offsets, 107 | const std::array &transforms 108 | ); 109 | 110 | const PieceType pieceType; 111 | const std::string name; 112 | const std::array blocks; 113 | const std::array rightOffsets; // = cwOffsets 114 | const std::array leftOffsets; // = ccwOffsets 115 | const std::array rotate180Offsets; 116 | const size_t offsetsSize; 117 | const size_t rotate180OffsetsSize; 118 | const std::array transforms; 119 | const int32_t uniqueRotateBit; 120 | const std::array sameShapeRotates; 121 | 122 | private: 123 | Piece( 124 | const PieceType pieceType, 125 | const std::string name, 126 | const std::array blocks, 127 | const std::array cwOffsets, 128 | const std::array ccwOffsets, 129 | const std::array rotate180Offsets, 130 | const size_t offsetsSize, 131 | const size_t rotate180OffsetsSize, 132 | const std::array transforms, 133 | const int32_t uniqueRotate, 134 | const std::array sameShapeRotates 135 | ) : pieceType(pieceType), name(name), blocks(blocks), 136 | rightOffsets(cwOffsets), leftOffsets(ccwOffsets), rotate180Offsets(rotate180Offsets), 137 | offsetsSize(offsetsSize), rotate180OffsetsSize(rotate180OffsetsSize), 138 | transforms(transforms), uniqueRotateBit(uniqueRotate), 139 | sameShapeRotates(sameShapeRotates) { 140 | } 141 | }; 142 | 143 | class Factory { 144 | public: 145 | static Factory create(); 146 | 147 | static Factory createForSRSPlus(); 148 | 149 | static Factory create( 150 | const Piece& t, 151 | const Piece& i, 152 | const Piece& l, 153 | const Piece& j, 154 | const Piece& s, 155 | const Piece& z, 156 | const Piece& o 157 | ); 158 | 159 | const Piece &get(PieceType piece) const; 160 | 161 | const Blocks &get(PieceType piece, RotateType rotate) const; 162 | 163 | private: 164 | Factory(const std::array &pieces, const std::array &blocks) : pieces(pieces), blocks(blocks) { 165 | }; 166 | 167 | const std::array pieces; 168 | const std::array blocks; 169 | }; 170 | } 171 | 172 | #endif //CORE_PIECE_HPP 173 | -------------------------------------------------------------------------------- /PerfectClearNET/PerfectClearNET/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | // Suppresses readonly suggestion 7 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044")] 8 | 9 | // Suppresses naming rule violation 10 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006")] 11 | 12 | namespace PerfectClearNET { 13 | /// 14 | /// The main PerfectClear class. 15 | /// Contains all methods for performing actions with the Perfect Clear Finder. 16 | /// 17 | public static class PerfectClear { 18 | /// 19 | /// Converts a piece's index to its regular string representation. 20 | /// 21 | public static readonly string[] ToChar = new string[7] { 22 | "S", "Z", "J", "L", "T", "O", "I" 23 | }; 24 | 25 | /// 26 | /// Converts a Finder piece's index to its regular index. 27 | /// 28 | public static readonly int[] FromFinder = new int[7] { 29 | 4, 6, 3, 2, 0, 1, 5 30 | }; 31 | 32 | /// Whether the search was successful or not. 33 | public delegate void FinishedEventHandler(bool success); 34 | 35 | /// 36 | /// Fires when the Perfect Clear Finder reaches the end of its search, whether naturally or after it was aborted. 37 | /// 38 | public static event FinishedEventHandler Finished; 39 | 40 | /// 41 | /// The latest search result from the most recent call to Find. 42 | /// 43 | public static List LastSolution = new List(); 44 | 45 | /// 46 | /// The amount of time the latest search took to complete. 47 | /// 48 | public static long LastTime = 0; 49 | 50 | /// 51 | /// Checks if the Perfect Clear Finder is currently searching for solutions. 52 | /// 53 | public static bool Running { get => Interface.Running; } 54 | 55 | static PerfectClear() {} 56 | 57 | /// 58 | /// Aborts the currently running search, if there is one. 59 | /// 60 | public static void Abort() { 61 | if (!Interface.Running) return; 62 | 63 | var abortWait = AbortCoordinator.CreateWaiter(); 64 | 65 | Interface.SetAbort(); 66 | 67 | abortWait(); 68 | } 69 | 70 | static bool inited = false; 71 | 72 | /// 73 | /// Initializes the Perfect Clear Finder for the desired game. 74 | /// 75 | public static void Initialize(PerfectClearGame game) { 76 | if (inited) return; 77 | 78 | if (Interface.init_finder(game)) { 79 | inited = true; 80 | } 81 | } 82 | 83 | /// 84 | /// Changes the thread count. 85 | /// 86 | /// Specifies the number of threads to search with. 87 | public static void SetThreads(uint threads) => Interface.set_threads(threads); 88 | 89 | /// 90 | /// Starts searching for a solution/decision for the given game state. 91 | /// Pieces should be formatted with numbers from 0 to 6 in the order of SZJLTOI. Empty state on the field should be formatted with 255. 92 | /// Since this method will begin a search in the background, it does not immediately return any data. 93 | /// When the search ends, the Finished event will fire and LastSolution will update. 94 | /// The search can be ended prematurely with the Abort method. 95 | /// 96 | /// A 2D array consisting of the field. Should be no smaller than int[10, height]. 97 | /// The piece queue, can be of any size. 98 | /// The current piece. 99 | /// The piece in hold. Should be null if empty. 100 | /// Is holding is allowed in the game. 101 | /// The maximum allowed height of the Perfect Clear. Used to control greed. 102 | /// Specifies if garbage blocking is enabled (from Puyo Puyo Tetris' Swap mode). If set to true, the Finder will prioritize PCs with a high combo. 103 | /// The search priority, in order: no softdrop, T-spin, All-spin with Mini, All-spin without Mini, TETR.IO Season 2 keep B2B. 104 | /// The combo count. 105 | /// Do you have back-to-back? 106 | /// Whether to optimize the current Perfect Clear for a two-line follow-up. 107 | public static async void Find( 108 | int[,] field, int[] queue, int current, int? hold, bool holdAllowed, 109 | int maxHeight, bool swap, SearchType searchType, int combo, bool b2b, bool two_line 110 | ) { 111 | 112 | int c = 0; 113 | int t = -1; 114 | string f = ""; 115 | 116 | for (int i = 19; i >= 0; i--) 117 | for (int j = 0; j < 10; j++) { 118 | if (field[j, i] == 255) { 119 | f += '_'; 120 | c += 1; 121 | } else { 122 | f += 'X'; 123 | if (t == -1) t = i + 1; 124 | } 125 | } 126 | 127 | if (t == -1) t = 2; 128 | 129 | string q = ToChar[current]; 130 | 131 | for (int i = 0; i < queue.Length; i++) 132 | q += ToChar[queue[i]]; 133 | 134 | string h = (hold == null)? "E" : ToChar[hold.Value]; 135 | if (!holdAllowed) h = "X"; 136 | 137 | string result = ""; 138 | 139 | await Task.Run(() => { 140 | result = Interface.Process(f, q, h, t, maxHeight, swap, (int)searchType, combo, b2b, two_line, out long time); 141 | 142 | LastSolution = new List(); 143 | LastTime = time; 144 | 145 | bool solved = !result.Equals("-1"); 146 | 147 | if (solved) { 148 | foreach (string op in result.Split('|')) 149 | if (op != "" && op != "0,-1,-1,0") 150 | LastSolution.Add(new Operation(op)); 151 | } 152 | 153 | Finished?.Invoke(solved); 154 | 155 | AbortCoordinator.WakeWaiters(); 156 | }); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/sfinder-dll.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15.0 15 | {F71BF46C-2C69-40AF-A5DF-A73E25A21997} 16 | Win32Proj 17 | sfinderdll 18 | 10.0 19 | 20 | 21 | 22 | 23 | 24 | DynamicLibrary 25 | true 26 | v143 27 | Unicode 28 | false 29 | 30 | 31 | DynamicLibrary 32 | false 33 | v143 34 | true 35 | Unicode 36 | false 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | true 52 | 53 | 54 | false 55 | 56 | 57 | 58 | NotUsing 59 | Level3 60 | Disabled 61 | true 62 | _DEBUG;SFINDERDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 63 | true 64 | stdcpp20 65 | /Zc:twoPhase- %(AdditionalOptions) 66 | C:\boost_1_88_0;%(AdditionalIncludeDirectories) 67 | 68 | 69 | Windows 70 | true 71 | C:\boost_1_88_0\stage\lib;%(AdditionalLibraryDirectories) 72 | 73 | 74 | 75 | 76 | NotUsing 77 | Level3 78 | MaxSpeed 79 | true 80 | true 81 | true 82 | NDEBUG;SFINDERDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) 83 | true 84 | stdcpp20 85 | /Zc:twoPhase- %(AdditionalOptions) 86 | C:\boost_1_88_0;%(AdditionalIncludeDirectories) 87 | AdvancedVectorExtensions2 88 | Fast 89 | 90 | 91 | Windows 92 | true 93 | true 94 | true 95 | C:\boost_1_88_0\stage\lib;%(AdditionalLibraryDirectories) 96 | UseLinkTimeCodeGeneration 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FINDER_THREAD_POOLS_HPP 2 | #define FINDER_THREAD_POOLS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define BOOST_THREAD_PROVIDES_FUTURE 11 | #define BOOST_THREAD_PROVIDES_VARIADIC_THREAD 12 | #define BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK 13 | #include 14 | #include 15 | 16 | namespace finder { 17 | class TaskStatus { 18 | public: 19 | void resume() { 20 | isAborted_ = false; 21 | } 22 | 23 | void abort() { 24 | isAborted_ = true; 25 | } 26 | 27 | void terminate() { 28 | isTerminated_ = true; 29 | } 30 | 31 | [[nodiscard]] bool terminated() const { 32 | return isTerminated_; 33 | } 34 | 35 | [[nodiscard]] bool aborted() const { 36 | return isAborted_; 37 | } 38 | 39 | [[nodiscard]] bool working() const { 40 | return !notWorking(); 41 | } 42 | 43 | [[nodiscard]] bool notWorking() const { 44 | return isAborted_ || isTerminated_; 45 | } 46 | 47 | private: 48 | std::atomic isTerminated_ = false; 49 | std::atomic isAborted_ = false; 50 | }; 51 | 52 | using Runnable = std::function; 53 | 54 | template 55 | using Callable = std::function; 56 | 57 | class Tasks { 58 | public: 59 | void push(const Runnable &runnable) { 60 | { 61 | boost::lock(mutexForQueue_, mutexForAbort_); 62 | boost::lock_guard lk1(mutexForQueue_, boost::adopt_lock); 63 | boost::lock_guard lk2(mutexForAbort_, boost::adopt_lock); 64 | 65 | if (status_.notWorking()) { 66 | throw std::runtime_error("Not working"); 67 | } 68 | 69 | queue_.push(runnable); 70 | counter += 1; 71 | } 72 | 73 | conditionForQueue_.notify_one(); 74 | conditionForSleep_.notify_one(); 75 | } 76 | 77 | void execute() { 78 | while (true) { 79 | Runnable runnable; 80 | 81 | { 82 | boost::unique_lock guard(mutexForQueue_); 83 | conditionForQueue_.wait(guard, [this] { return status_.notWorking() || !queue_.empty(); }); 84 | 85 | if (queue_.empty()) { 86 | if (status_.notWorking()) { 87 | if (status_.terminated()) { 88 | // All tasks completed, so finish pool 89 | return; 90 | } 91 | 92 | // All tasks completed, so go to sleep 93 | conditionForAbort_.notify_all(); 94 | conditionForSleep_.wait(guard, [this] { 95 | return status_.working() || status_.terminated(); 96 | }); 97 | } 98 | 99 | // Working but not found task or after sleeping 100 | continue; 101 | } 102 | 103 | // Execute task 104 | 105 | runnable = queue_.front(); 106 | queue_.pop(); 107 | } 108 | 109 | runnable(status_); 110 | 111 | { 112 | boost::lock_guard guard(mutexForAbort_); 113 | counter -= 1; 114 | } 115 | } 116 | } 117 | 118 | void abort() { 119 | { 120 | boost::lock_guard guard(mutexForQueue_); 121 | status_.abort(); 122 | } 123 | 124 | conditionForQueue_.notify_all(); 125 | conditionForSleep_.notify_all(); 126 | 127 | // sleep until completed all tasks 128 | { 129 | boost::unique_lock guard(mutexForAbort_); 130 | conditionForAbort_.wait(guard, [this] { 131 | return counter == 0; 132 | }); 133 | } 134 | 135 | { 136 | boost::lock_guard guard(mutexForQueue_); 137 | status_.resume(); 138 | } 139 | 140 | conditionForQueue_.notify_all(); 141 | conditionForSleep_.notify_all(); 142 | } 143 | 144 | void shutdown() { 145 | { 146 | boost::lock_guard guard(mutexForQueue_); 147 | status_.abort(); 148 | } 149 | 150 | conditionForQueue_.notify_all(); 151 | conditionForSleep_.notify_all(); 152 | 153 | // sleep until completed all tasks 154 | { 155 | boost::unique_lock guard(mutexForAbort_); 156 | conditionForAbort_.wait(guard, [this] { 157 | return counter == 0; 158 | }); 159 | } 160 | 161 | { 162 | boost::lock_guard guard(mutexForQueue_); 163 | status_.terminate(); 164 | } 165 | 166 | conditionForQueue_.notify_all(); 167 | conditionForSleep_.notify_all(); 168 | conditionForAbort_.notify_all(); 169 | } 170 | 171 | void shutdownNow() { 172 | { 173 | boost::lock_guard guard(mutexForQueue_); 174 | status_.terminate(); 175 | 176 | counter -= queue_.size(); 177 | 178 | std::queue empty{}; 179 | std::swap(queue_, empty); 180 | } 181 | 182 | conditionForQueue_.notify_all(); 183 | conditionForSleep_.notify_all(); 184 | conditionForAbort_.notify_all(); 185 | } 186 | 187 | bool terminated() const { 188 | return status_.terminated(); 189 | } 190 | 191 | private: 192 | boost::mutex mutexForQueue_; 193 | boost::mutex mutexForAbort_; 194 | 195 | TaskStatus status_{}; 196 | 197 | int counter = 0; 198 | std::queue queue_{}; 199 | 200 | boost::condition_variable conditionForQueue_{}; 201 | boost::condition_variable conditionForSleep_{}; 202 | boost::condition_variable conditionForAbort_{}; 203 | }; 204 | 205 | /** 206 | * This class is NOT thread-safe. 207 | */ 208 | class ThreadPool { 209 | public: 210 | explicit ThreadPool(int n) : tasks_(std::make_unique()) { 211 | start(n); 212 | } 213 | 214 | ~ThreadPool() { 215 | if (tasks_->terminated()) { 216 | return; 217 | } 218 | 219 | tasks_->shutdownNow(); 220 | for (auto &thread : threads_) { 221 | if (thread.joinable()) { 222 | thread.join(); 223 | } 224 | } 225 | threads_.clear(); 226 | } 227 | 228 | // Execute the task 229 | void execute(const Runnable &runnable) { 230 | if (tasks_->terminated()) { 231 | throw std::runtime_error("Thread pool is terminated"); 232 | } 233 | 234 | tasks_->push(runnable); 235 | } 236 | 237 | // Execute the task returning result. 238 | template 239 | boost::future execute(const Callable &callable) { 240 | if (tasks_->terminated()) { 241 | throw std::runtime_error("Thread pool is terminated"); 242 | } 243 | 244 | auto task = std::make_shared>(callable); 245 | tasks_->push([task](const TaskStatus& status) { 246 | (*task)(status); 247 | }); 248 | return std::move(task->get_future()); 249 | } 250 | 251 | // Change current status to "aborted" and wait all tasks is completed. 252 | // The task is notified of the "aborted" status via TaskStatus, but attempts to complete all processing. 253 | // How long the task ends depends on the task implementation. 254 | void abort() { 255 | if (tasks_->terminated()) { 256 | return; 257 | } 258 | 259 | tasks_->abort(); 260 | } 261 | 262 | // After abort tasks and wait they are completed, change current status to "Terminated". 263 | // Thread pool cannot be operated after shutdown. 264 | void shutdown() { 265 | if (tasks_->terminated()) { 266 | return; 267 | } 268 | 269 | tasks_->shutdown(); 270 | for (auto &thread : threads_) { 271 | if (thread.joinable()) { 272 | thread.join(); 273 | } 274 | } 275 | threads_.clear(); 276 | } 277 | 278 | // Change the number of threads. 279 | // Running tasks are aborted and wait for changes to complete. 280 | void changeThreadCount(int n) { 281 | shutdown(); 282 | tasks_ = std::make_unique(); 283 | start(n); 284 | } 285 | 286 | private: 287 | void start(int n) { 288 | assert(0 < n); 289 | threads_.clear(); 290 | for (int count = 0; count < n; ++count) { 291 | threads_.emplace_back(std::thread([this]() { 292 | tasks_->execute(); 293 | })); 294 | } 295 | } 296 | 297 | std::vector threads_{}; 298 | std::unique_ptr tasks_; 299 | }; 300 | } 301 | 302 | #endif //FINDER_THREAD_POOLS_HPP -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/spins.hpp: -------------------------------------------------------------------------------- 1 | #ifndef FINDER_SPINS_HPP 2 | #define FINDER_SPINS_HPP 3 | 4 | #include "../core/piece.hpp" 5 | #include "../core/moves.hpp" 6 | #include "../core/types.hpp" 7 | 8 | namespace finder { 9 | enum TSpinShapes { 10 | NoShape, 11 | RegularShape, 12 | MiniOrTSTShape, 13 | }; 14 | 15 | namespace { 16 | constexpr int kFieldWidth = 10; 17 | constexpr int kFieldHeight = 24; 18 | 19 | bool isBlock(const core::Field &field, int x, int y) { 20 | if (x < 0 || kFieldWidth <= x || y < 0) { 21 | return true; 22 | } 23 | return !field.isEmpty(x, y); 24 | } 25 | } 26 | 27 | inline TSpinShapes getTSpinShape(const core::Field &field, int x, int y, core::RotateType rotateType) { 28 | assert(0 <= x && x < kFieldWidth); 29 | assert(0 <= y); 30 | 31 | auto b1 = isBlock(field, x - 1, y - 1); 32 | auto b2 = isBlock(field, x - 1, y + 1); 33 | auto b3 = isBlock(field, x + 1, y - 1); 34 | auto b4 = isBlock(field, x + 1, y + 1); 35 | 36 | auto shape = (b1 || b2) && (b1 || b3) && (b1 || b4) && (b2 || b3) && (b2 || b4) && (b3 || b4); 37 | if (!shape) { 38 | return TSpinShapes::NoShape; 39 | } 40 | 41 | switch (rotateType) { 42 | case core::RotateType::Spawn: 43 | return b1 && b3 ? TSpinShapes::MiniOrTSTShape : TSpinShapes::RegularShape; 44 | case core::RotateType::Right: 45 | return b1 && b2 ? TSpinShapes::MiniOrTSTShape : TSpinShapes::RegularShape; 46 | case core::RotateType::Reverse: 47 | return b2 && b4 ? TSpinShapes::MiniOrTSTShape : TSpinShapes::RegularShape; 48 | case core::RotateType::Left: 49 | return b3 && b4 ? TSpinShapes::MiniOrTSTShape : TSpinShapes::RegularShape; 50 | } 51 | 52 | assert(false); 53 | return TSpinShapes::NoShape; 54 | } 55 | 56 | // Caution: mini attack is 0 57 | template 58 | constexpr int getAttackIfTSpin( 59 | core::srs::MoveGenerator &moveGenerator, 60 | core::srs_rotate_end::Reachable &reachable, 61 | const core::Factory &factory, const core::Field &field, 62 | core::PieceType pieceType, const core::Move &move, int numCleared, bool b2b 63 | ) { 64 | if (pieceType != core::PieceType::T) { 65 | return 0; 66 | } 67 | 68 | if (numCleared == 0) { 69 | return 0; 70 | } 71 | 72 | auto rotateType = move.rotateType; 73 | auto shapes = getTSpinShape(field, move.x, move.y, rotateType); 74 | if (shapes == TSpinShapes::NoShape) { 75 | return 0; 76 | } 77 | 78 | if (!reachable.checks(field, pieceType, rotateType, move.x, move.y, kFieldHeight)) { 79 | return 0; 80 | } 81 | 82 | if (shapes == TSpinShapes::RegularShape) { 83 | int baseAttack = numCleared * 2; 84 | return b2b ? baseAttack + 1 : baseAttack; 85 | } 86 | 87 | // Checks mini or regular (Last SRS test pattern) 88 | 89 | auto &piece = factory.get(pieceType); 90 | auto &toBlocks = factory.get(pieceType, rotateType); 91 | 92 | auto toX = move.x; 93 | auto toY = move.y; 94 | 95 | // Rotate right 96 | { 97 | // Direction before right rotation 98 | auto fromRotate = static_cast((rotateType + 3) % 4); 99 | auto &fromBlocks = factory.get(pieceType, fromRotate); 100 | 101 | // Last SRS offset 102 | int lastOffsetIndex = fromRotate * 5 + (piece.offsetsSize - 1); 103 | auto &offset = piece.rightOffsets[lastOffsetIndex]; 104 | 105 | // Change the direction to `from` 106 | int toLeftX = toX + fromBlocks.minX; 107 | int toLowerY = toY + fromBlocks.minY; 108 | 109 | int fromLeftX = toLeftX - offset.x; 110 | int fromLowerY = toLowerY - offset.y; 111 | 112 | int width = kFieldWidth - fromBlocks.width; 113 | 114 | if (0 <= fromLeftX && fromLeftX <= width && 0 <= fromLowerY && 115 | field.canPutAtMaskIndex(fromBlocks, fromLeftX, fromLowerY)) { 116 | int fromX = toX - offset.x; 117 | int fromY = toY - offset.y; 118 | int srsResult = core::srs::right(field, piece, fromRotate, toBlocks, fromX, fromY); 119 | if (srsResult == lastOffsetIndex) { 120 | // T-Spin Regular if come back to the place 121 | if (moveGenerator.canReach(field, pieceType, fromRotate, fromX, fromY, kFieldHeight)) { 122 | int baseAttack = numCleared * 2; 123 | return b2b ? baseAttack + 1 : baseAttack; 124 | } 125 | } 126 | 127 | // Mini 128 | } 129 | } 130 | 131 | // Rotate left 132 | { 133 | // Direction before left rotation 134 | auto fromRotate = static_cast((rotateType + 1) % 4); 135 | auto &fromBlocks = factory.get(pieceType, fromRotate); 136 | 137 | // Last SRS offset 138 | int lastOffsetIndex = fromRotate * 5 + (piece.offsetsSize - 1); 139 | auto &offset = piece.leftOffsets[lastOffsetIndex]; 140 | 141 | // Change the direction to `from` 142 | int toLeftX = toX + fromBlocks.minX; 143 | int toLowerY = toY + fromBlocks.minY; 144 | 145 | int fromLeftX = toLeftX - offset.x; 146 | int fromLowerY = toLowerY - offset.y; 147 | 148 | int width = kFieldWidth - fromBlocks.width; 149 | 150 | if (0 <= fromLeftX && fromLeftX <= width && 0 <= fromLowerY && 151 | field.canPutAtMaskIndex(fromBlocks, fromLeftX, fromLowerY)) { 152 | int fromX = toX - offset.x; 153 | int fromY = toY - offset.y; 154 | int srsResult = core::srs::left(field, piece, fromRotate, toBlocks, fromX, fromY); 155 | if (srsResult == lastOffsetIndex) { 156 | // T-Spin Regular if come back to the place 157 | if (moveGenerator.canReach(field, pieceType, fromRotate, fromX, fromY, kFieldHeight)) { 158 | int baseAttack = numCleared * 2; 159 | return b2b ? baseAttack + 1 : baseAttack; 160 | } 161 | } 162 | 163 | // Mini 164 | } 165 | } 166 | 167 | return b2b ? 1 : 0; 168 | } 169 | 170 | template 171 | constexpr int getAttackIfAllSpins( 172 | core::srs::MoveGenerator &moveGenerator, 173 | core::srs_rotate_end::Reachable &reachable, 174 | const core::Factory &factory, const core::Field &field, 175 | core::PieceType pieceType, const core::Move &move, int numCleared, bool b2b 176 | ) { 177 | if (pieceType == core::PieceType::O) { 178 | return 0; 179 | } 180 | 181 | if (numCleared == 0) { 182 | return 0; 183 | } 184 | 185 | auto rotateType = move.rotateType; 186 | if (!reachable.checks(field, pieceType, rotateType, move.x, move.y, kFieldHeight)) { 187 | return 0; 188 | } 189 | 190 | auto &blocks = factory.get(pieceType, move.rotateType); 191 | 192 | auto x = move.x; 193 | auto y = move.y; 194 | if (!( 195 | (x + blocks.minX - 1 < 0 || !field.canPut(blocks, x - 1, y)) 196 | && (kFieldWidth <= x + blocks.maxX + 1 || !field.canPut(blocks, x + 1, y)) 197 | && !field.canPut(blocks, x, y + 1) 198 | )) { 199 | // It's not immobile 200 | return 0; 201 | } 202 | 203 | // It's spin 204 | int baseAttack = numCleared * 2; 205 | 206 | if constexpr (!AlwaysRegularAttack) { 207 | auto &piece = factory.get(pieceType); 208 | 209 | // Rotate right 210 | { 211 | // Direction before right rotation 212 | auto fromRotate = static_cast((rotateType + 3) % 4); 213 | auto &fromBlocks = factory.get(pieceType, fromRotate); 214 | 215 | // Last SRS offset 216 | int lastOffsetIndex = fromRotate * 5; 217 | auto &offset = piece.rightOffsets[lastOffsetIndex]; 218 | 219 | if (offset.x == 0 && offset.y == 0) { 220 | if (0 <= x + fromBlocks.minX && 221 | x + fromBlocks.maxX < kFieldWidth && 222 | 0 <= y + fromBlocks.minY && 223 | field.canPut(fromBlocks, x, y) && 224 | moveGenerator.canReach(field, pieceType, fromRotate, x, y, kFieldHeight)) { 225 | // NOT kicked 226 | // Regular is enable or regular spin 227 | return b2b ? baseAttack + 1 : baseAttack; 228 | } 229 | } 230 | } 231 | 232 | // Rotate left 233 | { 234 | // Direction before left rotation 235 | auto fromRotate = static_cast((rotateType + 1) % 4); 236 | auto &fromBlocks = factory.get(pieceType, fromRotate); 237 | 238 | // Last SRS offset 239 | int lastOffsetIndex = fromRotate * 5; 240 | auto &offset = piece.leftOffsets[lastOffsetIndex]; 241 | 242 | if (offset.x == 0 && offset.y == 0) { 243 | if (0 <= x + fromBlocks.minX && 244 | x + fromBlocks.maxX < kFieldWidth && 245 | 0 <= y + fromBlocks.minY && 246 | field.canPut(fromBlocks, x, y) && 247 | moveGenerator.canReach(field, pieceType, fromRotate, x, y, kFieldHeight)) { 248 | // NOT kicked 249 | // Regular is enable or regular spin 250 | return b2b ? baseAttack + 1 : baseAttack; 251 | } 252 | } 253 | } 254 | 255 | // Need kick 256 | 257 | // If `AlwaysRegularAttack` is false, mini attack is 0. 258 | // Judged as mini if doesn't clear every line it occupies. 259 | if (numCleared != blocks.height) { 260 | // mini 261 | return b2b ? 1 : 0; 262 | } 263 | 264 | // NOT mini 265 | } 266 | 267 | // If `AlwaysRegularAttack` is true, all spins attack is judged as regular 268 | // Regular is enable or regular spin 269 | return b2b ? baseAttack + 1 : baseAttack; 270 | } 271 | } 272 | 273 | #endif //FINDER_SPINS_HPP 274 | -------------------------------------------------------------------------------- /PerfectClearNET/Tester/MainForm.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using System.Windows.Forms; 6 | 7 | using MisaMinoNET; 8 | using PerfectClearNET; 9 | 10 | namespace Tester { 11 | public partial class MainForm: Form { 12 | public MainForm() { 13 | DialogResult result; 14 | do { 15 | result = MessageBox.Show( 16 | "Is this TETR.IO?", 17 | "PerfectClear Tester", 18 | MessageBoxButtons.YesNo, 19 | MessageBoxIcon.Question 20 | ); 21 | } while (result != DialogResult.Yes && result != DialogResult.No); 22 | 23 | if (result == DialogResult.Yes) { 24 | PerfectClear.Initialize(PerfectClearGame.TETRIO); 25 | } else { 26 | PerfectClear.Initialize(PerfectClearGame.PPT); 27 | } 28 | 29 | InitializeComponent(); 30 | } 31 | 32 | int[,] field1 = new int[10, 40] { 33 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 34 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 35 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 36 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 37 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 38 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 39 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 40 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 41 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 42 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 43 | }; 44 | 45 | int[,] field2 = new int[10, 40] { 46 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 47 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 48 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 49 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 50 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 51 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 52 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 53 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 54 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 55 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 56 | }; 57 | 58 | int[,] field3 = new int[10, 40] { 59 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 60 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 61 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 62 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 63 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 64 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 65 | {255, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 66 | {0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 67 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 68 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 69 | }; 70 | 71 | int[,] field4 = new int[10, 40] { 72 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 73 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 74 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 75 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 76 | {0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 77 | {0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 78 | {255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 79 | {255, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 80 | {0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 81 | {0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 82 | }; 83 | 84 | int[,] fieldUsed; 85 | 86 | private void Finished(bool success) { 87 | Display.Text = $"{PerfectClear.LastTime}ms {success.ToString()}"; 88 | 89 | if (success) { 90 | Display.Text += $" => {string.Join("; ", PerfectClear.LastSolution.Select(i => i.ToString()))} "; 91 | 92 | bool spinUsed = false; 93 | 94 | List result = MisaMino.FindPath( 95 | fieldUsed, 96 | 21, 97 | PerfectClear.LastSolution[0].Piece, 98 | PerfectClear.LastSolution[0].X, 99 | PerfectClear.LastSolution[0].Y, 100 | PerfectClear.LastSolution[0].R, 101 | false, 102 | ref spinUsed, 103 | out bool _ 104 | ); 105 | 106 | Display.Text += string.Join(", ", result); 107 | } 108 | } 109 | 110 | private void MainForm_Load(object sender, EventArgs e) { 111 | PerfectClear.Finished += async (bool success) => { 112 | if (Display.InvokeRequired) 113 | await Task.Run(() => { 114 | Invoke(new PerfectClear.FinishedEventHandler(Finished), new object[] { success }); 115 | }); 116 | 117 | else Finished(success); 118 | }; 119 | } 120 | 121 | private void Run1_Click(object sender, EventArgs e) { 122 | if (PerfectClear.Running) { 123 | PerfectClear.Abort(); 124 | 125 | } else { 126 | Display.Text = "Started 1"; 127 | 128 | PerfectClear.SetThreads(1); 129 | PerfectClear.Find(fieldUsed = field1, new int[] { 6, 0, 3, 0 }, 6, 4, true, 10, false, SearchType.AllSpins, 0, false, true); 130 | } 131 | } 132 | 133 | private void Run2_Click(object sender, EventArgs e) { 134 | if (PerfectClear.Running) { 135 | PerfectClear.Abort(); 136 | 137 | } else { 138 | Display.Text = "Started 2"; 139 | 140 | PerfectClear.SetThreads(12); 141 | PerfectClear.Find(fieldUsed = field2, new int[] { 0, 6, 2, 3, 5, 4, 3, 2, 0, 6 }, 1, null, true, 8, false, SearchType.AllSpins, 0, false, true); 142 | } 143 | } 144 | 145 | private void Run3_Click(object sender, EventArgs e) { 146 | if (PerfectClear.Running) { 147 | PerfectClear.Abort(); 148 | 149 | } else { 150 | Display.Text = "Started 3"; 151 | 152 | PerfectClear.SetThreads(1); 153 | PerfectClear.Find(fieldUsed = field3, new int[] { 2, 6 }, 3, 4, true, 10, false, SearchType.AllSpins, 0, false, true); 154 | } 155 | } 156 | 157 | private void Run4_Click(object sender, EventArgs e) { 158 | if (PerfectClear.Running) { 159 | PerfectClear.Abort(); 160 | 161 | } else { 162 | Display.Text = "Started 4"; 163 | 164 | PerfectClear.SetThreads(1); 165 | PerfectClear.Find(fieldUsed = field4, new int[] { 5, 4 }, 3, 2, true, 10, false, SearchType.AllSpins, 0, false, true); 166 | } 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/field.cpp: -------------------------------------------------------------------------------- 1 | #include "field.hpp" 2 | 3 | namespace core { 4 | namespace { 5 | const uint64_t VALID_BOARD_RANGE = 0xfffffffffffffffL; 6 | 7 | uint64_t getXMask(int x, int y) { 8 | assert(0 <= x && x < FIELD_WIDTH); 9 | assert(0 <= y && y < MAX_FIELD_HEIGHT); 10 | 11 | return 1LLU << (x + y * FIELD_WIDTH); 12 | } 13 | } 14 | 15 | void Field::setBlock(int x, int y) { 16 | assert(0 <= x && x < FIELD_WIDTH); 17 | assert(0 <= y && y < MAX_FIELD_HEIGHT); 18 | 19 | int index = y / 6; 20 | boards[index] |= getXMask(x, y - 6 * index); 21 | } 22 | 23 | void Field::removeBlock(int x, int y) { 24 | assert(0 <= x && x < FIELD_WIDTH); 25 | assert(0 <= y && y < MAX_FIELD_HEIGHT); 26 | 27 | int index = y / 6; 28 | boards[index] &= ~getXMask(x, y - 6 * index); 29 | } 30 | 31 | bool Field::isEmpty(int x, int y) const { 32 | assert(0 <= x && x < FIELD_WIDTH); 33 | assert(0 <= y && y < MAX_FIELD_HEIGHT); 34 | 35 | int index = y / 6; 36 | uint64_t mask = getXMask(x, y - 6 * index); 37 | return (boards[index] & mask) == 0; 38 | } 39 | 40 | void Field::put(const Blocks &blocks, int x, int y) { 41 | putAtMaskIndex(blocks, x + blocks.minX, y + blocks.minY); 42 | } 43 | 44 | void Field::putAtMaskIndex(const Blocks &blocks, int leftX, int lowerY) { 45 | assert(0 <= leftX && leftX < FIELD_WIDTH); 46 | assert(0 <= lowerY && lowerY < MAX_FIELD_HEIGHT); 47 | 48 | int index = lowerY / 6; 49 | BlocksMask mask = blocks.mask(leftX, lowerY - 6 * index); 50 | 51 | boards[index] |= mask.low; 52 | if (index <= 2) { 53 | boards[index + 1] |= mask.high; 54 | } 55 | } 56 | 57 | void Field::remove(const Blocks &blocks, int x, int y) { 58 | putAtMaskIndex(blocks, x + blocks.minX, y + blocks.minY); 59 | } 60 | 61 | void Field::removeAtMaskIndex(const Blocks &blocks, int leftX, int lowerY) { 62 | assert(0 <= leftX && leftX < FIELD_WIDTH); 63 | assert(0 <= lowerY && lowerY < MAX_FIELD_HEIGHT); 64 | 65 | int index = lowerY / 6; 66 | BlocksMask mask = blocks.mask(leftX, lowerY - 6 * index); 67 | 68 | boards[index] &= ~mask.low; 69 | if (index <= 2) { 70 | boards[index + 1] &= ~mask.high; 71 | } 72 | } 73 | 74 | bool Field::canPut(const Blocks &blocks, int x, int y) const { 75 | return canPutAtMaskIndex(blocks, x + blocks.minX, y + blocks.minY); 76 | } 77 | 78 | bool Field::canPutAtMaskIndex(const Blocks &blocks, int leftX, int lowerY) const { 79 | assert(0 <= leftX && leftX < FIELD_WIDTH); 80 | assert(0 <= lowerY && lowerY < MAX_FIELD_HEIGHT); 81 | 82 | int index = lowerY / 6; 83 | BlocksMask mask = blocks.mask(leftX, lowerY - 6 * index); 84 | 85 | if (index <= 2) { 86 | return (boards[index] & mask.low) == 0 && (boards[index + 1] & mask.high) == 0; 87 | } 88 | 89 | return (boards[index] & mask.low) == 0; 90 | } 91 | 92 | bool Field::isOnGround(const Blocks &blocks, int x, int y) const { 93 | return y <= -blocks.minY || !canPut(blocks, x, y - 1); 94 | } 95 | 96 | int Field::getYOnHarddrop(const Blocks &blocks, int x, int startY) const { 97 | int min = -blocks.minY; 98 | for (int y = startY - 1; min <= y; y--) 99 | if (!canPut(blocks, x, y)) 100 | return y + 1; 101 | return min; 102 | } 103 | 104 | bool Field::canReachOnHarddrop(const Blocks &blocks, int x, int y) const { 105 | const int leftX = x + blocks.minX; 106 | const int lowerY = y + blocks.minY; 107 | 108 | assert(0 <= leftX && leftX < FIELD_WIDTH); 109 | assert(0 <= lowerY && lowerY < MAX_FIELD_HEIGHT); 110 | 111 | Collider collider = blocks.harddrop(leftX, lowerY); 112 | 113 | return (boards[0] & collider.boards[0]) == 0 && 114 | (boards[1] & collider.boards[1]) == 0 && 115 | (boards[2] & collider.boards[2]) == 0 && 116 | (boards[3] & collider.boards[3]) == 0; 117 | } 118 | 119 | LineKey getDeleteKey(Bitboard board) { 120 | uint64_t a1010101010 = 768614336404564650L; 121 | auto b1 = (board & a1010101010) >> 1 & board; 122 | uint64_t a0101010000 = 378672165735973200L; 123 | auto b2 = (b1 & a0101010000) >> 4 & b1; 124 | uint64_t a0000010100 = 22540009865236500L; 125 | auto b3 = (b2 & a0000010100) >> 2 & b2; 126 | uint64_t a0000000100 = 4508001973047300L; 127 | return (b3 & a0000000100) >> 2 & b3; 128 | } 129 | 130 | void Field::deleteLine_( 131 | LineKey deleteKeyLow, LineKey deleteKeyMidLow, LineKey deleteKeyMidHigh, LineKey deleteKeyHigh 132 | ) { 133 | // Lower half 134 | Bitboard newXBoardLow = deleteLine(xBoardLow, deleteKeyLow); 135 | 136 | Bitboard newXBoardMidLow = deleteLine(xBoardMidLow, deleteKeyMidLow); 137 | 138 | int deleteLineLow = bitCount(deleteKeyLow); 139 | 140 | Bitboard low = (newXBoardLow | (newXBoardMidLow << (6 - deleteLineLow) * 10)) & VALID_BOARD_RANGE; 141 | Bitboard midLow = newXBoardMidLow >> deleteLineLow * 10; 142 | 143 | int deleteLineMidLow = bitCount(deleteKeyMidLow); 144 | int deleteLineBottom = deleteLineLow + deleteLineMidLow; 145 | 146 | // Upper half 147 | Bitboard newXBoardMidHigh = deleteLine(xBoardMidHigh, deleteKeyMidHigh); 148 | 149 | Bitboard newXBoardHigh = deleteLine(xBoardHigh, deleteKeyHigh); 150 | 151 | int deleteLineMidHigh = bitCount(deleteKeyMidHigh); 152 | 153 | Bitboard midHigh = (newXBoardMidHigh | (newXBoardHigh << (6 - deleteLineMidHigh) * 10)) & VALID_BOARD_RANGE; 154 | Bitboard high = newXBoardHigh >> deleteLineMidHigh * 10; 155 | 156 | // Merge the upper and lower halves 157 | if (deleteLineBottom < 6) { 158 | xBoardLow = low; 159 | xBoardMidLow = (midLow | (midHigh << (6 - deleteLineBottom) * 10)) & VALID_BOARD_RANGE; 160 | xBoardMidHigh = 161 | ((midHigh >> deleteLineBottom * 10) | (high << (6 - deleteLineBottom) * 10)) & VALID_BOARD_RANGE; 162 | xBoardHigh = high >> deleteLineBottom * 10; 163 | } else { 164 | int slide = deleteLineBottom - 6; 165 | xBoardLow = (low | (midHigh << (6 - slide) * 10)) & VALID_BOARD_RANGE; 166 | xBoardMidLow = ((midHigh >> slide * 10) | (high << (6 - slide) * 10)) & VALID_BOARD_RANGE; 167 | xBoardMidHigh = high >> slide * 10; 168 | xBoardHigh = 0L; 169 | } 170 | } 171 | 172 | void Field::clearLine() { 173 | LineKey deleteKeyLow = getDeleteKey(xBoardLow); 174 | LineKey deleteKeyMidLow = getDeleteKey(xBoardMidLow); 175 | LineKey deleteKeyMidHigh = getDeleteKey(xBoardMidHigh); 176 | LineKey deleteKeyHigh = getDeleteKey(xBoardHigh); 177 | 178 | deleteLine_(deleteKeyLow, deleteKeyMidLow, deleteKeyMidHigh, deleteKeyHigh); 179 | } 180 | 181 | LineKey Field::clearLineReturnKey() { 182 | LineKey deleteKeyLow = getDeleteKey(xBoardLow); 183 | LineKey deleteKeyMidLow = getDeleteKey(xBoardMidLow); 184 | LineKey deleteKeyMidHigh = getDeleteKey(xBoardMidHigh); 185 | LineKey deleteKeyHigh = getDeleteKey(xBoardHigh); 186 | 187 | deleteLine_(deleteKeyLow, deleteKeyMidLow, deleteKeyMidHigh, deleteKeyHigh); 188 | 189 | return deleteKeyLow | (deleteKeyMidLow << 1) | (deleteKeyMidHigh << 2) | (deleteKeyHigh << 3); 190 | } 191 | 192 | int Field::getBlockOnX(int x, int maxY) const { 193 | assert(0 <= maxY && maxY <= MAX_FIELD_HEIGHT); 194 | 195 | if (maxY < 12) { 196 | if (maxY < 6) { 197 | Bitboard mask = getColumnOneLineBelowY(maxY) << x; 198 | return bitCount(xBoardLow & mask); 199 | } else { 200 | Bitboard fullMask = getColumnOneLineBelowY(6) << x; 201 | Bitboard mask = getColumnOneLineBelowY(maxY - 6) << x; 202 | return bitCount(xBoardLow & fullMask) 203 | + bitCount(xBoardMidLow & mask); 204 | } 205 | } else { 206 | if (maxY < 18) { 207 | Bitboard fullMask = getColumnOneLineBelowY(6) << x; 208 | Bitboard mask = getColumnOneLineBelowY(maxY - 12) << x; 209 | return bitCount(xBoardLow & fullMask) 210 | + bitCount(xBoardMidLow & fullMask) + 211 | bitCount(xBoardMidHigh & mask); 212 | } else { 213 | Bitboard fullMask = getColumnOneLineBelowY(6) << x; 214 | Bitboard mask = getColumnOneLineBelowY(maxY - 18) << x; 215 | return bitCount(xBoardLow & fullMask) 216 | + bitCount(xBoardMidLow & fullMask) 217 | + bitCount(xBoardMidHigh & fullMask) 218 | + bitCount(xBoardHigh & mask); 219 | } 220 | } 221 | } 222 | 223 | bool Field::isWallBetween(int x, int maxY) const { 224 | assert(0 <= maxY && maxY <= MAX_FIELD_HEIGHT); 225 | 226 | if (maxY == 0) { 227 | return true; 228 | } 229 | 230 | if (maxY < 12) { 231 | if (maxY < 6) { 232 | // Check Low 233 | return isWallBetweenLeft(x, maxY, xBoardLow); 234 | } else { 235 | // Check Low 236 | if (!isWallBetweenLeft(x, 6, xBoardLow)) 237 | return false; 238 | 239 | // Check MidLow 240 | return isWallBetweenLeft(x, maxY - 6, xBoardMidLow); 241 | } 242 | } else { 243 | if (maxY < 18) { 244 | // Check Low 245 | if (!isWallBetweenLeft(x, 6, xBoardLow)) 246 | return false; 247 | 248 | // Check MidLow 249 | if (!isWallBetweenLeft(x, 6, xBoardMidLow)) 250 | return false; 251 | 252 | // Check MidHigh 253 | return isWallBetweenLeft(x, maxY - 12, xBoardMidHigh); 254 | } else { 255 | // Check Low 256 | if (!isWallBetweenLeft(x, 6, xBoardLow)) 257 | return false; 258 | 259 | // Check MidLow 260 | if (!isWallBetweenLeft(x, 6, xBoardMidLow)) 261 | return false; 262 | 263 | // Check MidHigh 264 | if (!isWallBetweenLeft(x, 6, xBoardMidHigh)) 265 | return false; 266 | 267 | // Check High 268 | return isWallBetweenLeft(x, maxY - 18, xBoardHigh); 269 | } 270 | } 271 | } 272 | 273 | int Field::clearLineReturnNum() { 274 | LineKey deleteKeyLow = getDeleteKey(xBoardLow); 275 | LineKey deleteKeyMidLow = getDeleteKey(xBoardMidLow); 276 | LineKey deleteKeyMidHigh = getDeleteKey(xBoardMidHigh); 277 | LineKey deleteKeyHigh = getDeleteKey(xBoardHigh); 278 | 279 | deleteLine_(deleteKeyLow, deleteKeyMidLow, deleteKeyMidHigh, deleteKeyHigh); 280 | 281 | return bitCount(deleteKeyLow | (deleteKeyMidLow << 1) | (deleteKeyMidHigh << 2) | (deleteKeyHigh << 3)); 282 | } 283 | 284 | std::string Field::toString(int height) const { 285 | auto str = std::string(""); 286 | for (int y = height - 1; 0 <= y; --y) { 287 | for (int x = 0; x < 10; ++x) { 288 | if (isEmpty(x, y)) { 289 | str += "_"; 290 | } else { 291 | str += "X"; 292 | } 293 | } 294 | str += "\n"; 295 | } 296 | return str; 297 | } 298 | 299 | Field createField(std::string marks) { 300 | assert(marks.length() < 240); 301 | assert(marks.length() % 10 == 0); 302 | 303 | int maxY = static_cast(marks.length() / 10); 304 | Field field = Field(); 305 | for (int y = 0; y < maxY; y++) { 306 | for (int x = 0; x < 10; x++) { 307 | char mark = marks.at((maxY - y - 1) * 10 + x); 308 | if (mark != ' ' && mark != '_') { 309 | field.setBlock(x, y); 310 | } 311 | } 312 | } 313 | 314 | return field; 315 | } 316 | 317 | int Field::getNumOfBlocks() const { 318 | return bitCount(xBoardLow) + bitCount(xBoardMidLow) + bitCount(xBoardMidHigh) + bitCount(xBoardHigh); 319 | } 320 | 321 | int Field::getNumOfVerticalTransitions() const { 322 | auto field = core::Field(xBoardLow, xBoardMidLow, xBoardMidHigh, xBoardHigh); 323 | field.deleteLine_(1ULL, 0ULL, 0ULL, 0ULL); 324 | 325 | auto low = ~xBoardLow & field.xBoardLow; 326 | auto midLow = ~xBoardMidLow & field.xBoardMidLow; 327 | auto midHigh = ~xBoardMidHigh & field.xBoardMidHigh; 328 | auto high = ~xBoardHigh & field.xBoardHigh; 329 | 330 | return bitCount(low) + bitCount(midLow) + bitCount(midHigh) + bitCount(high); 331 | } 332 | 333 | int Field::getNumOfHoles() const { 334 | auto field = core::Field(xBoardLow, xBoardMidLow, xBoardMidHigh, xBoardHigh); 335 | field.fillBelowSurface(); 336 | 337 | auto low = ~xBoardLow & field.xBoardLow; 338 | auto midLow = ~xBoardMidLow & field.xBoardMidLow; 339 | auto midHigh = ~xBoardMidHigh & field.xBoardMidHigh; 340 | auto high = ~xBoardHigh & field.xBoardHigh; 341 | 342 | return bitCount(low) + bitCount(midLow) + bitCount(midHigh) + bitCount(high); 343 | } 344 | 345 | int Field::getMaxY() const { 346 | if (0ULL < xBoardHigh) { 347 | return (mostSignificantDigit(xBoardHigh) - 1) / 10 + 18; 348 | } else if (0ULL < xBoardMidHigh) { 349 | return (mostSignificantDigit(xBoardMidHigh) - 1) / 10 + 12; 350 | } else if (0ULL < xBoardMidLow) { 351 | return (mostSignificantDigit(xBoardMidLow) - 1) / 10 + 6; 352 | } else if (0ULL < xBoardLow) { 353 | return (mostSignificantDigit(xBoardLow) - 1) / 10; 354 | } 355 | return -1; 356 | } 357 | 358 | void Field::fillBelowSurface() { 359 | if (0ULL < xBoardHigh) { 360 | xBoardHigh = fillVertical(xBoardHigh); 361 | xBoardMidHigh = fillVertical(xBoardMidHigh | ((xBoardHigh << 50UL) & VALID_BOARD_RANGE)); 362 | xBoardMidLow = fillVertical(xBoardMidLow | ((xBoardMidHigh << 50UL) & VALID_BOARD_RANGE)); 363 | xBoardLow = fillVertical(xBoardLow | ((xBoardMidLow << 50UL) & VALID_BOARD_RANGE)); 364 | } else if (0ULL < xBoardMidHigh) { 365 | xBoardMidHigh = fillVertical(xBoardMidHigh); 366 | xBoardMidLow = fillVertical(xBoardMidLow | ((xBoardMidHigh << 50UL) & VALID_BOARD_RANGE)); 367 | xBoardLow = fillVertical(xBoardLow | ((xBoardMidLow << 50UL) & VALID_BOARD_RANGE)); 368 | } else if (0ULL < xBoardMidLow) { 369 | xBoardMidLow = fillVertical(xBoardMidLow); 370 | xBoardLow = fillVertical(xBoardLow | ((xBoardMidLow << 50UL) & VALID_BOARD_RANGE)); 371 | } else if (0ULL < xBoardLow) { 372 | xBoardLow = fillVertical(xBoardLow); 373 | } 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/finder/perfect_clear.cpp: -------------------------------------------------------------------------------- 1 | #include "perfect_clear.hpp" 2 | 3 | namespace finder { 4 | int extractLastHoldPriority(uint8_t priority, core::PieceType hold) { 5 | int slide = hold != core::PieceType::Empty ? hold : 7; 6 | uint8_t bit = priority >> static_cast(slide); 7 | return bit & 1U; 8 | } 9 | 10 | int compareToLastHoldPriority(uint8_t priority, int bestBit, core::PieceType newHold) { 11 | // Priority is given when the least significant bit is 1 12 | 13 | int newBit = extractLastHoldPriority(priority, newHold); 14 | 15 | // When the least significant bits are different 16 | if (0 < (newBit ^ bestBit)) { 17 | // Return true when giving priority to new 18 | return 0 < newBit ? 1 : -1; 19 | } 20 | 21 | return 0; 22 | } 23 | 24 | // For fast search 25 | void Recorder::clear() { 26 | best_ = FastRecord{ 27 | std::vector{}, 28 | core::PieceType::Empty, 29 | INT_MAX, 30 | INT_MAX, 31 | INT_MAX, 32 | 0, 33 | INT_MAX, 34 | INT_MAX, 35 | INT_MAX, 36 | INT_MAX, 37 | 0, 38 | 0, 39 | 0, 40 | }; 41 | } 42 | 43 | void Recorder::update( 44 | const Configure &configure, const FastCandidate ¤t, const Solution &solution 45 | ) { 46 | auto hold = 0 <= current.holdIndex ? configure.pieces[current.holdIndex] : core::PieceType::Empty; 47 | best_ = FastRecord{ 48 | // new 49 | solution, hold, extractLastHoldPriority(configure.lastHoldPriority, hold), 50 | // from candidate 51 | current.currentIndex, current.holdIndex, current.leftLine, current.depth, 52 | current.softdropCount, current.holdCount, current.lineClearCount, 53 | current.currentCombo, current.maxCombo, current.frames 54 | }; 55 | } 56 | 57 | void Recorder::update(const FastRecord &record) { 58 | best_ = FastRecord{record}; 59 | } 60 | 61 | bool Recorder::isWorseThanBest( 62 | bool leastLineClears, const FastCandidate ¤t 63 | ) const { 64 | if (best_.holdPriority == 0) { 65 | return false; 66 | } 67 | 68 | return best_.softdropCount < current.softdropCount; 69 | } 70 | 71 | bool shouldUpdateFrames( 72 | const FastRecord& oldRecord, const FastCandidate& newRecord 73 | ) { 74 | int newFrames = newRecord.holdCount + newRecord.frames; 75 | int oldFrames = oldRecord.holdCount + oldRecord.frames; 76 | 77 | return newFrames == oldFrames 78 | ? newRecord.holdCount < oldRecord.holdCount 79 | : newFrames < oldFrames; 80 | } 81 | 82 | bool shouldUpdateLeastLineClear( 83 | const FastRecord &oldRecord, const FastCandidate &newRecord 84 | ) { 85 | if (newRecord.softdropCount != oldRecord.softdropCount) { 86 | return newRecord.softdropCount < oldRecord.softdropCount; 87 | } 88 | 89 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 90 | return newRecord.lineClearCount < oldRecord.lineClearCount; 91 | } 92 | 93 | return shouldUpdateFrames(oldRecord, newRecord); 94 | } 95 | 96 | bool shouldUpdateMostLineClear( 97 | const FastRecord &oldRecord, const FastCandidate &newRecord 98 | ) { 99 | if (newRecord.softdropCount != oldRecord.softdropCount) { 100 | return newRecord.softdropCount < oldRecord.softdropCount; 101 | } 102 | 103 | if (newRecord.maxCombo != oldRecord.maxCombo) { 104 | return oldRecord.maxCombo < newRecord.maxCombo; 105 | } 106 | 107 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 108 | return oldRecord.lineClearCount < newRecord.lineClearCount; 109 | } 110 | 111 | return shouldUpdateFrames(oldRecord, newRecord); 112 | } 113 | 114 | bool Recorder::shouldUpdate( 115 | const Configure &configure, const FastCandidate &newRecord 116 | ) const { 117 | if (best_.solution.empty()) { 118 | return true; 119 | } 120 | 121 | core::PieceType newHold = 0 <= newRecord.holdIndex 122 | ? configure.pieces[newRecord.holdIndex] 123 | : core::PieceType::Empty; 124 | auto compare = compareToLastHoldPriority(configure.lastHoldPriority, best_.holdPriority, newHold); 125 | if (compare != 0) { 126 | return 0 < compare; 127 | } 128 | 129 | if (configure.leastLineClears) { 130 | return shouldUpdateLeastLineClear(best_, newRecord); 131 | } else { 132 | return shouldUpdateMostLineClear(best_, newRecord); 133 | } 134 | } 135 | 136 | 137 | // For T-Spin search 138 | void Recorder::clear() { 139 | best_ = TSpinRecord{ 140 | std::vector{}, 141 | core::PieceType::Empty, 142 | INT_MAX, 143 | INT_MAX, 144 | INT_MAX, 145 | 0, 146 | INT_MAX, 147 | INT_MAX, 148 | INT_MAX, 149 | INT_MAX, 150 | 0, 151 | 0, 152 | 0, 153 | false, 154 | 0, 155 | 0, 156 | }; 157 | } 158 | 159 | void Recorder::update( 160 | const Configure &configure, const TSpinCandidate ¤t, const Solution &solution 161 | ) { 162 | auto hold = 0 <= current.holdIndex ? configure.pieces[current.holdIndex] : core::PieceType::Empty; 163 | best_ = TSpinRecord{ 164 | // new 165 | solution, hold, extractLastHoldPriority(configure.lastHoldPriority, hold), 166 | // from candidate 167 | current.currentIndex, current.holdIndex, current.leftLine, current.depth, 168 | current.softdropCount, current.holdCount, current.lineClearCount, 169 | current.currentCombo, current.maxCombo, current.tSpinAttack, current.b2b, current.leftNumOfT, 170 | }; 171 | } 172 | 173 | void Recorder::update(const TSpinRecord &record) { 174 | best_ = TSpinRecord{record}; 175 | } 176 | 177 | bool Recorder::isWorseThanBest( 178 | bool leastLineClears, const TSpinCandidate ¤t 179 | ) const { 180 | if (best_.holdPriority == 0) { 181 | return false; 182 | } 183 | 184 | if (current.leftNumOfT == 0) { 185 | if (current.tSpinAttack != best_.tSpinAttack) { 186 | return current.tSpinAttack < best_.tSpinAttack; 187 | } 188 | 189 | return best_.softdropCount < current.softdropCount; 190 | } 191 | 192 | return false; 193 | } 194 | 195 | bool shouldUpdateFrames( 196 | const TSpinRecord& oldRecord, const TSpinCandidate& newRecord 197 | ) { 198 | int newFrames = newRecord.holdCount + newRecord.frames; 199 | int oldFrames = oldRecord.holdCount + oldRecord.frames; 200 | 201 | return newFrames == oldFrames 202 | ? newRecord.holdCount < oldRecord.holdCount 203 | : newFrames < oldFrames; 204 | } 205 | 206 | bool shouldUpdateLeastLineClear( 207 | const TSpinRecord &oldRecord, const TSpinCandidate &newRecord 208 | ) { 209 | if (newRecord.tSpinAttack != oldRecord.tSpinAttack) { 210 | return oldRecord.tSpinAttack < newRecord.tSpinAttack; 211 | } 212 | 213 | if (newRecord.softdropCount != oldRecord.softdropCount) { 214 | return newRecord.softdropCount < oldRecord.softdropCount; 215 | } 216 | 217 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 218 | return newRecord.lineClearCount < oldRecord.lineClearCount; 219 | } 220 | 221 | return shouldUpdateFrames(oldRecord, newRecord); 222 | } 223 | 224 | bool shouldUpdateMostLineClear( 225 | const TSpinRecord &oldRecord, const TSpinCandidate &newRecord 226 | ) { 227 | if (newRecord.tSpinAttack != oldRecord.tSpinAttack) { 228 | return oldRecord.tSpinAttack < newRecord.tSpinAttack; 229 | } 230 | 231 | if (newRecord.softdropCount != oldRecord.softdropCount) { 232 | return newRecord.softdropCount < oldRecord.softdropCount; 233 | } 234 | 235 | if (newRecord.maxCombo != oldRecord.maxCombo) { 236 | return oldRecord.maxCombo < newRecord.maxCombo; 237 | } 238 | 239 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 240 | return oldRecord.lineClearCount < newRecord.lineClearCount; 241 | } 242 | 243 | return shouldUpdateFrames(oldRecord, newRecord); 244 | } 245 | 246 | bool Recorder::shouldUpdate( 247 | const Configure &configure, const TSpinCandidate &newRecord 248 | ) const { 249 | if (best_.solution.empty()) { 250 | return true; 251 | } 252 | 253 | core::PieceType newHold = 0 <= newRecord.holdIndex 254 | ? configure.pieces[newRecord.holdIndex] 255 | : core::PieceType::Empty; 256 | auto compare = compareToLastHoldPriority(configure.lastHoldPriority, best_.holdPriority, newHold); 257 | if (compare != 0) { 258 | return 0 < compare; 259 | } 260 | 261 | if (configure.leastLineClears) { 262 | return shouldUpdateLeastLineClear(best_, newRecord); 263 | } else { 264 | return shouldUpdateMostLineClear(best_, newRecord); 265 | } 266 | } 267 | 268 | 269 | // For all spins search 270 | void Recorder::clear() { 271 | best_ = AllSpinsRecord{ 272 | std::vector{}, 273 | core::PieceType::Empty, 274 | INT_MAX, 275 | INT_MAX, 276 | INT_MAX, 277 | 0, 278 | INT_MAX, 279 | INT_MAX, 280 | INT_MAX, 281 | INT_MAX, 282 | 0, 283 | 0, 284 | 0, 285 | false, 286 | 0, 287 | }; 288 | } 289 | 290 | void Recorder::update( 291 | const Configure &configure, const AllSpinsCandidate ¤t, const Solution &solution 292 | ) { 293 | auto hold = 0 <= current.holdIndex ? configure.pieces[current.holdIndex] : core::PieceType::Empty; 294 | best_ = AllSpinsRecord{ 295 | // new 296 | solution, hold, extractLastHoldPriority(configure.lastHoldPriority, hold), 297 | // from candidate 298 | current.currentIndex, current.holdIndex, current.leftLine, current.depth, 299 | current.softdropCount, current.holdCount, current.lineClearCount, 300 | current.currentCombo, current.maxCombo, current.spinAttack, current.b2b, current.frames 301 | }; 302 | } 303 | 304 | void Recorder::update(const AllSpinsRecord &record) { 305 | best_ = AllSpinsRecord{record}; 306 | } 307 | 308 | bool Recorder::isWorseThanBest( 309 | bool leastLineClears, const AllSpinsCandidate ¤t 310 | ) const { 311 | // There is a high possibility of spin attack until the last piece. so, it's difficult to prune along the way 312 | return false; 313 | } 314 | 315 | bool shouldUpdateFrames( 316 | const AllSpinsRecord& oldRecord, const AllSpinsCandidate& newRecord 317 | ) { 318 | int newFrames = newRecord.holdCount + newRecord.frames; 319 | int oldFrames = oldRecord.holdCount + oldRecord.frames; 320 | 321 | return newFrames == oldFrames 322 | ? newRecord.holdCount < oldRecord.holdCount 323 | : newFrames < oldFrames; 324 | } 325 | 326 | bool shouldUpdateLeastLineClear( 327 | const AllSpinsRecord &oldRecord, const AllSpinsCandidate &newRecord 328 | ) { 329 | if (newRecord.spinAttack != oldRecord.spinAttack) { 330 | return oldRecord.spinAttack < newRecord.spinAttack; 331 | } 332 | 333 | if (newRecord.softdropCount != oldRecord.softdropCount) { 334 | return newRecord.softdropCount < oldRecord.softdropCount; 335 | } 336 | 337 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 338 | return newRecord.lineClearCount < oldRecord.lineClearCount; 339 | } 340 | 341 | return shouldUpdateFrames(oldRecord, newRecord); 342 | } 343 | 344 | bool shouldUpdateMostLineClear( 345 | const AllSpinsRecord &oldRecord, const AllSpinsCandidate &newRecord 346 | ) { 347 | if (newRecord.spinAttack != oldRecord.spinAttack) { 348 | return oldRecord.spinAttack < newRecord.spinAttack; 349 | } 350 | 351 | if (newRecord.softdropCount != oldRecord.softdropCount) { 352 | return newRecord.softdropCount < oldRecord.softdropCount; 353 | } 354 | 355 | if (newRecord.maxCombo != oldRecord.maxCombo) { 356 | return oldRecord.maxCombo < newRecord.maxCombo; 357 | } 358 | 359 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 360 | return oldRecord.lineClearCount < newRecord.lineClearCount; 361 | } 362 | 363 | return shouldUpdateFrames(oldRecord, newRecord); 364 | } 365 | 366 | bool Recorder::shouldUpdate( 367 | const Configure &configure, const AllSpinsCandidate &newRecord 368 | ) const { 369 | if (best_.solution.empty()) { 370 | return true; 371 | } 372 | 373 | core::PieceType newHold = 0 <= newRecord.holdIndex 374 | ? configure.pieces[newRecord.holdIndex] 375 | : core::PieceType::Empty; 376 | auto compare = compareToLastHoldPriority(configure.lastHoldPriority, best_.holdPriority, newHold); 377 | if (compare != 0) { 378 | return 0 < compare; 379 | } 380 | 381 | if (configure.leastLineClears) { 382 | return shouldUpdateLeastLineClear(best_, newRecord); 383 | } else { 384 | return shouldUpdateMostLineClear(best_, newRecord); 385 | } 386 | } 387 | 388 | // For TETR.IO Season 2 search 389 | void Recorder::clear() { 390 | best_ = TETRIOS2Record{ 391 | std::vector{}, 392 | core::PieceType::Empty, 393 | INT_MAX, 394 | INT_MAX, 395 | INT_MAX, 396 | 0, 397 | INT_MAX, 398 | INT_MAX, 399 | INT_MAX, 400 | INT_MAX, 401 | 0, 402 | 0, 403 | 0, 404 | 0, 405 | 0, 406 | false, 407 | false, 408 | }; 409 | } 410 | 411 | void Recorder::update( 412 | const Configure &configure, const TETRIOS2Candidate ¤t, const Solution &solution 413 | ) { 414 | auto hold = 0 <= current.holdIndex ? configure.pieces[current.holdIndex] : core::PieceType::Empty; 415 | best_ = TETRIOS2Record{ 416 | // new 417 | solution, hold, extractLastHoldPriority(configure.lastHoldPriority, hold), 418 | // from candidate 419 | current.currentIndex, current.holdIndex, current.leftLine, current.depth, 420 | current.softdropCount, current.holdCount, current.lineClearCount, current.currentCombo, 421 | current.maxCombo, current.spinAttack, current.b2b, current.frames, current.isClean, current.isFlatI 422 | }; 423 | } 424 | 425 | void Recorder::update(const TETRIOS2Record &record) { 426 | best_ = TETRIOS2Record{record}; 427 | } 428 | 429 | bool Recorder::isWorseThanBest( 430 | bool leastLineClears, const TETRIOS2Candidate ¤t 431 | ) const { 432 | // There is a high possibility of spin attack until the last piece. so, it's difficult to prune along the way 433 | return false; 434 | } 435 | 436 | bool shouldUpdateFrames( 437 | const TETRIOS2Record& oldRecord, const TETRIOS2Candidate& newRecord 438 | ) { 439 | int newFrames = newRecord.holdCount + newRecord.frames; 440 | int oldFrames = oldRecord.holdCount + oldRecord.frames; 441 | 442 | return newFrames == oldFrames 443 | ? newRecord.holdCount < oldRecord.holdCount 444 | : newFrames < oldFrames; 445 | } 446 | 447 | bool shouldUpdateMostLineClear( 448 | const TETRIOS2Record &oldRecord, const TETRIOS2Candidate &newRecord 449 | ) { 450 | // non-Spin endings result in really hard to deal with boards if we are forced to tank garbage 451 | // so we really really really never want to take one of those unless it's the only option 452 | // flat I ending boards are not as bad and they allow for 4 B2B per 4L PC so they are worth it 453 | bool newIsSafe = newRecord.isClean || newRecord.isFlatI; 454 | bool oldIsSafe = oldRecord.isClean || oldRecord.isFlatI; 455 | 456 | if (newIsSafe != oldIsSafe) { 457 | return newIsSafe; 458 | } 459 | 460 | // prefer larger B2B per PC 461 | if (newRecord.b2b != oldRecord.b2b) { 462 | return oldRecord.b2b < newRecord.b2b; 463 | } 464 | 465 | // prefer larger spin attack, but add slight preference for keeping a clean ending 466 | int newScore = newRecord.spinAttack + (newRecord.isClean ? 2 : 0); 467 | int oldScore = oldRecord.spinAttack + (oldRecord.isClean ? 2 : 0); 468 | 469 | if (newScore != oldScore) { 470 | return oldScore < newScore; 471 | } 472 | 473 | if (newRecord.isClean != oldRecord.isClean) { 474 | return newRecord.isClean; 475 | } 476 | 477 | if (newRecord.spinAttack != oldRecord.spinAttack) { 478 | return oldRecord.spinAttack < newRecord.spinAttack; 479 | } 480 | 481 | if (newRecord.maxCombo != oldRecord.maxCombo) { 482 | return oldRecord.maxCombo < newRecord.maxCombo; 483 | } 484 | 485 | if (newRecord.lineClearCount != oldRecord.lineClearCount) { 486 | return oldRecord.lineClearCount < newRecord.lineClearCount; 487 | } 488 | 489 | // Irrelevant for TETR.IO 490 | //if (newRecord.softdropCount != oldRecord.softdropCount) { 491 | // return newRecord.softdropCount < oldRecord.softdropCount; 492 | //} 493 | 494 | return shouldUpdateFrames(oldRecord, newRecord); 495 | } 496 | 497 | bool Recorder::shouldUpdate( 498 | const Configure &configure, const TETRIOS2Candidate &newRecord 499 | ) const { 500 | if (best_.solution.empty()) { 501 | return true; 502 | } 503 | 504 | core::PieceType newHold = 0 <= newRecord.holdIndex 505 | ? configure.pieces[newRecord.holdIndex] 506 | : core::PieceType::Empty; 507 | auto compare = compareToLastHoldPriority(configure.lastHoldPriority, best_.holdPriority, newHold); 508 | if (compare != 0) { 509 | return 0 < compare; 510 | } 511 | 512 | // always want to do MostLineClear 513 | return shouldUpdateMostLineClear(best_, newRecord); 514 | } 515 | } -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/piece.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "piece.hpp" 4 | 5 | namespace core { 6 | namespace { 7 | const uint64_t VALID_BOARD_RANGE = 0xfffffffffffffffL; 8 | 9 | constexpr std::array tTransforms{ 10 | Transform{Offset{0, 0}, RotateType::Spawn}, 11 | Transform{Offset{0, 0}, RotateType::Right}, 12 | Transform{Offset{0, 0}, RotateType::Reverse}, 13 | Transform{Offset{0, 0}, RotateType::Left}, 14 | }; 15 | constexpr std::array iTransforms{ 16 | Transform{Offset{0, 0}, RotateType::Spawn}, 17 | Transform{Offset{0, -1}, RotateType::Left}, 18 | Transform{Offset{-1, 0}, RotateType::Spawn}, 19 | Transform{Offset{0, 0}, RotateType::Left}, 20 | }; 21 | constexpr std::array sTransforms{ 22 | Transform{Offset{0, 0}, RotateType::Spawn}, 23 | Transform{Offset{1, 0}, RotateType::Left}, 24 | Transform{Offset{0, -1}, RotateType::Spawn}, 25 | Transform{Offset{0, 0}, RotateType::Left}, 26 | }; 27 | constexpr std::array zTransforms{ 28 | Transform{Offset{0, 0}, RotateType::Spawn}, 29 | Transform{Offset{0, 0}, RotateType::Right}, 30 | Transform{Offset{0, -1}, RotateType::Spawn}, 31 | Transform{Offset{-1, 0}, RotateType::Right}, 32 | }; 33 | constexpr std::array oTransforms{ 34 | Transform{Offset{0, 0}, RotateType::Spawn}, 35 | Transform{Offset{0, -1}, RotateType::Spawn}, 36 | Transform{Offset{-1, -1}, RotateType::Spawn}, 37 | Transform{Offset{-1, 0}, RotateType::Spawn}, 38 | }; 39 | 40 | std::array rotateRight_(std::array points) { 41 | return std::array{ 42 | Point{points[0].y, -points[0].x}, 43 | Point{points[1].y, -points[1].x}, 44 | Point{points[2].y, -points[2].x}, 45 | Point{points[3].y, -points[3].x}, 46 | }; 47 | } 48 | 49 | std::array rotateLeft_(std::array points) { 50 | return std::array{ 51 | Point{-points[0].y, points[0].x}, 52 | Point{-points[1].y, points[1].x}, 53 | Point{-points[2].y, points[2].x}, 54 | Point{-points[3].y, points[3].x}, 55 | }; 56 | } 57 | 58 | std::array rotateReverse_(std::array points) { 59 | return std::array{ 60 | Point{-points[0].x, -points[0].y}, 61 | Point{-points[1].x, -points[1].y}, 62 | Point{-points[2].x, -points[2].y}, 63 | Point{-points[3].x, -points[3].y}, 64 | }; 65 | } 66 | 67 | uint64_t getXMask(int x, int y) { 68 | assert(0 <= x && x < FIELD_WIDTH); 69 | assert(0 <= y && y < MAX_FIELD_HEIGHT); 70 | 71 | return 1LLU << (x + y * FIELD_WIDTH); 72 | } 73 | 74 | Collider mergeCollider(const Collider &prev, const Bitboard mask, int height, int lowerY) { 75 | auto collider = Collider{prev}; 76 | assert(0 <= lowerY && lowerY + height <= MAX_FIELD_HEIGHT); 77 | 78 | int index = lowerY / 6; 79 | int localY = lowerY - 6 * index; 80 | if (6 < localY + height) { 81 | // Over 82 | collider.boards[index] |= (mask << (localY * FIELD_WIDTH)) & VALID_BOARD_RANGE; 83 | collider.boards[index + 1] |= mask >> ((6 - localY) * FIELD_WIDTH); 84 | } else { 85 | // Fit in the lower 6 86 | collider.boards[index] |= mask << (localY * FIELD_WIDTH); 87 | } 88 | 89 | return collider; 90 | } 91 | } 92 | 93 | Blocks Blocks::create(const RotateType rotateType, const std::array &points) { 94 | MinMax minmaxX = std::minmax({points[0].x, points[1].x, points[2].x, points[3].x}); 95 | MinMax minmaxY = std::minmax({points[0].y, points[1].y, points[2].y, points[3].y}); 96 | 97 | // Left align 98 | Bitboard mask = 0; 99 | for (const auto &point : points) { 100 | mask |= getXMask(point.x - minmaxX.first, point.y - minmaxY.first); 101 | } 102 | 103 | // Create colliders for harddrop 104 | std::array harddropColliders{}; 105 | int height = minmaxY.second - minmaxY.first + 1; 106 | int max = MAX_FIELD_HEIGHT - height; 107 | harddropColliders[max] = mergeCollider(Collider{}, mask, height, max); 108 | for (int index = max - 1; 0 <= index; --index) { 109 | harddropColliders[index] = mergeCollider(harddropColliders[index + 1], mask, height, index); 110 | } 111 | 112 | return Blocks(rotateType, points, mask, harddropColliders, minmaxX, minmaxY); 113 | } 114 | 115 | template 116 | Piece Piece::create( 117 | const PieceType pieceType, 118 | const std::string &name, 119 | const std::array &points, 120 | const std::array, 4> &offsets, 121 | const std::array &transforms 122 | ) { 123 | return create(pieceType, name, points,offsets, {}, transforms); 124 | } 125 | 126 | template 127 | Piece Piece::create( 128 | PieceType pieceType, 129 | const std::string &name, 130 | const std::array &points, 131 | const std::array, 4> &offsets, 132 | const std::array &rotate180Offsets, 133 | const std::array &transforms 134 | ) { 135 | std::array rightOffsets{}; 136 | for (int rotate = 0; rotate < 4; ++rotate) { 137 | const auto &from = offsets[rotate]; 138 | const auto &to = offsets[(rotate + 1) % 4]; 139 | 140 | assert(from.size() == to.size()); 141 | 142 | auto size = from.size(); 143 | for (int index = 0; index < 5; ++index) { 144 | if (index < size) { 145 | rightOffsets[rotate * 5 + index] = {from[index].x - to[index].x, from[index].y - to[index].y}; 146 | } else { 147 | rightOffsets[rotate * 5 + index] = {0, 0}; 148 | } 149 | } 150 | } 151 | 152 | std::array leftOffsets{}; 153 | for (int rotate = 0; rotate < 4; ++rotate) { 154 | const auto &from = offsets[rotate]; 155 | const auto &to = offsets[(rotate + 3) % 4]; 156 | 157 | assert(from.size() == to.size()); 158 | 159 | auto size = from.size(); 160 | for (int index = 0; index < 5; ++index) { 161 | if (index < size) { 162 | leftOffsets[rotate * 5 + index] = {from[index].x - to[index].x, from[index].y - to[index].y}; 163 | } else { 164 | leftOffsets[rotate * 5 + index] = {0, 0}; 165 | } 166 | } 167 | } 168 | 169 | return create( 170 | pieceType, name, points, rightOffsets, leftOffsets, rotate180Offsets, transforms 171 | ); 172 | } 173 | 174 | template 175 | Piece Piece::create( 176 | const PieceType pieceType, 177 | const std::string &name, 178 | const std::array &points, 179 | const std::array &cwOffsets, 180 | const std::array &ccwOffsets, 181 | const std::array &rotate180Offsets, 182 | const std::array &transforms 183 | ) { 184 | const Blocks &spawn = Blocks::create(RotateType::Spawn, points); 185 | const Blocks &right = Blocks::create(RotateType::Right, rotateRight_(points)); 186 | const Blocks &reverse = Blocks::create(RotateType::Reverse, rotateReverse_(points)); 187 | const Blocks &left = Blocks::create(RotateType::Left, rotateLeft_(points)); 188 | 189 | int32_t uniqueRotate = 0; 190 | for (int rotate = 0; rotate < 4; ++rotate) { 191 | const auto &transform = transforms[rotate]; 192 | uniqueRotate |= 1 << transform.toRotate; 193 | } 194 | 195 | // Find same shape rotate 196 | std::array sameShapeRotates{}; 197 | for (int rotate = 0; rotate < 4; ++rotate) { 198 | int32_t sameRotate = 0; 199 | for (int target = 0; target < 4; ++target) { 200 | if (rotate == transforms[target].toRotate) { 201 | sameRotate |= 1 << target; 202 | } 203 | } 204 | sameShapeRotates[rotate] = sameRotate; 205 | } 206 | 207 | // Update all rotates that have the same shape 208 | for (int rotate = 0; rotate < 4; ++rotate) { 209 | RotateType afterRotate = transforms[rotate].toRotate; 210 | if (rotate != afterRotate) { 211 | sameShapeRotates[rotate] = sameShapeRotates[afterRotate]; 212 | } 213 | } 214 | 215 | return Piece(pieceType, name, std::array{ 216 | spawn, right, reverse, left 217 | }, cwOffsets, ccwOffsets, rotate180Offsets, OffsetSizeRotate90, OffsetSizeRotate180, transforms, uniqueRotate, sameShapeRotates); 218 | } 219 | 220 | BlocksMask Blocks::mask(int leftX, int lowerY) const { 221 | assert(0 <= leftX && leftX <= FIELD_WIDTH - width); 222 | assert(0 <= lowerY && lowerY < 6); 223 | 224 | if (6 < lowerY + height) { 225 | // Over 226 | const auto slide = mask_ << leftX; 227 | return { 228 | (slide << (lowerY * FIELD_WIDTH)) & VALID_BOARD_RANGE, slide >> ((6 - lowerY) * FIELD_WIDTH) 229 | }; 230 | } else { 231 | // Fit in the lower 6 232 | return { 233 | mask_ << (lowerY * FIELD_WIDTH + leftX), 0 234 | }; 235 | } 236 | } 237 | 238 | Collider Blocks::harddrop(int leftX, int lowerY) const { 239 | assert(0 <= leftX && leftX <= FIELD_WIDTH - width); 240 | assert(0 <= lowerY && lowerY < MAX_FIELD_HEIGHT); 241 | 242 | auto &collider = harddropColliders[lowerY]; 243 | return Collider{ 244 | collider.boards[0] << leftX, 245 | collider.boards[1] << leftX, 246 | collider.boards[2] << leftX, 247 | collider.boards[3] << leftX, 248 | }; 249 | } 250 | 251 | Factory Factory::create() { 252 | using namespace std::literals::string_literals; 253 | 254 | constexpr auto iOffsets = std::array, 4>{ 255 | std::array{Offset{0, 0}, {-1, 0}, {2, 0}, {-1, 0}, {2, 0}}, 256 | std::array{Offset{-1, 0}, {0, 0}, {0, 0}, {0, 1}, {0, -2}}, 257 | std::array{Offset{-1, 1}, {1, 1}, {-2, 1}, {1, 0}, {-2, 0}}, 258 | std::array{Offset{0, 1}, {0, 1}, {0, 1}, {0, -1}, {0, 2}}, 259 | }; 260 | 261 | constexpr auto oOffsets = std::array, 4>{ 262 | std::array{Offset{0, 0}}, 263 | std::array{Offset{0, -1}}, 264 | std::array{Offset{-1, -1}}, 265 | std::array{Offset{-1, 0}}, 266 | }; 267 | 268 | constexpr auto otherOffsets = std::array, 4>{ 269 | std::array{Offset{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 270 | std::array{Offset{0, 0}, {1, 0}, {1, -1}, {0, 2}, {1, 2}}, 271 | std::array{Offset{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 272 | std::array{Offset{0, 0}, {-1, 0}, {-1, -1}, {0, 2}, {-1, 2}}, 273 | }; 274 | 275 | const auto t = Piece::create(PieceType::T, "T"s, std::array{ 276 | Point{0, 0}, {-1, 0}, {1, 0}, {0, 1}, 277 | }, otherOffsets, tTransforms); 278 | 279 | const auto i = Piece::create(PieceType::I, "I"s, std::array{ 280 | Point{0, 0}, {-1, 0}, {1, 0}, {2, 0} 281 | }, iOffsets, iTransforms); 282 | 283 | const auto l = Piece::create(PieceType::L, "L"s, std::array{ 284 | Point{0, 0}, {-1, 0}, {1, 0}, {1, 1} 285 | }, otherOffsets, tTransforms); 286 | 287 | const auto j = Piece::create(PieceType::J, "J"s, std::array{ 288 | Point{0, 0}, {-1, 0}, {1, 0}, {-1, 1} 289 | }, otherOffsets, tTransforms); 290 | 291 | const auto s = Piece::create(PieceType::S, "S"s, std::array{ 292 | Point{0, 0}, {-1, 0}, {0, 1}, {1, 1} 293 | }, otherOffsets, sTransforms); 294 | 295 | const auto z = Piece::create(PieceType::Z, "Z"s, std::array{ 296 | Point{0, 0}, {1, 0}, {0, 1}, {-1, 1} 297 | }, otherOffsets, zTransforms); 298 | 299 | const auto o = Piece::create(PieceType::O, "O"s, std::array{ 300 | Point{0, 0}, {1, 0}, {0, 1}, {1, 1} 301 | }, oOffsets, oTransforms); 302 | 303 | return create(t, i, l, j, s, z, o); 304 | } 305 | 306 | Factory Factory::createForSRSPlus() { 307 | using namespace std::literals::string_literals; 308 | 309 | constexpr std::array iCwOffsets{ 310 | // from Spawn 311 | Offset{1, 0}, {2, 0},{ -1, 0},{-1, -1},{ 2,2}, 312 | // from Right 313 | Offset{0, -1}, {-1, -1},{ 2, -1},{-1,1},{ 2, -2}, 314 | // from Reverse 315 | Offset{-1, 0}, { 1, 0},{-2, 0},{ 1,1},{-2, -2}, 316 | // from Left 317 | Offset{0, 1}, {1, 1},{ -2, 1},{ 2, -1},{-2,2}, 318 | }; 319 | constexpr std::array iCcwOffsets{ 320 | // from Spawn 321 | Offset{0, -1}, { -1, -1},{2, -1},{ 2, -2},{-1,2}, 322 | // from Right 323 | Offset{-1, 0}, { -2, 0},{1, 0},{-2, -2},{ 1,1}, 324 | // from Reverse 325 | Offset{0, 1}, {-2, 1},{ 1, 1},{-2,2},{ 1, -1}, 326 | // from Left 327 | Offset{1, 0}, { 2, 0},{-1, 0},{ 2,2},{-1, -1}, 328 | }; 329 | 330 | constexpr auto oOffsets = std::array, 4>{ 331 | std::array{Offset{0, 0}}, 332 | std::array{Offset{0, -1}}, 333 | std::array{Offset{-1, -1}}, 334 | std::array{Offset{-1, 0}}, 335 | }; 336 | 337 | constexpr auto otherOffsets = std::array, 4>{ 338 | std::array{Offset{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 339 | std::array{Offset{0, 0}, {1, 0}, {1, -1}, {0, 2}, {1, 2}}, 340 | std::array{Offset{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}}, 341 | std::array{Offset{0, 0}, {-1, 0}, {-1, -1}, {0, 2}, {-1, 2}}, 342 | }; 343 | 344 | constexpr std::array oRotate180Offsets{ 345 | // from Spawn 346 | Offset{1, 1}, {},{},{},{},{}, 347 | // from Right 348 | Offset{1, -1}, {},{},{},{},{}, 349 | // from Reverse 350 | Offset{-1, -1}, {},{},{},{},{}, 351 | // from Left 352 | Offset{-1, 1}, {},{},{},{},{}, 353 | }; 354 | 355 | constexpr std::array otherRotate180Offsets{ 356 | // from Spawn 357 | Offset{0, 0}, { 0, 1},{1, 1},{ -1, 1},{1, 0},{-1,0}, 358 | // from Right 359 | Offset{0, 0}, { 1, 0},{1, 2},{1, 1},{ 0,2},{0,1}, 360 | // from Reverse 361 | Offset{0, 0}, { 0, -1},{-1, -1},{ 1, -1},{-1, 0},{1,0}, 362 | // from Left 363 | Offset{0, 0}, { -1, 0},{-1, 2},{ -1,1},{0, 2},{0,1}, 364 | }; 365 | 366 | constexpr auto i0To2Offset = Offset{1, -1}; 367 | constexpr auto iRToLOffset = Offset{-1, -1}; 368 | const std::array iRotate180Offsets{ 369 | // from Spawn 370 | otherRotate180Offsets[0] + i0To2Offset, otherRotate180Offsets[1] + i0To2Offset, otherRotate180Offsets[2] + i0To2Offset, 371 | otherRotate180Offsets[3] + i0To2Offset, otherRotate180Offsets[4] + i0To2Offset, otherRotate180Offsets[5] + i0To2Offset, 372 | // from Right 373 | otherRotate180Offsets[6] + iRToLOffset, otherRotate180Offsets[7] + iRToLOffset, otherRotate180Offsets[8] + iRToLOffset, 374 | otherRotate180Offsets[9] + iRToLOffset, otherRotate180Offsets[10] + iRToLOffset, otherRotate180Offsets[11] + iRToLOffset, 375 | // from Reverse 376 | otherRotate180Offsets[12] - i0To2Offset, otherRotate180Offsets[13] - i0To2Offset, otherRotate180Offsets[14] - i0To2Offset, 377 | otherRotate180Offsets[15] - i0To2Offset, otherRotate180Offsets[16] - i0To2Offset, otherRotate180Offsets[17] - i0To2Offset, 378 | // from Left 379 | otherRotate180Offsets[18] - iRToLOffset, otherRotate180Offsets[19] - iRToLOffset, otherRotate180Offsets[20] - iRToLOffset, 380 | otherRotate180Offsets[21] - iRToLOffset, otherRotate180Offsets[22] - iRToLOffset, otherRotate180Offsets[23] - iRToLOffset, 381 | }; 382 | 383 | const auto t = Piece::create<5, 6>(PieceType::T, "T"s, std::array{ 384 | Point{0, 0}, {-1, 0}, {1, 0}, {0, 1}, 385 | }, otherOffsets, otherRotate180Offsets, tTransforms); 386 | 387 | const auto i = Piece::create<5, 6>(PieceType::I, "I"s, std::array{ 388 | Point{0, 0}, {-1, 0}, {1, 0}, {2, 0} 389 | }, iCwOffsets, iCcwOffsets, iRotate180Offsets, iTransforms); 390 | 391 | const auto l = Piece::create<5, 6>(PieceType::L, "L"s, std::array{ 392 | Point{0, 0}, {-1, 0}, {1, 0}, {1, 1} 393 | }, otherOffsets, otherRotate180Offsets, tTransforms); 394 | 395 | const auto j = Piece::create<5, 6>(PieceType::J, "J"s, std::array{ 396 | Point{0, 0}, {-1, 0}, {1, 0}, {-1, 1} 397 | }, otherOffsets, otherRotate180Offsets, tTransforms); 398 | 399 | const auto s = Piece::create<5, 6>(PieceType::S, "S"s, std::array{ 400 | Point{0, 0}, {-1, 0}, {0, 1}, {1, 1} 401 | }, otherOffsets, otherRotate180Offsets, sTransforms); 402 | 403 | const auto z = Piece::create<5, 6>(PieceType::Z, "Z"s, std::array{ 404 | Point{0, 0}, {1, 0}, {0, 1}, {-1, 1} 405 | }, otherOffsets, otherRotate180Offsets, zTransforms); 406 | 407 | const auto o = Piece::create<1, 1>(PieceType::O, "O"s, std::array{ 408 | Point{0, 0}, {1, 0}, {0, 1}, {1, 1} 409 | }, oOffsets, oRotate180Offsets, oTransforms); 410 | 411 | return create(t, i, l, j, s, z, o); 412 | } 413 | 414 | Factory Factory::create( 415 | const Piece& t, 416 | const Piece& i, 417 | const Piece& l, 418 | const Piece& j, 419 | const Piece& s, 420 | const Piece& z, 421 | const Piece& o 422 | ) { 423 | const std::array pieces{ 424 | t, i, l, j, s, z, o 425 | }; 426 | const std::array blocks{ 427 | t.blocks[RotateType::Spawn], t.blocks[RotateType::Right], 428 | t.blocks[RotateType::Reverse], t.blocks[RotateType::Left], 429 | 430 | i.blocks[RotateType::Spawn], i.blocks[RotateType::Right], 431 | i.blocks[RotateType::Reverse], i.blocks[RotateType::Left], 432 | 433 | l.blocks[RotateType::Spawn], l.blocks[RotateType::Right], 434 | l.blocks[RotateType::Reverse], l.blocks[RotateType::Left], 435 | 436 | j.blocks[RotateType::Spawn], j.blocks[RotateType::Right], 437 | j.blocks[RotateType::Reverse], j.blocks[RotateType::Left], 438 | 439 | s.blocks[RotateType::Spawn], s.blocks[RotateType::Right], 440 | s.blocks[RotateType::Reverse], s.blocks[RotateType::Left], 441 | 442 | z.blocks[RotateType::Spawn], z.blocks[RotateType::Right], 443 | z.blocks[RotateType::Reverse], z.blocks[RotateType::Left], 444 | 445 | o.blocks[RotateType::Spawn], o.blocks[RotateType::Right], 446 | o.blocks[RotateType::Reverse], o.blocks[RotateType::Left], 447 | }; 448 | return Factory{pieces, blocks}; 449 | } 450 | 451 | const Piece &Factory::get(PieceType piece) const { 452 | return pieces[piece]; 453 | } 454 | 455 | const Blocks &Factory::get(PieceType piece, RotateType rotate) const { 456 | int index = piece * 4 + rotate; 457 | assert(0 <= index && index < blocks.size()); 458 | return blocks[index]; 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /PerfectClearNET/sfinder-dll/core/bits.cpp: -------------------------------------------------------------------------------- 1 | #include "bits.hpp" 2 | 3 | namespace core { 4 | Bitboard deleteLine_(Bitboard x, LineKey key) { 5 | switch (key) { 6 | case 3072: 7 | return (x & 1023ULL) | ((x >> 10) & 1073740800ULL) | ((x >> 20) & 1098437885952ULL); 8 | case 2048: 9 | return (x & 1099511627775ULL) | ((x >> 10) & 1124800395214848ULL); 10 | case 1024: 11 | return (x & 1023ULL) | ((x >> 10) & 1125899906841600ULL); 12 | case 0: 13 | return (x); 14 | case 3073: 15 | return ((x >> 20) & 1048575ULL) | ((x >> 30) & 1072693248ULL); 16 | case 2049: 17 | return ((x >> 10) & 1073741823ULL) | ((x >> 20) & 1098437885952ULL); 18 | case 1025: 19 | return ((x >> 20) & 1099511627775ULL); 20 | case 1: 21 | return ((x >> 10) & 1125899906842623ULL); 22 | case 3074: 23 | return (x & 1023ULL) | ((x >> 10) & 1047552ULL) | ((x >> 30) & 1072693248ULL); 24 | case 2050: 25 | return (x & 1073741823ULL) | ((x >> 20) & 1098437885952ULL); 26 | case 1026: 27 | return (x & 1023ULL) | ((x >> 10) & 1047552ULL) | ((x >> 20) & 1099510579200ULL); 28 | case 2: 29 | return (x & 1073741823ULL) | ((x >> 10) & 1125898833100800ULL); 30 | case 3075: 31 | return ((x >> 20) & 1023ULL) | ((x >> 40) & 1047552ULL); 32 | case 2051: 33 | return ((x >> 10) & 1048575ULL) | ((x >> 30) & 1072693248ULL); 34 | case 1027: 35 | return ((x >> 20) & 1023ULL) | ((x >> 30) & 1073740800ULL); 36 | case 3: 37 | return ((x >> 10) & 1048575ULL) | ((x >> 20) & 1099510579200ULL); 38 | case 1051648: 39 | return (x & 1023ULL) | ((x >> 20) & 1047552ULL) | ((x >> 30) & 1072693248ULL); 40 | case 1050624: 41 | return (x & 1048575ULL) | ((x >> 10) & 1072693248ULL) | ((x >> 20) & 1098437885952ULL); 42 | case 1049600: 43 | return (x & 1023ULL) | ((x >> 20) & 1099511626752ULL); 44 | case 1048576: 45 | return (x & 1048575ULL) | ((x >> 10) & 1125899905794048ULL); 46 | case 1051649: 47 | return ((x >> 30) & 1023ULL) | ((x >> 40) & 1047552ULL); 48 | case 1050625: 49 | return ((x >> 10) & 1023ULL) | ((x >> 20) & 1047552ULL) | ((x >> 30) & 1072693248ULL); 50 | case 1049601: 51 | return ((x >> 30) & 1073741823ULL); 52 | case 1048577: 53 | return ((x >> 10) & 1023ULL) | ((x >> 20) & 1099511626752ULL); 54 | case 1051650: 55 | return (x & 1023ULL) | ((x >> 40) & 1047552ULL); 56 | case 1050626: 57 | return (x & 1048575ULL) | ((x >> 30) & 1072693248ULL); 58 | case 1049602: 59 | return (x & 1023ULL) | ((x >> 30) & 1073740800ULL); 60 | case 1048578: 61 | return (x & 1048575ULL) | ((x >> 20) & 1099510579200ULL); 62 | case 1051651: 63 | return ((x >> 50) & 1023ULL); 64 | case 1050627: 65 | return ((x >> 10) & 1023ULL) | ((x >> 40) & 1047552ULL); 66 | case 1049603: 67 | return ((x >> 40) & 1048575ULL); 68 | case 1048579: 69 | return ((x >> 10) & 1023ULL) | ((x >> 30) & 1073740800ULL); 70 | case 2100224: 71 | return (x & 1023ULL) | ((x >> 10) & 1073740800ULL); 72 | case 2099200: 73 | return (x & 1099511627775ULL); 74 | case 2098176: 75 | return (x & 1023ULL) | ((x >> 10) & 1099511626752ULL); 76 | case 2097152: 77 | return (x & 1125899906842623ULL); 78 | case 2100225: 79 | return ((x >> 20) & 1048575ULL); 80 | case 2099201: 81 | return ((x >> 10) & 1073741823ULL); 82 | case 2098177: 83 | return ((x >> 20) & 1073741823ULL); 84 | case 2097153: 85 | return ((x >> 10) & 1099511627775ULL); 86 | case 2100226: 87 | return (x & 1023ULL) | ((x >> 10) & 1047552ULL); 88 | case 2099202: 89 | return (x & 1073741823ULL); 90 | case 2098178: 91 | return (x & 1023ULL) | ((x >> 10) & 1047552ULL) | ((x >> 20) & 1072693248ULL); 92 | case 2097154: 93 | return (x & 1073741823ULL) | ((x >> 10) & 1098437885952ULL); 94 | case 2100227: 95 | return ((x >> 20) & 1023ULL); 96 | case 2099203: 97 | return ((x >> 10) & 1048575ULL); 98 | case 2098179: 99 | return ((x >> 20) & 1023ULL) | ((x >> 30) & 1047552ULL); 100 | case 2097155: 101 | return ((x >> 10) & 1048575ULL) | ((x >> 20) & 1072693248ULL); 102 | case 3148800: 103 | return (x & 1023ULL) | ((x >> 20) & 1047552ULL); 104 | case 3147776: 105 | return (x & 1048575ULL) | ((x >> 10) & 1072693248ULL); 106 | case 3146752: 107 | return (x & 1023ULL) | ((x >> 20) & 1073740800ULL); 108 | case 3145728: 109 | return (x & 1048575ULL) | ((x >> 10) & 1099510579200ULL); 110 | case 3148801: 111 | return ((x >> 30) & 1023ULL); 112 | case 3147777: 113 | return ((x >> 10) & 1023ULL) | ((x >> 20) & 1047552ULL); 114 | case 3146753: 115 | return ((x >> 30) & 1048575ULL); 116 | case 3145729: 117 | return ((x >> 10) & 1023ULL) | ((x >> 20) & 1073740800ULL); 118 | case 3148802: 119 | return (x & 1023ULL); 120 | case 3147778: 121 | return (x & 1048575ULL); 122 | case 3146754: 123 | return (x & 1023ULL) | ((x >> 30) & 1047552ULL); 124 | case 3145730: 125 | return (x & 1048575ULL) | ((x >> 20) & 1072693248ULL); 126 | case 3148803: 127 | return (0ULL); 128 | case 3147779: 129 | return ((x >> 10) & 1023ULL); 130 | case 3146755: 131 | return ((x >> 40) & 1023ULL); 132 | case 3145731: 133 | return ((x >> 10) & 1023ULL) | ((x >> 30) & 1047552ULL); 134 | default: 135 | assert(false); 136 | return x; 137 | } 138 | } 139 | 140 | Bitboard deleteLine(Bitboard x, LineKey mask) { 141 | // 1073741823 = (1 << 30) - 1 142 | LineKey key = (mask >> 29) | (mask & 1073741823ULL); 143 | return deleteLine_(x, key); 144 | } 145 | 146 | Bitboard insertBlackLine_(Bitboard x, LineKey key) { 147 | switch (key) { 148 | case 3072: 149 | return (x & 1023ULL) | ((x & 1073740800ULL) << 10) | ((x & 1098437885952ULL) << 20) | 150 | (1124800396262400ULL); 151 | case 2048: 152 | return (x & 1099511627775ULL) | ((x & 1124800395214848ULL) << 10) | (1124800395214848ULL); 153 | case 1024: 154 | return (x & 1023ULL) | ((x & 1125899906841600ULL) << 10) | (1047552ULL); 155 | case 0: 156 | return (x); 157 | case 3073: 158 | return ((x & 1048575ULL) << 20) | ((x & 1072693248ULL) << 30) | (1124800396263423ULL); 159 | case 2049: 160 | return ((x & 1073741823ULL) << 10) | ((x & 1098437885952ULL) << 20) | (1124800395215871ULL); 161 | case 1025: 162 | return ((x & 1099511627775ULL) << 20) | (1048575ULL); 163 | case 1: 164 | return ((x & 1125899906842623ULL) << 10) | (1023ULL); 165 | case 3074: 166 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1072693248ULL) << 30) | (1125898834148352ULL); 167 | case 2050: 168 | return (x & 1073741823ULL) | ((x & 1098437885952ULL) << 20) | (1125898833100800ULL); 169 | case 1026: 170 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1099510579200ULL) << 20) | (1098438933504ULL); 171 | case 2: 172 | return (x & 1073741823ULL) | ((x & 1125898833100800ULL) << 10) | (1098437885952ULL); 173 | case 3075: 174 | return ((x & 1023ULL) << 20) | ((x & 1047552ULL) << 40) | (1125898834149375ULL); 175 | case 2051: 176 | return ((x & 1048575ULL) << 10) | ((x & 1072693248ULL) << 30) | (1125898833101823ULL); 177 | case 1027: 178 | return ((x & 1023ULL) << 20) | ((x & 1073740800ULL) << 30) | (1098438934527ULL); 179 | case 3: 180 | return ((x & 1048575ULL) << 10) | ((x & 1099510579200ULL) << 20) | (1098437886975ULL); 181 | case 1051648: 182 | return (x & 1023ULL) | ((x & 1047552ULL) << 20) | ((x & 1072693248ULL) << 30) | (1124801468955648ULL); 183 | case 1050624: 184 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 10) | ((x & 1098437885952ULL) << 20) | 185 | (1124801467908096ULL); 186 | case 1049600: 187 | return (x & 1023ULL) | ((x & 1099511626752ULL) << 20) | (1073740800ULL); 188 | case 1048576: 189 | return (x & 1048575ULL) | ((x & 1125899905794048ULL) << 10) | (1072693248ULL); 190 | case 1051649: 191 | return ((x & 1023ULL) << 30) | ((x & 1047552ULL) << 40) | (1124801468956671ULL); 192 | case 1050625: 193 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 20) | ((x & 1072693248ULL) << 30) | 194 | (1124801467909119ULL); 195 | case 1049601: 196 | return ((x & 1073741823ULL) << 30) | (1073741823ULL); 197 | case 1048577: 198 | return ((x & 1023ULL) << 10) | ((x & 1099511626752ULL) << 20) | (1072694271ULL); 199 | case 1051650: 200 | return (x & 1023ULL) | ((x & 1047552ULL) << 40) | (1125899906841600ULL); 201 | case 1050626: 202 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 30) | (1125899905794048ULL); 203 | case 1049602: 204 | return (x & 1023ULL) | ((x & 1073740800ULL) << 30) | (1099511626752ULL); 205 | case 1048578: 206 | return (x & 1048575ULL) | ((x & 1099510579200ULL) << 20) | (1099510579200ULL); 207 | case 1051651: 208 | return ((x & 1023ULL) << 50) | (1125899906842623ULL); 209 | case 1050627: 210 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 40) | (1125899905795071ULL); 211 | case 1049603: 212 | return ((x & 1048575ULL) << 40) | (1099511627775ULL); 213 | case 1048579: 214 | return ((x & 1023ULL) << 10) | ((x & 1073740800ULL) << 30) | (1099510580223ULL); 215 | case 2100224: 216 | return (x & 1023ULL) | ((x & 1073740800ULL) << 10) | (1152920405096266752ULL); 217 | case 2099200: 218 | return (x & 1099511627775ULL) | (1152920405095219200ULL); 219 | case 2098176: 220 | return (x & 1023ULL) | ((x & 1099511626752ULL) << 10) | (1151795604701051904ULL); 221 | case 2097152: 222 | return (x & 1125899906842623ULL) | (1151795604700004352ULL); 223 | case 2100225: 224 | return ((x & 1048575ULL) << 20) | (1152920405096267775ULL); 225 | case 2099201: 226 | return ((x & 1073741823ULL) << 10) | (1152920405095220223ULL); 227 | case 2098177: 228 | return ((x & 1073741823ULL) << 20) | (1151795604701052927ULL); 229 | case 2097153: 230 | return ((x & 1099511627775ULL) << 10) | (1151795604700005375ULL); 231 | case 2100226: 232 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | (1152921503534152704ULL); 233 | case 2099202: 234 | return (x & 1073741823ULL) | (1152921503533105152ULL); 235 | case 2098178: 236 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1072693248ULL) << 20) | 237 | (1151796703138937856ULL); 238 | case 2097154: 239 | return (x & 1073741823ULL) | ((x & 1098437885952ULL) << 10) | (1151796703137890304ULL); 240 | case 2100227: 241 | return ((x & 1023ULL) << 20) | (1152921503534153727ULL); 242 | case 2099203: 243 | return ((x & 1048575ULL) << 10) | (1152921503533106175ULL); 244 | case 2098179: 245 | return ((x & 1023ULL) << 20) | ((x & 1047552ULL) << 30) | (1151796703138938879ULL); 246 | case 2097155: 247 | return ((x & 1048575ULL) << 10) | ((x & 1072693248ULL) << 20) | (1151796703137891327ULL); 248 | case 3148800: 249 | return (x & 1023ULL) | ((x & 1047552ULL) << 20) | (1152920406168960000ULL); 250 | case 3147776: 251 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 10) | (1152920406167912448ULL); 252 | case 3146752: 253 | return (x & 1023ULL) | ((x & 1073740800ULL) << 20) | (1151795605773745152ULL); 254 | case 3145728: 255 | return (x & 1048575ULL) | ((x & 1099510579200ULL) << 10) | (1151795605772697600ULL); 256 | case 3148801: 257 | return ((x & 1023ULL) << 30) | (1152920406168961023ULL); 258 | case 3147777: 259 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 20) | (1152920406167913471ULL); 260 | case 3146753: 261 | return ((x & 1048575ULL) << 30) | (1151795605773746175ULL); 262 | case 3145729: 263 | return ((x & 1023ULL) << 10) | ((x & 1073740800ULL) << 20) | (1151795605772698623ULL); 264 | case 3148802: 265 | return (x & 1023ULL) | (1152921504606845952ULL); 266 | case 3147778: 267 | return (x & 1048575ULL) | (1152921504605798400ULL); 268 | case 3146754: 269 | return (x & 1023ULL) | ((x & 1047552ULL) << 30) | (1151796704211631104ULL); 270 | case 3145730: 271 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 20) | (1151796704210583552ULL); 272 | case 3148803: 273 | return (1152921504606846975ULL); 274 | case 3147779: 275 | return ((x & 1023ULL) << 10) | (1152921504605799423ULL); 276 | case 3146755: 277 | return ((x & 1023ULL) << 40) | (1151796704211632127ULL); 278 | case 3145731: 279 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 30) | (1151796704210584575ULL); 280 | default: 281 | assert(false); 282 | return x; 283 | } 284 | } 285 | 286 | Bitboard insertBlackLine(Bitboard x, LineKey mask) { 287 | // 1073741823 = (1 << 30) - 1 288 | LineKey key = (mask >> 29) | (mask & 1073741823ULL); 289 | return insertBlackLine_(x, key); 290 | } 291 | 292 | Bitboard insertWhiteLine_(Bitboard x, LineKey key) { 293 | switch (key) { 294 | case 3072: 295 | return (x & 1023ULL) | ((x & 1073740800ULL) << 10) | ((x & 1098437885952ULL) << 20); 296 | case 2048: 297 | return (x & 1099511627775ULL) | ((x & 1124800395214848ULL) << 10); 298 | case 1024: 299 | return (x & 1023ULL) | ((x & 1125899906841600ULL) << 10); 300 | case 0: 301 | return (x); 302 | case 3073: 303 | return ((x & 1048575ULL) << 20) | ((x & 1072693248ULL) << 30); 304 | case 2049: 305 | return ((x & 1073741823ULL) << 10) | ((x & 1098437885952ULL) << 20); 306 | case 1025: 307 | return ((x & 1099511627775ULL) << 20); 308 | case 1: 309 | return ((x & 1125899906842623ULL) << 10); 310 | case 3074: 311 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1072693248ULL) << 30); 312 | case 2050: 313 | return (x & 1073741823ULL) | ((x & 1098437885952ULL) << 20); 314 | case 1026: 315 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1099510579200ULL) << 20); 316 | case 2: 317 | return (x & 1073741823ULL) | ((x & 1125898833100800ULL) << 10); 318 | case 3075: 319 | return ((x & 1023ULL) << 20) | ((x & 1047552ULL) << 40); 320 | case 2051: 321 | return ((x & 1048575ULL) << 10) | ((x & 1072693248ULL) << 30); 322 | case 1027: 323 | return ((x & 1023ULL) << 20) | ((x & 1073740800ULL) << 30); 324 | case 3: 325 | return ((x & 1048575ULL) << 10) | ((x & 1099510579200ULL) << 20); 326 | case 1051648: 327 | return (x & 1023ULL) | ((x & 1047552ULL) << 20) | ((x & 1072693248ULL) << 30); 328 | case 1050624: 329 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 10) | ((x & 1098437885952ULL) << 20); 330 | case 1049600: 331 | return (x & 1023ULL) | ((x & 1099511626752ULL) << 20); 332 | case 1048576: 333 | return (x & 1048575ULL) | ((x & 1125899905794048ULL) << 10); 334 | case 1051649: 335 | return ((x & 1023ULL) << 30) | ((x & 1047552ULL) << 40); 336 | case 1050625: 337 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 20) | ((x & 1072693248ULL) << 30); 338 | case 1049601: 339 | return ((x & 1073741823ULL) << 30); 340 | case 1048577: 341 | return ((x & 1023ULL) << 10) | ((x & 1099511626752ULL) << 20); 342 | case 1051650: 343 | return (x & 1023ULL) | ((x & 1047552ULL) << 40); 344 | case 1050626: 345 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 30); 346 | case 1049602: 347 | return (x & 1023ULL) | ((x & 1073740800ULL) << 30); 348 | case 1048578: 349 | return (x & 1048575ULL) | ((x & 1099510579200ULL) << 20); 350 | case 1051651: 351 | return ((x & 1023ULL) << 50); 352 | case 1050627: 353 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 40); 354 | case 1049603: 355 | return ((x & 1048575ULL) << 40); 356 | case 1048579: 357 | return ((x & 1023ULL) << 10) | ((x & 1073740800ULL) << 30); 358 | case 2100224: 359 | return (x & 1023ULL) | ((x & 1073740800ULL) << 10); 360 | case 2099200: 361 | return (x & 1099511627775ULL); 362 | case 2098176: 363 | return (x & 1023ULL) | ((x & 1099511626752ULL) << 10); 364 | case 2097152: 365 | return (x & 1125899906842623ULL); 366 | case 2100225: 367 | return ((x & 1048575ULL) << 20); 368 | case 2099201: 369 | return ((x & 1073741823ULL) << 10); 370 | case 2098177: 371 | return ((x & 1073741823ULL) << 20); 372 | case 2097153: 373 | return ((x & 1099511627775ULL) << 10); 374 | case 2100226: 375 | return (x & 1023ULL) | ((x & 1047552ULL) << 10); 376 | case 2099202: 377 | return (x & 1073741823ULL); 378 | case 2098178: 379 | return (x & 1023ULL) | ((x & 1047552ULL) << 10) | ((x & 1072693248ULL) << 20); 380 | case 2097154: 381 | return (x & 1073741823ULL) | ((x & 1098437885952ULL) << 10); 382 | case 2100227: 383 | return ((x & 1023ULL) << 20); 384 | case 2099203: 385 | return ((x & 1048575ULL) << 10); 386 | case 2098179: 387 | return ((x & 1023ULL) << 20) | ((x & 1047552ULL) << 30); 388 | case 2097155: 389 | return ((x & 1048575ULL) << 10) | ((x & 1072693248ULL) << 20); 390 | case 3148800: 391 | return (x & 1023ULL) | ((x & 1047552ULL) << 20); 392 | case 3147776: 393 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 10); 394 | case 3146752: 395 | return (x & 1023ULL) | ((x & 1073740800ULL) << 20); 396 | case 3145728: 397 | return (x & 1048575ULL) | ((x & 1099510579200ULL) << 10); 398 | case 3148801: 399 | return ((x & 1023ULL) << 30); 400 | case 3147777: 401 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 20); 402 | case 3146753: 403 | return ((x & 1048575ULL) << 30); 404 | case 3145729: 405 | return ((x & 1023ULL) << 10) | ((x & 1073740800ULL) << 20); 406 | case 3148802: 407 | return (x & 1023ULL); 408 | case 3147778: 409 | return (x & 1048575ULL); 410 | case 3146754: 411 | return (x & 1023ULL) | ((x & 1047552ULL) << 30); 412 | case 3145730: 413 | return (x & 1048575ULL) | ((x & 1072693248ULL) << 20); 414 | case 3148803: 415 | return (0ULL); 416 | case 3147779: 417 | return ((x & 1023ULL) << 10); 418 | case 3146755: 419 | return ((x & 1023ULL) << 40); 420 | case 3145731: 421 | return ((x & 1023ULL) << 10) | ((x & 1047552ULL) << 30); 422 | default: 423 | assert(false); 424 | return x; 425 | } 426 | } 427 | 428 | Bitboard insertWhiteLine(Bitboard x, LineKey mask) { 429 | // 1073741823 = (1 << 30) - 1 430 | LineKey key = (mask >> 29) | (mask & 1073741823ULL); 431 | return insertWhiteLine_(x, key); 432 | } 433 | 434 | const Bitboard kColumnOneLineBelowY[]{ 435 | 0ULL, 0x1ULL, 0x401ULL, 0x100401ULL, 0x40100401ULL, 0x10040100401ULL, 0x4010040100401ULL 436 | }; 437 | 438 | Bitboard getColumnOneLineBelowY(int maxY) { 439 | assert(0 <= maxY && maxY <= 6); 440 | return kColumnOneLineBelowY[maxY]; 441 | } 442 | 443 | // Returns true if there is no wall (no gap) between the x column and its left column 444 | bool isWallBetweenLeft(int x, int maxY, Bitboard board) { 445 | assert(1 <= x && x < 10); 446 | 447 | Bitboard maskHigh = getColumnOneLineBelowY(maxY); 448 | Bitboard reverseXBoardHigh = ~board; 449 | Bitboard columnHigh = maskHigh << x; 450 | Bitboard rightHigh = reverseXBoardHigh & columnHigh; 451 | Bitboard leftHigh = reverseXBoardHigh & (columnHigh >> 1); 452 | return ((leftHigh << 1) & rightHigh) == 0L; 453 | } 454 | 455 | int bitCount(uint64_t b) { 456 | b -= (b >> 1) & 0x5555555555555555ULL; 457 | b = ((b >> 2) & 0x3333333333333333ULL) + (b & 0x3333333333333333ULL); 458 | b = ((b >> 4) + b) & 0x0F0F0F0F0F0F0F0FULL; 459 | return static_cast((b * 0x0101010101010101ULL) >> 56); 460 | } 461 | 462 | // 0b000000 => 0 463 | // 0b000001 => 1 464 | // 0b000010 => 2 465 | // ... 466 | // 0b100000 => 6 467 | int mostSignificantDigit(uint64_t b) { 468 | b |= b >> 1U; 469 | b |= b >> 2U; 470 | b |= b >> 4U; 471 | b |= b >> 8U; 472 | b |= b >> 16U; 473 | b |= b >> 32U; 474 | return bitCount(b); 475 | } 476 | 477 | uint64_t fillVertical(uint64_t b) { 478 | b |= b >> 10U; 479 | b |= b >> 10U; 480 | b |= b >> 30U; 481 | return b; 482 | } 483 | } --------------------------------------------------------------------------------