├── 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 | [](https://www.nuget.org/packages/PerfectClearNET/)
3 | [](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 | }
--------------------------------------------------------------------------------