Chess Challenge Project
This commit is contained in:
parent
29e522df30
commit
a268bc3cad
60 changed files with 7316 additions and 0 deletions
6
.editorconfig
Normal file
6
.editorconfig
Normal file
|
@ -0,0 +1,6 @@
|
|||
[*.cs]
|
||||
|
||||
# CS8602: Dereference of a possibly null reference.
|
||||
dotnet_diagnostic.CS8602.severity = silent
|
||||
# CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
dotnet_diagnostic.CS8618.severity = silent
|
30
Chess-Challenge.sln
Normal file
30
Chess-Challenge.sln
Normal file
|
@ -0,0 +1,30 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.3.32901.215
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Chess-Challenge", "Chess-Challenge\Chess-Challenge.csproj", "{2803E64F-15AC-430B-A5A2-69C00EA7505F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9FC5D849-F0B6-4C89-A541-C36A341AE67C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2803E64F-15AC-430B-A5A2-69C00EA7505F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2803E64F-15AC-430B-A5A2-69C00EA7505F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2803E64F-15AC-430B-A5A2-69C00EA7505F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2803E64F-15AC-430B-A5A2-69C00EA7505F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {DC54E848-1F03-426F-9B00-9CBC391363F6}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
60
Chess-Challenge/Chess-Challenge.csproj
Normal file
60
Chess-Challenge/Chess-Challenge.csproj
Normal file
|
@ -0,0 +1,60 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Chess_Challenge</RootNamespace>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Raylib-cs" Version="4.5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.CodeAnalysis">
|
||||
<HintPath>src\Token Counter\Microsoft.CodeAnalysis.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CodeAnalysis.CSharp">
|
||||
<HintPath>src\Token Counter\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="src\My Bot\MyBot.cs">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
<Compile Update="src\Framework\Application\UI\BotBrainCapacityUI.cs">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
<Compile Update="src\Evil Bot\EvilBot.cs">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="resources\Fens.txt">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="resources\Fonts\OPENSANS-SEMIBOLD.TTF">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="resources\Fonts\sdf.fs">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="resources\Pieces.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="src\Framework\Chess\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
500
Chess-Challenge/resources/Fens.txt
Normal file
500
Chess-Challenge/resources/Fens.txt
Normal file
|
@ -0,0 +1,500 @@
|
|||
rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1
|
||||
r1bq1rk1/pp3ppp/2n1p3/3n4/1b1P4/2N2N2/PP2BPPP/R1BQ1RK1 w - - 0 10
|
||||
rn1q1rk1/pp2b1pp/3pbn2/4p3/8/1N1BB3/PPPN1PPP/R2Q1RK1 w - - 8 11
|
||||
1rbq1rk1/p3ppbp/3p1np1/2pP4/1nP5/RP3NP1/1BQNPPBP/4K2R w K - 1 13
|
||||
r1b2rk1/pppp1pp1/2n2q1p/8/1bP5/2N2N2/PP2PPPP/R2QKB1R w KQ - 0 9
|
||||
r2q1rk1/bppb1pp1/p2p2np/2PPp3/1P2P1n1/P3BN2/2Q1BPPP/RN3RK1 w - - 2 15
|
||||
rnbq1rk1/pp2b1pp/2p2n2/3p1p2/4p3/3PP1PP/PPPNNPB1/R1BQ1RK1 w - - 5 9
|
||||
rnbqk2r/5ppp/p2bpn2/1p6/2BP4/7P/PP2NPP1/RNBQ1RK1 w kq - 0 10
|
||||
rn2kb1r/1bqp1ppp/p3pn2/1p6/3NP3/2P1BB2/PP3PPP/RN1QK2R w KQkq - 6 9
|
||||
r1b1k2r/pp2bp1p/1qn1p3/2ppPp2/5P2/2PP1N1P/PP4P1/RNBQ1RK1 w kq - 1 11
|
||||
r1bk1r2/ppp3pp/3b1n2/4p1B1/2B1P3/8/PPP3PP/RN3RK1 w - - 4 12
|
||||
r1bq1rk1/4ppbp/p1np1np1/1pp5/4P3/P1NP1N1P/1PPBBPP1/R2Q1RK1 w - - 0 10
|
||||
2rq1rk1/p2nbpp1/1p2p2p/3n4/3P1B2/5NP1/PP3PBP/2RQR1K1 w - - 3 16
|
||||
r3k2r/p1qn1ppp/2pbpnb1/1p6/3P2P1/1BN2P2/PPPBNQ1P/R3K2R w KQkq - 5 14
|
||||
r3k2r/pppqbpp1/2np1n2/5b2/2PP4/2N1BBPp/PP1QNP1P/R3K2R w KQkq - 6 12
|
||||
r4rk1/pppn2b1/4q1p1/4ppBp/4Q2P/2P2NP1/PP2PP2/R3K2R w Q - 0 15
|
||||
r2qk2r/pp1bb1pp/2pp1n2/8/8/2N5/PPPP1PPP/R1BQK1NR w KQkq - 0 9
|
||||
r2q1rk1/2p3pp/p3pbn1/2Pppb2/1P6/PNP1B3/4NPPP/R2Q1RK1 w - - 3 15
|
||||
r1b1r1k1/ppqn1pbp/2pp1np1/4p3/1PP5/P2PPN2/1B1NBPPP/R2Q1RK1 w - - 1 11
|
||||
rnb2rk1/p3qpb1/1pp2npp/4p3/1PB1P3/P1N1BN2/2P2PPP/R2QR1K1 w - - 0 12
|
||||
r2r2k1/pp2bpp1/2q1pn1p/6B1/8/2P5/PP2QPPP/1B1R1RK1 w - - 0 17
|
||||
r4rk1/pp1nqpp1/2pbpn1p/3p1b2/2PP4/1PN1PN2/PB2BPPP/R2QR1K1 w - - 6 11
|
||||
r1bqk2r/ppp2pbp/3p1np1/3P2B1/2BnP3/5Q2/PP3PPP/RN2K1NR w KQkq - 1 9
|
||||
r1bq1rk1/pp2bppp/3p1n2/4p3/4PB2/2N3P1/PPPQ1PBP/R3K2R w KQ - 0 11
|
||||
r2qk2r/ppp1ppbp/1n2b1pn/4P3/3P4/2P1BN2/PP4PP/RN1QKB1R w KQkq - 1 9
|
||||
r2q1rk1/p1p1b1pp/2p5/3p1p2/3PnB2/5Q2/PPP2PPP/RN2R1K1 w - - 0 13
|
||||
r2q1k1r/pp4p1/2nb1p2/2pN2B1/6bp/3P1N2/PPP1QPPP/R4RK1 w - - 0 17
|
||||
r2q1rk1/pp2bppp/3ppn2/2p5/2BnP3/P1NQ3P/1PPP1PP1/R1B1R1K1 w - - 7 11
|
||||
rnbqk2r/pp3pp1/4p2p/2bpP3/8/2NQ1PP1/PPP1N2P/2KR1B1R w kq - 0 13
|
||||
rnbqk2r/pp3ppp/5n2/3P4/4p3/2PB4/P4PPP/R1BQK1NR w KQkq - 0 10
|
||||
r2q1rk1/pp2npbp/2npp1p1/2p5/2P1PPb1/2NPBN2/PP2B1PP/R2Q1RK1 w - - 4 10
|
||||
r1bnkbnr/1p2pp1p/p5p1/4N3/8/8/PPP2PPP/RNB1KB1R w KQkq - 0 9
|
||||
r2qkb1r/pp3ppp/2n1p3/3p4/3Pn1b1/2N1PN2/PP2BPPP/R1BQ1RK1 w kq - 2 9
|
||||
5rk1/1p2ppbp/3p1np1/nP3b2/5P2/2N1PN2/3PB1PP/B4RK1 w - - 1 17
|
||||
r2qkb1r/pp2pppp/2p2n2/5b2/3P3Q/5N2/PPP2PPP/R1B1KB1R w KQkq - 5 9
|
||||
r1b2rk1/ppq1bppp/2nppn2/2p5/4P3/1P1P1NP1/P1PN1PBP/R1BQ1RK1 w - - 1 9
|
||||
r1bq1rk1/1p2bppp/p1nppn2/8/3NPP2/1BN5/PPP3PP/R1BQ1RK1 w - - 1 10
|
||||
rnbq1rk1/ppp3bp/3p2p1/4p3/2P1Pn1N/2NP1BP1/PP5P/R1BQ1RK1 w - - 1 12
|
||||
r1bqk2r/pp1nbpp1/4p2p/3p4/2pP4/2PBPNP1/PP1N1PP1/R2QK2R w KQkq - 0 11
|
||||
r1b1k2r/1pqpbppp/p1n1pn2/8/P3P3/1NNB4/1PP2PPP/R1BQK2R w KQkq - 1 9
|
||||
r2q1rk1/pb1pbppp/1p2pn2/2n3B1/2P5/2N1PN2/PP2BPPP/R2Q1RK1 w - - 2 10
|
||||
r2q1rk1/pb1nbppp/1p2pn2/2p5/3P4/2NBPN2/PP1B1PPP/2RQR1K1 w - - 0 12
|
||||
r1bq1rk1/ppp2pp1/1bnp1n1p/4p3/1PB1P3/P1NP1N2/2P2PPP/R1BQ1RK1 w - - 0 9
|
||||
r2qkb1r/p2nnp2/bp2p1p1/2ppP2p/3P1P1P/2P2NP1/PP4B1/RNBQR2K w kq - 3 13
|
||||
r1b2rk1/pp2q1p1/5n1p/3p4/3Pp3/P7/1P1N1PPP/R2QRBK1 w - - 1 18
|
||||
r3qrk1/3bb2p/p1n1pp2/1ppp1p2/3P3B/2P1PN2/PP1NQPPP/R4RK1 w - - 0 18
|
||||
r1bq1rk1/1pp2ppp/2np1n2/2b1p1B1/p1B1P3/P1PP1Q2/1P1N1PPP/R3K1NR w KQ - 0 9
|
||||
r2qr1k1/1pp2p2/p2b1n1p/3p2p1/1P1P4/P1N1PQ1P/1B3PP1/2R2RK1 w - - 0 17
|
||||
r2q1rk1/pp2bppp/3pbn2/4n3/4PB2/1NNB4/PPP3PP/R2Q1R1K w - - 1 12
|
||||
rnbqk2r/4bppp/2p1pn2/p2p4/2BP4/1P2PN2/P4PPP/RNBQK2R w KQkq - 1 9
|
||||
r2qk2r/pp2bpp1/2p1pnp1/8/2BP1Q2/2P3NP/PP3PP1/R3K2R w KQkq - 3 15
|
||||
r2qk1nr/ppp2ppp/4b3/2b1p3/4P3/3B4/PPP2PPP/RNBQ1RK1 w kq - 2 9
|
||||
rn1q1rk1/pbp3pp/1p1pp3/5p2/2PP4/2Q1PN2/PP2BPPP/2KR3R w - - 0 12
|
||||
r1bqkb1r/p2p1ppp/2p5/4P3/4n3/8/PP3PPP/RNBQKB1R w KQkq - 1 9
|
||||
r1bq1rk1/pp1nn1pp/1bpBp3/3p1p2/1P1P2QN/2P1P2P/P4PP1/RN2KB1R w KQ - 0 12
|
||||
r4rk1/pp3ppp/1np1pq2/5b2/8/3P1N2/PPPQBPPP/3R1RK1 w - - 0 15
|
||||
r3kb1r/pb2q2p/1pn2pp1/2p1p3/4N3/1P1P1B2/P1P2PPP/R1BQ1RK1 w kq - 4 14
|
||||
2kr3r/ppq2pp1/2pb1n1p/4p3/2P5/2N1PB1P/PP3PP1/R2Q1RK1 w - - 1 14
|
||||
r2q1rk1/ppp1bppp/2np4/3n3b/3P4/P2B1N1P/1PP2PP1/RNBQ1RK1 w - - 1 10
|
||||
r3k2r/ppqn2pp/2pbpp2/3p4/3PnPN1/2P1P3/PP2B1PP/R1BQ1RK1 w kq - 2 12
|
||||
r2q1rk1/pbpp2pp/1pn1pn2/5p2/2PP4/P1B1PN1P/1P3PP1/R2QKB1R w KQ - 1 10
|
||||
r1bq1rk1/pp2npbp/2n1p1p1/2pp4/2P1PP2/2NP1NP1/PP4BP/R1BQK2R w KQ - 0 9
|
||||
r1bqk2r/p2nppb1/1npp2p1/1p4Pp/3PP3/4BPN1/PPPQ3P/R3KBNR w KQkq - 1 11
|
||||
r3k2r/pp1nbppp/2p1bn2/3pN3/3P1B2/2N5/PPP2PPP/2KR1B1R w kq - 6 11
|
||||
2r1k2r/1p2bppp/p1n1pn2/2pq4/Q5b1/N1PPBN2/PP2BPPP/R4RK1 w k - 0 11
|
||||
r3r1k1/p2nppbp/1qp3p1/3p1b2/Q2P1B2/2N3PP/PP2PPB1/R4RK1 w - - 3 14
|
||||
r1bq1rk1/ppp1b1p1/2np1n1p/1B2p1B1/4P3/2N2N1P/PPP2PP1/R2Q1RK1 w - - 0 10
|
||||
rn2k1nr/pp2qppp/2pbp3/3p1b2/3P4/2N1PP1N/PP1BBQPP/R3K2R w KQkq - 2 10
|
||||
r2q1rk1/ppp2p2/2np1n1p/2bNp1p1/2B1P1b1/3P1NB1/PPP2PPP/R2QK2R w KQ - 4 10
|
||||
2kr1b1r/pp1q1pp1/2n2n2/2pp1P1p/7P/2NPPQ2/PPP2P2/R1B1K1NR w KQ - 0 12
|
||||
r3k2r/pp1bbp2/1q1p1n1p/4pP2/3p2P1/3P3P/PPP1N1B1/R1BQ1RK1 w kq - 1 15
|
||||
r3k2r/1p3ppp/p1nqpn2/3p4/P1pPb2N/2P1P2P/1P1NBPP1/R2QK2R w KQkq - 4 16
|
||||
r2q1rk1/pp2npb1/2p2np1/3p3p/PP1Pp3/2P1P1PP/3N1PB1/R1BQ1RK1 w - - 0 14
|
||||
r1b1k2r/1pqnbppp/p2ppn2/8/4P3/3B1NN1/PPPB1PPP/R2Q1RK1 w kq - 4 11
|
||||
r4rk1/pp1nqppp/2p1p1b1/3pPn2/1P1P4/2P2N2/P2NBPPP/R2Q1RK1 w - - 1 12
|
||||
r2qr1k1/p1p1ppb1/1p3np1/7p/3P3P/B1PQ2PN/P1P2PK1/R3R3 w - - 10 17
|
||||
r4rk1/1ppq1ppp/p1np1n2/2b5/4P1b1/2NB1N2/PPP2PPP/R1BQR1K1 w - - 4 12
|
||||
r1b2rk1/pp1n1pb1/1q1p2pp/2p5/2PpP2B/3B1P2/PP2N1PP/1R1Q1RK1 w - - 4 14
|
||||
2rqkb1r/1bpn1ppp/pp1p1n2/4p3/3PP3/2N1BN2/PPP1BPPP/R2Q1RK1 w k - 0 9
|
||||
r2q1rk1/4bppp/p1p1pn2/2Pp1bB1/3P4/2N2N2/PP3PPP/R2QK2R w KQ - 0 12
|
||||
r2qkb1r/pb3ppp/p1n1pn2/2p5/2Np4/1P1P4/1BP1PPPP/R2QKBNR w KQkq - 2 9
|
||||
2rq1rk1/pb1n1pp1/1p2pn1p/2p5/2QP3B/P3PN2/1P2BPPP/R4RK1 w - - 0 14
|
||||
2kr1bbr/pppq4/2np2pp/3nppN1/8/3PP3/PPPBBPPP/R2QRNK1 w - - 0 15
|
||||
r2qk3/1ppb1p2/p1np1p2/2b1p3/2B1P3/2PP1P2/PP2N3/RN2K2Q w Qq - 1 14
|
||||
rnbqk2r/4bppp/p3pn2/1p6/3N4/2NBP3/PP3PPP/R1BQ1RK1 w kq - 0 10
|
||||
1rb2rk1/p1q1ppbp/2np1np1/1pp5/4PPP1/3P3P/PPPNN1B1/R1BQ1RK1 w - - 0 11
|
||||
r1bq1rk1/p1pn1ppp/1p1p1n2/3Pp3/2P1P3/2PQ1N1P/P3BPP1/R1B1K2R w KQ - 3 11
|
||||
r2q1rk1/pp3pbp/2np1np1/2p1p3/4P1b1/2PP1NP1/PP1N1PBP/R1BQR1K1 w - - 1 10
|
||||
r2qkb1r/pp1n1ppp/4pn2/3p4/3P1P2/2PQ1N2/PP4PP/RNB1K2R w KQkq - 1 9
|
||||
r2r2k1/4bppp/p1n1p3/1p1bP3/2p2P2/P1P1BNP1/1P4BP/R4RK1 w - - 1 18
|
||||
r2q1rk1/pbp1bppp/1p1p1n2/4p3/2BP4/2N1P3/PPP1QPPP/R1B2RK1 w - - 0 11
|
||||
r4rk1/ppp1qppp/1bnp1n2/4p1B1/2P1P3/2PP1N1P/P4PP1/RN1Q1RK1 w - - 1 11
|
||||
r2q1rk1/1p2b1pp/p1p1p3/3nPp1b/8/P3P2P/BPQN1PP1/R1B2RK1 w - - 0 15
|
||||
1rbbk2r/2p2ppp/p1n2n2/1p2p3/4P3/1B3N2/PPP2PPP/RNB1R1K1 w k - 2 11
|
||||
r2q1rk1/1pp2ppp/2nb1n2/p2pp3/6b1/1P1PPN2/PBPNBPPP/R2Q1RK1 w - - 0 9
|
||||
r2q1rk1/pp1nbppp/2p1p1b1/3pP3/1P1P4/P1NPBN1P/3Q1PP1/R4RK1 w - - 1 15
|
||||
r1b1qrk1/bpp2pp1/3p1nnp/pP2p3/P1B1P3/1QPP1N2/5PPP/RNB2R1K w - - 2 14
|
||||
1r1qkb1r/N1pn1ppp/2Q1pn2/8/3P2b1/8/PPP3PP/R1B1KBNR w KQk - 0 10
|
||||
r2qk2r/ppp2pbp/2b2np1/n2N4/8/P3PQ2/BP2NPPP/R1B1K2R w KQkq - 1 12
|
||||
r2q1rk1/1b2bppp/p1np1n2/1p2pPB1/4P3/1BN2N2/PPP3PP/R2Q1RK1 w - - 2 13
|
||||
r1b2rk1/pp2qpp1/2p2n1p/8/2BN4/4P3/PP3PPP/2RQ1RK1 w - - 1 15
|
||||
1r2k2r/pp1nnpp1/2p1p1bp/2PpP3/3P1PPP/2N5/PP4B1/2KR2NR w k - 1 17
|
||||
r1bq1rk1/ppp2ppp/2np4/4p1b1/2PPP3/PQN1PN2/1P2B1PP/R4RK1 w - - 1 12
|
||||
r3kbnr/pp1qpppp/2n5/2pp4/6b1/3P1NP1/PPP1PP1P/RNBQR1KB w kq - 11 10
|
||||
rn1qk2r/2p1n1pp/p4p2/1p1Pp3/1b1P4/1PNBBP2/P1P2P1P/R2QK2R w KQkq - 1 12
|
||||
r1bq1rk1/1p3ppp/p1np1b2/2p1p3/2B1P3/P1NP1N2/1PP2PPP/R2Q1RK1 w - - 0 10
|
||||
r1b1rbk1/1pqn1ppp/2p2n2/p3p3/2P1P3/1PN2N1P/PBQ1BPP1/R4RK1 w - - 4 13
|
||||
r1bqk1nr/p5pp/5p2/4p3/4p3/2P3B1/P4PPP/R2QKB1R w KQkq - 0 13
|
||||
r1bq1rk1/2p1bppp/p1n2n2/1p1pp3/4P3/1BPP1N2/PP3PPP/RNBQ1RK1 w - - 0 9
|
||||
rnbqnrk1/pp3ppp/2pp4/4p1b1/4P1P1/2PP1Q1P/PP1NBP2/R1B1K1NR w KQ - 0 9
|
||||
rnbqk2r/p4ppp/1pp1pn2/2Pp4/3P4/3BPN2/PP3PPP/RN1QK2R w KQkq - 0 9
|
||||
r2qk2r/pp1n1pp1/2pb1p1p/3p4/3P2b1/2N1PN2/PPP1BPPP/R2Q1RK1 w kq - 5 9
|
||||
rn3rk1/ppb2ppp/2p2q2/3p3b/1P1P4/P1N1PN1P/4BPP1/R2QK2R w KQ - 1 14
|
||||
r2q1rk1/1b3ppp/4pn2/p1b5/Pp2P3/3B1NN1/1P3PPP/R2Q1RK1 w - - 0 16
|
||||
r2q1rk1/pp2b1pp/2nppn2/2p5/4P3/4BN2/PPPN1PPP/R2Q1RK1 w - - 0 11
|
||||
r2qk2r/ppp1bppp/2np1nb1/8/4PBP1/3P1N1P/PPP1BK2/RN1Q3R w kq - 1 12
|
||||
r4rk1/pbq1bppp/1pn1pn2/2pp4/3P4/1PP1PNP1/PB1N1PBP/R2Q1RK1 w - - 2 11
|
||||
q4rk1/1bp1bppp/1n2pn2/rp6/3P4/P3PN2/BBQN1PPP/2R2RK1 w - - 2 15
|
||||
r1bq1rk1/1p3ppn/2pp3p/p1nPp1b1/P1P1P3/2N3NP/1PB2PP1/R1BQ1RK1 w - - 3 13
|
||||
r3kb1r/ppp2pp1/3pb2p/4p3/3nP2N/3P2P1/PPP2PBP/RN2K2R w KQkq - 1 11
|
||||
r2qk2r/ppp2pp1/1nnbp2p/5b2/5B2/1BNP1N2/PPP2PPP/R2Q1RK1 w kq - 2 10
|
||||
2r1qrk1/1p2bppp/p3pn2/1b1pN1B1/3P4/P1N4P/1PP2PP1/2RQ1RK1 w - - 1 14
|
||||
rn1r2k1/pp3pp1/2pb1qnp/P3p3/4P1b1/2P2NN1/1P2BPPP/R1BQ1RK1 w - - 3 14
|
||||
rn1qr1k1/pp3pbp/2p2np1/3p4/3P2b1/2P2NP1/PP2NPBP/R1BQ1RK1 w - - 1 11
|
||||
rn1qk2r/pp2ppb1/2pp2p1/4PnPp/3PNP2/8/PPP1N2P/R1BQ1RK1 w kq - 0 12
|
||||
r2qk3/ppp2p1p/3p1pr1/3Pp3/1bBn4/3P2NP/P1P1QPP1/R4RK1 w q - 2 16
|
||||
rnb1r1k1/pppp1ppp/1b3q2/3P4/3Pp3/PN6/1PP1QPPP/R1B1KB1R w KQ - 1 12
|
||||
r2qk2r/pppb2pp/2n2n2/2b1pp2/2B5/3P2N1/PPP2PPP/RNBQ1RK1 w kq - 1 9
|
||||
r1bq1rk1/ppb2ppp/2pp1n2/4n3/1PP5/P1NBP3/1B1QNPPP/R4RK1 w - - 4 13
|
||||
r1bq1rk1/ppp2pbp/3p1np1/2nPp3/2P1P3/2N2N2/PP2BPPP/R1BQ1RK1 w - - 3 9
|
||||
r4rk1/ppb2pp1/2n2q1p/4p3/2B1b3/4BN1P/PPP1QPP1/R2R2K1 w - - 2 15
|
||||
r2qk2r/ppp1bppp/8/4n3/N2Pp1n1/8/PPP1N1PP/R1BQK2R w KQkq - 1 12
|
||||
rnbqk2r/pp2bp1p/5npP/3p2B1/3Pp3/2N5/PPP2PP1/R2QKBNR w KQkq - 2 9
|
||||
r2q1rk1/pb2bppp/1pn2n2/2pp4/3P4/1P2PN2/PB1NBPPP/R2Q1RK1 w - - 0 11
|
||||
r1b1kb1r/2qn1ppp/p2ppn2/1p4B1/3NPP2/2N2Q2/PPP3PP/2KR1B1R w kq - 0 10
|
||||
rnbq1rk1/1p3pbp/p2p1np1/2pPp3/P1P2B2/2N2N2/1PQ1PPPP/R3KB1R w KQ e6 0 9
|
||||
r2qkb1r/ppp2ppp/2n1p3/3p1b2/3P1Pn1/N1PBPN2/PP4PP/R1BQ1RK1 w kq - 5 9
|
||||
rnbqk2r/ppp2ppp/3p4/8/4P3/2PPnN2/PP4PP/RN1QKB1R w KQkq - 0 9
|
||||
2r2rk1/p4ppp/2bqpn2/1p1p4/3P2P1/2N2P1P/PP1QP1B1/2R2RK1 w - - 0 18
|
||||
2rr2k1/1pq2ppp/p1nbbn2/4p3/8/2N1PNBP/PPQ1BPP1/R4RK1 w - - 6 16
|
||||
rn1qbrk1/pp4pp/2pbpn2/3p1p2/2PP4/1P1BPN2/PBQN1PPP/R3K2R w KQ - 3 10
|
||||
r1b1k2r/ppp1nppp/3b4/3p4/3P4/1P2PN2/P4PPP/RNB2RK1 w kq - 1 11
|
||||
2rq1rk1/pp2bppp/4pn2/3n4/3P4/2N1BBP1/PP3P1P/R2QR1K1 w - - 2 14
|
||||
r1b1k2r/ppp1qppp/3p1nn1/3Pp3/4P3/2NB4/PPP1NPPP/R2QK2R w KQkq - 2 9
|
||||
3r1rk1/1pp1bppp/p1n2n2/8/1P4b1/P4N2/1BPNBPPP/R4RK1 w - - 2 13
|
||||
rn2k2r/pp3pb1/3qp1p1/2p5/3PpPPp/2P1P2P/PP2Q1K1/RNB2R2 w kq - 0 15
|
||||
r1bqk2r/pppp1ppp/1b6/3nP3/3P4/2P2N2/P3NPPP/R2QKB1R w KQkq - 1 12
|
||||
r1bqr1k1/2p1bppp/p1pp1n2/4p3/4P3/1PNP1N2/PBP2PPP/R2Q1RK1 w - - 3 10
|
||||
r4rk1/pp3ppp/4bq2/2p2n2/5B2/1P1P1NQ1/1PP2RPP/R5K1 w - - 0 17
|
||||
r2q1rk1/1pp2ppp/p1npbn2/2b1p1B1/2B1P3/2NP1N1P/PPP2PP1/R2Q1RK1 w - - 0 9
|
||||
rn1qnrk1/1bpp1pp1/1p2p2p/8/3P4/2P1PN2/P2BBPPP/R2QK2R w KQ - 0 11
|
||||
rnbqk1nb/ppp2p2/8/3pP1p1/2BP1p2/5N2/PPP3P1/RNBQK3 w Qq - 0 10
|
||||
r1bqk2r/pp1nbppp/2p2n2/P2p4/3Pp3/1N2P3/1PP1BPPP/RNBQ1RK1 w kq - 1 10
|
||||
r3k2r/pbqnbppp/1p1ppn2/2p5/4P3/2PPBN1P/PP1NBPP1/R2QK2R w KQkq - 4 10
|
||||
1nbq1rk1/1p1pbppp/2p1pn2/1P6/2P5/4P3/3P1PPP/BN1QKBNR w K - 0 9
|
||||
r1b2r2/pp1nqpkp/2p2np1/4p3/4P3/2NB1N2/PPPQ1PPP/2KR3R w - - 0 12
|
||||
rn3rk1/ppp1bpp1/5n1p/3p4/3P2b1/3B1N2/PPP2PPP/RNB1K2R w KQ - 2 12
|
||||
rnb2rk1/pp3ppp/1q1p4/2pPp1b1/2P1P3/1PNB4/P3NPPP/R2QK2R w KQ - 3 11
|
||||
r1bqkb1r/p1n1p1p1/2pn4/1p1p1p1p/1P1P1P1P/2P5/P1N1PNP1/R1BQKB1R w KQkq - 1 12
|
||||
rn1k2nr/pp4pp/2pbbp2/4p3/4P3/2N5/PPPBNPPP/R3KB1R w KQ - 4 9
|
||||
r2q1rk1/1b3pbp/p1np2p1/1p2p3/4P3/P1BB1N1P/1PP2PP1/R2Q1RK1 w - - 2 16
|
||||
rnbq1rk1/pp2bp1p/4p1p1/8/3NP3/2PB4/P1Q2PPP/R1B1K2R w KQ - 0 11
|
||||
2r1kbnr/1bqp1ppp/ppn1p3/2p5/2P1P3/2NP1N1P/PP2BPP1/R1BQ1RK1 w k - 4 9
|
||||
r3kbnr/ppp2ppp/3p4/1b1Pq3/4P3/2N5/PP3PPP/R1BQK2R w KQkq - 0 10
|
||||
rnb2rk1/ppp1qppp/8/3p4/3P4/5N2/PP2PPPP/R2QKB1R w KQ - 2 9
|
||||
rn1q1rk1/1b2bppp/p2ppn2/1p6/3PP3/2NB1N2/PP3PPP/R1BQR1K1 w - - 2 11
|
||||
rnb1k2r/pp2bpp1/1qpp1n1p/4p3/2B1P3/3P1Q1P/PPPBNPP1/R3K1NR w KQkq - 4 9
|
||||
r1b1kb1r/2qp1ppp/p1n1pn2/1p6/4P3/1NNB1P2/PPP3PP/R1BQK2R w KQkq - 0 9
|
||||
r3kb1r/1p1n1pp1/2p3p1/3p4/1p1PnB2/3BP2P/PP2NPP1/R3K2R w KQkq - 2 15
|
||||
r3nrk1/1pqb1pbp/6p1/p1pP4/P1B5/2N2N1P/1PQ2PP1/3R1RK1 w - - 3 18
|
||||
r3kb1r/1bpn1ppp/p2qpn2/1p6/3P4/2NBBN2/PPP2PPP/R2QR1K1 w kq - 0 10
|
||||
r4rk1/1ppq1pbp/2np1np1/p3p3/4P1b1/1P1P1NP1/PBPN1PBP/R2QR1K1 w - - 0 11
|
||||
r3r1k1/1p3ppp/p1n2q2/3p1b2/3P4/2P2N1P/P1Q1BPP1/R4RK1 w - - 2 15
|
||||
r1bqk2r/pp2ppb1/3p1np1/2pPn2p/4PB1P/2N2P2/PPP1B1P1/R2QK1NR w KQkq c6 0 9
|
||||
r1bq1rk1/ppp2ppp/2n2b2/3p4/3P4/2P2N2/P1P1BPPP/R1BQ1RK1 w - - 0 11
|
||||
rnb3k1/p4rpp/4p3/3p4/1BpPn3/P6N/2P1BPPP/R3K2R w KQ - 4 17
|
||||
r2q1rk1/pppn1ppp/2b1pb2/8/3P4/3B1NN1/PPP1QPPP/R3K2R w KQ - 8 11
|
||||
r4rk1/pp2npbp/3p1qp1/2pP4/3pP3/3Q1B2/PPP1NPPP/R2R2K1 w - c6 0 14
|
||||
2rqrnk1/pb3ppp/1p1bpn2/1P6/3P4/1BN2N2/PB3PPP/R2QR1K1 w - - 5 15
|
||||
r2q1rk1/pb1pb1pp/1pn1p1n1/2p1Pp2/5P2/P1NPBN2/1PP1B1PP/R2Q1RK1 w - - 1 11
|
||||
r2q1rk1/pb3pp1/2p2n1p/n1b1N3/B3p3/8/PPPPQPPP/RNB2RK1 w - - 6 13
|
||||
r1bq1rk1/pp2ppbp/2n2np1/3p4/2PP4/2NB1N2/PP3PPP/R1BQ1RK1 w - - 1 9
|
||||
r1bqrbk1/1pp2pp1/p1np1n1p/4p3/P1B1P3/2PPBN1P/1P3PP1/R2QKN1R w KQ - 1 11
|
||||
rnb1nrk1/1pq3bp/p2p2p1/2pPp3/P1P1Pp2/2NBBN2/1PQ2PPP/R4RK1 w - - 0 13
|
||||
r1bq1rk1/ppp1bpp1/3p1n1p/n3p3/2B1P3/2PP1N1P/PP3PP1/RNBQR1K1 w - - 1 9
|
||||
r2qkb1r/2p2pp1/p2p1n1p/1p2p3/3nP3/1BN2Q1P/PPPP1PP1/R1B1R1K1 w kq - 1 11
|
||||
2r1k2r/1bqnbppp/pn1pp3/1p4P1/4P3/1NN1BP2/PPPQB2P/1K1R3R w k - 1 15
|
||||
r1bqr1k1/pppn1pbp/3p1np1/4p3/2PP4/2NBPN2/PPQ2PPP/R1B2RK1 w - - 2 9
|
||||
r3kb1r/1q3ppp/p3p1b1/3pPn2/2pP4/1N3N2/PPP2PPP/R1BQ1RK1 w kq - 0 14
|
||||
r2qkbnr/p2b1ppp/4p3/3pP3/3p4/5N2/PP3PPP/RNBQ1RK1 w kq - 0 10
|
||||
r1bqr1k1/p1p2ppp/2pp4/8/4n3/2P2N1P/PPP2PP1/R1BQ1RK1 w - - 0 11
|
||||
r4rk1/pp2ppbp/2pq1np1/8/2BPQ3/1P5P/P1PB1PP1/3R1RK1 w - - 1 17
|
||||
r2q1rk1/1p1b1ppp/p1n1pn2/bB1p4/Q2P4/P1N1PN2/1P1B1PPP/R4RK1 w - - 0 12
|
||||
r1bq1rk1/p4pbp/1p4p1/3P4/1P2p3/4PN2/P3BPPP/2RQ1RK1 w - - 0 17
|
||||
r1b1k1nr/ppq2ppp/8/4p3/3p4/1N6/PP2BPPP/R2Q1RK1 w kq - 0 13
|
||||
3r1rk1/pbp2ppp/1pn1pq2/8/3P3P/5N2/PPPQBPP1/2KR3R w - - 1 13
|
||||
r4rk1/p3ppbp/2pp2p1/4n3/N2qP1b1/1B6/PPPBQPPP/1R2R1K1 w - - 16 17
|
||||
r4rk1/pp1qbppp/2n1bn2/2p5/2P2P2/2N2N2/PP1PB1PP/R1BQ1RK1 w - - 8 10
|
||||
rnbqkb1r/pp2pp1p/3p1np1/P7/1PP5/8/1B1P1PPP/RN1QKBNR w KQkq - 2 9
|
||||
rnb2rk1/p3ppbp/2p2np1/qp6/3PP3/1QN2N2/PP2BPPP/R1B1K2R w KQ - 2 10
|
||||
2rq1rk1/3bbp1p/ppnppnp1/1Np5/2PP4/4PN2/PPQBBPPP/1R3RK1 w - - 0 13
|
||||
r1bqk1nr/ppp2ppp/2np4/8/2BPP3/5N2/P2N1PPP/R2QK2R w KQkq - 0 10
|
||||
r1b1kb1r/pp1p1p1p/4pp2/q7/8/P1N3P1/2P1PPBP/R2Q1RK1 w kq - 2 12
|
||||
r2qr1k1/ppp2pbp/2np1np1/4p3/2P3b1/1PN1P1PN/PB1P1PBP/R2Q1RK1 w - - 1 10
|
||||
r2q1rk1/1bpnbppp/1p1ppn2/p7/P3P3/3P1NP1/1PPNQPBP/R1B2RK1 w - - 0 10
|
||||
r4rk1/pb1q3p/1p1bppp1/2p4Q/2PPpP2/1P2P1P1/PB4BP/2R2RK1 w - - 0 18
|
||||
r2q1rk1/pb2bppp/3p4/p1pPn3/1nP5/2N1BP2/1P2N1PP/RB1Q1RK1 w - - 1 16
|
||||
r1bqk2r/pp1np1bp/2n1p1p1/3pN3/2pP1P2/8/PPP1B1PP/RNBQK2R w KQkq - 2 11
|
||||
r3kb1r/1bqp1ppp/p1p1pn2/8/4P3/2N3P1/PPP2PBP/R1BQ1RK1 w kq - 2 10
|
||||
r2q1rk1/pp1bbppp/2n1pn2/2pp4/3P4/2PBPN2/PP3PPP/RNBQR1K1 w - - 8 9
|
||||
r1bqr1k1/pp1nbpp1/2p1pn1p/3p4/2PP4/2N1PNB1/PP3PPP/2RQKB1R w K - 4 10
|
||||
r4rk1/ppp2pp1/2pb1q1p/4p3/4P1b1/3P1N2/PPPN1PPP/R2Q1RK1 w - - 4 10
|
||||
rn1q1rk1/5ppp/3p1b2/1ppP4/8/5N1P/PPQ2PP1/RN3RK1 w - - 0 15
|
||||
2kr3r/pbpqbn1p/1pnp1pp1/4pP2/4P3/P2PB1PP/1PPNN1B1/R2QK2R w KQ - 0 13
|
||||
r2qr1k1/1ppn1ppp/p2b1n2/6B1/3P4/2NQ1N1P/PP3PP1/R4RK1 w - - 2 14
|
||||
2r2rk1/1p1bb1pp/p4n2/3pq3/8/P3B2P/1PN1BPP1/R2Q1RK1 w - - 0 17
|
||||
rn2k1nr/pp1b1ppp/1q2p3/2bpP3/6P1/2NB1N2/PPP2P1P/R1BQK2R w KQkq - 0 9
|
||||
r2q1rk1/pb4pp/1p2pb2/3nB3/2BQ4/5N2/2P2PPP/R3R1K1 w - - 2 18
|
||||
r2q1rk1/ppp1nppp/2n1p1b1/1B1pP1B1/3P3N/P7/P1P2PPP/R2QK2R w KQ - 5 11
|
||||
r2q1rk1/pppbb1pp/2n1pp2/3p4/3PnP2/2P1PN2/PP2B1PP/RNBQ1RK1 w - - 2 12
|
||||
1rbq1rk1/4ppbp/pn1p1np1/1pp5/4P1P1/3P1NNP/PPPB1PB1/R2Q1RK1 w - - 4 12
|
||||
r2q1rk1/pbpn1pbp/1p1pp1pn/3PP3/2P2P2/P1N2N2/1P2B1PP/R1BQ1RK1 w - - 1 12
|
||||
r2q1rk1/pp1n1ppp/4p3/3pP3/PbBP2b1/5N2/1P2QPPP/R1B2RK1 w - - 0 13
|
||||
r2qr1k1/5pp1/2pb1pp1/pp1n4/3P4/2PQ1N1P/PP3PP1/R1B2RK1 w - - 2 18
|
||||
5rk1/p2nqpp1/1p2p2p/2rb4/3Q4/P2BPN2/1P3PPP/R4RK1 w - - 5 18
|
||||
r5k1/pp1bqpp1/2n4p/2Pp4/4r3/P1PQ1N1P/2P2PP1/R1B1R1K1 w - - 2 16
|
||||
r2q1rk1/1b1nnpbp/pp1pp1p1/2p5/4P3/1NPPBNP1/PP1Q1PBP/R3R1K1 w - - 0 13
|
||||
r1bq1rk1/pp1nbppp/2n1p3/2ppP3/3P1P2/2P2N2/PP2N1PP/R1BQKB1R w KQ - 1 9
|
||||
r2q1rk1/1b3ppp/2np1b2/p3p3/1pP1P3/3BN3/PP3PPP/R1BQ1RK1 w - - 0 15
|
||||
r2q1rk1/1pp3pp/p1nppn2/2b1p3/P3P3/2NP1N1P/1PP2PP1/R1BQ1RK1 w - - 0 10
|
||||
r3r1k1/pppqppbp/5np1/3Pn1B1/2P5/2NB4/PP3PPP/R2QK2R w KQ - 1 12
|
||||
1k1r3r/ppp2ppp/2nbp3/3n1b2/3P4/1NP2N2/PP2BPPP/R1B2RK1 w - - 6 12
|
||||
r1bq1rk1/p1pp2pp/1pn1p3/1B2Pp2/3P4/P1P2P2/2P3PP/R2QK1NR w KQ - 0 11
|
||||
rn1q1rk1/pbpp1npp/1p2p3/4Pp2/1bPP4/2N1BP2/PP2N1PP/R2QKB1R w KQ - 3 9
|
||||
r1bqk2r/pp3pb1/3p2p1/2pPn3/4P3/2N2PpP/PP2B1P1/R1BQ1RK1 w kq - 0 15
|
||||
r1bq1rk1/p4ppp/1pp5/3n4/2B5/2P5/PP1N1PPP/R2Q1RK1 w - - 0 13
|
||||
1r1qk1nr/pQpnb1pp/3pp3/1N6/4P3/8/PP3PPP/R1B1K1NR w KQk - 3 10
|
||||
r1b2r2/ppqn1pkp/2pp1np1/3Pp3/2P1P3/2N2P2/PP1Q2PP/R3KBNR w KQ - 0 11
|
||||
r3k2r/ppbnqppp/2p1pn2/2Pp4/3P4/2N1PB1P/PPQ2PP1/R1B1K2R w KQkq - 3 11
|
||||
r1b2rk1/p4pp1/1pn1p3/2ppn1P1/8/2PBP1B1/PP1N2PP/R3K2R w KQ - 0 15
|
||||
2kr3r/ppp1npp1/2p1b2p/8/1b2PB2/2NB1P2/PPP3PP/R4RK1 w - - 1 13
|
||||
r1bqr1k1/1p3pbp/p2p1np1/2pPn3/P3P3/2N2B2/1P1N1PPP/R1BQ1RK1 w - - 5 13
|
||||
r2q1rk1/pp2bppp/2ppbn2/8/4P3/1BNQ4/PPP2PPP/R1B2RK1 w - - 0 11
|
||||
rn1q1rk1/ppp1bppp/2bp4/2P5/2B5/2N1Bp2/PPP2PPP/R2QR1K1 w - - 0 12
|
||||
r1b2rk1/1p2b1pp/pqn1pn2/3p4/3P1P2/P2B1N2/1P2N1PP/R1BQ1RK1 w - - 1 14
|
||||
r2qk2r/ppp1b1pp/4p1n1/3pPp2/3P4/2P5/PP1N1PPP/R1BQ1RK1 w kq - 2 12
|
||||
r2q1rk1/pp1n1ppp/2p1pn2/3p4/2PP1Pb1/2N2N2/PP2PPBP/R2Q1RK1 w - - 1 10
|
||||
r2q1rk1/pp1bp1b1/2pp1npp/5p2/1PPP4/P1N1PN1P/5PP1/1R1QKB1R w K - 3 12
|
||||
rn1q1rk1/pb2nppp/4p3/1p6/2pPPB2/2P2N2/4BPPP/R2Q1RK1 w - - 6 12
|
||||
r5nr/pp1k1ppp/4p3/2bpP3/3q4/8/PP1N1PPP/R1BQ1RK1 w - - 2 12
|
||||
5rk1/3nbppp/rq2p3/pN1p4/8/PP2P3/1B3PPP/R2Q1RK1 w - - 2 18
|
||||
rn2k2r/pp2q3/2p3p1/3pPp1p/4nP1P/2PB4/PP4P1/RN1QK2R w KQkq - 3 14
|
||||
2kr2r1/pbpq1pbp/1p1ppnp1/8/4PP2/1P1P4/PBPNB1PP/R2Q1RK1 w - - 4 13
|
||||
3r1rk1/ppp2ppp/2qbb3/3n4/8/2P1BN2/PPQ1BPPP/R4RK1 w - - 8 15
|
||||
3rkb1r/ppq2pp1/2n1pnp1/2p5/4PPP1/2P4P/PPQNB3/R1B1K2R w KQk - 1 14
|
||||
r1bq1rk1/pp1nb1pp/2n1pp2/2ppP3/3P1P2/2P2N2/PPQ1N1PP/R1B1KB1R w KQ - 4 10
|
||||
r1b1k1nr/pp3ppp/2n5/4p3/2B2B2/2P1P3/P4PPP/2KR2NR w kq - 0 12
|
||||
r1bqk2r/pp1n1ppp/2p1p3/3pP3/1bP2P2/2N1P3/PP4PP/R1BQKB1R w KQkq - 1 9
|
||||
rn1qk2r/3p1pp1/pb2p2p/1p1bP3/1P1P4/P3BN2/4BPPP/R2Q1RK1 w kq - 0 14
|
||||
2rq1rk1/1p1nbppp/p2pbn2/4p1B1/4P3/1NN2B2/PPPQ1PPP/R4RK1 w - - 10 12
|
||||
r2qk2r/pbpp1pb1/1p2p3/n6p/2B1PPn1/2PP1NB1/PP1N2P1/R2QK2R w KQkq - 3 13
|
||||
r1bq1rk1/pp2npbp/2np2p1/2p1p3/2P1P3/2NP2P1/PP2NPBP/R1BQ1RK1 w - - 2 9
|
||||
r1bqr1k1/bpp2ppp/3p1n2/p3n3/2P5/P1N1PN2/1P2BPPP/R1BQR1K1 w - - 0 11
|
||||
r1bqk1nr/1p4pp/p1nbp3/3p1p2/B2P4/2N1PN2/PP3PPP/R1BQK2R w KQkq - 2 9
|
||||
r1b1k1nr/ppp2ppp/1b3q2/3Pp3/4P3/2N2N2/PP3PPP/R2QKB1R w KQkq - 0 10
|
||||
r2q1rk1/3nbppp/bppp1n2/p3p3/4P3/PP1P1NP1/1BPNQPBP/R4RK1 w - - 3 12
|
||||
rnbq1rk1/1pp1bppp/5n2/p7/1P2p3/P3P3/1BPNNPPP/R2QKB1R w KQ - 0 9
|
||||
rnb1qrk1/pp2b1pp/2p2n2/3p1pB1/3P4/2N3PN/PP2PPBP/R2Q1RK1 w - - 6 10
|
||||
r2q1rk1/pp4pp/2p1b3/2npPp2/8/2PB1P2/P5PP/R1BQR1K1 w - - 0 16
|
||||
r1b3kr/3p1ppp/p1nQpn2/1p6/4P3/P1N1q3/1PPN2PP/1K1R1B1R w - - 0 16
|
||||
r2q1rk1/ppp1bppp/3p1n2/4n3/4P3/2NBBQ1P/PPP2PP1/R3K2R w KQ - 1 11
|
||||
2r2rk1/pbqn1pp1/1p2pn1p/2pp4/P1PP3B/2P1PN2/4BPPP/R2Q1RK1 w - - 0 15
|
||||
r1bqk2r/pppp1ppp/2n5/8/2P1PP2/2P5/P5PP/R1BQKB1R w KQkq - 1 10
|
||||
r1b2rk1/1p3qpp/ppn5/4pp2/8/P2P1N2/1PP1BPPP/R2QR1K1 w - - 0 14
|
||||
rn1qkbnr/pp4pp/5pb1/2p1p3/3pP1P1/3P3P/PPPBNPB1/R2QK1NR w KQkq - 0 9
|
||||
r3k2r/1bq2pp1/p3pn1p/3p4/1b4P1/2N1B3/PPPQBP1P/2KR3R w kq - 1 15
|
||||
r2qk2r/pp2bppp/2pp1n2/8/2BpPP2/7P/PPPP2P1/R1BQK2R w KQkq - 0 11
|
||||
rnb1k2r/1pq1bppp/p2ppn2/6B1/3NPP2/2N2Q2/PPP3PP/R3KB1R w KQkq - 3 9
|
||||
r4rk1/4ppbp/p1qp1np1/1p6/3BP3/2P2Q1P/PP1N1PP1/R4RK1 w - - 0 16
|
||||
r1bqkb1r/p1p4p/1p1p2p1/3P1p2/2Pp4/3P1Q2/PP3PPP/R1B1KB1R w KQkq - 0 11
|
||||
2r1r1k1/1p1q1pbp/p2p2p1/P2Pp2n/1P2P1b1/2N1BN2/5PPP/2RQ1RK1 w - - 1 18
|
||||
2rq1rk1/pp3ppp/4bn2/3pn3/8/PN1QP1P1/1P3PBP/R1B2RK1 w - - 3 16
|
||||
2kr1b1r/ppp1ppp1/5n1p/3p1b2/3P4/2P2N2/PP1NPPPP/R3KB1R w KQ - 0 9
|
||||
r1bqk2r/ppp2ppp/3p3n/2bNp1NQ/2BnP3/5P2/PPPP2PP/R1B1K2R w KQkq - 5 9
|
||||
r1b2rk1/ppq1bppp/4pn2/2p5/2BP4/2P1BQ2/PP2NPPP/R4RK1 w - - 2 13
|
||||
r4rk1/pb1q2pp/1pnb1n2/5pB1/3p4/3P1N2/PP1N1PPP/R2QRBK1 w - - 2 15
|
||||
r2qkb1r/1p1npppb/p2p3p/2pP4/4P2B/2N2N2/PPP2PPP/R2QK2R w KQkq - 2 11
|
||||
2k2bnr/pppb2p1/2npr1Bp/8/5P2/2P1BN2/PP4PP/RN2R1K1 w - - 1 14
|
||||
r2qk2r/pp1b1ppp/2np1n2/1B2p3/Q7/2P2N2/PP1P1PPP/R1B2RK1 w kq - 0 10
|
||||
r3qrk1/ppp3pp/2pb1nb1/6B1/3Pp3/2P1N3/PP1N1PPP/R2QK2R w KQ - 9 12
|
||||
3q1rk1/1pp2pbp/2n1b1p1/4p3/r7/3PPNP1/PB2QPBP/R4RK1 w - - 0 15
|
||||
r2q1rk1/pbppnnpp/1p2p3/4Pp2/3P4/P1PBBP1N/2PQ2PP/R3K2R w KQ - 5 12
|
||||
rnb4r/p3kppp/2pb4/3n4/1p1P1p2/1B3N2/PPP1N1PP/R1B1K2R w KQ - 0 12
|
||||
r2q1rk1/1b1p2bp/p5p1/npp1p3/4Pp2/P1NP1N2/1PP2PPP/R1BQR1K1 w - - 0 15
|
||||
r1b2rk1/1p1n2bp/pq1p1np1/2pP4/4P3/2NB1N2/PP1B2PP/2RQ1RK1 w - - 2 14
|
||||
2kr2nr/ppp2ppp/2nb4/7b/4q3/2P2N1P/PP2BPP1/RNBQ1RK1 w - - 2 10
|
||||
r6r/pp1kn1pp/2n1bp2/1N1p4/3Pp3/1PP3P1/1P1N1PP1/R3KB1R w KQ - 5 15
|
||||
rnbq2k1/pp3pbp/6p1/8/3Nr3/8/PP2BBPP/R2Q1RK1 w - - 0 15
|
||||
2rqk2r/pb3ppp/1pn1pb2/8/2BP4/P1N1PN2/1PQ3PP/2KR3R w k - 4 14
|
||||
r1b1k2r/pppqbppp/2n5/8/3pN3/5N2/PPP2PPP/R1BQR1K1 w kq - 1 10
|
||||
r3kb1r/p2b1ppp/2p1pn2/1B2n3/4P3/P1N5/1P3PPP/R1B1K1NR w KQkq - 0 11
|
||||
r2qk2r/ppp3b1/2npP1pp/4pp2/2P5/1P2P3/PB3PPP/RN1QK2R w KQkq - 1 14
|
||||
r1bq1rk1/pp3ppp/2n1p3/2b5/8/2nBPN2/PP3PPP/R1BQ1RK1 w - - 0 10
|
||||
r3kb1r/2q2ppp/pn2p3/3b2B1/3N4/PPp2P2/4B1PP/2RQ1R1K w kq - 0 17
|
||||
r3k1nr/1bqpbpp1/p1n1p3/1pp4p/4P2P/2NP2P1/PPP1NPB1/R1B1QRK1 w kq - 1 10
|
||||
rnb1kb1r/p2p1ppp/4p3/2PpP3/q2P4/5Q2/P2B1PPP/RN2K1NR w KQkq - 3 12
|
||||
r1bq1rk1/pp1p1ppp/5b2/n2Bp3/4P3/2N2N2/PP3PPP/R2Q1RK1 w - - 2 11
|
||||
rn1q1rk1/pbppb1pp/1p2p3/5p2/2PPn3/2N1PN1P/PP2BPP1/R1BQ1RK1 w - - 1 9
|
||||
r5k1/pb2ppbp/1pn2np1/1Bp5/4P3/2N1BN2/PPP2PPP/3R2K1 w - - 3 13
|
||||
r3r1k1/ppp2bbp/2nq1pp1/3p4/3P4/2PBPP2/PPQN2P1/2KR2NR w - - 4 14
|
||||
r1bq1rk1/4bppp/p1n1pn2/1p1p4/3P4/2NBPNB1/PP3PPP/R2QK2R w KQ - 0 11
|
||||
2kr1b1r/1pp2ppp/p1nqpnb1/8/6P1/2NPBN1P/PPP1QPB1/R3K2R w KQ - 0 12
|
||||
r3k2r/ppq1bppp/2p1pn2/8/3PB1b1/2P2N2/PP1B1PPP/R2QR1K1 w kq - 1 13
|
||||
r2qk2r/2p2ppp/p2p1n2/1p2p2b/1PBbP3/P2P1N1P/2PN1PP1/1R1Q1RK1 w kq - 0 13
|
||||
r3kb1r/pp1b1ppp/1qn1p3/3pPn2/1P1P4/P4N2/1B3PPP/RN1QKB1R w KQkq - 3 10
|
||||
rnbq1rk1/pp3pbp/3p1np1/2pP4/4PP2/2NB4/PP4PP/R1BQK1NR w KQ - 1 9
|
||||
r3r1k1/p1p2pp1/Bbpq1n1p/3b4/N6B/3Q4/PPP2PPP/R4RK1 w - - 2 18
|
||||
r2q1rk1/1p1bbppp/p1n1pn2/3p4/3P4/P1NBBN1P/1PP2PP1/R2Q1RK1 w - - 1 11
|
||||
r1bqk3/ppp2p1p/2n1pn2/8/3Pp1rQ/P1P4N/2P2PPP/R1B1KB1R w KQq - 7 11
|
||||
r2q1rk1/pp2bppp/4n3/3pN3/2pPpBQ1/P1P1P3/1P3PPP/R3K2R w KQ - 3 15
|
||||
r2q1rk1/pb1n1ppp/1p1ppn2/2p5/2PP4/4PNP1/PPQN1PBP/R4RK1 w - - 0 11
|
||||
r4rk1/1b3ppp/pq2pn2/1pb5/8/P3PN2/1P2BPPP/R1BQ1RK1 w - - 3 15
|
||||
r2qk2r/1b2bppp/p2p1n2/1pp1p3/4P3/1PP2N2/1PNP1PPP/R1BQR1K1 w kq - 4 12
|
||||
r3k2r/1bq2pbp/p3pnp1/1pp1p3/4P3/2PP1P2/PP2BNPP/R1BQ1RK1 w kq - 1 14
|
||||
r4rk1/pppbqppp/2n1pn2/3p4/3P4/P1NBPN2/1PP2PPP/R2QK2R w KQ - 5 14
|
||||
r4rk1/p2qbppp/1pbppn2/1N6/2P2B2/P5P1/1P2PPBP/R2Q1RK1 w - - 0 14
|
||||
r2r2k1/1b3p1p/ppn1pp2/2b5/2BN4/2N1P3/PP3PPP/2RR2K1 w - - 2 16
|
||||
r3kbnr/pp1b1ppp/1q2p3/3pP3/3n4/3B1N2/PP3PPP/RNBQ1RK1 w kq - 0 9
|
||||
r4rk1/ppp2ppp/4p1b1/2bqP3/6P1/2P3NP/PP2QP2/R1B2RK1 w - - 3 17
|
||||
r2q1rk1/1pp1bppp/p1np1n2/4p3/4P3/1PNP1N1P/1PP2PP1/R1BQ1RK1 w - - 0 10
|
||||
r1bqkb1r/5ppp/p1np1n2/1p2p1B1/4P3/N1N5/PPP2PPP/R2QKB1R w KQkq - 0 9
|
||||
rn1q1rk1/pb3pp1/5b1p/2pp4/8/2NBPN2/PP3PPP/2RQ1RK1 w - - 0 13
|
||||
rn2k2r/pb3pp1/1ppbpq1p/1B6/3P4/4BN2/PPPQ1PPP/R3K2R w KQkq - 0 11
|
||||
r2qkb1r/p2b1ppp/2p1p1n1/3pP3/3P4/5N2/PP3PPP/RNBQ1RK1 w kq - 2 10
|
||||
1rq1r1k1/1p1bbpp1/p2p1nnp/2pPp3/P1P1P3/2NB2BP/1P1Q1PPN/R4RK1 w - - 5 17
|
||||
r1bq1rk1/p1p3pp/2p2b2/5p2/2PPpB2/P1N5/1P3PPP/R2Q1RK1 w - - 2 15
|
||||
r1bq1rk1/pp2bppp/2nppn2/8/3NP3/2N5/PPP1BPPP/R1BQ1RK1 w - - 1 9
|
||||
1rbq1rk1/p1p1ppbp/2B2np1/8/2pP4/6P1/PP2PP1P/RNBQ1RK1 w - - 1 10
|
||||
r4rk1/pppq1ppp/2nb4/4p3/1P6/P1P2NPb/4PPBP/R1BQR1K1 w - - 1 12
|
||||
r2q1rk1/1pp2pp1/p1np1n1p/8/1PBQP3/P1B2P1P/2P2P2/R3R1K1 w - - 1 16
|
||||
rn1q1rk1/pp2bpp1/2p2n1p/3p4/3P2bB/2N1PN2/PPQ2PPP/R3KB1R w KQ - 1 10
|
||||
r2qkb1r/ppp2ppp/2n1b3/3n4/8/2N2N2/PPP1QPPP/R1B1KB1R w KQkq - 0 9
|
||||
r1bq1rk1/pp2ppbp/1nnp2p1/8/3P4/2PB1N2/PP1N1PPP/R1BQ1RK1 w - - 3 10
|
||||
r1bnkb1r/pp3ppp/5n2/4p3/4p3/2N3N1/PPP2PPP/R1B1KB1R w KQkq - 0 9
|
||||
r1bq1rk1/pp2bppp/4pn2/3p4/2PP4/P1N5/1P1BBPPP/R2QK2R w KQ - 1 11
|
||||
r3k2r/pp1n1ppp/3b4/3np3/8/2P2N2/PP2B1PP/R1B2RK1 w kq - 0 14
|
||||
r1b1k2r/pp2nppp/2pp1q2/5P2/2BbPB2/2N5/PP3QPP/R4RK1 w kq - 0 18
|
||||
r2qk1nr/1p2ppbp/p2p2p1/8/3nP3/4B1P1/PP2QPBP/RN3RK1 w kq - 0 11
|
||||
r3kb1r/ppqnp1pp/2p1pn2/3pN3/3P2P1/8/PPP2PKP/RNBQ1R2 w kq - 1 11
|
||||
r3k2r/pp3ppp/2p1bn2/4p3/NP1qP3/P4PP1/3PQ1BP/2R1K2R w Kkq - 0 16
|
||||
r1b1k2r/1pqpbpp1/p1n1pn1p/8/3NP2B/2N5/PPP1BPPP/R2Q1RK1 w kq - 2 10
|
||||
4rrk1/pp3ppp/2pbb3/n2p2Bn/3P4/P1NB1N1P/1PP2PP1/R4RK1 w - - 5 16
|
||||
r1b1k2r/p1pnqppp/2p5/3pP3/8/2PB1Q2/P1P2PPP/R1B1K2R w KQkq - 1 11
|
||||
2kr2nr/pppq2pp/1b2p3/4P3/3p1B2/1N6/PPP1QPPP/2KR3R w - - 4 14
|
||||
rnbq1rk1/ppp2pbp/3p1np1/8/8/2N2NP1/PPPPQPBP/R1B1K2R w KQ - 2 9
|
||||
rn1q1rk1/1p1bbppp/p1pp1n2/1P2p3/P1P5/2N3P1/1B1PPPBP/R2QK1NR w KQ - 1 9
|
||||
r1bqkbnr/pp4pp/2n1pp2/3pP3/3P1P2/2P5/6PP/RNBQKBNR w KQkq - 0 9
|
||||
r4rk1/2p1bppp/ppnp1q2/4p3/2P1P1b1/3P1N2/PP1BBPPP/R2Q1RK1 w - - 0 11
|
||||
r2q1rk1/pp2bbpp/2p2n2/3p1p2/2PP4/1PN2PP1/PB1Q2BP/4RRK1 w - - 0 16
|
||||
rnb1r1k1/ppbn1ppp/4p3/4P1B1/2B2P2/1N4P1/PPK1N2P/R6R w - - 1 17
|
||||
r4rk1/pbq2ppp/1p2pn2/2b5/2P5/1P1B1N2/PB3PPP/R2QR1K1 w - - 2 15
|
||||
r1bqk2r/3nppbp/p2p1np1/1p6/3NP3/1P1B4/PBP2PPP/RN1QR1K1 w kq - 0 10
|
||||
r1bq1rk1/4ppbp/p1n3p1/1pp5/B2PP3/2P1BN2/P4PPP/R2Q1RK1 w - - 0 12
|
||||
r2q1rk1/ppp2pbp/5np1/3Pp3/4P3/2NBBP1b/PPPQ3P/R4RK1 w - - 1 13
|
||||
rn1q1rk1/ppp2pp1/5pbp/8/1b1PP3/2NB1N2/PPP3PP/R2QK2R w KQ - 5 10
|
||||
r2q1rk1/1p1nbppp/p2pbn2/4p3/4P3/2NB1N1P/PPP1QPP1/R1B2RK1 w - - 8 11
|
||||
rnbq1rk1/pp2bppp/4pn2/3pN1B1/3P4/1QN1P3/PP3PPP/R3KB1R w KQ - 1 10
|
||||
r4rk1/2q1bppp/p1nppn2/1p6/4P3/P1N1BP2/1PP1N1PP/R2Q1R1K w - - 0 15
|
||||
r3rnk1/pp1b1qpp/2p2p2/3p4/3P1P2/2NB1R1P/PP4P1/R4Q1K w - - 4 18
|
||||
2kr1b1r/ppp1qp1p/5pp1/3P1b2/2P5/5Q2/PP2BPPP/R1B1K2R w KQ - 5 12
|
||||
r2qkb1r/1p1n1ppp/p2pbn2/3Np3/P3P3/1N6/1PP2PPP/R1BQKB1R w KQkq - 3 9
|
||||
rnbqkb1r/pp2p1pp/8/3p1pB1/2QP4/8/PP2PPPP/R3KBNR w KQkq - 0 9
|
||||
r2nk2r/b1p1n1pp/p2pqp2/8/P1N1P2B/1QP2N2/5PPP/1R3RK1 w kq - 0 18
|
||||
r1br2k1/2qnppbp/p2p2p1/2pP3n/P1p1PB2/2N2NP1/1P1Q1PBP/R3R1K1 w - - 5 15
|
||||
r3kbnr/pp3ppp/8/5b2/2Pp4/P7/1P1NBPPP/R1B1K2R w KQkq - 1 12
|
||||
r1b1k1nr/pp1np2p/3p2p1/q1pP1pB1/2P5/1QP2P2/P3P1PP/R3KBNR w KQkq - 1 9
|
||||
rn1q1rk1/pp1b2pp/2pbpn2/3p1p2/2PP4/1P3NP1/PB2PPBP/RN1Q1RK1 w - - 0 9
|
||||
r1bq1rk1/1pp2ppp/p1pbn3/8/3P4/5N2/PPP2PPP/RNBQR1K1 w - - 3 11
|
||||
r2qk2r/pb1nbppp/2p1pn2/1p4B1/2pPP3/2N2NP1/PP3PBP/R2Q1RK1 w kq - 6 10
|
||||
r2qk2r/ppp1bppp/2n5/3p4/3Pn1b1/3BBN2/PPP2PPP/RN1Q1RK1 w kq - 6 9
|
||||
r1br1nk1/pp2q1p1/2p1p2p/3p1p2/2PP1P2/1PN1P3/P5PP/1BRQ1RK1 w - - 0 18
|
||||
r2q1rk1/1bp1bppp/p3p3/np6/3PP3/2P2N2/P1B2PPP/R1BQ1RK1 w - - 6 13
|
||||
r2rb1k1/pp3ppp/1qp1pn2/8/4P3/2QN1P2/PPP3PP/R3KB1R w KQ - 6 14
|
||||
r1bq1rk1/ppp2pbp/3p1np1/3Pp2P/2PnP3/2N5/PP2BPP1/R1BQK1NR w KQ - 1 9
|
||||
r3k2r/3n1pp1/p1pqp3/3p4/Q1pP1P1p/1NP1P3/PP3P1P/R3K2R w KQkq - 0 16
|
||||
r2q1rk1/ppp1bpp1/2np1n1p/4p2b/2P1P3/1P1P1N2/PB1NBPPP/R2Q1RK1 w - - 0 10
|
||||
r3k1nr/p1q2ppp/np2p3/2ppP3/3P4/P1P1BN2/2P2PPP/R2QK2R w KQkq - 0 10
|
||||
r1br2k1/p3qpp1/1pn1p2p/2p5/3P4/1BPQPN2/P4PPP/R4RK1 w - - 2 15
|
||||
r1b1k2r/1pqn1ppp/p3p1n1/3pP1Q1/7P/2N2N2/PPP2PP1/R3KB1R w KQkq - 2 13
|
||||
r3kb1r/ppqn1pp1/2p2n1p/4p3/4P2B/2N2B1P/PPP2PP1/R2QK2R w KQkq - 0 11
|
||||
rn1qk2r/p3bppp/b1p1pn2/8/P1pPPB2/1pN2N2/1P3PPP/R2QKB1R w KQkq - 2 11
|
||||
r4rk1/2qb2pp/p1p1pp2/2pnR3/5P2/2NP1Q2/PPP2PBP/4R1K1 w - - 0 17
|
||||
r2qk2r/pp3p2/2n1p1p1/3pP1Pp/3P3P/P1PQPN2/8/R3K2R w KQkq - 0 18
|
||||
1r1q1rk1/3nbp1p/1pp1p1p1/1b6/pnNPP3/3Q1N2/PPB2PPP/R1B1R1K1 w - - 1 18
|
||||
4k2r/2pqnpb1/prnp3p/1B4p1/Q2PPpb1/2P2N2/PP1N2PP/R1B2RK1 w k - 0 14
|
||||
r4rk1/pbpnqppp/1p6/4p3/3P4/P1P1P3/3NBPPP/R2Q1RK1 w - - 0 14
|
||||
r2q1rk1/ppp1bppp/2n2n2/4p3/3pP3/2PP1B1P/PPQ2PP1/RNB2RK1 w - - 0 10
|
||||
rn4k1/pp3pbp/2p1bnp1/4p3/2P1P3/2N1BP2/PP2N1PP/3K1B1R w - - 1 12
|
||||
rn1qr1k1/pppb1pbp/3p1np1/4p3/2PP4/2N1PN1P/PP2BPP1/R1BQ1RK1 w - - 0 9
|
||||
2rqr1k1/1b1n1ppp/pp1Bpn2/3p4/2PP4/1P3NP1/P4PBP/R2Q1RK1 w - - 1 15
|
||||
r2qk2r/pb1n1ppp/1p2p3/2ppP3/3Pn3/B2B1N2/P1P1QPPP/R4RK1 w kq - 0 13
|
||||
2rq1rk1/pp1bppbp/2np1np1/8/3NPP2/2N1B3/PPPQB1PP/R4RK1 w - - 1 11
|
||||
r2qk2r/1b1nbppp/4pn2/p7/PpBN4/4P3/NP1BQPPP/2R2RK1 w kq - 3 14
|
||||
r2q1rk1/pp3ppp/n1pbbn2/4N3/3P4/2N3Q1/PP2PPBP/R1B2RK1 w - - 0 15
|
||||
2kr3r/pppq4/2n1p3/1B1p2pp/3P1b2/2N2R1P/PPP2PP1/R2Q3K w - - 0 17
|
||||
r2q1rk1/1pp3p1/1pnppn1p/4p3/4P2B/3P1N2/PPP2PPP/R2Q1RK1 w - - 0 12
|
||||
r1b1kb1r/pp2pppp/8/3qN3/3P4/8/PP1B1PPP/R2QK2R w KQkq - 3 14
|
||||
r3k2r/pp2qppp/2n2n2/2b2bN1/4p3/2N1P3/PPQPBPPP/R1B1K2R w KQkq - 4 10
|
||||
r1bq1rk1/1p3ppp/p1n1pn2/3p4/1bPNP3/2N1BP2/PP1Q2PP/R3KB1R w KQ - 0 10
|
||||
r2q1rk1/pp1bbppp/2nppn2/8/4P1P1/2N4P/PPP1NPB1/R1BQK2R w KQ - 3 10
|
||||
2k2rnr/ppp1q3/2b1p1p1/3pP3/1P1P1p2/P1N2NPp/2PQ1P1P/2R2RK1 w - - 0 18
|
||||
r4rk1/bpp2p2/p1p2q1p/8/3P2bp/2N2N2/PPPQ1PPP/3R1RK1 w - - 2 16
|
||||
r2q1rk1/5ppp/p1p1p1b1/np1n4/3P4/P1P1PN1P/BB3PP1/2RQ1RK1 w - - 0 16
|
||||
r2qkb1r/2pb1ppp/p1n1pn2/1p6/3P1B2/2Q1PN2/PP3PPP/RN2KB1R w KQkq - 0 11
|
||||
rn1qk2r/4ppbp/b2p1np1/2pP4/8/2N3P1/PP2PPBP/R1BQK1NR w KQkq - 2 9
|
||||
r2qk2r/1bp2pp1/p2p1n1p/1p2pP2/3bP3/1BNP4/PPPBQ1PP/R3K2R w KQkq - 0 12
|
||||
r1b2rk1/ppqnbpp1/2p2n1p/4p3/2P1P3/2NBBN2/PPQ2PPP/R4RK1 w - - 0 11
|
||||
rn1q1rk1/pp1bppbp/5np1/1B1p4/3P4/2N1PN2/PP3PPP/R1BQ1RK1 w - - 6 9
|
||||
r3k2r/pbpnqpb1/1p1ppnpp/8/3P1BPP/2P1PN2/PP1N1P2/R2QKBR1 w Qkq - 0 11
|
||||
r2q1rk1/1pp1bppn/p3b2p/4N3/3B4/1PN4P/1PP2PP1/R2Q1RK1 w - - 1 15
|
||||
r1bq1rk1/p3ppbp/1p4p1/3n4/3P4/5B2/PP1N1PPP/R1BQ1RK1 w - - 0 12
|
||||
r1bq1k1r/pp2bpp1/2n1p1np/2Pp4/5B1P/P1N3Q1/1PP2PP1/R3KBNR w KQ - 4 13
|
||||
r1bq1rk1/pp3ppp/2pb1n2/8/2PBp3/PP2P1N1/3P2PP/R2QKB1R w KQ - 0 13
|
||||
r4rk1/pp1q1pp1/2nbbn1p/3p4/3P4/2NBBN1P/PP3PP1/2RQ1RK1 w - - 4 13
|
||||
2kr3r/pppn1pp1/2p1b2p/8/1b2P3/2NB1PP1/PPPB2P1/2KR3R w - - 2 14
|
||||
r4rk1/pbqn1pbp/1p1ppnp1/2p3B1/3PPP2/N1PB4/PPQ1N1PP/3R1RK1 w - - 1 12
|
||||
r1bqk2r/pp1n1ppp/4p3/2bpP3/3N1P2/2N5/PPP3PP/R2QKB1R w KQkq - 1 10
|
||||
2r2rk1/1p1bb1pp/p1n1pp2/3pP3/3P4/NPPB1N1P/1P4P1/R4RK1 w - - 0 17
|
||||
r1b1k2r/ppppqppp/8/2b1n3/2P2B2/4P3/PP1N1PPP/R2QKB1R w KQkq - 0 9
|
||||
r2qkb1r/pp3ppp/2p1pn2/5b2/3P4/5N2/PPP1BPPP/R1BQ1RK1 w kq - 0 9
|
||||
r2qk2r/pp2bppp/1nn1p3/3pP3/3P2b1/1B2BN2/PP1N1PPP/R2Q1RK1 w kq - 2 12
|
||||
r3k2r/1ppq1ppp/p1pbbn2/4p1B1/4P3/2NP1N2/PPP2PPP/R2Q1RK1 w kq - 3 9
|
||||
rn1qr1k1/pp3ppp/2p2p2/3p1b2/3P4/P1Q1P3/1PP1NPPP/R3KB1R w KQ - 0 10
|
||||
1rbq1rk1/3pppbp/p1n2np1/1pp5/4PPP1/2NP1N1P/PPP3B1/R1BQK2R w KQ - 0 10
|
||||
r4rk1/ppp2qb1/4bn1p/5pp1/2P1pB2/1PN1P3/P1Q1BPPP/R2R2K1 w - - 0 18
|
||||
r1b1k2r/pp1n1pp1/3p1q1p/1BpP4/4P3/2P2N2/P4PPP/R2QK2R w KQkq - 2 12
|
||||
r2qk2r/ppp2pp1/2n2n1p/4p1B1/1bB1P1b1/2NP1N2/PPP3PP/R2QK2R w KQkq - 0 9
|
||||
r2r4/1b3pkp/2p2np1/p1n1p3/1p2P1P1/5P1P/PPPRBN2/2K3NR w - - 4 18
|
||||
r1b1k1nr/ppq1bppp/2p5/4n3/4P3/2N1BN2/PPP3PP/R2QKB1R w KQkq - 0 9
|
||||
r2q1r2/pp1b1pk1/2n1pn2/6B1/2BP4/P1P5/2Q2PPP/R4RK1 w - - 1 16
|
||||
r1bqr1k1/pppnppb1/3p1np1/8/2BPP1p1/2N1BP2/PPPQN2P/R3K2R w KQ - 0 10
|
||||
r2qkbnr/pp2p1pp/2n2pb1/1B1p4/6PP/2N2N2/PPPP1P2/R1BQK2R w KQkq - 0 9
|
||||
r1bqk2r/p3ppbp/5np1/3p4/8/2N5/PPP1BPPP/R1BQ1RK1 w kq - 0 10
|
||||
rnb1r1k1/ppp2pbp/5np1/4p1B1/2P1P3/2N2N2/PP2BPPP/R3K2R w KQ - 2 10
|
||||
r2qk2r/pp1nbpp1/2p2n1p/3p4/3P1B2/2NQPN2/PP3PPP/R4RK1 w kq - 0 11
|
||||
rnbq1rk1/p3ppbp/1p3np1/2p5/2P1PP2/2N1BN2/PP4PP/R2QKB1R w KQ - 0 9
|
||||
r2qk2r/pp2ppbp/2np1np1/2p5/4P3/2P4P/PPBPQPP1/RNB1K2R w KQkq - 2 9
|
||||
r1bq1rk1/p1p2ppp/1p1p1n2/n2Pp3/2P5/2P3P1/P1QNPPBP/R1B1R1K1 w - - 4 14
|
||||
rn1q1rk1/ppp1b1p1/3p1n2/6Np/3PPBb1/2NQ4/PPP3PP/R3K2R w KQ - 0 11
|
||||
r1bq1rk1/ppp3pp/2n1p3/3p1p2/2PPn3/P1P1PN2/3B1PPP/R2QKB1R w KQ - 0 10
|
||||
r2k3r/1ppbn1pp/1pnp1q2/1N3p2/1P3B2/P2B1Q2/2P2PPP/R3R1K1 w - - 2 18
|
||||
r1b2rk1/pp2qppp/2np1n2/2p3B1/2P5/4PN2/PP3PPP/R2QKB1R w KQ - 3 10
|
||||
rnbqnrk1/ppp1b1pp/3p4/3Pp1P1/4Pp2/5N1P/PPPB1P2/RN1QKB1R w KQ - 1 9
|
||||
r2q1rk1/pp1nbppp/2p2n2/3p2Bb/3P4/2N1PN1P/PP2BPP1/2RQK2R w K - 3 11
|
||||
rnbq1rk1/1p2bppp/p2p1n2/2p5/3NPB2/1BN5/PPP2PPP/R2QK2R w KQ - 0 9
|
||||
r1b1k2r/pp2nppp/2n1p3/q2pP3/2pP1PP1/P1PB1N2/2PB3P/R2QK2R w KQkq - 0 12
|
||||
2kr3r/pp2p2p/3p1np1/q1pPnp2/b1P2N2/2P1PP2/P2BB1PP/RQ3RK1 w - - 10 14
|
||||
r2qk2r/1p2bppp/p1b1pn2/2pp4/8/1PN1PN1P/PBPP1PP1/R2Q1RK1 w kq - 3 10
|
||||
rn1qk2r/ppp2pp1/3bpn1p/8/3P2b1/2PB1N2/PPQ3PP/RNB2RK1 w kq - 2 9
|
||||
r2q1rk1/1b1nppbp/pp4p1/2p5/2BP4/BQP1PN2/P4PPP/2R2RK1 w - - 0 13
|
||||
r1bq1rk1/p4pbp/2p1pnp1/3p4/2P1P3/2N1B3/PP1QBPPP/R3K2R w KQ - 0 11
|
||||
r2qk2r/pp1nbpp1/2p1pn1p/3p1b2/2P5/1P1P1NP1/PB2PPBP/RN1Q1RK1 w kq - 1 9
|
||||
rnbqkb1r/1p3ppp/p2p4/4p3/3NP3/2N1Q3/PPP2PPP/R3KB1R w KQkq - 0 9
|
||||
2kr2nr/ppp3pp/4bp2/1B2p3/1b2P3/1P5P/PBPN1PP1/R1K4R w - - 4 14
|
||||
r1bq1rk1/pp2ppbp/2n2np1/3p4/2PP4/2N2N2/PP2BPPP/R1BQ1RK1 w - - 1 9
|
||||
rn1q1rk1/pp3pbp/3p1np1/3P4/4PBb1/2NB1N2/PP4PP/R2QK2R w KQ - 3 11
|
||||
r2q1rk1/pp2ppbp/2np1np1/2p3B1/2P3b1/P1NPP1P1/1P3PBP/R2QK1NR w KQ - 1 9
|
||||
r1bqk2r/ppp2p2/4p2p/3n2p1/3P4/P4NB1/4QPPP/R3K2R w KQkq - 0 15
|
||||
r4rk1/1pp1qppp/p1pb1n2/4p3/1P2P1b1/2PPBN2/P2N1PPP/R2QK2R w KQ - 1 10
|
||||
r1bq1rk1/pp2bppp/3p1n2/1Np5/2P2Q2/4P3/PP3PPP/R1B1KB1R w KQ - 0 10
|
||||
r1bq1rk1/2p2ppp/p2b1n2/1p2R3/8/1BP4P/PP1P1PP1/RNBQ2K1 w - - 1 13
|
||||
r4rk1/pp3pp1/1qp2nbp/3p4/3N4/2PP4/PP1QBPPP/1R3RK1 w - - 4 18
|
||||
rnb2rk1/ppp1bppp/3p1n2/8/8/3P1N2/PPP1BPPP/RNB1K2R w KQ - 2 9
|
||||
r1b1k2r/p3ppb1/2pp1npp/q7/4P2P/2N1B3/PPPQ1PP1/R3KB1R w KQkq - 0 11
|
||||
r1bq1rk1/1pppnpp1/p6p/n1b1P3/2B2B2/2N2N2/PP3PPP/R2QR1K1 w - - 2 11
|
||||
r1bq1rk1/pp1nbpp1/2p2n1p/3p4/3P3B/2NBPN2/PP3PPP/R2QK2R w KQ - 0 10
|
||||
r1bq1rk1/1p3ppp/p1np4/2p5/4P3/1NP5/P1P1BPPP/R2Q1RK1 w - - 1 12
|
||||
r1bq1rk1/pp2ppbp/2n3p1/2p5/2BPP3/2P1B3/P3NPPP/R2QK2R w KQ - 4 10
|
||||
rnbq1r1k/ppp1b1pp/3p1n2/4p3/4N3/1QPB1N2/PP1P1PPP/R1B2RK1 w - - 3 9
|
||||
r1bq1rk1/4ppbp/2pp1np1/p7/4PP2/2NB4/PPP3PP/R1BQ1R1K w - - 0 11
|
||||
r1b2rk1/1pq2ppp/p1nppn2/2b5/2PNP3/2N1BP2/PP2B1PP/R2Q1RK1 w - - 2 11
|
||||
r4rk1/pp2ppb1/1qpp1npp/8/2P2Pb1/2NPPNP1/PP1Q2BP/1R3RK1 w - - 9 14
|
||||
r1bq1rk1/ppp2pp1/2np1b1p/4p3/2P5/3P1NP1/PP2PPBP/RN1Q1RK1 w - - 2 9
|
||||
r2qr3/p2b1kbp/2p2np1/3pp1B1/Q7/2N2P2/PPP3PP/2KR1B1R w - - 0 15
|
||||
2kr2nr/pppq1ppp/2bb4/8/4P3/5N2/PPP2PPP/RNBQ1RK1 w - - 2 9
|
||||
r4rk1/1pq2ppp/p1nbpn2/3p4/3P4/1PNQPN2/PB3PPP/R4RK1 w - - 1 13
|
||||
rnbqr1k1/pp3pbp/3p1np1/2pP4/2P2P2/2N2N2/PP2B1PP/R1BQK2R w KQ - 1 10
|
||||
r4rk1/pp1nbpp1/1qp1pn1p/7P/3P4/3Q1NN1/PPPB1PP1/1K1RR3 w - - 8 15
|
||||
r1bq1rk1/p1n2pbp/3p2p1/1ppP4/4P3/P4N1P/1PQ1BPP1/R1B1K2R w KQ - 0 13
|
||||
2r2rk1/pp2bpp1/1qn1p3/3pPn1p/1P1P1P2/P2Q1N2/3BN1PP/2R1K2R w K - 5 16
|
||||
r1b2r2/p1n1qpkp/1p1p2p1/2pPp3/2P1P3/PP1B1N2/2Q2PPP/R4RK1 w - - 1 18
|
||||
2rq1rk1/pb1n1pp1/1p3b1p/3p1B2/2pP4/2N1PN2/PP2QPPP/2R2RK1 w - - 0 15
|
||||
r4rk1/pp1q1pbp/2n3p1/3p4/3Pb3/4B1PP/PP1QNPBK/R4R2 w - - 5 16
|
||||
r2qkb1r/pb1n1pp1/1pp2n1p/4pP2/4P3/3BBNP1/PPP4P/RN1Q1RK1 w kq - 0 12
|
||||
r1br2k1/pp1nq1bp/2p2pp1/P1p1p1B1/2N1P3/3P1N1P/1PP2PP1/R2Q1RK1 w - - 0 13
|
||||
r1b1k2r/pp1pbp1p/2q1pp2/2p5/4P3/3P1N2/PPP2PPP/RN1Q1RK1 w kq - 0 9
|
||||
r1b1k2r/pp2ppbp/2n3p1/q7/3PP3/3BB3/P3NPPP/R2QK2R w KQkq - 1 11
|
||||
r1b1k2r/p4p2/2pp3p/2b1p1pn/4P3/2P1PN2/PP2B1PP/R2N1RK1 w kq - 1 17
|
||||
r2qkbnr/pp1npppb/2p4p/7P/3P4/5NN1/PPP2PP1/R1BQKB1R w KQkq - 1 9
|
||||
r1bq1rk1/ppp2pp1/5n1p/2bPp1N1/2P5/8/PPP1QPPP/RNB2RK1 w - - 0 10
|
BIN
Chess-Challenge/resources/Fonts/OPENSANS-SEMIBOLD.TTF
Normal file
BIN
Chess-Challenge/resources/Fonts/OPENSANS-SEMIBOLD.TTF
Normal file
Binary file not shown.
26
Chess-Challenge/resources/Fonts/sdf.fs
Normal file
26
Chess-Challenge/resources/Fonts/sdf.fs
Normal file
|
@ -0,0 +1,26 @@
|
|||
#version 330
|
||||
|
||||
// Input vertex attributes (from vertex shader)
|
||||
in vec2 fragTexCoord;
|
||||
in vec4 fragColor;
|
||||
|
||||
// Input uniform values
|
||||
uniform sampler2D texture0;
|
||||
uniform vec4 colDiffuse;
|
||||
|
||||
// Output fragment color
|
||||
out vec4 finalColor;
|
||||
|
||||
// NOTE: Add here your custom variables
|
||||
|
||||
void main()
|
||||
{
|
||||
// Texel color fetching from texture sampler
|
||||
// NOTE: Calculate alpha using signed distance field (SDF)
|
||||
float distanceFromOutline = texture(texture0, fragTexCoord).a - 0.5;
|
||||
float distanceChangePerFragment = length(vec2(dFdx(distanceFromOutline), dFdy(distanceFromOutline)));
|
||||
float alpha = smoothstep(-distanceChangePerFragment, distanceChangePerFragment, distanceFromOutline);
|
||||
|
||||
// Calculate final fragment color
|
||||
finalColor = vec4(fragColor.rgb, fragColor.a*alpha);
|
||||
}
|
BIN
Chess-Challenge/resources/Pieces.png
Normal file
BIN
Chess-Challenge/resources/Pieces.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 88 KiB |
127
Chess-Challenge/src/API/BitboardHelper.cs
Normal file
127
Chess-Challenge/src/API/BitboardHelper.cs
Normal file
|
@ -0,0 +1,127 @@
|
|||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
using ChessChallenge.Chess;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for working with bitboards.
|
||||
/// Bitboards are represented with the ulong type (unsigned 64 bit integer).
|
||||
/// </summary>
|
||||
public static class BitboardHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the given square on the bitboard to 1.
|
||||
/// </summary>
|
||||
public static void SetSquare(ref ulong bitboard, Square square)
|
||||
{
|
||||
bitboard |= 1ul << square.Index;
|
||||
}
|
||||
/// <summary>
|
||||
/// Clear the given square on the bitboard to 0.
|
||||
/// </summary>
|
||||
public static void ClearSquare(ref ulong bitboard, Square square)
|
||||
{
|
||||
bitboard &= ~(1ul << square.Index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggle the given square on the bitboard between 0 and 1.
|
||||
/// </summary>
|
||||
public static void ToggleSquare(ref ulong bitboard, Square square)
|
||||
{
|
||||
bitboard ^= 1ul << square.Index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given square is set to 1 on the bitboard, otherwise false.
|
||||
/// </summary>
|
||||
public static bool SquareIsSet(ulong bitboard, Square square)
|
||||
{
|
||||
return ((bitboard >> square.Index) & 1) != 0;
|
||||
}
|
||||
|
||||
public static int ClearAndGetIndexOfLSB(ref ulong bitboard)
|
||||
{
|
||||
return BitBoardUtility.PopLSB(ref bitboard);
|
||||
}
|
||||
|
||||
public static int GetNumberOfSetBits(ulong bitboard)
|
||||
{
|
||||
return BitBoardUtility.PopCount(bitboard);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a bitboard where each bit that is set to 1 represents a square that the given
|
||||
/// sliding piece type is able to attack. These attacks are calculated from the given square,
|
||||
/// and take the given board state into account (so attacks will be blocked by pieces that are in the way).
|
||||
/// Valid only for sliding piece types (queen, rook, and bishop).
|
||||
/// </summary>
|
||||
public static ulong GetSliderAttacks(PieceType pieceType, Square square, Board board)
|
||||
{
|
||||
return pieceType switch
|
||||
{
|
||||
PieceType.Rook => GetRookAttacks(square, board.AllPiecesBitboard),
|
||||
PieceType.Bishop => GetBishopAttacks(square, board.AllPiecesBitboard),
|
||||
PieceType.Queen => GetQueenAttacks(square, board.AllPiecesBitboard),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a bitboard where each bit that is set to 1 represents a square that the given
|
||||
/// sliding piece type is able to attack. These attacks are calculated from the given square,
|
||||
/// and take the given blocker bitboard into account (so attacks will be blocked by pieces that are in the way).
|
||||
/// Valid only for sliding piece types (queen, rook, and bishop).
|
||||
/// </summary>
|
||||
public static ulong GetSliderAttacks(PieceType pieceType, Square square, ulong blockers)
|
||||
{
|
||||
return pieceType switch
|
||||
{
|
||||
PieceType.Rook => GetRookAttacks(square, blockers),
|
||||
PieceType.Bishop => GetBishopAttacks(square, blockers),
|
||||
PieceType.Queen => GetQueenAttacks(square, blockers),
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a bitboard of squares that a knight can attack from the given square.
|
||||
/// </summary>
|
||||
public static ulong GetKnightAttacks(Square square) => Bits.KnightAttacks[square.Index];
|
||||
/// <summary>
|
||||
/// Gets a bitboard of squares that a king can attack from the given square.
|
||||
/// </summary>
|
||||
public static ulong GetKingAttacks(Square square) => Bits.KingMoves[square.Index];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a bitboard of squares that a pawn (of the given colour) can attack from the given square.
|
||||
/// </summary>
|
||||
public static ulong GetPawnAttacks(Square square, bool isWhite)
|
||||
{
|
||||
return isWhite ? Bits.WhitePawnAttacks[square.Index] : Bits.BlackPawnAttacks[square.Index];
|
||||
}
|
||||
static ulong GetRookAttacks(Square square, ulong blockers)
|
||||
{
|
||||
ulong mask = Magic.RookMask[square.Index];
|
||||
ulong magic = PrecomputedMagics.RookMagics[square.Index];
|
||||
int shift = PrecomputedMagics.RookShifts[square.Index];
|
||||
|
||||
ulong key = ((blockers & mask) * magic) >> shift;
|
||||
return Magic.RookAttacks[square.Index][key];
|
||||
}
|
||||
|
||||
static ulong GetBishopAttacks(Square square, ulong blockers)
|
||||
{
|
||||
ulong mask = Magic.BishopMask[square.Index];
|
||||
ulong magic = PrecomputedMagics.BishopMagics[square.Index];
|
||||
int shift = PrecomputedMagics.BishopShifts[square.Index];
|
||||
|
||||
ulong key = ((blockers & mask) * magic) >> shift;
|
||||
return Magic.BishopAttacks[square.Index][key];
|
||||
}
|
||||
|
||||
static ulong GetQueenAttacks(Square square, ulong blockers)
|
||||
{
|
||||
return GetRookAttacks(square, blockers) | GetBishopAttacks(square, blockers);
|
||||
}
|
||||
}
|
||||
}
|
275
Chess-Challenge/src/API/Board.cs
Normal file
275
Chess-Challenge/src/API/Board.cs
Normal file
|
@ -0,0 +1,275 @@
|
|||
namespace ChessChallenge.API
|
||||
{
|
||||
using ChessChallenge.Application.APIHelpers;
|
||||
using ChessChallenge.Chess;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public sealed class Board
|
||||
{
|
||||
readonly Chess.Board board;
|
||||
readonly APIMoveGen moveGen;
|
||||
|
||||
readonly HashSet<ulong> repetitionHistory;
|
||||
readonly PieceList[] allPieceLists;
|
||||
readonly PieceList[] validPieceLists;
|
||||
readonly Piece[] pieces;
|
||||
|
||||
Move[] cachedLegalMoves;
|
||||
bool hasCachedMoves;
|
||||
Move[] cachedLegalCaptureMoves;
|
||||
bool hasCachedCaptureMoves;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new board. Note: this should not be used in the challenge,
|
||||
/// use the board provided in the Think method instead.
|
||||
/// </summary>
|
||||
public Board(Chess.Board board)
|
||||
{
|
||||
this.board = board;
|
||||
moveGen = new APIMoveGen();
|
||||
cachedLegalMoves = Array.Empty<Move>();
|
||||
cachedLegalCaptureMoves = Array.Empty<Move>();
|
||||
|
||||
// Init piece lists
|
||||
List<PieceList> validPieceLists = new();
|
||||
allPieceLists = new PieceList[board.pieceLists.Length];
|
||||
for (int i = 0; i < board.pieceLists.Length; i++)
|
||||
{
|
||||
if (board.pieceLists[i] != null)
|
||||
{
|
||||
allPieceLists[i] = new PieceList(board.pieceLists[i], this, i);
|
||||
validPieceLists.Add(allPieceLists[i]);
|
||||
}
|
||||
}
|
||||
this.validPieceLists = validPieceLists.ToArray();
|
||||
|
||||
// Init rep history
|
||||
repetitionHistory = new HashSet<ulong>(board.RepetitionPositionHistory);
|
||||
repetitionHistory.Remove(board.ZobristKey);
|
||||
|
||||
// Create piece array
|
||||
pieces = new Piece[64];
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
int p = board.Square[i];
|
||||
pieces[i] = new Piece((PieceType)PieceHelper.PieceType(p), PieceHelper.IsWhite(p), new Square(i));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the board state with the given move.
|
||||
/// The move is assumed to be legal, and may result in errors if it is not.
|
||||
/// Can be undone with the UndoMove method.
|
||||
/// </summary>
|
||||
public void MakeMove(Move move)
|
||||
{
|
||||
hasCachedMoves = false;
|
||||
hasCachedCaptureMoves = false;
|
||||
if (!move.IsNull)
|
||||
{
|
||||
repetitionHistory.Add(board.ZobristKey);
|
||||
board.MakeMove(new Chess.Move(move.RawValue), inSearch: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Undo a move that was made with the MakeMove method
|
||||
/// </summary>
|
||||
public void UndoMove(Move move)
|
||||
{
|
||||
hasCachedMoves = false;
|
||||
hasCachedCaptureMoves = false;
|
||||
if (!move.IsNull)
|
||||
{
|
||||
board.UndoMove(new Chess.Move(move.RawValue), inSearch: true);
|
||||
repetitionHistory.Remove(board.ZobristKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try skip the current turn
|
||||
/// This will fail and return false if in check
|
||||
/// Note: skipping a turn is not allowed in the game, but it can be used as a search technique
|
||||
/// </summary>
|
||||
public bool TrySkipTurn()
|
||||
{
|
||||
if (IsInCheck())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
hasCachedMoves = false;
|
||||
hasCachedCaptureMoves = false;
|
||||
board.MakeNullMove();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Undo a turn that was succesfully skipped with the TrySkipTurn method
|
||||
/// </summary>
|
||||
public void UndoSkipTurn()
|
||||
{
|
||||
hasCachedMoves = false;
|
||||
hasCachedCaptureMoves = false;
|
||||
board.UnmakeNullMove();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an array of the legal moves in the current position.
|
||||
/// Can choose to get only capture moves with the optional 'capturesOnly' parameter.
|
||||
/// </summary>
|
||||
public Move[] GetLegalMoves(bool capturesOnly = false)
|
||||
{
|
||||
if (capturesOnly)
|
||||
{
|
||||
return GetLegalCaptureMoves();
|
||||
}
|
||||
|
||||
if (!hasCachedMoves)
|
||||
{
|
||||
cachedLegalMoves = moveGen.GenerateMoves(board, includeQuietMoves: true);
|
||||
hasCachedMoves = true;
|
||||
}
|
||||
|
||||
return cachedLegalMoves;
|
||||
}
|
||||
|
||||
|
||||
Move[] GetLegalCaptureMoves()
|
||||
{
|
||||
if (!hasCachedCaptureMoves)
|
||||
{
|
||||
cachedLegalCaptureMoves = moveGen.GenerateMoves(board, includeQuietMoves: false);
|
||||
hasCachedCaptureMoves = true;
|
||||
}
|
||||
return cachedLegalCaptureMoves;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if the player to move is in check in the current position.
|
||||
/// </summary>
|
||||
public bool IsInCheck() => board.IsInCheck();
|
||||
|
||||
/// <summary>
|
||||
/// Test if the current position is checkmate
|
||||
/// </summary>
|
||||
public bool IsInCheckmate() => IsInCheck() && GetLegalMoves().Length == 0;
|
||||
|
||||
/// <summary>
|
||||
/// Test if the current position is a draw due stalemate,
|
||||
/// 3-fold repetition, insufficient material, or 50-move rule.
|
||||
/// </summary>
|
||||
public bool IsDraw()
|
||||
{
|
||||
return IsFiftyMoveDraw() || Arbiter.InsufficentMaterial(board) || IsInStalemate() || IsRepetition();
|
||||
|
||||
bool IsInStalemate() => !IsInCheck() && GetLegalMoves().Length == 0;
|
||||
bool IsFiftyMoveDraw() => board.currentGameState.fiftyMoveCounter >= 100;
|
||||
bool IsRepetition() => repetitionHistory.Contains(board.ZobristKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does the given player still have the right to castle kingside?
|
||||
/// Note that having the right to castle doesn't necessarily mean castling is legal right now
|
||||
/// (for example, a piece might be in the way, or player might be in check, etc).
|
||||
/// </summary>
|
||||
public bool HasKingsideCastleRight(bool white) => board.currentGameState.HasKingsideCastleRight(white);
|
||||
|
||||
/// <summary>
|
||||
/// Does the given player still have the right to castle queenside?
|
||||
/// Note that having the right to castle doesn't necessarily mean castling is legal right now
|
||||
/// (for example, a piece might be in the way, or player might be in check, etc).
|
||||
/// </summary>
|
||||
public bool HasQueensideCastleRight(bool white) => board.currentGameState.HasQueensideCastleRight(white);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the square that the king (of the given colour) is currently on.
|
||||
/// </summary>
|
||||
public Square GetKingSquare(bool white)
|
||||
{
|
||||
int colIndex = white ? Chess.Board.WhiteIndex : Chess.Board.BlackIndex;
|
||||
return new Square(board.KingSquare[colIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the piece on the given square. If the square is empty, the piece will have a PieceType of None.
|
||||
/// </summary>
|
||||
public Piece GetPiece(Square square)
|
||||
{
|
||||
return pieces[square.Index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of pieces of the given type and colour
|
||||
/// </summary>
|
||||
public PieceList GetPieceList(PieceType pieceType, bool white)
|
||||
{
|
||||
return allPieceLists[PieceHelper.MakePiece((int)pieceType, white)];
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets an array of all the piece lists. In order these are:
|
||||
/// Pawns(white), Knights (white), Bishops (white), Rooks (white), Queens (white), King (white),
|
||||
/// Pawns (white), Knights (black), Bishops (black), Rooks (black), Queens (black), King (black)
|
||||
/// </summary>
|
||||
public PieceList[] GetAllPieceLists()
|
||||
{
|
||||
return validPieceLists;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the given square attacked by the opponent?
|
||||
/// (opponent being whichever player doesn't currently have the right to move)
|
||||
/// </summary>
|
||||
public bool SquareIsAttackedByOpponent(Square square)
|
||||
{
|
||||
if (!hasCachedMoves)
|
||||
{
|
||||
GetLegalMoves();
|
||||
}
|
||||
return BitboardHelper.SquareIsSet(moveGen.opponentAttackMap, square);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// FEN representation of the current position
|
||||
/// </summary>
|
||||
public string GetFenString() => FenUtility.CurrentFen(board);
|
||||
|
||||
/// <summary>
|
||||
/// 64-bit number where each bit that is set to 1 represents a
|
||||
/// square that contains a piece of the given type and colour.
|
||||
/// </summary>
|
||||
public ulong GetPieceBitboard(PieceType pieceType, bool white)
|
||||
{
|
||||
return board.pieceBitboards[PieceHelper.MakePiece((int)pieceType, white)];
|
||||
}
|
||||
/// <summary>
|
||||
/// 64-bit number where each bit that is set to 1 represents a square that contains any type of white piece.
|
||||
/// </summary>
|
||||
public ulong WhitePiecesBitboard => board.colourBitboards[Chess.Board.WhiteIndex];
|
||||
/// <summary>
|
||||
/// 64-bit number where each bit that is set to 1 represents a square that contains any type of black piece.
|
||||
/// </summary>
|
||||
public ulong BlackPiecesBitboard => board.colourBitboards[Chess.Board.BlackIndex];
|
||||
|
||||
/// <summary>
|
||||
/// 64-bit number where each bit that is set to 1 represents a
|
||||
/// square that contains a piece of any type or colour.
|
||||
/// </summary>
|
||||
public ulong AllPiecesBitboard => board.allPiecesBitboard;
|
||||
|
||||
|
||||
public bool IsWhiteToMove => board.IsWhiteToMove;
|
||||
|
||||
/// <summary>
|
||||
/// Number of ply (a single move by either white or black) played so far
|
||||
/// </summary>
|
||||
public int PlyCount => board.plyCount;
|
||||
|
||||
/// <summary>
|
||||
/// 64-bit hash of the current position
|
||||
/// </summary>
|
||||
public ulong ZobristKey => board.ZobristKey;
|
||||
|
||||
}
|
||||
}
|
8
Chess-Challenge/src/API/IChessBot.cs
Normal file
8
Chess-Challenge/src/API/IChessBot.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
public interface IChessBot
|
||||
{
|
||||
Move Think(Board board, Timer timer);
|
||||
}
|
||||
}
|
76
Chess-Challenge/src/API/Move.cs
Normal file
76
Chess-Challenge/src/API/Move.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using ChessChallenge.Chess;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
public readonly struct Move : IEquatable<Move>
|
||||
{
|
||||
public Square StartSquare => new Square(move.StartSquareIndex);
|
||||
public Square TargetSquare => new Square(move.TargetSquareIndex);
|
||||
public PieceType MovePieceType => (PieceType)(pieceTypeData & 0b111);
|
||||
public PieceType CapturePieceType => (PieceType)(pieceTypeData >> 3);
|
||||
public PieceType PromotionPieceType => (PieceType)move.PromotionPieceType;
|
||||
public bool IsCapture => (pieceTypeData >> 3) != 0;
|
||||
public bool IsEnPassant => move.MoveFlag == Chess.Move.EnPassantCaptureFlag;
|
||||
|
||||
public bool IsPromotion => move.IsPromotion;
|
||||
public bool IsCastles => move.MoveFlag == Chess.Move.CastleFlag;
|
||||
public bool IsNull => move.IsNull;
|
||||
public ushort RawValue => move.Value;
|
||||
public static readonly Move NullMove = new();
|
||||
|
||||
readonly Chess.Move move;
|
||||
readonly ushort pieceTypeData;
|
||||
|
||||
/// <summary>
|
||||
/// Create a null/invalid move.
|
||||
/// This is simply an invalid move that can be used as a placeholder until a valid move has been found
|
||||
/// </summary>
|
||||
public Move()
|
||||
{
|
||||
move = Chess.Move.NullMove;
|
||||
pieceTypeData = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a move from UCI notation, for example: "e2e4" to move a piece from e2 to e4.
|
||||
/// If promoting, piece type must be included, for example: "d7d8q".
|
||||
/// </summary>
|
||||
public Move(string moveName, Board board)
|
||||
{
|
||||
var data = Application.APIHelpers.MoveHelper.CreateMoveFromName(moveName, board);
|
||||
move = data.move;
|
||||
pieceTypeData = (ushort)((int)data.pieceType | ((int)data.captureType << 3));
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal move constructor. Do not use.
|
||||
/// </summary>
|
||||
public Move(Chess.Move move, int movePieceType, int capturePieceType)
|
||||
{
|
||||
this.move = move;
|
||||
pieceTypeData = (ushort)(movePieceType | (capturePieceType << 3));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string moveName = MoveUtility.GetMoveNameUCI(move);
|
||||
return $"Move: '{moveName}'";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if two moves are the same.
|
||||
/// This is true if they move to/from the same square, and move/capture/promote the same piece type
|
||||
/// </summary>
|
||||
public bool Equals(Move other)
|
||||
{
|
||||
return RawValue == other.RawValue && pieceTypeData == other.pieceTypeData;
|
||||
}
|
||||
|
||||
public static bool operator ==(Move lhs, Move rhs) => lhs.Equals(rhs);
|
||||
public static bool operator !=(Move lhs, Move rhs) => !lhs.Equals(rhs);
|
||||
public override bool Equals(object? obj) => base.Equals(obj);
|
||||
public override int GetHashCode() => RawValue;
|
||||
}
|
||||
}
|
55
Chess-Challenge/src/API/Piece.cs
Normal file
55
Chess-Challenge/src/API/Piece.cs
Normal file
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
public readonly struct Piece : IEquatable<Piece>
|
||||
{
|
||||
public readonly bool IsWhite;
|
||||
public readonly PieceType PieceType;
|
||||
/// <summary>
|
||||
/// The square that the piece is on. Note that this value will not be updated if the
|
||||
/// piece is moved, it is a snapshot of the state of the piece when it was looked up.
|
||||
/// </summary>
|
||||
public readonly Square Square;
|
||||
|
||||
public bool IsNull => PieceType is PieceType.None;
|
||||
public bool IsRook => PieceType is PieceType.Rook;
|
||||
public bool IsKnight => PieceType is PieceType.Knight;
|
||||
public bool IsBishop => PieceType is PieceType.Bishop;
|
||||
public bool IsQueen => PieceType is PieceType.Queen;
|
||||
public bool IsKing => PieceType is PieceType.King;
|
||||
public bool IsPawn => PieceType is PieceType.Pawn;
|
||||
|
||||
/// <summary>
|
||||
/// Create a piece from its type, colour, and square
|
||||
/// </summary>
|
||||
public Piece(PieceType pieceType, bool isWhite, Square square)
|
||||
{
|
||||
PieceType = pieceType;
|
||||
Square = square;
|
||||
IsWhite = isWhite;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (IsNull)
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
string col = IsWhite ? "White" : "Black";
|
||||
return $"{col} {PieceType}";
|
||||
}
|
||||
|
||||
// Comparisons:
|
||||
public static bool operator ==(Piece lhs, Piece rhs) => lhs.Equals(rhs);
|
||||
public static bool operator !=(Piece lhs, Piece rhs) => !lhs.Equals(rhs);
|
||||
public override bool Equals(object? obj) => base.Equals(obj);
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
|
||||
public bool Equals(Piece other)
|
||||
{
|
||||
return IsWhite == other.IsWhite && PieceType == other.PieceType && Square == other.Square;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
48
Chess-Challenge/src/API/PieceList.cs
Normal file
48
Chess-Challenge/src/API/PieceList.cs
Normal file
|
@ -0,0 +1,48 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
/// <summary>
|
||||
/// A special list for storing pieces of a particular type and colour
|
||||
/// </summary>
|
||||
public sealed class PieceList : IEnumerable<Piece>
|
||||
{
|
||||
public int Count => list.Count;
|
||||
public readonly bool IsWhitePieceList;
|
||||
public readonly PieceType TypeOfPieceInList;
|
||||
public Piece GetPiece(int index) => this[index];
|
||||
|
||||
readonly Chess.PieceList list;
|
||||
readonly Board board;
|
||||
|
||||
/// <summary>
|
||||
/// Piece List constructor (you shouldn't be creating your own piece lists in
|
||||
/// this challenge, but rather accessing the existing lists from the board).
|
||||
/// </summary>
|
||||
public PieceList(Chess.PieceList list, Board board, int piece)
|
||||
{
|
||||
this.board = board;
|
||||
this.list = list;
|
||||
TypeOfPieceInList = (PieceType)Chess.PieceHelper.PieceType(piece);
|
||||
IsWhitePieceList = Chess.PieceHelper.IsWhite(piece);
|
||||
}
|
||||
|
||||
|
||||
public Piece this[int index] => board.GetPiece(new Square(list[index]));
|
||||
|
||||
// Allow piece list to be iterated over with 'foreach'
|
||||
public IEnumerator<Piece> GetEnumerator()
|
||||
{
|
||||
for (int i = 0; i < Count; i++)
|
||||
{
|
||||
yield return GetPiece(i);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
13
Chess-Challenge/src/API/PieceType.cs
Normal file
13
Chess-Challenge/src/API/PieceType.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace ChessChallenge.API
|
||||
{
|
||||
public enum PieceType
|
||||
{
|
||||
None, // 0
|
||||
Pawn, // 1
|
||||
Knight, // 2
|
||||
Bishop, // 3
|
||||
Rook, // 4
|
||||
Queen, // 5
|
||||
King // 6
|
||||
}
|
||||
}
|
62
Chess-Challenge/src/API/Square.cs
Normal file
62
Chess-Challenge/src/API/Square.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
public readonly struct Square : IEquatable<Square>
|
||||
{
|
||||
/// <summary>
|
||||
/// Value from 0 to 7 representing files 'a' to 'h'
|
||||
/// </summary>
|
||||
public int File => Chess.BoardHelper.FileIndex(Index);
|
||||
/// <summary>
|
||||
/// Value from 0 to 7 representing ranks '1' to '8'
|
||||
/// </summary>
|
||||
public int Rank => Chess.BoardHelper.RankIndex(Index);
|
||||
/// <summary>
|
||||
/// Value from 0 to 63. The values map to the board like so:
|
||||
/// 0 – 7 : a1 – h1, 8 – 15 : a2 – h2, ..., 56 – 63 : a8 – h8
|
||||
/// </summary>
|
||||
public readonly int Index;
|
||||
|
||||
/// <summary>
|
||||
/// The algebraic name of the square, e.g. "e4"
|
||||
/// </summary>
|
||||
public string Name => Chess.BoardHelper.SquareNameFromIndex(Index);
|
||||
|
||||
/// <summary>
|
||||
/// Create a square from its algebraic name, e.g. "e4"
|
||||
/// </summary>
|
||||
public Square(string name)
|
||||
{
|
||||
Index = Chess.BoardHelper.SquareIndexFromName(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a square from an index [0, 63]
|
||||
/// </summary>
|
||||
public Square(int index)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a square from a file and rank [0, 7]
|
||||
/// </summary>
|
||||
public Square(int file, int rank)
|
||||
{
|
||||
Index = Chess.BoardHelper.IndexFromCoord(file, rank);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"'{Name}' (Index = {Index}, File = {File}, Rank = {Rank})";
|
||||
}
|
||||
|
||||
// Comparisons
|
||||
public bool Equals(Square other) => Index == other.Index;
|
||||
public static bool operator ==(Square lhs, Square rhs) => lhs.Equals(rhs);
|
||||
public static bool operator !=(Square lhs, Square rhs) => !lhs.Equals(rhs);
|
||||
public override bool Equals(object? obj) => base.Equals(obj);
|
||||
public override int GetHashCode() => base.GetHashCode();
|
||||
}
|
||||
}
|
26
Chess-Challenge/src/API/Timer.cs
Normal file
26
Chess-Challenge/src/API/Timer.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
|
||||
namespace ChessChallenge.API
|
||||
{
|
||||
public sealed class Timer
|
||||
{
|
||||
/// <summary>
|
||||
/// Amount of time left on clock for current player (in milliseconds)
|
||||
/// </summary>
|
||||
public int MillisecondsRemaining => Math.Max(0, initialMillisRemaining - (int)sw.ElapsedMilliseconds);
|
||||
/// <summary>
|
||||
/// Amount of time elapsed since current player started thinking (in milliseconds)
|
||||
/// </summary>
|
||||
public int MillisecondsElapsedThisTurn => (int)sw.ElapsedMilliseconds;
|
||||
|
||||
System.Diagnostics.Stopwatch sw;
|
||||
readonly int initialMillisRemaining;
|
||||
|
||||
public Timer(int millisRemaining)
|
||||
{
|
||||
initialMillisRemaining = millisRemaining;
|
||||
sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
54
Chess-Challenge/src/Evil Bot/EvilBot.cs
Normal file
54
Chess-Challenge/src/Evil Bot/EvilBot.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using ChessChallenge.API;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.Example
|
||||
{
|
||||
// A simple bot that can spot mate in one, and always captures the most valuable piece it can.
|
||||
// Plays randomly otherwise.
|
||||
public class EvilBot : IChessBot
|
||||
{
|
||||
// Piece values: null, pawn, knight, bishop, rook, queen, king
|
||||
int[] pieceValues = { 0, 100, 300, 300, 500, 900, 10000 };
|
||||
|
||||
public Move Think(Board board, Timer timer)
|
||||
{
|
||||
Move[] allMoves = board.GetLegalMoves();
|
||||
|
||||
// Pick a random move to play if nothing better is found
|
||||
Random rng = new();
|
||||
Move moveToPlay = allMoves[rng.Next(allMoves.Length)];
|
||||
int highestValueCapture = 0;
|
||||
|
||||
foreach (Move move in allMoves)
|
||||
{
|
||||
// Always play checkmate in one
|
||||
if (MoveIsCheckmate(board, move))
|
||||
{
|
||||
moveToPlay = move;
|
||||
break;
|
||||
}
|
||||
|
||||
// Find highest value capture
|
||||
Piece capturedPiece = board.GetPiece(move.TargetSquare);
|
||||
int capturedPieceValue = pieceValues[(int)capturedPiece.PieceType];
|
||||
|
||||
if (capturedPieceValue > highestValueCapture)
|
||||
{
|
||||
moveToPlay = move;
|
||||
highestValueCapture = capturedPieceValue;
|
||||
}
|
||||
}
|
||||
|
||||
return moveToPlay;
|
||||
}
|
||||
|
||||
// Test if this move gives checkmate
|
||||
bool MoveIsCheckmate(Board board, Move move)
|
||||
{
|
||||
board.MakeMove(move);
|
||||
bool isMate = board.IsInCheckmate();
|
||||
board.UndoMove(move);
|
||||
return isMate;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,451 @@
|
|||
using ChessChallenge.Chess;
|
||||
using ChessChallenge.Example;
|
||||
using Raylib_cs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static ChessChallenge.Application.Settings;
|
||||
using static ChessChallenge.Application.ConsoleHelper;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public class ChallengeController
|
||||
{
|
||||
public enum PlayerType
|
||||
{
|
||||
Human,
|
||||
MyBot,
|
||||
EvilBot
|
||||
}
|
||||
|
||||
// Game state
|
||||
int gameID;
|
||||
bool isPlaying;
|
||||
Board board;
|
||||
public ChessPlayer PlayerWhite { get; private set; }
|
||||
public ChessPlayer PlayerBlack {get;private set;}
|
||||
|
||||
float lastMoveMadeTime;
|
||||
bool isWaitingToPlayMove;
|
||||
Move moveToPlay;
|
||||
float playMoveTime;
|
||||
public bool HumanWasWhiteLastGame { get; private set; }
|
||||
|
||||
// Bot match state
|
||||
readonly string[] botMatchStartFens;
|
||||
int botMatchGameIndex;
|
||||
public BotMatchStats BotStatsA { get; private set; }
|
||||
public BotMatchStats BotStatsB {get;private set;}
|
||||
bool botAPlaysWhite;
|
||||
|
||||
|
||||
// Bot task
|
||||
AutoResetEvent botTaskWaitHandle;
|
||||
bool hasBotTaskException;
|
||||
ExceptionDispatchInfo botExInfo;
|
||||
|
||||
// Other
|
||||
readonly BoardUI boardUI;
|
||||
readonly MoveGenerator moveGenerator;
|
||||
readonly int tokenCount;
|
||||
readonly StringBuilder pgns;
|
||||
|
||||
public ChallengeController()
|
||||
{
|
||||
tokenCount = GetTokenCount();
|
||||
Warmer.Warm();
|
||||
|
||||
moveGenerator = new();
|
||||
boardUI = new BoardUI();
|
||||
board = new Board();
|
||||
pgns = new();
|
||||
|
||||
BotStatsA = new BotMatchStats("IBot");
|
||||
BotStatsB = new BotMatchStats("IBot");
|
||||
botMatchStartFens = FileHelper.ReadResourceFile("Fens.txt").Split('\n');
|
||||
botTaskWaitHandle = new AutoResetEvent(false);
|
||||
|
||||
StartNewGame(PlayerType.Human, PlayerType.MyBot);
|
||||
}
|
||||
|
||||
public void StartNewGame(PlayerType whiteType, PlayerType blackType)
|
||||
{
|
||||
// End any ongoing game
|
||||
EndGame(GameResult.DrawByArbiter, log: false, autoStartNextBotMatch: false);
|
||||
gameID++;
|
||||
|
||||
// Stop prev task and create a new one
|
||||
if (RunBotsOnSeparateThread)
|
||||
{
|
||||
// Allow task to terminate
|
||||
botTaskWaitHandle.Set();
|
||||
// Create new task
|
||||
botTaskWaitHandle = new AutoResetEvent(false);
|
||||
Task.Factory.StartNew(BotThinkerThread, TaskCreationOptions.LongRunning);
|
||||
}
|
||||
// Board Setup
|
||||
board = new Board();
|
||||
bool isGameWithHuman = whiteType is PlayerType.Human || blackType is PlayerType.Human;
|
||||
int fenIndex = isGameWithHuman ? 0 : botMatchGameIndex / 2;
|
||||
board.LoadPosition(botMatchStartFens[fenIndex]);
|
||||
|
||||
// Player Setup
|
||||
PlayerWhite = CreatePlayer(whiteType);
|
||||
PlayerBlack = CreatePlayer(blackType);
|
||||
PlayerWhite.SubscribeToMoveChosenEventIfHuman(OnMoveChosen);
|
||||
PlayerBlack.SubscribeToMoveChosenEventIfHuman(OnMoveChosen);
|
||||
|
||||
// UI Setup
|
||||
boardUI.UpdatePosition(board);
|
||||
boardUI.ResetSquareColours();
|
||||
SetBoardPerspective();
|
||||
|
||||
// Start
|
||||
isPlaying = true;
|
||||
NotifyTurnToMove();
|
||||
}
|
||||
|
||||
void BotThinkerThread()
|
||||
{
|
||||
int threadID = gameID;
|
||||
//Console.WriteLine("Starting thread: " + threadID);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Sleep thread until notified
|
||||
botTaskWaitHandle.WaitOne();
|
||||
// Get bot move
|
||||
if (threadID == gameID)
|
||||
{
|
||||
var move = GetBotMove();
|
||||
|
||||
if (threadID == gameID)
|
||||
{
|
||||
OnMoveChosen(move);
|
||||
}
|
||||
}
|
||||
// Terminate if no longer playing this game
|
||||
if (threadID != gameID)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Console.WriteLine("Exitting thread: " + threadID);
|
||||
}
|
||||
|
||||
Move GetBotMove()
|
||||
{
|
||||
// Board b = new Board();
|
||||
// b.LoadPosition(FenUtility.CurrentFen(board));
|
||||
API.Board botBoard = new(new(board));
|
||||
try
|
||||
{
|
||||
API.Timer timer = new(PlayerToMove.TimeRemainingMs);
|
||||
API.Move move = PlayerToMove.Bot.Think(botBoard, timer);
|
||||
return new Move(move.RawValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log("An error occurred while bot was thinking.\n" + e.ToString(), true, ConsoleColor.Red);
|
||||
hasBotTaskException = true;
|
||||
botExInfo = ExceptionDispatchInfo.Capture(e);
|
||||
}
|
||||
return Move.NullMove;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void NotifyTurnToMove()
|
||||
{
|
||||
//playerToMove.NotifyTurnToMove(board);
|
||||
if (PlayerToMove.IsHuman)
|
||||
{
|
||||
PlayerToMove.Human.SetPosition(FenUtility.CurrentFen(board));
|
||||
PlayerToMove.Human.NotifyTurnToMove();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RunBotsOnSeparateThread)
|
||||
{
|
||||
botTaskWaitHandle.Set();
|
||||
}
|
||||
else
|
||||
{
|
||||
double startThinkTime = Raylib.GetTime();
|
||||
var move = GetBotMove();
|
||||
double thinkDuration = Raylib.GetTime() - startThinkTime;
|
||||
PlayerToMove.UpdateClock(thinkDuration);
|
||||
OnMoveChosen(move);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetBoardPerspective()
|
||||
{
|
||||
// Board perspective
|
||||
if (PlayerWhite.IsHuman || PlayerBlack.IsHuman)
|
||||
{
|
||||
boardUI.SetPerspective(PlayerWhite.IsHuman);
|
||||
HumanWasWhiteLastGame = PlayerWhite.IsHuman;
|
||||
}
|
||||
else if (PlayerWhite.Bot is MyBot && PlayerBlack.Bot is MyBot)
|
||||
{
|
||||
boardUI.SetPerspective(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
boardUI.SetPerspective(PlayerWhite.Bot is MyBot);
|
||||
}
|
||||
}
|
||||
|
||||
ChessPlayer CreatePlayer(PlayerType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PlayerType.MyBot => new ChessPlayer(new MyBot(), type, GameDurationMilliseconds),
|
||||
PlayerType.EvilBot => new ChessPlayer(new EvilBot(), type, GameDurationMilliseconds),
|
||||
_ => new ChessPlayer(new HumanPlayer(boardUI), type)
|
||||
};
|
||||
}
|
||||
|
||||
static int GetTokenCount()
|
||||
{
|
||||
string path = Path.Combine(Directory.GetCurrentDirectory(), "src", "My Bot", "MyBot.cs");
|
||||
|
||||
using StreamReader reader = new(path);
|
||||
string txt = reader.ReadToEnd();
|
||||
return TokenCounter.CountTokens(txt);
|
||||
}
|
||||
|
||||
void OnMoveChosen(Move chosenMove)
|
||||
{
|
||||
if (IsLegal(chosenMove))
|
||||
{
|
||||
if (PlayerToMove.IsBot)
|
||||
{
|
||||
moveToPlay = chosenMove;
|
||||
isWaitingToPlayMove = true;
|
||||
const float minDelay = 0.1f;
|
||||
playMoveTime = lastMoveMadeTime + minDelay;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayMove(chosenMove);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string moveName = MoveUtility.GetMoveNameUCI(chosenMove);
|
||||
string log = $"Illegal move: {moveName} in position: {FenUtility.CurrentFen(board)}";
|
||||
Log(log, true, ConsoleColor.Red);
|
||||
GameResult result = PlayerToMove == PlayerWhite ? GameResult.WhiteIllegalMove : GameResult.BlackIllegalMove;
|
||||
EndGame(result);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayMove(Move move)
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
bool animate = PlayerToMove.IsBot;
|
||||
lastMoveMadeTime = (float)Raylib.GetTime();
|
||||
|
||||
board.MakeMove(move, false);
|
||||
boardUI.UpdatePosition(board, move, animate);
|
||||
|
||||
GameResult result = Arbiter.GetGameState(board);
|
||||
if (result == GameResult.InProgress)
|
||||
{
|
||||
NotifyTurnToMove();
|
||||
}
|
||||
else
|
||||
{
|
||||
EndGame(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EndGame(GameResult result, bool log = true, bool autoStartNextBotMatch = true)
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
isPlaying = false;
|
||||
isWaitingToPlayMove = false;
|
||||
|
||||
if (log)
|
||||
{
|
||||
Log("Game Over: " + result, false, ConsoleColor.Blue);
|
||||
}
|
||||
|
||||
string pgn = PGNCreator.CreatePGN(board, result, GetPlayerName(PlayerWhite), GetPlayerName(PlayerBlack));
|
||||
pgns.AppendLine(pgn);
|
||||
|
||||
// If 2 bots playing each other, start next game automatically.
|
||||
if (PlayerWhite.IsBot && PlayerBlack.IsBot)
|
||||
{
|
||||
UpdateBotMatchStats(result);
|
||||
botMatchGameIndex++;
|
||||
int numGamesToPlay = botMatchStartFens.Length * 2;
|
||||
|
||||
if (botMatchGameIndex < numGamesToPlay && autoStartNextBotMatch)
|
||||
{
|
||||
botAPlaysWhite = !botAPlaysWhite;
|
||||
const int startNextGameDelayMs = 600;
|
||||
System.Timers.Timer autoNextTimer = new(startNextGameDelayMs);
|
||||
int originalGameID = gameID;
|
||||
autoNextTimer.Elapsed += (s, e) => AutoStartNextBotMatchGame(originalGameID, autoNextTimer);
|
||||
autoNextTimer.AutoReset = false;
|
||||
autoNextTimer.Start();
|
||||
|
||||
}
|
||||
else if (autoStartNextBotMatch)
|
||||
{
|
||||
Log("Match finished", false, ConsoleColor.Blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoStartNextBotMatchGame(int originalGameID, System.Timers.Timer timer)
|
||||
{
|
||||
if (originalGameID == gameID)
|
||||
{
|
||||
StartNewGame(PlayerBlack.PlayerType, PlayerWhite.PlayerType);
|
||||
}
|
||||
timer.Close();
|
||||
}
|
||||
|
||||
|
||||
void UpdateBotMatchStats(GameResult result)
|
||||
{
|
||||
UpdateStats(BotStatsA, botAPlaysWhite);
|
||||
UpdateStats(BotStatsB, !botAPlaysWhite);
|
||||
|
||||
void UpdateStats(BotMatchStats stats, bool isWhiteStats)
|
||||
{
|
||||
// Draw
|
||||
if (Arbiter.IsDrawResult(result))
|
||||
{
|
||||
stats.NumDraws++;
|
||||
}
|
||||
// Win
|
||||
else if (Arbiter.IsWhiteWinsResult(result) == isWhiteStats)
|
||||
{
|
||||
stats.NumWins++;
|
||||
}
|
||||
// Loss
|
||||
else
|
||||
{
|
||||
stats.NumLosses++;
|
||||
stats.NumTimeouts += (result is GameResult.WhiteTimeout or GameResult.BlackTimeout) ? 1 : 0;
|
||||
stats.NumIllegalMoves += (result is GameResult.WhiteIllegalMove or GameResult.BlackIllegalMove) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (isPlaying)
|
||||
{
|
||||
PlayerWhite.Update();
|
||||
PlayerBlack.Update();
|
||||
|
||||
PlayerToMove.UpdateClock(Raylib.GetFrameTime());
|
||||
if (PlayerToMove.TimeRemainingMs <= 0)
|
||||
{
|
||||
EndGame(PlayerToMove == PlayerWhite ? GameResult.WhiteTimeout : GameResult.BlackTimeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isWaitingToPlayMove && Raylib.GetTime() > playMoveTime)
|
||||
{
|
||||
isWaitingToPlayMove = false;
|
||||
PlayMove(moveToPlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasBotTaskException)
|
||||
{
|
||||
hasBotTaskException = false;
|
||||
botExInfo.Throw();
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
boardUI.Draw();
|
||||
string nameW = GetPlayerName(PlayerWhite);
|
||||
string nameB = GetPlayerName(PlayerBlack);
|
||||
boardUI.DrawPlayerNames(nameW, nameB, PlayerWhite.TimeRemainingMs, PlayerBlack.TimeRemainingMs, isPlaying);
|
||||
}
|
||||
public void DrawOverlay()
|
||||
{
|
||||
BotBrainCapacityUI.Draw(tokenCount, MaxTokenCount);
|
||||
MenuUI.DrawButtons(this);
|
||||
MatchStatsUI.DrawMatchStats(this);
|
||||
}
|
||||
|
||||
static string GetPlayerName(ChessPlayer player) => GetPlayerName(player.PlayerType);
|
||||
static string GetPlayerName(PlayerType type) => type.ToString();
|
||||
|
||||
public void StartNewBotMatch(PlayerType botTypeA, PlayerType botTypeB)
|
||||
{
|
||||
EndGame(GameResult.DrawByArbiter, log: false, autoStartNextBotMatch: false);
|
||||
botMatchGameIndex = 0;
|
||||
string nameA = GetPlayerName(botTypeA);
|
||||
string nameB = GetPlayerName(botTypeB);
|
||||
if (nameA == nameB)
|
||||
{
|
||||
nameA += " (A)";
|
||||
nameB += " (B)";
|
||||
}
|
||||
BotStatsA = new BotMatchStats(nameA);
|
||||
BotStatsB = new BotMatchStats(nameB);
|
||||
botAPlaysWhite = true;
|
||||
Log($"Starting new match: {nameA} vs {nameB}", false, ConsoleColor.Blue);
|
||||
StartNewGame(botTypeA, botTypeB);
|
||||
}
|
||||
|
||||
|
||||
ChessPlayer PlayerToMove => board.IsWhiteToMove ? PlayerWhite : PlayerBlack;
|
||||
public int TotalGameCount => botMatchStartFens.Length * 2;
|
||||
public int CurrGameNumber => Math.Min(TotalGameCount, botMatchGameIndex + 1);
|
||||
public string AllPGNs => pgns.ToString();
|
||||
|
||||
|
||||
bool IsLegal(Move givenMove)
|
||||
{
|
||||
var moves = moveGenerator.GenerateMoves(board);
|
||||
foreach (var legalMove in moves)
|
||||
{
|
||||
if (givenMove.Value == legalMove.Value)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public class BotMatchStats
|
||||
{
|
||||
public string BotName;
|
||||
public int NumWins;
|
||||
public int NumLosses;
|
||||
public int NumDraws;
|
||||
public int NumTimeouts;
|
||||
public int NumIllegalMoves;
|
||||
|
||||
public BotMatchStats(string name) => BotName = name;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
boardUI.Release();
|
||||
}
|
||||
}
|
||||
}
|
113
Chess-Challenge/src/Framework/Application/Core/Program.cs
Normal file
113
Chess-Challenge/src/Framework/Application/Core/Program.cs
Normal file
|
@ -0,0 +1,113 @@
|
|||
using Raylib_cs;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
const bool hideRaylibLogs = true;
|
||||
static Camera2D cam;
|
||||
|
||||
public static void Main()
|
||||
{
|
||||
Vector2 loadedWindowSize = GetSavedWindowSize();
|
||||
int screenWidth = (int)loadedWindowSize.X;
|
||||
int screenHeight = (int)loadedWindowSize.Y;
|
||||
|
||||
if (hideRaylibLogs)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
Raylib.SetTraceLogCallback(&LogCustom);
|
||||
}
|
||||
}
|
||||
|
||||
Raylib.InitWindow(screenWidth, screenHeight, "Chess Coding Challenge");
|
||||
Raylib.SetTargetFPS(60);
|
||||
|
||||
UpdateCamera();
|
||||
|
||||
ChallengeController controller = new();
|
||||
|
||||
while (!Raylib.WindowShouldClose())
|
||||
{
|
||||
Raylib.BeginDrawing();
|
||||
Raylib.ClearBackground(new Color(22, 22, 22, 255));
|
||||
Raylib.BeginMode2D(cam);
|
||||
|
||||
controller.Update();
|
||||
controller.Draw();
|
||||
|
||||
Raylib.EndMode2D();
|
||||
|
||||
controller.DrawOverlay();
|
||||
|
||||
Raylib.EndDrawing();
|
||||
}
|
||||
|
||||
Raylib.CloseWindow();
|
||||
|
||||
controller.Release();
|
||||
UIHelper.Release();
|
||||
}
|
||||
|
||||
public static void SetWindowSize(Vector2 size)
|
||||
{
|
||||
Raylib.SetWindowSize((int)size.X, (int)size.Y);
|
||||
UpdateCamera();
|
||||
SaveWindowSize();
|
||||
}
|
||||
|
||||
public static Vector2 ScreenToWorldPos(Vector2 screenPos) => Raylib.GetScreenToWorld2D(screenPos, cam);
|
||||
|
||||
static void UpdateCamera()
|
||||
{
|
||||
cam = new Camera2D();
|
||||
int screenWidth = Raylib.GetScreenWidth();
|
||||
int screenHeight = Raylib.GetScreenHeight();
|
||||
cam.target = new Vector2(0, 15);
|
||||
cam.offset = new Vector2(screenWidth / 2f, screenHeight / 2f);
|
||||
cam.zoom = screenWidth / 1280f * 0.7f;
|
||||
}
|
||||
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(System.Runtime.CompilerServices.CallConvCdecl) })]
|
||||
private static unsafe void LogCustom(int logLevel, sbyte* text, sbyte* args)
|
||||
{
|
||||
}
|
||||
|
||||
static Vector2 GetSavedWindowSize()
|
||||
{
|
||||
if (File.Exists(FileHelper.PrefsFilePath))
|
||||
{
|
||||
string prefs = File.ReadAllText(FileHelper.PrefsFilePath);
|
||||
if (!string.IsNullOrEmpty(prefs))
|
||||
{
|
||||
if (prefs[0] == '0')
|
||||
{
|
||||
return Settings.ScreenSizeSmall;
|
||||
}
|
||||
else if (prefs[0] == '1')
|
||||
{
|
||||
return Settings.ScreenSizeBig;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Settings.ScreenSizeSmall;
|
||||
}
|
||||
|
||||
static void SaveWindowSize()
|
||||
{
|
||||
Directory.CreateDirectory(FileHelper.AppDataPath);
|
||||
bool isBigWindow = Raylib.GetScreenWidth() > Settings.ScreenSizeSmall.X;
|
||||
File.WriteAllText(FileHelper.PrefsFilePath, isBigWindow ? "1" : "0");
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
23
Chess-Challenge/src/Framework/Application/Core/Settings.cs
Normal file
23
Chess-Challenge/src/Framework/Application/Core/Settings.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using System.Numerics;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
public const int GameDurationMilliseconds = 60 * 1000;
|
||||
public const int MaxTokenCount = 1024;
|
||||
public static readonly bool RunBotsOnSeparateThread = true;
|
||||
|
||||
public const LogType MessagesToLog = LogType.All;
|
||||
|
||||
public static readonly Vector2 ScreenSizeSmall = new(1280, 720);
|
||||
public static readonly Vector2 ScreenSizeBig = new(1920, 1080);
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
None,
|
||||
ErrorOnly,
|
||||
All
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,580 @@
|
|||
using ChessChallenge.Chess;
|
||||
using System;
|
||||
using static ChessChallenge.Chess.PrecomputedMoveData;
|
||||
|
||||
namespace ChessChallenge.Application.APIHelpers
|
||||
{
|
||||
|
||||
public class APIMoveGen
|
||||
{
|
||||
|
||||
public const int MaxMoves = 218;
|
||||
|
||||
public MoveGenerator.PromotionMode promotionsToGenerate = MoveGenerator.PromotionMode.All;
|
||||
|
||||
// ---- Instance variables ----
|
||||
bool isWhiteToMove;
|
||||
int friendlyColour;
|
||||
int friendlyKingSquare;
|
||||
int friendlyIndex;
|
||||
int enemyIndex;
|
||||
|
||||
bool inCheck;
|
||||
bool inDoubleCheck;
|
||||
|
||||
// If in check, this bitboard contains squares in line from checking piece up to king
|
||||
// If not in check, all bits are set to 1
|
||||
ulong checkRayBitmask;
|
||||
|
||||
ulong pinRays;
|
||||
ulong notPinRays;
|
||||
ulong opponentAttackMapNoPawns;
|
||||
public ulong opponentAttackMap;
|
||||
public ulong opponentPawnAttackMap;
|
||||
ulong opponentSlidingAttackMap;
|
||||
|
||||
bool generateNonCapture;
|
||||
Board board;
|
||||
int currMoveIndex;
|
||||
|
||||
ulong enemyPieces;
|
||||
ulong friendlyPieces;
|
||||
ulong allPieces;
|
||||
ulong emptySquares;
|
||||
ulong emptyOrEnemySquares;
|
||||
// If only captures should be generated, this will have 1s only in positions of enemy pieces.
|
||||
// Otherwise it will have 1s everywhere.
|
||||
ulong moveTypeMask;
|
||||
API.Move[] moves;
|
||||
|
||||
public APIMoveGen()
|
||||
{
|
||||
board = new Board();
|
||||
moves = new API.Move[MaxMoves];
|
||||
}
|
||||
|
||||
// Generates list of legal moves in current position.
|
||||
// Quiet moves (non captures) can optionally be excluded. This is used in quiescence search.
|
||||
public API.Move[] GenerateMoves(Board board, bool includeQuietMoves = true)
|
||||
{
|
||||
this.board = board;
|
||||
generateNonCapture = includeQuietMoves;
|
||||
|
||||
Init();
|
||||
|
||||
GenerateKingMoves();
|
||||
|
||||
// Only king moves are valid in a double check position, so can return early.
|
||||
if (!inDoubleCheck)
|
||||
{
|
||||
GenerateSlidingMoves();
|
||||
GenerateKnightMoves();
|
||||
GeneratePawnMoves();
|
||||
}
|
||||
|
||||
return moves.AsSpan().Slice(0, currMoveIndex).ToArray();
|
||||
}
|
||||
|
||||
// Note, this will only return correct value after GenerateMoves() has been called in the current position
|
||||
public bool InCheck()
|
||||
{
|
||||
return inCheck;
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
// Reset state
|
||||
currMoveIndex = 0;
|
||||
inCheck = false;
|
||||
inDoubleCheck = false;
|
||||
checkRayBitmask = 0;
|
||||
pinRays = 0;
|
||||
|
||||
// Store some info for convenience
|
||||
isWhiteToMove = board.MoveColour == PieceHelper.White;
|
||||
friendlyColour = board.MoveColour;
|
||||
friendlyKingSquare = board.KingSquare[board.MoveColourIndex];
|
||||
friendlyIndex = board.MoveColourIndex;
|
||||
enemyIndex = 1 - friendlyIndex;
|
||||
|
||||
// Store some bitboards for convenience
|
||||
enemyPieces = board.colourBitboards[enemyIndex];
|
||||
friendlyPieces = board.colourBitboards[friendlyIndex];
|
||||
allPieces = board.allPiecesBitboard;
|
||||
emptySquares = ~allPieces;
|
||||
emptyOrEnemySquares = emptySquares | enemyPieces;
|
||||
moveTypeMask = generateNonCapture ? ulong.MaxValue : enemyPieces;
|
||||
|
||||
CalculateAttackData();
|
||||
}
|
||||
|
||||
API.Move CreateAPIMove(int startSquare, int targetSquare, int flag)
|
||||
{
|
||||
int movePieceType = PieceHelper.PieceType(board.Square[startSquare]);
|
||||
int capturePieceType = PieceHelper.PieceType(board.Square[targetSquare]);
|
||||
API.Move apiMove = new(new Move(startSquare, targetSquare, flag), movePieceType, capturePieceType);
|
||||
return apiMove;
|
||||
}
|
||||
|
||||
API.Move CreateAPIMove(int startSquare, int targetSquare, int flag, int movePieceType)
|
||||
{
|
||||
int capturePieceType = PieceHelper.PieceType(board.Square[targetSquare]);
|
||||
API.Move apiMove = new(new Move(startSquare, targetSquare, flag), movePieceType, capturePieceType);
|
||||
return apiMove;
|
||||
}
|
||||
|
||||
void GenerateKingMoves()
|
||||
{
|
||||
ulong legalMask = ~(opponentAttackMap | friendlyPieces);
|
||||
ulong kingMoves = Bits.KingMoves[friendlyKingSquare] & legalMask & moveTypeMask;
|
||||
while (kingMoves != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref kingMoves);
|
||||
moves[currMoveIndex++] = CreateAPIMove(friendlyKingSquare, targetSquare, 0, PieceHelper.King);
|
||||
}
|
||||
|
||||
// Castling
|
||||
if (!inCheck && generateNonCapture)
|
||||
{
|
||||
ulong castleBlockers = opponentAttackMap | board.allPiecesBitboard;
|
||||
if (board.currentGameState.HasKingsideCastleRight(board.IsWhiteToMove))
|
||||
{
|
||||
ulong castleMask = board.IsWhiteToMove ? Bits.WhiteKingsideMask : Bits.BlackKingsideMask;
|
||||
if ((castleMask & castleBlockers) == 0)
|
||||
{
|
||||
int targetSquare = board.IsWhiteToMove ? BoardHelper.g1 : BoardHelper.g8;
|
||||
moves[currMoveIndex++] = CreateAPIMove(friendlyKingSquare, targetSquare, Move.CastleFlag, PieceHelper.King);
|
||||
}
|
||||
}
|
||||
if (board.currentGameState.HasQueensideCastleRight(board.IsWhiteToMove))
|
||||
{
|
||||
ulong castleMask = board.IsWhiteToMove ? Bits.WhiteQueensideMask2 : Bits.BlackQueensideMask2;
|
||||
ulong castleBlockMask = board.IsWhiteToMove ? Bits.WhiteQueensideMask : Bits.BlackQueensideMask;
|
||||
if ((castleMask & castleBlockers) == 0 && (castleBlockMask & board.allPiecesBitboard) == 0)
|
||||
{
|
||||
int targetSquare = board.IsWhiteToMove ? BoardHelper.c1 : BoardHelper.c8;
|
||||
moves[currMoveIndex++] = CreateAPIMove(friendlyKingSquare, targetSquare, Move.CastleFlag, PieceHelper.King);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateSlidingMoves()
|
||||
{
|
||||
// Limit movement to empty or enemy squares, and must block check if king is in check.
|
||||
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
||||
|
||||
ulong othogonalSliders = board.FriendlyOrthogonalSliders;
|
||||
ulong diagonalSliders = board.FriendlyDiagonalSliders;
|
||||
|
||||
// Pinned pieces cannot move if king is in check
|
||||
if (inCheck)
|
||||
{
|
||||
othogonalSliders &= ~pinRays;
|
||||
diagonalSliders &= ~pinRays;
|
||||
}
|
||||
|
||||
// Ortho
|
||||
while (othogonalSliders != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref othogonalSliders);
|
||||
ulong moveSquares = Magic.GetRookAttacks(startSquare, allPieces) & moveMask;
|
||||
|
||||
// If piece is pinned, it can only move along the pin ray
|
||||
if (IsPinned(startSquare))
|
||||
{
|
||||
moveSquares &= alignMask[startSquare, friendlyKingSquare];
|
||||
}
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Diag
|
||||
while (diagonalSliders != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref diagonalSliders);
|
||||
ulong moveSquares = Magic.GetBishopAttacks(startSquare, allPieces) & moveMask;
|
||||
|
||||
// If piece is pinned, it can only move along the pin ray
|
||||
if (IsPinned(startSquare))
|
||||
{
|
||||
moveSquares &= alignMask[startSquare, friendlyKingSquare];
|
||||
}
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GenerateKnightMoves()
|
||||
{
|
||||
int friendlyKnightPiece = PieceHelper.MakePiece(PieceHelper.Knight, board.MoveColour);
|
||||
// bitboard of all non-pinned knights
|
||||
ulong knights = board.pieceBitboards[friendlyKnightPiece] & notPinRays;
|
||||
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
||||
|
||||
while (knights != 0)
|
||||
{
|
||||
int knightSquare = BitBoardUtility.PopLSB(ref knights);
|
||||
ulong moveSquares = Bits.KnightAttacks[knightSquare] & moveMask;
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = CreateAPIMove(knightSquare, targetSquare, 0, PieceHelper.Knight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratePawnMoves()
|
||||
{
|
||||
int pushDir = board.IsWhiteToMove ? 1 : -1;
|
||||
int pushOffset = pushDir * 8;
|
||||
|
||||
int friendlyPawnPiece = PieceHelper.MakePiece(PieceHelper.Pawn, board.MoveColour);
|
||||
ulong pawns = board.pieceBitboards[friendlyPawnPiece];
|
||||
|
||||
ulong promotionRankMask = board.IsWhiteToMove ? Bits.Rank8 : Bits.Rank1;
|
||||
|
||||
ulong singlePush = (BitBoardUtility.Shift(pawns, pushOffset)) & emptySquares;
|
||||
|
||||
ulong pushPromotions = singlePush & promotionRankMask & checkRayBitmask;
|
||||
|
||||
|
||||
ulong captureEdgeFileMask = board.IsWhiteToMove ? Bits.NotAFile : Bits.NotHFile;
|
||||
ulong captureEdgeFileMask2 = board.IsWhiteToMove ? Bits.NotHFile : Bits.NotAFile;
|
||||
ulong captureA = BitBoardUtility.Shift(pawns & captureEdgeFileMask, pushDir * 7) & enemyPieces;
|
||||
ulong captureB = BitBoardUtility.Shift(pawns & captureEdgeFileMask2, pushDir * 9) & enemyPieces;
|
||||
|
||||
ulong singlePushNoPromotions = singlePush & ~promotionRankMask & checkRayBitmask;
|
||||
|
||||
ulong capturePromotionsA = captureA & promotionRankMask & checkRayBitmask;
|
||||
ulong capturePromotionsB = captureB & promotionRankMask & checkRayBitmask;
|
||||
|
||||
captureA &= checkRayBitmask & ~promotionRankMask;
|
||||
captureB &= checkRayBitmask & ~promotionRankMask;
|
||||
|
||||
// Single / double push
|
||||
if (generateNonCapture)
|
||||
{
|
||||
// Generate single pawn pushes
|
||||
while (singlePushNoPromotions != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref singlePushNoPromotions);
|
||||
int startSquare = targetSquare - pushOffset;
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate double pawn pushes
|
||||
ulong doublePushTargetRankMask = board.IsWhiteToMove ? Bits.Rank4 : Bits.Rank5;
|
||||
ulong doublePush = BitBoardUtility.Shift(singlePush, pushOffset) & emptySquares & doublePushTargetRankMask & checkRayBitmask;
|
||||
|
||||
while (doublePush != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref doublePush);
|
||||
int startSquare = targetSquare - pushOffset * 2;
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PawnTwoUpFlag, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Captures
|
||||
while (captureA != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref captureA);
|
||||
int startSquare = targetSquare - pushDir * 7;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
|
||||
while (captureB != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref captureB);
|
||||
int startSquare = targetSquare - pushDir * 9;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, 0, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Promotions
|
||||
if (generateNonCapture)
|
||||
{
|
||||
while (pushPromotions != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref pushPromotions);
|
||||
int startSquare = targetSquare - pushOffset;
|
||||
if (!IsPinned(startSquare))
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while (capturePromotionsA != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref capturePromotionsA);
|
||||
int startSquare = targetSquare - pushDir * 7;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
while (capturePromotionsB != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref capturePromotionsB);
|
||||
int startSquare = targetSquare - pushDir * 9;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
// En passant
|
||||
if (board.currentGameState.enPassantFile > 0)
|
||||
{
|
||||
int epFileIndex = board.currentGameState.enPassantFile - 1;
|
||||
int epRankIndex = board.IsWhiteToMove ? 5 : 2;
|
||||
int targetSquare = epRankIndex * 8 + epFileIndex;
|
||||
int capturedPawnSquare = targetSquare - pushOffset;
|
||||
|
||||
if (BitBoardUtility.ContainsSquare(checkRayBitmask, capturedPawnSquare))
|
||||
{
|
||||
ulong pawnsThatCanCaptureEp = pawns & BitBoardUtility.PawnAttacks(1ul << targetSquare, !board.IsWhiteToMove);
|
||||
|
||||
while (pawnsThatCanCaptureEp != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref pawnsThatCanCaptureEp);
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
if (!InCheckAfterEnPassant(startSquare, targetSquare, capturedPawnSquare))
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.EnPassantCaptureFlag, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratePromotions(int startSquare, int targetSquare)
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToQueenFlag, PieceHelper.Pawn);
|
||||
// Don't generate non-queen promotions in q-search
|
||||
if (generateNonCapture)
|
||||
{
|
||||
if (promotionsToGenerate == MoveGenerator.PromotionMode.All)
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToKnightFlag, PieceHelper.Pawn);
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToRookFlag, PieceHelper.Pawn);
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToBishopFlag, PieceHelper.Pawn);
|
||||
}
|
||||
else if (promotionsToGenerate == MoveGenerator.PromotionMode.QueenAndKnight)
|
||||
{
|
||||
moves[currMoveIndex++] = CreateAPIMove(startSquare, targetSquare, Move.PromoteToKnightFlag, PieceHelper.Pawn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPinned(int square)
|
||||
{
|
||||
return ((pinRays >> square) & 1) != 0;
|
||||
}
|
||||
|
||||
void GenSlidingAttackMap()
|
||||
{
|
||||
opponentSlidingAttackMap = 0;
|
||||
|
||||
UpdateSlideAttack(board.EnemyOrthogonalSliders, true);
|
||||
UpdateSlideAttack(board.EnemyDiagonalSliders, false);
|
||||
|
||||
void UpdateSlideAttack(ulong pieceBoard, bool ortho)
|
||||
{
|
||||
ulong blockers = board.allPiecesBitboard & ~(1ul << friendlyKingSquare);
|
||||
|
||||
while (pieceBoard != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref pieceBoard);
|
||||
ulong moveBoard = Magic.GetSliderAttacks(startSquare, blockers, ortho);
|
||||
|
||||
opponentSlidingAttackMap |= moveBoard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateAttackData()
|
||||
{
|
||||
GenSlidingAttackMap();
|
||||
// Search squares in all directions around friendly king for checks/pins by enemy sliding pieces (queen, rook, bishop)
|
||||
int startDirIndex = 0;
|
||||
int endDirIndex = 8;
|
||||
|
||||
if (board.queens[enemyIndex].Count == 0)
|
||||
{
|
||||
startDirIndex = (board.rooks[enemyIndex].Count > 0) ? 0 : 4;
|
||||
endDirIndex = (board.bishops[enemyIndex].Count > 0) ? 8 : 4;
|
||||
}
|
||||
|
||||
for (int dir = startDirIndex; dir < endDirIndex; dir++)
|
||||
{
|
||||
bool isDiagonal = dir > 3;
|
||||
ulong slider = isDiagonal ? board.EnemyDiagonalSliders : board.EnemyOrthogonalSliders;
|
||||
if ((dirRayMask[dir, friendlyKingSquare] & slider) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int n = numSquaresToEdge[friendlyKingSquare][dir];
|
||||
int directionOffset = directionOffsets[dir];
|
||||
bool isFriendlyPieceAlongRay = false;
|
||||
ulong rayMask = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int squareIndex = friendlyKingSquare + directionOffset * (i + 1);
|
||||
rayMask |= 1ul << squareIndex;
|
||||
int piece = board.Square[squareIndex];
|
||||
|
||||
// This square contains a piece
|
||||
if (piece != PieceHelper.None)
|
||||
{
|
||||
if (PieceHelper.IsColour(piece, friendlyColour))
|
||||
{
|
||||
// First friendly piece we have come across in this direction, so it might be pinned
|
||||
if (!isFriendlyPieceAlongRay)
|
||||
{
|
||||
isFriendlyPieceAlongRay = true;
|
||||
}
|
||||
// This is the second friendly piece we've found in this direction, therefore pin is not possible
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This square contains an enemy piece
|
||||
else
|
||||
{
|
||||
int pieceType = PieceHelper.PieceType(piece);
|
||||
|
||||
// Check if piece is in bitmask of pieces able to move in current direction
|
||||
if (isDiagonal && PieceHelper.IsDiagonalSlider(pieceType) || !isDiagonal && PieceHelper.IsOrthogonalSlider(pieceType))
|
||||
{
|
||||
// Friendly piece blocks the check, so this is a pin
|
||||
if (isFriendlyPieceAlongRay)
|
||||
{
|
||||
pinRays |= rayMask;
|
||||
}
|
||||
// No friendly piece blocking the attack, so this is a check
|
||||
else
|
||||
{
|
||||
checkRayBitmask |= rayMask;
|
||||
inDoubleCheck = inCheck; // if already in check, then this is double check
|
||||
inCheck = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This enemy piece is not able to move in the current direction, and so is blocking any checks/pins
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stop searching for pins if in double check, as the king is the only piece able to move in that case anyway
|
||||
if (inDoubleCheck)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
notPinRays = ~pinRays;
|
||||
|
||||
ulong opponentKnightAttacks = 0;
|
||||
ulong knights = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.Knight, board.OpponentColour)];
|
||||
ulong friendlyKingBoard = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.King, board.MoveColour)];
|
||||
|
||||
while (knights != 0)
|
||||
{
|
||||
int knightSquare = BitBoardUtility.PopLSB(ref knights);
|
||||
ulong knightAttacks = Bits.KnightAttacks[knightSquare];
|
||||
opponentKnightAttacks |= knightAttacks;
|
||||
|
||||
if ((knightAttacks & friendlyKingBoard) != 0)
|
||||
{
|
||||
inDoubleCheck = inCheck;
|
||||
inCheck = true;
|
||||
checkRayBitmask |= 1ul << knightSquare;
|
||||
}
|
||||
}
|
||||
|
||||
// Pawn attacks
|
||||
PieceList opponentPawns = board.pawns[enemyIndex];
|
||||
opponentPawnAttackMap = 0;
|
||||
|
||||
ulong opponentPawnsBoard = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.Pawn, board.OpponentColour)];
|
||||
opponentPawnAttackMap = BitBoardUtility.PawnAttacks(opponentPawnsBoard, !isWhiteToMove);
|
||||
if (BitBoardUtility.ContainsSquare(opponentPawnAttackMap, friendlyKingSquare))
|
||||
{
|
||||
inDoubleCheck = inCheck; // if already in check, then this is double check
|
||||
inCheck = true;
|
||||
ulong possiblePawnAttackOrigins = board.IsWhiteToMove ? Bits.WhitePawnAttacks[friendlyKingSquare] : Bits.BlackPawnAttacks[friendlyKingSquare];
|
||||
ulong pawnCheckMap = opponentPawnsBoard & possiblePawnAttackOrigins;
|
||||
checkRayBitmask |= pawnCheckMap;
|
||||
}
|
||||
|
||||
int enemyKingSquare = board.KingSquare[enemyIndex];
|
||||
|
||||
opponentAttackMapNoPawns = opponentSlidingAttackMap | opponentKnightAttacks | Bits.KingMoves[enemyKingSquare];
|
||||
opponentAttackMap = opponentAttackMapNoPawns | opponentPawnAttackMap;
|
||||
|
||||
if (!inCheck)
|
||||
{
|
||||
checkRayBitmask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Test if capturing a pawn with en-passant reveals a sliding piece attack against the king
|
||||
// Note: this is only used for cases where pawn appears to not be pinned due to opponent pawn being on same rank
|
||||
// (therefore only need to check orthogonal sliders)
|
||||
bool InCheckAfterEnPassant(int startSquare, int targetSquare, int epCaptureSquare)
|
||||
{
|
||||
ulong enemyOrtho = board.EnemyOrthogonalSliders;
|
||||
|
||||
if (enemyOrtho != 0)
|
||||
{
|
||||
ulong maskedBlockers = (allPieces ^ (1ul << epCaptureSquare | 1ul << startSquare | 1ul << targetSquare));
|
||||
ulong rookAttacks = Magic.GetRookAttacks(friendlyKingSquare, maskedBlockers);
|
||||
return (rookAttacks & enemyOrtho) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
using ChessChallenge.Chess;
|
||||
using System;
|
||||
using ChessChallenge.API;
|
||||
|
||||
namespace ChessChallenge.Application.APIHelpers
|
||||
{
|
||||
|
||||
public class MoveHelper
|
||||
{
|
||||
public static (Chess.Move move, PieceType pieceType, PieceType captureType) CreateMoveFromName(string moveNameUCI, API.Board board)
|
||||
{
|
||||
int indexStart = BoardHelper.SquareIndexFromName(moveNameUCI[0] + "" + moveNameUCI[1]);
|
||||
int indexTarget = BoardHelper.SquareIndexFromName(moveNameUCI[2] + "" + moveNameUCI[3]);
|
||||
char promoteChar = moveNameUCI.Length > 3 ? moveNameUCI[^1] : ' ';
|
||||
|
||||
PieceType promotePieceType = promoteChar switch
|
||||
{
|
||||
'q' => PieceType.Queen,
|
||||
'r' => PieceType.Rook,
|
||||
'n' => PieceType.Knight,
|
||||
'b' => PieceType.Bishop,
|
||||
_ => PieceType.None
|
||||
};
|
||||
|
||||
Square startSquare = new Square(indexStart);
|
||||
Square targetSquare = new Square(indexTarget);
|
||||
|
||||
|
||||
PieceType movedPieceType = board.GetPiece(startSquare).PieceType;
|
||||
PieceType capturedPieceType = board.GetPiece(targetSquare).PieceType;
|
||||
|
||||
// Figure out move flag
|
||||
int flag = Chess.Move.NoFlag;
|
||||
|
||||
if (movedPieceType == PieceType.Pawn)
|
||||
{
|
||||
if (targetSquare.Rank is 7 or 0)
|
||||
{
|
||||
flag = promotePieceType switch
|
||||
{
|
||||
PieceType.Queen => Chess.Move.PromoteToQueenFlag,
|
||||
PieceType.Rook => Chess.Move.PromoteToRookFlag,
|
||||
PieceType.Knight => Chess.Move.PromoteToKnightFlag,
|
||||
PieceType.Bishop => Chess.Move.PromoteToBishopFlag,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Math.Abs(targetSquare.Rank - startSquare.Rank) == 2)
|
||||
{
|
||||
flag = Chess.Move.PawnTwoUpFlag;
|
||||
}
|
||||
// En-passant
|
||||
else if (startSquare.File != targetSquare.File && board.GetPiece(targetSquare).IsNull)
|
||||
{
|
||||
flag = Chess.Move.EnPassantCaptureFlag;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (movedPieceType == PieceType.King)
|
||||
{
|
||||
if (Math.Abs(startSquare.File - targetSquare.File) > 1)
|
||||
{
|
||||
flag = Chess.Move.CastleFlag;
|
||||
}
|
||||
}
|
||||
|
||||
Chess.Move coreMove = new Chess.Move(startSquare.Index, targetSquare.Index, flag);
|
||||
return (coreMove, movedPieceType, capturedPieceType);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System;
|
||||
using static ChessChallenge.Application.Settings;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class ConsoleHelper
|
||||
{
|
||||
public static void Log(string msg, bool isError = false, ConsoleColor col = ConsoleColor.White)
|
||||
{
|
||||
bool log = MessagesToLog == LogType.All || (isError && MessagesToLog == LogType.ErrorOnly);
|
||||
|
||||
if (log)
|
||||
{
|
||||
Console.ForegroundColor = col;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class FileHelper
|
||||
{
|
||||
|
||||
public static string AppDataPath
|
||||
{
|
||||
get
|
||||
{
|
||||
string dir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
return Path.Combine(dir, "ChessCodingChallenge");
|
||||
}
|
||||
}
|
||||
|
||||
public static string SavedGamesPath => Path.Combine(AppDataPath, "Games");
|
||||
public static string PrefsFilePath => Path.Combine(AppDataPath, "prefs.txt");
|
||||
|
||||
public static string GetUniqueFileName(string path, string fileName, string fileExtension)
|
||||
{
|
||||
if (fileExtension[0] != '.')
|
||||
{
|
||||
fileExtension = "." + fileExtension;
|
||||
}
|
||||
|
||||
string uniqueName = fileName;
|
||||
int index = 0;
|
||||
|
||||
while (File.Exists(Path.Combine(path, uniqueName + fileExtension)))
|
||||
{
|
||||
index++;
|
||||
uniqueName = fileName + index;
|
||||
}
|
||||
return uniqueName + fileExtension;
|
||||
}
|
||||
|
||||
public static string GetResourcePath(params string[] localPath)
|
||||
{
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "resources", Path.Combine(localPath));
|
||||
}
|
||||
|
||||
public static string ReadResourceFile(string localPath)
|
||||
{
|
||||
return File.ReadAllText(GetResourcePath(localPath));
|
||||
}
|
||||
|
||||
// Thanks to https://github.com/dotnet/runtime/issues/17938
|
||||
public static void OpenUrl(string url)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(url);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
url = url.Replace("&", "^&");
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
Process.Start("xdg-open", url);
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||
{
|
||||
Process.Start("open", url);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
270
Chess-Challenge/src/Framework/Application/Helpers/Tester.cs
Normal file
270
Chess-Challenge/src/Framework/Application/Helpers/Tester.cs
Normal file
|
@ -0,0 +1,270 @@
|
|||
using ChessChallenge.API;
|
||||
using ChessChallenge.Chess;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class Tester
|
||||
{
|
||||
static MoveGenerator moveGen;
|
||||
static API.Board boardAPI;
|
||||
|
||||
public static void Run()
|
||||
{
|
||||
MoveGenTest();
|
||||
PieceListTest();
|
||||
DrawTest();
|
||||
CheckTest();
|
||||
MiscTest();
|
||||
TestBitboards();
|
||||
TestMoveCreate();
|
||||
Console.WriteLine("Tests Finished");
|
||||
}
|
||||
|
||||
static void TestMoveCreate()
|
||||
{
|
||||
Console.WriteLine("Testing move create");
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("2rqk2r/1p3p1p/p2p1n2/2PPn3/8/3B1QP1/PR1K1P1p/2B1R3 b k - 1 27");
|
||||
boardAPI = new(board);
|
||||
|
||||
var move = new API.Move("b7b5", boardAPI);
|
||||
boardAPI.MakeMove(move);
|
||||
move = new API.Move("c5b6", boardAPI);
|
||||
Assert(move.IsEnPassant, "En passant wrong");
|
||||
move = new API.Move("h2h1q", boardAPI);
|
||||
Assert(move.IsPromotion && move.PromotionPieceType == PieceType.Queen, "Promotion wrong");
|
||||
move = new API.Move("e8g8", boardAPI);
|
||||
Assert(move.IsCastles && move.MovePieceType == PieceType.King, "Castles wrong");
|
||||
}
|
||||
|
||||
static void TestBitboards()
|
||||
{
|
||||
|
||||
Console.WriteLine("Testing Bitboards");
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("r2q2k1/pp2rppp/3p1n2/1R1Pn3/8/2PB1Q1P/P4PP1/2B2RK1 w - - 7 16");
|
||||
boardAPI = new(board);
|
||||
|
||||
ulong rookTest = BitboardHelper.GetSliderAttacks(PieceType.Rook, new Square("b5"), boardAPI);
|
||||
Assert(BitboardHelper.GetNumberOfSetBits(rookTest) == 9, "Bitboard error");
|
||||
ulong queenTest = BitboardHelper.GetSliderAttacks(PieceType.Queen, new Square("f3"), boardAPI);
|
||||
Assert(BitboardHelper.GetNumberOfSetBits(queenTest) == 15, "Bitboard error");
|
||||
ulong bishopTest = BitboardHelper.GetSliderAttacks(PieceType.Bishop, new Square("d3"), boardAPI);
|
||||
Assert(BitboardHelper.GetNumberOfSetBits(bishopTest) == 10, "Bitboard error");
|
||||
ulong pawnTest = BitboardHelper.GetPawnAttacks(new Square("c3"), true);
|
||||
Assert(BitboardHelper.SquareIsSet(pawnTest, new Square("b4")), "Pawn bitboard error");
|
||||
Assert(BitboardHelper.SquareIsSet(pawnTest, new Square("d4")), "Pawn bitboard error");
|
||||
ulong knightTest = BitboardHelper.GetKnightAttacks(new Square("a1"));
|
||||
Assert(BitboardHelper.GetNumberOfSetBits(knightTest) == 2, "Knight bb wrong");
|
||||
ulong king = BitboardHelper.GetKingAttacks(new Square("a1"));
|
||||
Assert(BitboardHelper.GetNumberOfSetBits(king) == 3, "King bb wrong");
|
||||
|
||||
Assert(boardAPI.SquareIsAttackedByOpponent(new Square("a6")), "Square attacked wrong");
|
||||
Assert(boardAPI.SquareIsAttackedByOpponent(new Square("f3")), "Square attacked wrong");
|
||||
Assert(!boardAPI.SquareIsAttackedByOpponent(new Square("c3")), "Square attacked wrong");
|
||||
Assert(!boardAPI.SquareIsAttackedByOpponent(new Square("h4")), "Square attacked wrong");
|
||||
boardAPI.MakeMove(new API.Move("b5b7", boardAPI));
|
||||
Assert(boardAPI.SquareIsAttackedByOpponent(new Square("e7")), "Square attacked wrong");
|
||||
Assert(boardAPI.SquareIsAttackedByOpponent(new Square("b8")), "Square attacked wrong");
|
||||
Assert(!boardAPI.SquareIsAttackedByOpponent(new Square("a5")), "Square attacked wrong");
|
||||
Assert(!boardAPI.SquareIsAttackedByOpponent(new Square("e8")), "Square attacked wrong");
|
||||
}
|
||||
|
||||
static void CheckTest()
|
||||
{
|
||||
Console.WriteLine("Testing Checks");
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("r2q1rk1/pp3ppp/3p1n2/3Pn3/8/2PB1Q1P/P4PP1/R1B2RK1 w - - 3 14");
|
||||
boardAPI = new(board);
|
||||
|
||||
Assert(!boardAPI.IsInCheck(), "Check wrong");
|
||||
API.Move move = new API.Move("d3h7", boardAPI);
|
||||
boardAPI.MakeMove(move);
|
||||
Assert(boardAPI.IsInCheck(), "Check wrong");
|
||||
boardAPI.UndoMove(move);
|
||||
Assert(!boardAPI.IsInCheck(), "Check wrong");
|
||||
|
||||
boardAPI.MakeMove(new API.Move("f3h5", boardAPI));
|
||||
boardAPI.MakeMove(new API.Move("f6d7", boardAPI));
|
||||
Assert(!boardAPI.IsInCheckmate(), "Checkmate wrong");
|
||||
boardAPI.MakeMove(new API.Move("h5h7", boardAPI));
|
||||
Assert(boardAPI.IsInCheckmate(), "Checkmate wrong");
|
||||
|
||||
}
|
||||
static void PieceListTest()
|
||||
{
|
||||
Console.WriteLine("Piece Lists Tests");
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("1q3rk1/P5p1/4p2p/2ppP1N1/5Qb1/1PP5/7P/2R2RK1 w - - 0 28");
|
||||
boardAPI = new(board);
|
||||
|
||||
API.PieceList[] pieceLists = boardAPI.GetAllPieceLists();
|
||||
int[] counts = { 5, 1, 0, 2, 1, 1, 5, 0, 1, 1, 1, 1 };
|
||||
for (int i = 0; i < pieceLists.Length; i++)
|
||||
{
|
||||
string msg = $"Wrong piece count: {pieceLists[i].Count} Type: {pieceLists[i].TypeOfPieceInList}";
|
||||
Assert(pieceLists[i].Count == counts[i], msg);
|
||||
}
|
||||
|
||||
Assert(boardAPI.GetKingSquare(true) == boardAPI.GetPieceList(PieceType.King, true)[0].Square, "King square wrong");
|
||||
Assert(boardAPI.GetKingSquare(true) == new Square(6, 0), "King square wrong");
|
||||
Assert(boardAPI.GetKingSquare(false) == boardAPI.GetPieceList(PieceType.King, false)[0].Square, "King square wrong");
|
||||
Assert(boardAPI.GetKingSquare(false) == new Square(6, 7), "King square wrong");
|
||||
Assert(boardAPI.GetPiece(new Square(4, 5)).IsPawn, "Wrong piece");
|
||||
Assert(!boardAPI.GetPiece(new Square(4, 5)).IsWhite, "Wrong colour");
|
||||
}
|
||||
|
||||
static void DrawTest()
|
||||
{
|
||||
Console.WriteLine("Draw test");
|
||||
|
||||
// Repetition test
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("r1r3k1/p1q5/3p2pQ/1p1Pp1N1/2B5/1PP2P2/K1b3P1/7R b - - 2 24");
|
||||
boardAPI = new(board);
|
||||
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("c7g7", boardAPI));
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("h6h4", boardAPI));
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("g7c7", boardAPI));
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("h4h6", boardAPI));
|
||||
Assert(boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.UndoMove(new API.Move("h4h6", boardAPI));
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
|
||||
// Stalemate test
|
||||
board = new Chess.Board();
|
||||
board.LoadPosition("7K/8/6k1/5q2/8/8/8/8 b - - 0 1");
|
||||
boardAPI = new(board);
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("f5f7", boardAPI));
|
||||
Assert(boardAPI.IsDraw(), "Draw wrong");
|
||||
|
||||
// Insufficient material
|
||||
board = new Chess.Board();
|
||||
board.LoadPosition("7K/3N4/6k1/2n5/8/8/8/8 b - - 0 1");
|
||||
boardAPI = new(board);
|
||||
Assert(!boardAPI.IsDraw(), "Draw wrong");
|
||||
boardAPI.MakeMove(new API.Move("c5d7", boardAPI));
|
||||
Assert(boardAPI.IsDraw(), "Draw wrong");
|
||||
}
|
||||
|
||||
static void MiscTest()
|
||||
{
|
||||
Console.WriteLine("Running Misc Tests");
|
||||
var board = new Chess.Board();
|
||||
board.LoadPosition("1q3rk1/P5p1/4p2p/2ppP1N1/5Qb1/1PP5/7P/2R2RK1 w - - 0 28");
|
||||
boardAPI = new(board);
|
||||
Assert(boardAPI.IsWhiteToMove, "Colour to move wrong");
|
||||
|
||||
//var moves = boardAPI.GetLegalMoves();
|
||||
var captures = boardAPI.GetLegalMoves(true);
|
||||
Assert(captures.Length == 4, "Captures wrong");
|
||||
int numTested = 0;
|
||||
foreach (var c in captures)
|
||||
{
|
||||
if (c.TargetSquare.Index == 57)
|
||||
{
|
||||
Assert(c.StartSquare == new Square(0, 6), "Start square wrong");
|
||||
Assert(c.CapturePieceType == PieceType.Queen, "Capture type wrong");
|
||||
Assert(c.MovePieceType == PieceType.Pawn, "Move piece type wrong");
|
||||
Assert(c.PromotionPieceType == PieceType.Queen, "Promote type wrong");
|
||||
numTested++;
|
||||
}
|
||||
if (c.TargetSquare.Index == 44)
|
||||
{
|
||||
Assert(c.StartSquare == new Square(6, 4), "Start square wrong");
|
||||
Assert(c.CapturePieceType == PieceType.Pawn, "Capture type wrong");
|
||||
Assert(c.MovePieceType == PieceType.Knight, "Move piece type wrong");
|
||||
numTested++;
|
||||
}
|
||||
if (c.TargetSquare.Index == 61)
|
||||
{
|
||||
Assert(c.CapturePieceType == PieceType.Rook, "Capture type wrong");
|
||||
Assert(c.MovePieceType == PieceType.Queen, "Move piece type wrong");
|
||||
numTested++;
|
||||
}
|
||||
if (c.TargetSquare.Index == 30)
|
||||
{
|
||||
Assert(c.CapturePieceType == PieceType.Bishop, "Capture type wrong");
|
||||
Assert(c.MovePieceType == PieceType.Queen, "Move piece type wrong");
|
||||
numTested++;
|
||||
}
|
||||
}
|
||||
Assert(numTested == 4, "Target square wrong");
|
||||
}
|
||||
|
||||
static void MoveGenTest()
|
||||
{
|
||||
Console.WriteLine("Running move gen tests");
|
||||
ChessChallenge.Chess.Board board = new();
|
||||
moveGen = new();
|
||||
|
||||
string[] testFens =
|
||||
{
|
||||
"8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - -",
|
||||
"r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq -",
|
||||
"r1bq2r1/1pppkppp/1b3n2/pP1PP3/2n5/2P5/P3QPPP/RNB1K2R w KQ a6 0 12",
|
||||
"2b1b3/1r1P4/3K3p/1p6/2p5/6k1/1P3p2/4B3 w - - 0 42"
|
||||
};
|
||||
|
||||
int[] testDepths = { 3, 4, 4, 5 };
|
||||
ulong[] testResults = { 2812, 4085603, 1280017, 5617302 };
|
||||
|
||||
|
||||
for (int i = 0; i < testFens.Length; i++)
|
||||
{
|
||||
board.LoadPosition(testFens[i]);
|
||||
boardAPI = new API.Board(board);
|
||||
ulong result = Search(testDepths[i]);
|
||||
Assert(result == testResults[i], "TEST FAILED");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
static ulong Search(int depth)
|
||||
{
|
||||
var moves = boardAPI.GetLegalMoves();
|
||||
|
||||
if (depth == 1)
|
||||
{
|
||||
return (ulong)moves.Length;
|
||||
}
|
||||
ulong numLocalNodes = 0;
|
||||
for (int i = 0; i < moves.Length; i++)
|
||||
{
|
||||
|
||||
boardAPI.MakeMove(moves[i]);
|
||||
|
||||
ulong numNodesFromThisPosition = Search(depth - 1);
|
||||
numLocalNodes += numNodesFromThisPosition;
|
||||
|
||||
boardAPI.UndoMove(moves[i]);
|
||||
|
||||
}
|
||||
return numLocalNodes;
|
||||
}
|
||||
|
||||
static void Assert(bool condition, string msg)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
LogError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void LogError(string msg)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine(msg);
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,58 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class TokenCounter
|
||||
{
|
||||
|
||||
static HashSet<SyntaxKind> tokensToIgnore = new(new SyntaxKind[]
|
||||
{
|
||||
SyntaxKind.PrivateKeyword,
|
||||
SyntaxKind.PublicKeyword,
|
||||
SyntaxKind.SemicolonToken,
|
||||
SyntaxKind.CommaToken,
|
||||
// only count open brace since I want to count the pair as a single token
|
||||
SyntaxKind.CloseBraceToken,
|
||||
SyntaxKind.CloseBracketToken,
|
||||
SyntaxKind.CloseParenToken
|
||||
});
|
||||
|
||||
public static int CountTokens(string code)
|
||||
{
|
||||
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
|
||||
SyntaxNode root = tree.GetRoot();
|
||||
return CountTokens(root);
|
||||
}
|
||||
|
||||
static int CountTokens(SyntaxNodeOrToken syntaxNode)
|
||||
{
|
||||
SyntaxKind kind = syntaxNode.Kind();
|
||||
int numTokensInChildren = 0;
|
||||
|
||||
|
||||
foreach (var child in syntaxNode.ChildNodesAndTokens())
|
||||
{
|
||||
numTokensInChildren += CountTokens(child);
|
||||
}
|
||||
|
||||
if (syntaxNode.IsToken && !tokensToIgnore.Contains(kind))
|
||||
{
|
||||
//Console.WriteLine(kind + " " + syntaxNode.ToString());
|
||||
|
||||
// String literals count for as many chars as are in the string
|
||||
if (kind is SyntaxKind.StringLiteralToken or SyntaxKind.InterpolatedStringTextToken)
|
||||
{
|
||||
return syntaxNode.ToString().Length;
|
||||
}
|
||||
|
||||
// Regular tokens count as just one token
|
||||
return 1;
|
||||
}
|
||||
|
||||
return numTokensInChildren;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
139
Chess-Challenge/src/Framework/Application/Helpers/UIHelper.cs
Normal file
139
Chess-Challenge/src/Framework/Application/Helpers/UIHelper.cs
Normal file
|
@ -0,0 +1,139 @@
|
|||
using Raylib_cs;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class UIHelper
|
||||
{
|
||||
static readonly bool SDF_Enabled = true;
|
||||
const string fontName = "OPENSANS-SEMIBOLD.TTF";
|
||||
const int referenceResolution = 1920;
|
||||
|
||||
static Font font;
|
||||
static Font fontSdf;
|
||||
static Shader shader;
|
||||
|
||||
public enum AlignH
|
||||
{
|
||||
Left,
|
||||
Centre,
|
||||
Right
|
||||
}
|
||||
public enum AlignV
|
||||
{
|
||||
Top,
|
||||
Centre,
|
||||
Bottom
|
||||
}
|
||||
|
||||
static UIHelper()
|
||||
{
|
||||
if (SDF_Enabled)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
const int baseSize = 64;
|
||||
uint fileSize = 0;
|
||||
var fileData = Raylib.LoadFileData(GetResourcePath("Fonts", fontName), ref fileSize);
|
||||
Font fontSdf = default;
|
||||
fontSdf.baseSize = baseSize;
|
||||
fontSdf.glyphCount = 95;
|
||||
fontSdf.glyphs = Raylib.LoadFontData(fileData, (int)fileSize, baseSize, null, 0, FontType.FONT_SDF);
|
||||
|
||||
Image atlas = Raylib.GenImageFontAtlas(fontSdf.glyphs, &fontSdf.recs, 95, baseSize, 0, 1);
|
||||
fontSdf.texture = Raylib.LoadTextureFromImage(atlas);
|
||||
Raylib.UnloadImage(atlas);
|
||||
Raylib.UnloadFileData(fileData);
|
||||
|
||||
Raylib.SetTextureFilter(fontSdf.texture, TextureFilter.TEXTURE_FILTER_BILINEAR);
|
||||
UIHelper.fontSdf = fontSdf;
|
||||
|
||||
}
|
||||
shader = Raylib.LoadShader("", GetResourcePath("Fonts", "sdf.fs"));
|
||||
}
|
||||
font = Raylib.LoadFontEx(GetResourcePath("Fonts", fontName), 128, null, 0);
|
||||
|
||||
}
|
||||
|
||||
public static void DrawText(string text, Vector2 pos, int size, int spacing, Color col, AlignH alignH = AlignH.Left, AlignV alignV = AlignV.Centre)
|
||||
{
|
||||
Vector2 boundSize = Raylib.MeasureTextEx(font, text, size, spacing);
|
||||
float offsetX = alignH == AlignH.Left ? 0 : (alignH == AlignH.Centre ? -boundSize.X / 2 : -boundSize.X);
|
||||
float offsetY = alignV == AlignV.Top ? 0 : (alignV == AlignV.Centre ? -boundSize.Y / 2 : -boundSize.Y);
|
||||
Vector2 offset = new(offsetX, offsetY);
|
||||
|
||||
if (SDF_Enabled)
|
||||
{
|
||||
Raylib.BeginShaderMode(shader);
|
||||
Raylib.DrawTextEx(fontSdf, text, pos + offset, size, spacing, col);
|
||||
Raylib.EndShaderMode();
|
||||
}
|
||||
else
|
||||
{
|
||||
Raylib.DrawTextEx(font, text, pos + offset, size, spacing, col);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Button(string text, Vector2 centre, Vector2 size)
|
||||
{
|
||||
Rectangle rec = new(centre.X - size.X / 2, centre.Y - size.Y / 2, size.X, size.Y);
|
||||
|
||||
Color normalCol = new(40, 40, 40, 255);
|
||||
Color hoverCol = new(3, 173, 252, 255);
|
||||
Color pressCol = new(2, 119, 173, 255);
|
||||
|
||||
bool mouseOver = MouseInRect(rec);
|
||||
bool pressed = mouseOver && Raylib.IsMouseButtonDown(MouseButton.MOUSE_BUTTON_LEFT);
|
||||
bool pressedThisFrame = pressed && Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT);
|
||||
Color col = mouseOver ? (pressed ? pressCol : hoverCol) : normalCol;
|
||||
|
||||
Raylib.DrawRectangleRec(rec, col);
|
||||
Color textCol = mouseOver ? Color.WHITE : new Color(180, 180, 180, 255);
|
||||
int fontSize = ScaleInt(32);
|
||||
|
||||
DrawText(text, centre, fontSize, 1, textCol, AlignH.Centre);
|
||||
|
||||
return pressedThisFrame;
|
||||
}
|
||||
|
||||
static bool MouseInRect(Rectangle rec)
|
||||
{
|
||||
Vector2 mousePos = Raylib.GetMousePosition();
|
||||
return mousePos.X >= rec.x && mousePos.Y >= rec.y && mousePos.X <= rec.x + rec.width && mousePos.Y <= rec.y + rec.height;
|
||||
}
|
||||
|
||||
public static string GetResourcePath(params string[] localPath)
|
||||
{
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "resources", Path.Combine(localPath));
|
||||
}
|
||||
|
||||
public static float Scale(float val, int referenceResolution = referenceResolution)
|
||||
{
|
||||
return Raylib.GetScreenWidth() / (float)referenceResolution * val;
|
||||
}
|
||||
|
||||
public static int ScaleInt(int val, int referenceResolution = referenceResolution)
|
||||
{
|
||||
return (int)Math.Round(Raylib.GetScreenWidth() / (float)referenceResolution * val);
|
||||
}
|
||||
|
||||
public static Vector2 Scale(Vector2 vec, int referenceResolution = referenceResolution)
|
||||
{
|
||||
float x = Scale(vec.X, referenceResolution);
|
||||
float y = Scale(vec.Y, referenceResolution);
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
public static void Release()
|
||||
{
|
||||
Raylib.UnloadFont(font);
|
||||
if (SDF_Enabled)
|
||||
{
|
||||
Raylib.UnloadFont(fontSdf);
|
||||
Raylib.UnloadShader(shader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
20
Chess-Challenge/src/Framework/Application/Helpers/Warmer.cs
Normal file
20
Chess-Challenge/src/Framework/Application/Helpers/Warmer.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using ChessChallenge.API;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class Warmer
|
||||
{
|
||||
|
||||
public static void Warm()
|
||||
{
|
||||
Chess.Board b = new();
|
||||
b.LoadStartPosition();
|
||||
Board board = new Board(b);
|
||||
Move[] moves = board.GetLegalMoves();
|
||||
|
||||
board.MakeMove(moves[0]);
|
||||
board.UndoMove(moves[0]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
using ChessChallenge.API;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public class ChessPlayer
|
||||
{
|
||||
// public event Action<Chess.Core.Move>? MoveChosen;
|
||||
|
||||
public readonly ChallengeController.PlayerType PlayerType;
|
||||
public readonly IChessBot? Bot;
|
||||
public readonly HumanPlayer? Human;
|
||||
|
||||
double secondsElapsed;
|
||||
int baseTimeMS;
|
||||
|
||||
public ChessPlayer(object instance, ChallengeController.PlayerType type, int baseTimeMS = int.MaxValue)
|
||||
{
|
||||
this.PlayerType = type;
|
||||
Bot = instance as IChessBot;
|
||||
Human = instance as HumanPlayer;
|
||||
this.baseTimeMS = baseTimeMS;
|
||||
|
||||
}
|
||||
|
||||
public bool IsHuman => Human != null;
|
||||
public bool IsBot => Bot != null;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (Human != null)
|
||||
{
|
||||
Human.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateClock(double dt)
|
||||
{
|
||||
secondsElapsed += dt;
|
||||
}
|
||||
|
||||
public int TimeRemainingMs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (baseTimeMS == int.MaxValue)
|
||||
{
|
||||
return baseTimeMS;
|
||||
}
|
||||
return (int)Math.Round(Math.Max(0, baseTimeMS - secondsElapsed * 1000.0));
|
||||
}
|
||||
}
|
||||
|
||||
public void SubscribeToMoveChosenEventIfHuman(Action<ChessChallenge.Chess.Move> action)
|
||||
{
|
||||
if (Human != null)
|
||||
{
|
||||
Human.MoveChosen += action;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
110
Chess-Challenge/src/Framework/Application/Players/HumanPlayer.cs
Normal file
110
Chess-Challenge/src/Framework/Application/Players/HumanPlayer.cs
Normal file
|
@ -0,0 +1,110 @@
|
|||
using ChessChallenge.Chess;
|
||||
using Raylib_cs;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public class HumanPlayer
|
||||
{
|
||||
public event System.Action<Move>? MoveChosen;
|
||||
|
||||
readonly Board board;
|
||||
readonly BoardUI boardUI;
|
||||
|
||||
// State
|
||||
bool isDragging;
|
||||
int selectedSquare;
|
||||
|
||||
|
||||
public HumanPlayer(BoardUI boardUI)
|
||||
{
|
||||
board = new();
|
||||
board.LoadStartPosition();
|
||||
this.boardUI = boardUI;
|
||||
}
|
||||
|
||||
public void NotifyTurnToMove()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetPosition(string fen)
|
||||
{
|
||||
board.LoadPosition(fen);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
Vector2 mouseScreenPos = Raylib.GetMousePosition();
|
||||
Vector2 mouseWorldPos = Program.ScreenToWorldPos(mouseScreenPos);
|
||||
|
||||
if (LeftMousePressedThisFrame())
|
||||
{
|
||||
if (boardUI.TryGetSquareAtPoint(mouseWorldPos, out int square))
|
||||
{
|
||||
int piece = board.Square[square];
|
||||
if (PieceHelper.IsColour(piece, board.IsWhiteToMove ? PieceHelper.White : PieceHelper.Black))
|
||||
{
|
||||
isDragging = true;
|
||||
selectedSquare = square;
|
||||
boardUI.HighlightLegalMoves(board, square);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isDragging)
|
||||
{
|
||||
if (LeftMouseReleasedThisFrame())
|
||||
{
|
||||
CancelDrag();
|
||||
if (boardUI.TryGetSquareAtPoint(mouseWorldPos, out int square))
|
||||
{
|
||||
TryMakeMove(selectedSquare, square);
|
||||
}
|
||||
}
|
||||
else if (RightMousePressedThisFrame())
|
||||
{
|
||||
CancelDrag();
|
||||
}
|
||||
else
|
||||
{
|
||||
boardUI.DragPiece(selectedSquare, mouseWorldPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CancelDrag()
|
||||
{
|
||||
isDragging = false;
|
||||
boardUI.ResetSquareColours(true);
|
||||
}
|
||||
|
||||
void TryMakeMove(int startSquare, int targetSquare)
|
||||
{
|
||||
bool isLegal = false;
|
||||
Move move = Move.NullMove;
|
||||
|
||||
MoveGenerator generator = new();
|
||||
var legalMoves = generator.GenerateMoves(board);
|
||||
foreach (var legalMove in legalMoves)
|
||||
{
|
||||
if (legalMove.StartSquareIndex == startSquare && legalMove.TargetSquareIndex == targetSquare)
|
||||
{
|
||||
isLegal = true;
|
||||
move = legalMove;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isLegal)
|
||||
{
|
||||
MoveChosen?.Invoke(move);
|
||||
}
|
||||
}
|
||||
|
||||
static bool LeftMousePressedThisFrame() => Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT);
|
||||
static bool LeftMouseReleasedThisFrame() => Raylib.IsMouseButtonReleased(MouseButton.MOUSE_BUTTON_LEFT);
|
||||
static bool RightMousePressedThisFrame() => Raylib.IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_RIGHT);
|
||||
|
||||
}
|
||||
}
|
28
Chess-Challenge/src/Framework/Application/UI/BoardTheme.cs
Normal file
28
Chess-Challenge/src/Framework/Application/UI/BoardTheme.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Raylib_cs;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public class BoardTheme
|
||||
{
|
||||
public Color LightCol = new Color(238, 216, 192, 255);
|
||||
public Color DarkCol = new Color(171, 121, 101, 255);
|
||||
|
||||
public Color selectedLight = new Color(236, 197, 123, 255);
|
||||
public Color selectedDark = new Color(200, 158, 80, 255);
|
||||
|
||||
public Color MoveFromLight = new Color(207, 172, 106, 255);
|
||||
public Color MoveFromDark = new Color(197, 158, 54, 255);
|
||||
|
||||
public Color MoveToLight = new Color(221, 208, 124, 255);
|
||||
public Color MoveToDark = new Color(197, 173, 96, 255);
|
||||
|
||||
public Color LegalLight = new Color(89, 171, 221, 255);
|
||||
public Color LegalDark = new Color(62, 144, 195, 255);
|
||||
|
||||
public Color CheckLight = new Color(234, 74, 74, 255);
|
||||
public Color CheckDark = new Color(207, 39, 39, 255);
|
||||
|
||||
public Color BorderCol = new Color(44, 44, 44, 255);
|
||||
}
|
||||
}
|
||||
|
379
Chess-Challenge/src/Framework/Application/UI/BoardUI.cs
Normal file
379
Chess-Challenge/src/Framework/Application/UI/BoardUI.cs
Normal file
|
@ -0,0 +1,379 @@
|
|||
using ChessChallenge.Chess;
|
||||
using Raylib_cs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public class BoardUI
|
||||
{
|
||||
const int squareSize = 100;
|
||||
const double moveAnimDuration = 0.15;
|
||||
bool whitePerspective = true;
|
||||
|
||||
// Text colours
|
||||
static readonly Color activeTextCol = new(200, 200, 200, 255);
|
||||
static readonly Color inactiveTextCol = new(100, 100, 100, 255);
|
||||
static readonly Color nameCol = new(67, 204, 101, 255);
|
||||
|
||||
// Colour state
|
||||
Color topTextCol;
|
||||
Color bottomTextCol;
|
||||
|
||||
// Drag state
|
||||
bool isDraggingPiece;
|
||||
int dragSquare;
|
||||
Vector2 dragPos;
|
||||
|
||||
static readonly int[] pieceImageOrder = { 5, 3, 2, 4, 1, 0 };
|
||||
Texture2D piecesTexture;
|
||||
BoardTheme theme;
|
||||
Dictionary<int, Color> squareColOverrides;
|
||||
Board board;
|
||||
Move lastMove;
|
||||
|
||||
// Animate move state
|
||||
Board animateMoveTargetBoardState;
|
||||
Move moveToAnimate;
|
||||
double moveAnimStartTime;
|
||||
bool isAnimatingMove;
|
||||
|
||||
|
||||
public enum HighlightType
|
||||
{
|
||||
MoveFrom,
|
||||
MoveTo,
|
||||
LegalMove,
|
||||
Check
|
||||
}
|
||||
|
||||
|
||||
public BoardUI()
|
||||
{
|
||||
theme = new BoardTheme();
|
||||
piecesTexture = Raylib.LoadTexture(UIHelper.GetResourcePath("Pieces.png"));
|
||||
Raylib.GenTextureMipmaps(ref piecesTexture);
|
||||
Raylib.SetTextureWrap(piecesTexture, TextureWrap.TEXTURE_WRAP_CLAMP);
|
||||
Raylib.SetTextureFilter(piecesTexture, TextureFilter.TEXTURE_FILTER_BILINEAR);
|
||||
|
||||
board = new Board();
|
||||
board.LoadStartPosition();
|
||||
squareColOverrides = new Dictionary<int, Color>();
|
||||
topTextCol = inactiveTextCol;
|
||||
bottomTextCol = inactiveTextCol;
|
||||
}
|
||||
|
||||
public void SetPerspective(bool whitePerspective)
|
||||
{
|
||||
this.whitePerspective = whitePerspective;
|
||||
}
|
||||
|
||||
public void UpdatePosition(Board board)
|
||||
{
|
||||
isAnimatingMove = false;
|
||||
|
||||
// Update
|
||||
this.board = new(board);
|
||||
lastMove = Move.NullMove;
|
||||
if (board.IsInCheck())
|
||||
{
|
||||
OverrideSquareColour(board.KingSquare[board.MoveColourIndex], HighlightType.Check);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePosition(Board board, Move moveMade, bool animate = false)
|
||||
{
|
||||
// Interrupt prev animation
|
||||
if (isAnimatingMove)
|
||||
{
|
||||
UpdatePosition(animateMoveTargetBoardState);
|
||||
isAnimatingMove = false;
|
||||
}
|
||||
|
||||
ResetSquareColours();
|
||||
if (animate)
|
||||
{
|
||||
OverrideSquareColour(moveMade.StartSquareIndex, HighlightType.MoveFrom);
|
||||
animateMoveTargetBoardState = new Board(board);
|
||||
moveToAnimate = moveMade;
|
||||
moveAnimStartTime = Raylib.GetTime();
|
||||
isAnimatingMove = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdatePosition(board);
|
||||
|
||||
if (!moveMade.IsNull)
|
||||
{
|
||||
HighlightMove(moveMade);
|
||||
lastMove = moveMade;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HighlightMove(Move move)
|
||||
{
|
||||
OverrideSquareColour(move.StartSquareIndex, HighlightType.MoveFrom);
|
||||
OverrideSquareColour(move.TargetSquareIndex, HighlightType.MoveTo);
|
||||
}
|
||||
|
||||
public void DragPiece(int square, Vector2 worldPos)
|
||||
{
|
||||
isDraggingPiece = true;
|
||||
dragSquare = square;
|
||||
dragPos = worldPos;
|
||||
}
|
||||
|
||||
public bool TryGetSquareAtPoint(Vector2 worldPos, out int squareIndex)
|
||||
{
|
||||
Vector2 boardStartPosWorld = new Vector2(squareSize, squareSize) * -4;
|
||||
Vector2 endPosWorld = boardStartPosWorld + new Vector2(8, 8) * squareSize;
|
||||
|
||||
float tx = (worldPos.X - boardStartPosWorld.X) / (endPosWorld.X - boardStartPosWorld.X);
|
||||
float ty = (worldPos.Y - boardStartPosWorld.Y) / (endPosWorld.Y - boardStartPosWorld.Y);
|
||||
|
||||
if (tx >= 0 && tx <= 1 && ty >= 0 && ty <= 1)
|
||||
{
|
||||
if (!whitePerspective)
|
||||
{
|
||||
tx = 1 - tx;
|
||||
ty = 1 - ty;
|
||||
}
|
||||
squareIndex = new Coord((int)(tx * 8), 7 - (int)(ty * 8)).SquareIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
squareIndex = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OverrideSquareColour(int square, HighlightType hightlightType)
|
||||
{
|
||||
bool isLight = new Coord(square).IsLightSquare();
|
||||
|
||||
Color col = hightlightType switch
|
||||
{
|
||||
HighlightType.MoveFrom => isLight ? theme.MoveFromLight : theme.MoveFromDark,
|
||||
HighlightType.MoveTo => isLight ? theme.MoveToLight : theme.MoveToDark,
|
||||
HighlightType.LegalMove => isLight ? theme.LegalLight : theme.LegalDark,
|
||||
HighlightType.Check => isLight ? theme.CheckLight : theme.CheckDark,
|
||||
_ => Color.PINK
|
||||
};
|
||||
|
||||
if (squareColOverrides.ContainsKey(square))
|
||||
{
|
||||
squareColOverrides[square] = col;
|
||||
}
|
||||
else
|
||||
{
|
||||
squareColOverrides.Add(square, col);
|
||||
}
|
||||
}
|
||||
|
||||
public void HighlightLegalMoves(Board board, int square)
|
||||
{
|
||||
MoveGenerator moveGenerator = new();
|
||||
var moves = moveGenerator.GenerateMoves(board);
|
||||
foreach (var move in moves)
|
||||
{
|
||||
if (move.StartSquareIndex == square)
|
||||
{
|
||||
OverrideSquareColour(move.TargetSquareIndex, HighlightType.LegalMove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
double animT = (Raylib.GetTime() - moveAnimStartTime) / moveAnimDuration;
|
||||
|
||||
if (isAnimatingMove && animT >= 1)
|
||||
{
|
||||
isAnimatingMove = false;
|
||||
UpdatePosition(animateMoveTargetBoardState, moveToAnimate, false);
|
||||
}
|
||||
|
||||
DrawBorder();
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
DrawSquare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDraggingPiece)
|
||||
{
|
||||
DrawPiece(board.Square[dragSquare], dragPos - new Vector2(squareSize * 0.5f, squareSize * 0.5f));
|
||||
}
|
||||
if (isAnimatingMove)
|
||||
{
|
||||
Coord startCoord = new Coord(moveToAnimate.StartSquareIndex);
|
||||
Coord targetCoord = new Coord(moveToAnimate.TargetSquareIndex);
|
||||
Vector2 startPos = GetSquarePos(startCoord.fileIndex, startCoord.rankIndex, whitePerspective);
|
||||
Vector2 targetPos = GetSquarePos(targetCoord.fileIndex, targetCoord.rankIndex, whitePerspective);
|
||||
|
||||
Vector2 animPos = Vector2.Lerp(startPos, targetPos, (float)animT);
|
||||
DrawPiece(board.Square[moveToAnimate.StartSquareIndex], animPos);
|
||||
|
||||
}
|
||||
|
||||
// Reset state
|
||||
isDraggingPiece = false;
|
||||
}
|
||||
|
||||
public void DrawPlayerNames(string nameWhite, string nameBlack, int timeWhite, int timeBlack, bool isPlaying)
|
||||
{
|
||||
string nameBottom = whitePerspective ? nameWhite : nameBlack;
|
||||
string nameTop = !whitePerspective ? nameWhite : nameBlack;
|
||||
int timeBottom = whitePerspective ? timeWhite : timeBlack;
|
||||
int timeTop = !whitePerspective ? timeWhite : timeBlack;
|
||||
bool bottomTurnToMove = whitePerspective == board.IsWhiteToMove && isPlaying;
|
||||
bool topTurnToMove = whitePerspective != board.IsWhiteToMove && isPlaying;
|
||||
|
||||
string colNameBottom = whitePerspective ? "White" : "Black";
|
||||
string colNameTop = !whitePerspective ? "White" : "Black";
|
||||
|
||||
int boardStartX = -squareSize * 4;
|
||||
int boardStartY = -squareSize * 4;
|
||||
const int spaceY = 35;
|
||||
|
||||
|
||||
Color textTopTargetCol = topTurnToMove ? activeTextCol : inactiveTextCol;
|
||||
Color textBottomTargetCol = bottomTurnToMove ? activeTextCol : inactiveTextCol;
|
||||
|
||||
float colLerpSpeed = 16;
|
||||
topTextCol = LerpColour(topTextCol, textTopTargetCol, Raylib.GetFrameTime() * colLerpSpeed);
|
||||
bottomTextCol = LerpColour(bottomTextCol, textBottomTargetCol, Raylib.GetFrameTime() * colLerpSpeed);
|
||||
|
||||
//Color textColTop = topTurnToMove ? activeTextCol : inactiveTextCol;
|
||||
|
||||
Draw(boardStartY + squareSize * 8 + spaceY, colNameBottom, nameBottom, timeBottom, bottomTextCol);
|
||||
Draw(boardStartY - spaceY, colNameTop, nameTop, timeTop, topTextCol);
|
||||
|
||||
void Draw(float y, string colName, string name, int timeMs, Color textCol)
|
||||
{
|
||||
const int fontSize = 36;
|
||||
const int fontSpacing = 1;
|
||||
var namePos = new Vector2(boardStartX, y);
|
||||
|
||||
UIHelper.DrawText($"{colName}: {name}", namePos, fontSize, fontSpacing, nameCol);
|
||||
var timePos = new Vector2(boardStartX + squareSize * 8, y);
|
||||
string timeText;
|
||||
if (timeMs == int.MaxValue)
|
||||
{
|
||||
timeText = "Time: Unlimited";
|
||||
}
|
||||
else
|
||||
{
|
||||
double secondsRemaining = timeMs / 1000.0;
|
||||
int numMinutes = (int)(secondsRemaining / 60);
|
||||
int numSeconds = (int)(secondsRemaining - numMinutes * 60);
|
||||
int dec = (int)((secondsRemaining - numMinutes * 60 - numSeconds) * 10);
|
||||
|
||||
timeText = $"Time: {numMinutes:00}:{numSeconds:00}.{dec}";
|
||||
}
|
||||
UIHelper.DrawText(timeText, timePos, fontSize, fontSpacing, textCol, UIHelper.AlignH.Right);
|
||||
}
|
||||
|
||||
|
||||
//Raylib.DrawText()
|
||||
}
|
||||
|
||||
public void ResetSquareColours(bool keepPrevMoveHighlight = false)
|
||||
{
|
||||
squareColOverrides.Clear();
|
||||
if (keepPrevMoveHighlight && !lastMove.IsNull)
|
||||
{
|
||||
HighlightMove(lastMove);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void DrawBorder()
|
||||
{
|
||||
int boardStartX = -squareSize * 4;
|
||||
int boardStartY = -squareSize * 4;
|
||||
int w = 12;
|
||||
Raylib.DrawRectangle(boardStartX - w, boardStartY - w, 8 * squareSize + w * 2, 8 * squareSize + w * 2, theme.BorderCol);
|
||||
}
|
||||
|
||||
void DrawSquare(int file, int rank)
|
||||
{
|
||||
|
||||
Coord coord = new Coord(file, rank);
|
||||
Color col = coord.IsLightSquare() ? theme.LightCol : theme.DarkCol;
|
||||
if (squareColOverrides.TryGetValue(coord.SquareIndex, out Color overrideCol))
|
||||
{
|
||||
col = overrideCol;
|
||||
}
|
||||
|
||||
// top left
|
||||
Vector2 pos = GetSquarePos(file, rank, whitePerspective);
|
||||
Raylib.DrawRectangle((int)pos.X, (int)pos.Y, squareSize, squareSize, col);
|
||||
int piece = board.Square[coord.SquareIndex];
|
||||
float alpha = isDraggingPiece && dragSquare == coord.SquareIndex ? 0.3f : 1;
|
||||
if (!isAnimatingMove || coord.SquareIndex != moveToAnimate.StartSquareIndex)
|
||||
{
|
||||
DrawPiece(piece, new Vector2((int)pos.X, (int)pos.Y), alpha);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 GetSquarePos(int file, int rank, bool whitePerspective)
|
||||
{
|
||||
const int boardStartX = -squareSize * 4;
|
||||
const int boardStartY = -squareSize * 4;
|
||||
|
||||
if (!whitePerspective)
|
||||
{
|
||||
file = 7 - file;
|
||||
rank = 7 - rank;
|
||||
}
|
||||
|
||||
int posX = boardStartX + file * squareSize;
|
||||
int posY = boardStartY + (7 - rank) * squareSize;
|
||||
return new Vector2(posX, posY);
|
||||
}
|
||||
|
||||
void DrawPiece(int piece, Vector2 posTopLeft, float alpha = 1)
|
||||
{
|
||||
if (piece != PieceHelper.None)
|
||||
{
|
||||
int type = PieceHelper.PieceType(piece);
|
||||
bool white = PieceHelper.IsWhite(piece);
|
||||
Rectangle srcRect = GetPieceTextureRect(type, white);
|
||||
Rectangle targRect = new Rectangle((int)posTopLeft.X, (int)posTopLeft.Y, squareSize, squareSize);
|
||||
|
||||
Color tint = new Color(255, 255, 255, (int)MathF.Round(255 * alpha));
|
||||
Raylib.DrawTexturePro(piecesTexture, srcRect, targRect, new Vector2(0, 0), 0, tint);
|
||||
}
|
||||
}
|
||||
|
||||
static Color LerpColour(Color a, Color b, float t)
|
||||
{
|
||||
int newR = (int)(Math.Round(Lerp(a.r, b.r, t)));
|
||||
int newG = (int)(Math.Round(Lerp(a.g, b.g, t)));
|
||||
int newB = (int)(Math.Round(Lerp(a.b, b.b, t)));
|
||||
int newA = (int)(Math.Round(Lerp(a.a, b.a, t)));
|
||||
return new Color(newR, newG, newB, newA);
|
||||
|
||||
float Lerp(float a, float b, float t)
|
||||
{
|
||||
t = Math.Min(1, Math.Max(t, 0));
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
Raylib.UnloadTexture(piecesTexture);
|
||||
}
|
||||
|
||||
static Rectangle GetPieceTextureRect(int pieceType, bool isWhite)
|
||||
{
|
||||
const int size = 333;
|
||||
return new Rectangle(size * pieceImageOrder[pieceType - 1], isWhite ? 0 : size, size, size);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
using Raylib_cs;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class BotBrainCapacityUI
|
||||
{
|
||||
static readonly Color green = new(17, 212, 73, 255);
|
||||
static readonly Color yellow = new(219, 161, 24, 255);
|
||||
static readonly Color orange = new(219, 96, 24, 255);
|
||||
static readonly Color red = new(219, 9, 9, 255);
|
||||
static readonly Color background = new Color(40, 40, 40, 255);
|
||||
|
||||
public static void Draw(int numTokens, int tokenLimit)
|
||||
{
|
||||
|
||||
int screenWidth = Raylib.GetScreenWidth();
|
||||
int screenHeight = Raylib.GetScreenHeight();
|
||||
int height = UIHelper.ScaleInt(48);
|
||||
int fontSize = UIHelper.ScaleInt(35);
|
||||
// Bg
|
||||
Raylib.DrawRectangle(0, screenHeight - height, screenWidth, height, background);
|
||||
// Bar
|
||||
double t = (double)numTokens / tokenLimit;
|
||||
|
||||
Color col;
|
||||
if (t <= 0.7)
|
||||
col = green;
|
||||
else if (t <= 0.85)
|
||||
col = yellow;
|
||||
else if (t <= 1)
|
||||
col = orange;
|
||||
else
|
||||
col = red;
|
||||
Raylib.DrawRectangle(0, screenHeight - height, (int)(screenWidth * t), height, col);
|
||||
|
||||
var textPos = new System.Numerics.Vector2(screenWidth / 2, screenHeight - height / 2);
|
||||
string text = $"Bot Brain Capacity: {numTokens}/{tokenLimit}";
|
||||
if (numTokens > tokenLimit)
|
||||
{
|
||||
text += " [LIMIT EXCEEDED]";
|
||||
}
|
||||
UIHelper.DrawText(text, textPos, fontSize, 1, Color.WHITE, UIHelper.AlignH.Centre);
|
||||
}
|
||||
}
|
||||
}
|
44
Chess-Challenge/src/Framework/Application/UI/MatchStatsUI.cs
Normal file
44
Chess-Challenge/src/Framework/Application/UI/MatchStatsUI.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Raylib_cs;
|
||||
using System.Numerics;
|
||||
using System;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class MatchStatsUI
|
||||
{
|
||||
public static void DrawMatchStats(ChallengeController controller)
|
||||
{
|
||||
if (controller.PlayerWhite.IsBot && controller.PlayerBlack.IsBot)
|
||||
{
|
||||
int nameFontSize = UIHelper.ScaleInt(40);
|
||||
int regularFontSize = UIHelper.ScaleInt(35);
|
||||
int headerFontSize = UIHelper.ScaleInt(45);
|
||||
Color col = new(180, 180, 180, 255);
|
||||
Vector2 startPos = UIHelper.Scale(new Vector2(1500, 250));
|
||||
float spacingY = UIHelper.Scale(35);
|
||||
|
||||
DrawNextText($"Game {controller.CurrGameNumber} of {controller.TotalGameCount}", headerFontSize, Color.WHITE);
|
||||
startPos.Y += spacingY * 2;
|
||||
|
||||
DrawStats(controller.BotStatsA);
|
||||
startPos.Y += spacingY * 2;
|
||||
DrawStats(controller.BotStatsB);
|
||||
|
||||
|
||||
void DrawStats(ChallengeController.BotMatchStats stats)
|
||||
{
|
||||
DrawNextText(stats.BotName + ":", nameFontSize, Color.WHITE);
|
||||
DrawNextText($"Score: +{stats.NumWins} ={stats.NumDraws} -{stats.NumLosses}", regularFontSize, col);
|
||||
DrawNextText($"Num Timeouts: {stats.NumTimeouts}", regularFontSize, col);
|
||||
DrawNextText($"Num Illegal Moves: {stats.NumIllegalMoves}", regularFontSize, col);
|
||||
}
|
||||
|
||||
void DrawNextText(string text, int fontSize, Color col)
|
||||
{
|
||||
UIHelper.DrawText(text, startPos, fontSize, 1, col);
|
||||
startPos.Y += spacingY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
Chess-Challenge/src/Framework/Application/UI/MenuUI.cs
Normal file
81
Chess-Challenge/src/Framework/Application/UI/MenuUI.cs
Normal file
|
@ -0,0 +1,81 @@
|
|||
using Raylib_cs;
|
||||
using System.Numerics;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace ChessChallenge.Application
|
||||
{
|
||||
public static class MenuUI
|
||||
{
|
||||
public static void DrawButtons(ChallengeController controller)
|
||||
{
|
||||
Vector2 buttonPos = UIHelper.Scale(new Vector2(260, 210));
|
||||
Vector2 buttonSize = UIHelper.Scale(new Vector2(260, 55));
|
||||
float spacing = buttonSize.Y * 1.2f;
|
||||
float breakSpacing = spacing * 0.6f;
|
||||
|
||||
// Game Buttons
|
||||
if (NextButtonInRow("Human vs MyBot", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
var whiteType = controller.HumanWasWhiteLastGame ? ChallengeController.PlayerType.MyBot : ChallengeController.PlayerType.Human;
|
||||
var blackType = !controller.HumanWasWhiteLastGame ? ChallengeController.PlayerType.MyBot : ChallengeController.PlayerType.Human;
|
||||
controller.StartNewGame(whiteType, blackType);
|
||||
}
|
||||
if (NextButtonInRow("MyBot vs MyBot", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
controller.StartNewBotMatch(ChallengeController.PlayerType.MyBot, ChallengeController.PlayerType.MyBot);
|
||||
}
|
||||
if (NextButtonInRow("MyBot vs EvilBot", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
controller.StartNewBotMatch(ChallengeController.PlayerType.MyBot, ChallengeController.PlayerType.EvilBot);
|
||||
}
|
||||
|
||||
// Page buttons
|
||||
buttonPos.Y += breakSpacing;
|
||||
|
||||
if (NextButtonInRow("Save Games", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
string pgns = controller.AllPGNs;
|
||||
string directoryPath = Path.Combine(FileHelper.AppDataPath, "Games");
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
string fileName = FileHelper.GetUniqueFileName(directoryPath, "games", ".txt");
|
||||
string fullPath = Path.Combine(directoryPath, fileName);
|
||||
File.WriteAllText(fullPath, pgns);
|
||||
ConsoleHelper.Log("Saved games to " + fullPath, false, ConsoleColor.Blue);
|
||||
}
|
||||
if (NextButtonInRow("Rules & Help", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
FileHelper.OpenUrl("https://github.com/SebLague/Chess-Challenge");
|
||||
}
|
||||
if (NextButtonInRow("Documentation", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
FileHelper.OpenUrl("https://seblague.github.io/chess-coding-challenge/documentation/");
|
||||
}
|
||||
if (NextButtonInRow("Submission Page", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
FileHelper.OpenUrl("https://forms.gle/6jjj8jxNQ5Ln53ie6");
|
||||
}
|
||||
|
||||
// Window and quit buttons
|
||||
buttonPos.Y += breakSpacing;
|
||||
|
||||
bool isBigWindow = Raylib.GetScreenWidth() > Settings.ScreenSizeSmall.X;
|
||||
string windowButtonName = isBigWindow ? "Smaller Window" : "Bigger Window";
|
||||
if (NextButtonInRow(windowButtonName, ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
Program.SetWindowSize(isBigWindow ? Settings.ScreenSizeSmall : Settings.ScreenSizeBig);
|
||||
}
|
||||
if (NextButtonInRow("Exit (ESC)", ref buttonPos, spacing, buttonSize))
|
||||
{
|
||||
Environment.Exit(0);
|
||||
}
|
||||
|
||||
bool NextButtonInRow(string name, ref Vector2 pos, float spacingY, Vector2 size)
|
||||
{
|
||||
bool pressed = UIHelper.Button(name, pos, size);
|
||||
pos.Y += spacingY;
|
||||
return pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
581
Chess-Challenge/src/Framework/Chess/Board/Board.cs
Normal file
581
Chess-Challenge/src/Framework/Chess/Board/Board.cs
Normal file
|
@ -0,0 +1,581 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Represents the current state of the board during a game.
|
||||
// The state includes things such as: positions of all pieces, side to move,
|
||||
// castling rights, en-passant square, etc. Some extra information is included
|
||||
// as well to help with evaluation and move generation.
|
||||
|
||||
// The initial state of the board can be set from a FEN string, and moves are
|
||||
// subsequently made (or undone) using the MakeMove and UnmakeMove functions.
|
||||
|
||||
public sealed class Board
|
||||
{
|
||||
|
||||
public ulong ZobristKey => currentGameState.zobristKey;
|
||||
public const int WhiteIndex = 0;
|
||||
public const int BlackIndex = 1;
|
||||
|
||||
// Side to move info
|
||||
public bool IsWhiteToMove;
|
||||
public int MoveColour => IsWhiteToMove ? PieceHelper.White : PieceHelper.Black;
|
||||
public int OpponentColour => IsWhiteToMove ? PieceHelper.Black : PieceHelper.White;
|
||||
public int MoveColourIndex => IsWhiteToMove ? WhiteIndex : BlackIndex;
|
||||
public int OpponentColourIndex => IsWhiteToMove ? BlackIndex : WhiteIndex;
|
||||
|
||||
// Stores piece code for each square on the board
|
||||
public int[] Square;
|
||||
|
||||
// Piece lists
|
||||
public PieceList[] rooks;
|
||||
public PieceList[] bishops;
|
||||
public PieceList[] queens;
|
||||
public PieceList[] knights;
|
||||
public PieceList[] pawns;
|
||||
public PieceList[] kings;
|
||||
public PieceList[] pieceLists;
|
||||
// Square index of white and black king
|
||||
public int[] KingSquare;
|
||||
|
||||
// --- Bitboards ---
|
||||
// Bitboard for each piece type and colour (white pawns, white knights, ... black pawns, etc.)
|
||||
public ulong[] pieceBitboards;
|
||||
// Bitboards for all pieces of either colour (all white pieces, all black pieces)
|
||||
public ulong[] colourBitboards;
|
||||
public ulong allPiecesBitboard;
|
||||
public ulong FriendlyOrthogonalSliders;
|
||||
public ulong FriendlyDiagonalSliders;
|
||||
public ulong EnemyOrthogonalSliders;
|
||||
public ulong EnemyDiagonalSliders;
|
||||
|
||||
// Total plies (half-moves) played in game
|
||||
public int plyCount;
|
||||
|
||||
// List of (hashed) positions since last pawn move or capture (for detecting 3-fold repetition)
|
||||
public Stack<ulong> RepetitionPositionHistory;
|
||||
|
||||
Stack<GameState> gameStateHistory;
|
||||
public GameState currentGameState;
|
||||
|
||||
public List<Move> AllGameMoves;
|
||||
public string GameStartFen { get; private set; }
|
||||
|
||||
// piece count excluding pawns and kings
|
||||
public int totalPieceCountWithoutPawnsAndKings;
|
||||
bool cachedInCheckValue;
|
||||
bool hasCachedInCheckValue;
|
||||
|
||||
|
||||
|
||||
public Board(Board source = null)
|
||||
{
|
||||
if (source != null)
|
||||
{
|
||||
string fen = FenUtility.CurrentFen(source);
|
||||
LoadPosition(fen);
|
||||
|
||||
RepetitionPositionHistory = new(source.RepetitionPositionHistory);
|
||||
//AllGameMoves = new(source.AllGameMoves);
|
||||
//Console.WriteLine(source.gameStateHistory.Count);
|
||||
//gameStateHistory = new(source.gameStateHistory);
|
||||
currentGameState = source.currentGameState;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Is current player in check?
|
||||
// Note: caches check value so calling multiple times does not require recalculating
|
||||
public bool IsInCheck()
|
||||
{
|
||||
if (hasCachedInCheckValue)
|
||||
{
|
||||
return cachedInCheckValue;
|
||||
}
|
||||
cachedInCheckValue = CalculateInCheckState();
|
||||
hasCachedInCheckValue = true;
|
||||
|
||||
return cachedInCheckValue;
|
||||
}
|
||||
|
||||
|
||||
// Update piece lists / bitboards based on given move info.
|
||||
// Note that this does not account for the following things, which must be handled separately:
|
||||
// 1. Removal of a captured piece
|
||||
// 2. Movement of rook when castling
|
||||
// 3. Removal of pawn from 1st/8th rank during pawn promotion
|
||||
// 4. Addition of promoted piece during pawn promotion
|
||||
void MovePiece(int piece, int startSquare, int targetSquare)
|
||||
{
|
||||
BitBoardUtility.ToggleSquares(ref pieceBitboards[piece], startSquare, targetSquare);
|
||||
BitBoardUtility.ToggleSquares(ref colourBitboards[MoveColourIndex], startSquare, targetSquare);
|
||||
|
||||
pieceLists[piece].MovePiece(startSquare, targetSquare);
|
||||
Square[startSquare] = PieceHelper.None;
|
||||
Square[targetSquare] = piece;
|
||||
}
|
||||
|
||||
// Make a move on the board
|
||||
// The inSearch parameter controls whether this move should be recorded in the game history.
|
||||
// (for detecting three-fold repetition)
|
||||
public void MakeMove(Move move, bool inSearch = true)
|
||||
{
|
||||
// Get info about move
|
||||
int startSquare = move.StartSquareIndex;
|
||||
int targetSquare = move.TargetSquareIndex;
|
||||
int moveFlag = move.MoveFlag;
|
||||
bool isPromotion = move.IsPromotion;
|
||||
bool isEnPassant = moveFlag is Move.EnPassantCaptureFlag;
|
||||
|
||||
int movedPiece = Square[startSquare];
|
||||
int movedPieceType = PieceHelper.PieceType(movedPiece);
|
||||
int capturedPiece = isEnPassant ? PieceHelper.MakePiece(PieceHelper.Pawn, OpponentColour) : Square[targetSquare];
|
||||
int capturedPieceType = PieceHelper.PieceType(capturedPiece);
|
||||
|
||||
int prevCastleState = currentGameState.castlingRights;
|
||||
int prevEnPassantFile = currentGameState.enPassantFile;
|
||||
ulong newZobristKey = currentGameState.zobristKey;
|
||||
int newCastlingRights = currentGameState.castlingRights;
|
||||
int newEnPassantFile = 0;
|
||||
|
||||
// Update bitboard of moved piece (pawn promotion is a special case and is corrected later)
|
||||
MovePiece(movedPiece, startSquare, targetSquare);
|
||||
|
||||
// Handle captures
|
||||
if (capturedPieceType != PieceHelper.None)
|
||||
{
|
||||
int captureSquare = targetSquare;
|
||||
|
||||
if (isEnPassant)
|
||||
{
|
||||
captureSquare = targetSquare + (IsWhiteToMove ? -8 : 8);
|
||||
Square[captureSquare] = PieceHelper.None;
|
||||
}
|
||||
if (capturedPieceType != PieceHelper.Pawn)
|
||||
{
|
||||
totalPieceCountWithoutPawnsAndKings--;
|
||||
}
|
||||
|
||||
// Remove captured piece from bitboards/piece list
|
||||
pieceLists[capturedPiece].RemovePieceAtSquare(captureSquare);
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[capturedPiece], captureSquare);
|
||||
BitBoardUtility.ToggleSquare(ref colourBitboards[OpponentColourIndex], captureSquare);
|
||||
newZobristKey ^= Zobrist.piecesArray[capturedPiece, captureSquare];
|
||||
}
|
||||
|
||||
// Handle king
|
||||
if (movedPieceType == PieceHelper.King)
|
||||
{
|
||||
KingSquare[MoveColourIndex] = targetSquare;
|
||||
newCastlingRights &= (IsWhiteToMove) ? 0b1100 : 0b0011;
|
||||
|
||||
// Handle castling
|
||||
if (moveFlag == Move.CastleFlag)
|
||||
{
|
||||
int rookPiece = PieceHelper.MakePiece(PieceHelper.Rook, MoveColour);
|
||||
bool kingside = targetSquare == BoardHelper.g1 || targetSquare == BoardHelper.g8;
|
||||
int castlingRookFromIndex = (kingside) ? targetSquare + 1 : targetSquare - 2;
|
||||
int castlingRookToIndex = (kingside) ? targetSquare - 1 : targetSquare + 1;
|
||||
|
||||
// Update rook position
|
||||
BitBoardUtility.ToggleSquares(ref pieceBitboards[rookPiece], castlingRookFromIndex, castlingRookToIndex);
|
||||
BitBoardUtility.ToggleSquares(ref colourBitboards[MoveColourIndex], castlingRookFromIndex, castlingRookToIndex);
|
||||
pieceLists[rookPiece].MovePiece(castlingRookFromIndex, castlingRookToIndex);
|
||||
Square[castlingRookFromIndex] = PieceHelper.None;
|
||||
Square[castlingRookToIndex] = PieceHelper.Rook | MoveColour;
|
||||
|
||||
newZobristKey ^= Zobrist.piecesArray[rookPiece, castlingRookFromIndex];
|
||||
newZobristKey ^= Zobrist.piecesArray[rookPiece, castlingRookToIndex];
|
||||
}
|
||||
}
|
||||
|
||||
// Handle promotion
|
||||
if (isPromotion)
|
||||
{
|
||||
totalPieceCountWithoutPawnsAndKings++;
|
||||
int promotionPieceType = moveFlag switch
|
||||
{
|
||||
Move.PromoteToQueenFlag => PieceHelper.Queen,
|
||||
Move.PromoteToRookFlag => PieceHelper.Rook,
|
||||
Move.PromoteToKnightFlag => PieceHelper.Knight,
|
||||
Move.PromoteToBishopFlag => PieceHelper.Bishop,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
int promotionPiece = PieceHelper.MakePiece(promotionPieceType, MoveColour);
|
||||
|
||||
// Remove pawn from promotion square and add promoted piece instead
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[movedPiece], targetSquare);
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[promotionPiece], targetSquare);
|
||||
pieceLists[movedPiece].RemovePieceAtSquare(targetSquare);
|
||||
pieceLists[promotionPiece].AddPieceAtSquare(targetSquare);
|
||||
Square[targetSquare] = promotionPiece;
|
||||
}
|
||||
|
||||
// Pawn has moved two forwards, mark file with en-passant flag
|
||||
if (moveFlag == Move.PawnTwoUpFlag)
|
||||
{
|
||||
int file = BoardHelper.FileIndex(startSquare) + 1;
|
||||
newEnPassantFile = file;
|
||||
newZobristKey ^= Zobrist.enPassantFile[file];
|
||||
}
|
||||
|
||||
// Update castling rights
|
||||
if (prevCastleState != 0)
|
||||
{
|
||||
// Any piece moving to/from rook square removes castling right for that side
|
||||
if (targetSquare == BoardHelper.h1 || startSquare == BoardHelper.h1)
|
||||
{
|
||||
newCastlingRights &= GameState.ClearWhiteKingsideMask;
|
||||
}
|
||||
else if (targetSquare == BoardHelper.a1 || startSquare == BoardHelper.a1)
|
||||
{
|
||||
newCastlingRights &= GameState.ClearWhiteQueensideMask;
|
||||
}
|
||||
if (targetSquare == BoardHelper.h8 || startSquare == BoardHelper.h8)
|
||||
{
|
||||
newCastlingRights &= GameState.ClearBlackKingsideMask;
|
||||
}
|
||||
else if (targetSquare == BoardHelper.a8 || startSquare == BoardHelper.a8)
|
||||
{
|
||||
newCastlingRights &= GameState.ClearBlackQueensideMask;
|
||||
}
|
||||
}
|
||||
|
||||
// Update zobrist key with new piece position and side to move
|
||||
newZobristKey ^= Zobrist.sideToMove;
|
||||
newZobristKey ^= Zobrist.piecesArray[movedPiece, startSquare];
|
||||
newZobristKey ^= Zobrist.piecesArray[Square[targetSquare], targetSquare];
|
||||
newZobristKey ^= Zobrist.enPassantFile[prevEnPassantFile];
|
||||
|
||||
if (newCastlingRights != prevCastleState)
|
||||
{
|
||||
newZobristKey ^= Zobrist.castlingRights[prevCastleState]; // remove old castling rights state
|
||||
newZobristKey ^= Zobrist.castlingRights[newCastlingRights]; // add new castling rights state
|
||||
}
|
||||
|
||||
// Change side to move
|
||||
IsWhiteToMove = !IsWhiteToMove;
|
||||
|
||||
plyCount++;
|
||||
int newFiftyMoveCounter = currentGameState.fiftyMoveCounter + 1;
|
||||
|
||||
// Update extra bitboards
|
||||
allPiecesBitboard = colourBitboards[WhiteIndex] | colourBitboards[BlackIndex];
|
||||
UpdateSliderBitboards();
|
||||
|
||||
// Pawn moves and captures reset the fifty move counter and clear 3-fold repetition history
|
||||
if (!inSearch && (movedPieceType == PieceHelper.Pawn || capturedPieceType != PieceHelper.None))
|
||||
{
|
||||
RepetitionPositionHistory.Clear();
|
||||
newFiftyMoveCounter = 0;
|
||||
}
|
||||
|
||||
GameState newState = new(capturedPieceType, newEnPassantFile, newCastlingRights, newFiftyMoveCounter, newZobristKey);
|
||||
gameStateHistory.Push(newState);
|
||||
currentGameState = newState;
|
||||
hasCachedInCheckValue = false;
|
||||
|
||||
if (!inSearch)
|
||||
{
|
||||
RepetitionPositionHistory.Push(newState.zobristKey);
|
||||
AllGameMoves.Add(move);
|
||||
}
|
||||
}
|
||||
|
||||
// Undo a move previously made on the board
|
||||
public void UndoMove(Move move, bool inSearch = true)
|
||||
{
|
||||
// Swap colour to move
|
||||
IsWhiteToMove = !IsWhiteToMove;
|
||||
|
||||
bool undoingWhiteMove = IsWhiteToMove;
|
||||
|
||||
// Get move info
|
||||
int movedFrom = move.StartSquareIndex;
|
||||
int movedTo = move.TargetSquareIndex;
|
||||
int moveFlag = move.MoveFlag;
|
||||
|
||||
bool undoingEnPassant = moveFlag == Move.EnPassantCaptureFlag;
|
||||
bool undoingPromotion = move.IsPromotion;
|
||||
bool undoingCapture = currentGameState.capturedPieceType != PieceHelper.None;
|
||||
|
||||
int movedPiece = undoingPromotion ? PieceHelper.MakePiece(PieceHelper.Pawn, MoveColour) : Square[movedTo];
|
||||
int movedPieceType = PieceHelper.PieceType(movedPiece);
|
||||
int capturedPieceType = currentGameState.capturedPieceType;
|
||||
|
||||
// If undoing promotion, then remove piece from promotion square and replace with pawn
|
||||
if (undoingPromotion)
|
||||
{
|
||||
int promotedPiece = Square[movedTo];
|
||||
int pawnPiece = PieceHelper.MakePiece(PieceHelper.Pawn, MoveColour);
|
||||
totalPieceCountWithoutPawnsAndKings--;
|
||||
|
||||
pieceLists[promotedPiece].RemovePieceAtSquare(movedTo);
|
||||
pieceLists[movedPiece].AddPieceAtSquare(movedTo);
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[promotedPiece], movedTo);
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[pawnPiece], movedTo);
|
||||
}
|
||||
|
||||
MovePiece(movedPiece, movedTo, movedFrom);
|
||||
|
||||
// Undo capture
|
||||
if (undoingCapture)
|
||||
{
|
||||
int captureSquare = movedTo;
|
||||
int capturedPiece = PieceHelper.MakePiece(capturedPieceType, OpponentColour);
|
||||
|
||||
if (undoingEnPassant)
|
||||
{
|
||||
captureSquare = movedTo + ((undoingWhiteMove) ? -8 : 8);
|
||||
}
|
||||
if (capturedPieceType != PieceHelper.Pawn)
|
||||
{
|
||||
totalPieceCountWithoutPawnsAndKings++;
|
||||
}
|
||||
|
||||
// Add back captured piece
|
||||
BitBoardUtility.ToggleSquare(ref pieceBitboards[capturedPiece], captureSquare);
|
||||
BitBoardUtility.ToggleSquare(ref colourBitboards[OpponentColourIndex], captureSquare);
|
||||
pieceLists[capturedPiece].AddPieceAtSquare(captureSquare);
|
||||
Square[captureSquare] = capturedPiece;
|
||||
}
|
||||
|
||||
|
||||
// Update king
|
||||
if (movedPieceType is PieceHelper.King)
|
||||
{
|
||||
KingSquare[MoveColourIndex] = movedFrom;
|
||||
|
||||
// Undo castling
|
||||
if (moveFlag is Move.CastleFlag)
|
||||
{
|
||||
int rookPiece = PieceHelper.MakePiece(PieceHelper.Rook, MoveColour);
|
||||
bool kingside = movedTo == BoardHelper.g1 || movedTo == BoardHelper.g8;
|
||||
int rookSquareBeforeCastling = kingside ? movedTo + 1 : movedTo - 2;
|
||||
int rookSquareAfterCastling = kingside ? movedTo - 1 : movedTo + 1;
|
||||
|
||||
// Undo castling by returning rook to original square
|
||||
BitBoardUtility.ToggleSquares(ref pieceBitboards[rookPiece], rookSquareAfterCastling, rookSquareBeforeCastling);
|
||||
BitBoardUtility.ToggleSquares(ref colourBitboards[MoveColourIndex], rookSquareAfterCastling, rookSquareBeforeCastling);
|
||||
Square[rookSquareAfterCastling] = PieceHelper.None;
|
||||
Square[rookSquareBeforeCastling] = rookPiece;
|
||||
pieceLists[rookPiece].MovePiece(rookSquareAfterCastling, rookSquareBeforeCastling);
|
||||
}
|
||||
}
|
||||
|
||||
allPiecesBitboard = colourBitboards[WhiteIndex] | colourBitboards[BlackIndex];
|
||||
UpdateSliderBitboards();
|
||||
|
||||
if (!inSearch && RepetitionPositionHistory.Count > 0)
|
||||
{
|
||||
RepetitionPositionHistory.Pop();
|
||||
}
|
||||
if (!inSearch)
|
||||
{
|
||||
AllGameMoves.RemoveAt(AllGameMoves.Count - 1);
|
||||
}
|
||||
|
||||
// Go back to previous state
|
||||
gameStateHistory.Pop();
|
||||
currentGameState = gameStateHistory.Peek();
|
||||
plyCount--;
|
||||
hasCachedInCheckValue = false;
|
||||
}
|
||||
|
||||
// Switch side to play without making a move (NOTE: must not be in check when called)
|
||||
public void MakeNullMove()
|
||||
{
|
||||
IsWhiteToMove = !IsWhiteToMove;
|
||||
|
||||
plyCount++;
|
||||
|
||||
ulong newZobristKey = currentGameState.zobristKey;
|
||||
newZobristKey ^= Zobrist.sideToMove;
|
||||
newZobristKey ^= Zobrist.enPassantFile[currentGameState.enPassantFile];
|
||||
|
||||
GameState newState = new(PieceHelper.None, 0, currentGameState.castlingRights, currentGameState.fiftyMoveCounter + 1, newZobristKey);
|
||||
currentGameState = newState;
|
||||
gameStateHistory.Push(currentGameState);
|
||||
UpdateSliderBitboards();
|
||||
hasCachedInCheckValue = true;
|
||||
cachedInCheckValue = false;
|
||||
}
|
||||
|
||||
public void UnmakeNullMove()
|
||||
{
|
||||
|
||||
IsWhiteToMove = !IsWhiteToMove;
|
||||
plyCount--;
|
||||
gameStateHistory.Pop();
|
||||
currentGameState = gameStateHistory.Peek();
|
||||
UpdateSliderBitboards();
|
||||
hasCachedInCheckValue = true;
|
||||
cachedInCheckValue = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Calculate in check value
|
||||
// Call IsInCheck instead for automatic caching of value
|
||||
public bool CalculateInCheckState()
|
||||
{
|
||||
int kingSquare = KingSquare[MoveColourIndex];
|
||||
ulong blockers = allPiecesBitboard;
|
||||
|
||||
if (EnemyOrthogonalSliders != 0)
|
||||
{
|
||||
ulong rookAttacks = Magic.GetRookAttacks(kingSquare, blockers);
|
||||
if ((rookAttacks & EnemyOrthogonalSliders) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (EnemyDiagonalSliders != 0)
|
||||
{
|
||||
ulong bishopAttacks = Magic.GetBishopAttacks(kingSquare, blockers);
|
||||
if ((bishopAttacks & EnemyDiagonalSliders) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
ulong enemyKnights = pieceBitboards[PieceHelper.MakePiece(PieceHelper.Knight, OpponentColour)];
|
||||
if ((Bits.KnightAttacks[kingSquare] & enemyKnights) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
ulong enemyPawns = pieceBitboards[PieceHelper.MakePiece(PieceHelper.Pawn, OpponentColour)];
|
||||
ulong pawnAttackMask = IsWhiteToMove ? Bits.WhitePawnAttacks[kingSquare] : Bits.BlackPawnAttacks[kingSquare];
|
||||
if ((pawnAttackMask & enemyPawns) != 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Load the starting position
|
||||
public void LoadStartPosition()
|
||||
{
|
||||
LoadPosition(FenUtility.StartPositionFEN);
|
||||
}
|
||||
|
||||
// Load custom position from fen string
|
||||
public void LoadPosition(string fen)
|
||||
{
|
||||
Initialize();
|
||||
GameStartFen = fen;
|
||||
FenUtility.PositionInfo posInfo = FenUtility.PositionFromFen(fen);
|
||||
|
||||
// Load pieces into board array and piece lists
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
int piece = posInfo.squares[squareIndex];
|
||||
int pieceType = PieceHelper.PieceType(piece);
|
||||
int colourIndex = PieceHelper.IsWhite(piece) ? WhiteIndex : BlackIndex;
|
||||
Square[squareIndex] = piece;
|
||||
|
||||
if (piece != PieceHelper.None)
|
||||
{
|
||||
BitBoardUtility.SetSquare(ref pieceBitboards[piece], squareIndex);
|
||||
BitBoardUtility.SetSquare(ref colourBitboards[colourIndex], squareIndex);
|
||||
|
||||
if (pieceType == PieceHelper.King)
|
||||
{
|
||||
KingSquare[colourIndex] = squareIndex;
|
||||
}
|
||||
|
||||
pieceLists[piece].AddPieceAtSquare(squareIndex);
|
||||
|
||||
totalPieceCountWithoutPawnsAndKings += (pieceType is PieceHelper.Pawn or PieceHelper.King) ? 0 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Side to move
|
||||
IsWhiteToMove = posInfo.whiteToMove;
|
||||
|
||||
// Set extra bitboards
|
||||
allPiecesBitboard = colourBitboards[WhiteIndex] | colourBitboards[BlackIndex];
|
||||
UpdateSliderBitboards();
|
||||
|
||||
// Create gamestate
|
||||
int whiteCastle = ((posInfo.whiteCastleKingside) ? 1 << 0 : 0) | ((posInfo.whiteCastleQueenside) ? 1 << 1 : 0);
|
||||
int blackCastle = ((posInfo.blackCastleKingside) ? 1 << 2 : 0) | ((posInfo.blackCastleQueenside) ? 1 << 3 : 0);
|
||||
int castlingRights = whiteCastle | blackCastle;
|
||||
|
||||
plyCount = (posInfo.moveCount - 1) * 2 + (IsWhiteToMove ? 0 : 1);
|
||||
|
||||
// Set game state (note: calculating zobrist key relies on current game state)
|
||||
currentGameState = new GameState(PieceHelper.None, posInfo.epFile, castlingRights, posInfo.fiftyMovePlyCount, 0);
|
||||
ulong zobristKey = Zobrist.CalculateZobristKey(this);
|
||||
currentGameState = new GameState(PieceHelper.None, posInfo.epFile, castlingRights, posInfo.fiftyMovePlyCount, zobristKey);
|
||||
|
||||
RepetitionPositionHistory.Push(zobristKey);
|
||||
|
||||
gameStateHistory.Push(currentGameState);
|
||||
}
|
||||
|
||||
void UpdateSliderBitboards()
|
||||
{
|
||||
int friendlyRook = PieceHelper.MakePiece(PieceHelper.Rook, MoveColour);
|
||||
int friendlyQueen = PieceHelper.MakePiece(PieceHelper.Queen, MoveColour);
|
||||
int friendlyBishop = PieceHelper.MakePiece(PieceHelper.Bishop, MoveColour);
|
||||
FriendlyOrthogonalSliders = pieceBitboards[friendlyRook] | pieceBitboards[friendlyQueen];
|
||||
FriendlyDiagonalSliders = pieceBitboards[friendlyBishop] | pieceBitboards[friendlyQueen];
|
||||
|
||||
int enemyRook = PieceHelper.MakePiece(PieceHelper.Rook, OpponentColour);
|
||||
int enemyQueen = PieceHelper.MakePiece(PieceHelper.Queen, OpponentColour);
|
||||
int enemyBishop = PieceHelper.MakePiece(PieceHelper.Bishop, OpponentColour);
|
||||
EnemyOrthogonalSliders = pieceBitboards[enemyRook] | pieceBitboards[enemyQueen];
|
||||
EnemyDiagonalSliders = pieceBitboards[enemyBishop] | pieceBitboards[enemyQueen];
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
AllGameMoves = new List<Move>();
|
||||
Square = new int[64];
|
||||
KingSquare = new int[2];
|
||||
|
||||
RepetitionPositionHistory = new Stack<ulong>(capacity: 64);
|
||||
gameStateHistory = new Stack<GameState>(capacity: 64);
|
||||
|
||||
currentGameState = new GameState();
|
||||
plyCount = 0;
|
||||
|
||||
knights = new PieceList[] { new PieceList(10), new PieceList(10) };
|
||||
pawns = new PieceList[] { new PieceList(8), new PieceList(8) };
|
||||
rooks = new PieceList[] { new PieceList(10), new PieceList(10) };
|
||||
bishops = new PieceList[] { new PieceList(10), new PieceList(10) };
|
||||
queens = new PieceList[] { new PieceList(9), new PieceList(9) };
|
||||
kings = new PieceList[] { new PieceList(1), new PieceList(1) };
|
||||
|
||||
|
||||
pieceLists = new PieceList[PieceHelper.MaxPieceIndex + 1];
|
||||
pieceLists[PieceHelper.WhitePawn] = pawns[WhiteIndex];
|
||||
pieceLists[PieceHelper.WhiteKnight] = knights[WhiteIndex];
|
||||
pieceLists[PieceHelper.WhiteBishop] = bishops[WhiteIndex];
|
||||
pieceLists[PieceHelper.WhiteRook] = rooks[WhiteIndex];
|
||||
pieceLists[PieceHelper.WhiteQueen] = queens[WhiteIndex];
|
||||
pieceLists[PieceHelper.WhiteKing] = kings[WhiteIndex];
|
||||
|
||||
pieceLists[PieceHelper.BlackPawn] = pawns[BlackIndex];
|
||||
pieceLists[PieceHelper.BlackKnight] = knights[BlackIndex];
|
||||
pieceLists[PieceHelper.BlackBishop] = bishops[BlackIndex];
|
||||
pieceLists[PieceHelper.BlackRook] = rooks[BlackIndex];
|
||||
pieceLists[PieceHelper.BlackQueen] = queens[BlackIndex];
|
||||
pieceLists[PieceHelper.BlackKing] = kings[BlackIndex];
|
||||
|
||||
totalPieceCountWithoutPawnsAndKings = 0;
|
||||
|
||||
// Initialize bitboards
|
||||
pieceBitboards = new ulong[PieceHelper.MaxPieceIndex + 1];
|
||||
colourBitboards = new ulong[2];
|
||||
allPiecesBitboard = 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
44
Chess-Challenge/src/Framework/Chess/Board/Coord.cs
Normal file
44
Chess-Challenge/src/Framework/Chess/Board/Coord.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using System;
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Structure for representing squares on the chess board as file/rank integer pairs.
|
||||
// (0, 0) = a1, (7, 7) = h8.
|
||||
// Coords can also be used as offsets. For example, while a Coord of (-1, 0) is not
|
||||
// a valid square, it can be used to represent the concept of moving 1 square left.
|
||||
|
||||
public readonly struct Coord : IComparable<Coord>
|
||||
{
|
||||
public readonly int fileIndex;
|
||||
public readonly int rankIndex;
|
||||
|
||||
public Coord(int fileIndex, int rankIndex)
|
||||
{
|
||||
this.fileIndex = fileIndex;
|
||||
this.rankIndex = rankIndex;
|
||||
}
|
||||
|
||||
public Coord(int squareIndex)
|
||||
{
|
||||
this.fileIndex = BoardHelper.FileIndex(squareIndex);
|
||||
this.rankIndex = BoardHelper.RankIndex(squareIndex);
|
||||
}
|
||||
|
||||
public bool IsLightSquare()
|
||||
{
|
||||
return (fileIndex + rankIndex) % 2 != 0;
|
||||
}
|
||||
|
||||
public int CompareTo(Coord other)
|
||||
{
|
||||
return (fileIndex == other.fileIndex && rankIndex == other.rankIndex) ? 0 : 1;
|
||||
}
|
||||
|
||||
public static Coord operator +(Coord a, Coord b) => new Coord(a.fileIndex + b.fileIndex, a.rankIndex + b.rankIndex);
|
||||
public static Coord operator -(Coord a, Coord b) => new Coord(a.fileIndex - b.fileIndex, a.rankIndex - b.rankIndex);
|
||||
public static Coord operator *(Coord a, int m) => new Coord(a.fileIndex * m, a.rankIndex * m);
|
||||
public static Coord operator *(int m, Coord a) => a * m;
|
||||
|
||||
public bool IsValidSquare() => fileIndex >= 0 && fileIndex < 8 && rankIndex >= 0 && rankIndex < 8;
|
||||
public int SquareIndex => BoardHelper.IndexFromCoord(this);
|
||||
}
|
||||
}
|
38
Chess-Challenge/src/Framework/Chess/Board/GameState.cs
Normal file
38
Chess-Challenge/src/Framework/Chess/Board/GameState.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public readonly struct GameState
|
||||
{
|
||||
public readonly int capturedPieceType;
|
||||
public readonly int enPassantFile;
|
||||
public readonly int castlingRights;
|
||||
public readonly int fiftyMoveCounter;
|
||||
public readonly ulong zobristKey;
|
||||
|
||||
public const int ClearWhiteKingsideMask = 0b1110;
|
||||
public const int ClearWhiteQueensideMask = 0b1101;
|
||||
public const int ClearBlackKingsideMask = 0b1011;
|
||||
public const int ClearBlackQueensideMask = 0b0111;
|
||||
|
||||
public GameState(int capturedPieceType, int enPassantFile, int castlingRights, int fiftyMoveCounter, ulong zobristKey)
|
||||
{
|
||||
this.capturedPieceType = capturedPieceType;
|
||||
this.enPassantFile = enPassantFile;
|
||||
this.castlingRights = castlingRights;
|
||||
this.fiftyMoveCounter = fiftyMoveCounter;
|
||||
this.zobristKey = zobristKey;
|
||||
}
|
||||
|
||||
public bool HasKingsideCastleRight(bool white)
|
||||
{
|
||||
int mask = white ? 1 : 4;
|
||||
return (castlingRights & mask) != 0;
|
||||
}
|
||||
|
||||
public bool HasQueensideCastleRight(bool white)
|
||||
{
|
||||
int mask = white ? 2 : 8;
|
||||
return (castlingRights & mask) != 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
80
Chess-Challenge/src/Framework/Chess/Board/Move.cs
Normal file
80
Chess-Challenge/src/Framework/Chess/Board/Move.cs
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
Compact (16bit) move representation to preserve memory during search.
|
||||
|
||||
The format is as follows (ffffttttttssssss)
|
||||
Bits 0-5: start square index
|
||||
Bits 6-11: target square index
|
||||
Bits 12-15: flag (promotion type, etc)
|
||||
*/
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public readonly struct Move
|
||||
{
|
||||
// 16bit move value
|
||||
readonly ushort moveValue;
|
||||
|
||||
// Flags
|
||||
public const int NoFlag = 0b0000;
|
||||
public const int EnPassantCaptureFlag = 0b0001;
|
||||
public const int CastleFlag = 0b0010;
|
||||
public const int PawnTwoUpFlag = 0b0011;
|
||||
|
||||
public const int PromoteToQueenFlag = 0b0100;
|
||||
public const int PromoteToKnightFlag = 0b0101;
|
||||
public const int PromoteToRookFlag = 0b0110;
|
||||
public const int PromoteToBishopFlag = 0b0111;
|
||||
|
||||
// Masks
|
||||
const ushort startSquareMask = 0b0000000000111111;
|
||||
const ushort targetSquareMask = 0b0000111111000000;
|
||||
const ushort flagMask = 0b1111000000000000;
|
||||
|
||||
public Move(ushort moveValue)
|
||||
{
|
||||
this.moveValue = moveValue;
|
||||
}
|
||||
|
||||
public Move(int startSquare, int targetSquare)
|
||||
{
|
||||
moveValue = (ushort)(startSquare | targetSquare << 6);
|
||||
}
|
||||
|
||||
public Move(int startSquare, int targetSquare, int flag)
|
||||
{
|
||||
moveValue = (ushort)(startSquare | targetSquare << 6 | flag << 12);
|
||||
}
|
||||
|
||||
public ushort Value => moveValue;
|
||||
public bool IsNull => moveValue == 0;
|
||||
|
||||
public int StartSquareIndex => moveValue & startSquareMask;
|
||||
public int TargetSquareIndex => (moveValue & targetSquareMask) >> 6;
|
||||
public bool IsPromotion => MoveFlag >= PromoteToQueenFlag;
|
||||
public int MoveFlag => moveValue >> 12;
|
||||
|
||||
public int PromotionPieceType
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (MoveFlag)
|
||||
{
|
||||
case PromoteToRookFlag:
|
||||
return PieceHelper.Rook;
|
||||
case PromoteToKnightFlag:
|
||||
return PieceHelper.Knight;
|
||||
case PromoteToBishopFlag:
|
||||
return PieceHelper.Bishop;
|
||||
case PromoteToQueenFlag:
|
||||
return PieceHelper.Queen;
|
||||
default:
|
||||
return PieceHelper.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Move NullMove => new Move(0);
|
||||
public static bool SameMove(Move a, Move b) => a.moveValue == b.moveValue;
|
||||
|
||||
|
||||
}
|
||||
}
|
70
Chess-Challenge/src/Framework/Chess/Board/PieceHelper.cs
Normal file
70
Chess-Challenge/src/Framework/Chess/Board/PieceHelper.cs
Normal file
|
@ -0,0 +1,70 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Contains definitions for each piece type (represented as integers),
|
||||
// as well as various helper functions for dealing with pieces.
|
||||
public static class PieceHelper
|
||||
{
|
||||
// Piece Types
|
||||
public const int None = 0;
|
||||
public const int Pawn = 1;
|
||||
public const int Knight = 2;
|
||||
public const int Bishop = 3;
|
||||
public const int Rook = 4;
|
||||
public const int Queen = 5;
|
||||
public const int King = 6;
|
||||
|
||||
// Piece Colours
|
||||
public const int White = 0;
|
||||
public const int Black = 8;
|
||||
|
||||
// Pieces
|
||||
public const int WhitePawn = Pawn | White; // 1
|
||||
public const int WhiteKnight = Knight | White; // 2
|
||||
public const int WhiteBishop = Bishop | White; // 3
|
||||
public const int WhiteRook = Rook | White; // 4
|
||||
public const int WhiteQueen = Queen | White; // 5
|
||||
public const int WhiteKing = King | White; // 6
|
||||
|
||||
public const int BlackPawn = Pawn | Black; // 9
|
||||
public const int BlackKnight = Knight | Black; // 10
|
||||
public const int BlackBishop = Bishop | Black; // 11
|
||||
public const int BlackRook = Rook | Black; // 12
|
||||
public const int BlackQueen = Queen | Black; // 13
|
||||
public const int BlackKing = King | Black; // 14
|
||||
|
||||
public const int MaxPieceIndex = BlackKing;
|
||||
|
||||
public static readonly int[] PieceIndices =
|
||||
{
|
||||
WhitePawn, WhiteKnight, WhiteBishop, WhiteRook, WhiteQueen, WhiteKing,
|
||||
BlackPawn, BlackKnight, BlackBishop, BlackRook, BlackQueen, BlackKing
|
||||
};
|
||||
|
||||
// Bit Masks
|
||||
const int typeMask = 0b0111;
|
||||
const int colourMask = 0b1000;
|
||||
|
||||
public static int MakePiece(int pieceType, int pieceColour) => pieceType | pieceColour;
|
||||
|
||||
public static int MakePiece(int pieceType, bool pieceIsWhite) => MakePiece(pieceType, pieceIsWhite ? White : Black);
|
||||
|
||||
// Returns true if given piece matches the given colour. If piece is of type 'none', result will always be false.
|
||||
public static bool IsColour(int piece, int colour) => (piece & colourMask) == colour && piece != 0;
|
||||
|
||||
public static bool IsWhite(int piece) => IsColour(piece, White);
|
||||
|
||||
public static int PieceColour(int piece) => piece & colourMask;
|
||||
|
||||
public static int PieceType(int piece) => piece & typeMask;
|
||||
|
||||
// Rook or Queen
|
||||
public static bool IsOrthogonalSlider(int piece) => PieceType(piece) is Queen or Rook;
|
||||
|
||||
// Bishop or Queen
|
||||
public static bool IsDiagonalSlider(int piece) => PieceType(piece) is Queen or Bishop;
|
||||
|
||||
// Bishop, Rook, or Queen
|
||||
public static bool IsSlidingPiece(int piece) => PieceType(piece) is Queen or Bishop or Rook;
|
||||
|
||||
}
|
||||
}
|
52
Chess-Challenge/src/Framework/Chess/Board/PieceList.cs
Normal file
52
Chess-Challenge/src/Framework/Chess/Board/PieceList.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public class PieceList
|
||||
{
|
||||
|
||||
// Indices of squares occupied by given piece type (only elements up to Count are valid, the rest are unused/garbage)
|
||||
public int[] occupiedSquares;
|
||||
// Map to go from index of a square, to the index in the occupiedSquares array where that square is stored
|
||||
int[] map;
|
||||
int numPieces;
|
||||
|
||||
public PieceList(int maxPieceCount = 16)
|
||||
{
|
||||
occupiedSquares = new int[maxPieceCount];
|
||||
map = new int[64];
|
||||
numPieces = 0;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
return numPieces;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddPieceAtSquare(int square)
|
||||
{
|
||||
occupiedSquares[numPieces] = square;
|
||||
map[square] = numPieces;
|
||||
numPieces++;
|
||||
}
|
||||
|
||||
public void RemovePieceAtSquare(int square)
|
||||
{
|
||||
int pieceIndex = map[square]; // get the index of this element in the occupiedSquares array
|
||||
occupiedSquares[pieceIndex] = occupiedSquares[numPieces - 1]; // move last element in array to the place of the removed element
|
||||
map[occupiedSquares[pieceIndex]] = pieceIndex; // update map to point to the moved element's new location in the array
|
||||
numPieces--;
|
||||
}
|
||||
|
||||
public void MovePiece(int startSquare, int targetSquare)
|
||||
{
|
||||
int pieceIndex = map[startSquare]; // get the index of this element in the occupiedSquares array
|
||||
occupiedSquares[pieceIndex] = targetSquare;
|
||||
map[targetSquare] = pieceIndex;
|
||||
}
|
||||
|
||||
public int this[int index] => occupiedSquares[index];
|
||||
|
||||
}
|
||||
}
|
88
Chess-Challenge/src/Framework/Chess/Board/Zobrist.cs
Normal file
88
Chess-Challenge/src/Framework/Chess/Board/Zobrist.cs
Normal file
|
@ -0,0 +1,88 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Helper class for the calculation of zobrist hash.
|
||||
// This is a single 64bit value that (non-uniquely) represents the current state of the game.
|
||||
|
||||
// It is mainly used for quickly detecting positions that have already been evaluated, to avoid
|
||||
// potentially performing lots of duplicate work during game search.
|
||||
|
||||
public static class Zobrist
|
||||
{
|
||||
// Random numbers are generated for each aspect of the game state, and are used for calculating the hash:
|
||||
|
||||
// piece type, colour, square index
|
||||
public static readonly ulong[,] piecesArray = new ulong[PieceHelper.MaxPieceIndex + 1, 64];
|
||||
// Each player has 4 possible castling right states: none, queenside, kingside, both.
|
||||
// So, taking both sides into account, there are 16 possible states.
|
||||
public static readonly ulong[] castlingRights = new ulong[16];
|
||||
// En passant file (0 = no ep).
|
||||
// Rank does not need to be specified since side to move is included in key
|
||||
public static readonly ulong[] enPassantFile = new ulong[9];
|
||||
public static readonly ulong sideToMove;
|
||||
|
||||
|
||||
static Zobrist()
|
||||
{
|
||||
|
||||
const int seed = 29426028;
|
||||
System.Random rng = new System.Random(seed);
|
||||
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
foreach (int piece in PieceHelper.PieceIndices)
|
||||
{
|
||||
piecesArray[piece, squareIndex] = RandomUnsigned64BitNumber(rng);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < castlingRights.Length; i++)
|
||||
{
|
||||
castlingRights[i] = RandomUnsigned64BitNumber(rng);
|
||||
}
|
||||
|
||||
for (int i = 0; i < enPassantFile.Length; i++)
|
||||
{
|
||||
enPassantFile[i] = i == 0 ? 0 : RandomUnsigned64BitNumber(rng);
|
||||
}
|
||||
|
||||
sideToMove = RandomUnsigned64BitNumber(rng);
|
||||
}
|
||||
|
||||
// Calculate zobrist key from current board position.
|
||||
// NOTE: this function is slow and should only be used when the board is initially set up from fen.
|
||||
// During search, the key should be updated incrementally instead.
|
||||
public static ulong CalculateZobristKey(Board board)
|
||||
{
|
||||
ulong zobristKey = 0;
|
||||
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
int piece = board.Square[squareIndex];
|
||||
|
||||
if (PieceHelper.PieceType(piece) != PieceHelper.None)
|
||||
{
|
||||
zobristKey ^= piecesArray[piece, squareIndex];
|
||||
}
|
||||
}
|
||||
|
||||
zobristKey ^= enPassantFile[board.currentGameState.enPassantFile];
|
||||
|
||||
if (board.MoveColour == PieceHelper.Black)
|
||||
{
|
||||
zobristKey ^= sideToMove;
|
||||
}
|
||||
|
||||
zobristKey ^= castlingRights[board.currentGameState.castlingRights];
|
||||
|
||||
return zobristKey;
|
||||
}
|
||||
|
||||
static ulong RandomUnsigned64BitNumber(System.Random rng)
|
||||
{
|
||||
byte[] buffer = new byte[8];
|
||||
rng.NextBytes(buffer);
|
||||
return System.BitConverter.ToUInt64(buffer, 0);
|
||||
}
|
||||
}
|
||||
}
|
90
Chess-Challenge/src/Framework/Chess/Helpers/BoardHelper.cs
Normal file
90
Chess-Challenge/src/Framework/Chess/Helpers/BoardHelper.cs
Normal file
|
@ -0,0 +1,90 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public static class BoardHelper
|
||||
{
|
||||
|
||||
public static readonly Coord[] RookDirections = { new Coord(-1, 0), new Coord(1, 0), new Coord(0, 1), new Coord(0, -1) };
|
||||
public static readonly Coord[] BishopDirections = { new Coord(-1, 1), new Coord(1, 1), new Coord(1, -1), new Coord(-1, -1) };
|
||||
|
||||
public const string fileNames = "abcdefgh";
|
||||
public const string rankNames = "12345678";
|
||||
|
||||
public const int a1 = 0;
|
||||
public const int b1 = 1;
|
||||
public const int c1 = 2;
|
||||
public const int d1 = 3;
|
||||
public const int e1 = 4;
|
||||
public const int f1 = 5;
|
||||
public const int g1 = 6;
|
||||
public const int h1 = 7;
|
||||
|
||||
public const int a8 = 56;
|
||||
public const int b8 = 57;
|
||||
public const int c8 = 58;
|
||||
public const int d8 = 59;
|
||||
public const int e8 = 60;
|
||||
public const int f8 = 61;
|
||||
public const int g8 = 62;
|
||||
public const int h8 = 63;
|
||||
|
||||
|
||||
// Rank (0 to 7) of square
|
||||
public static int RankIndex(int squareIndex)
|
||||
{
|
||||
return squareIndex >> 3;
|
||||
}
|
||||
|
||||
// File (0 to 7) of square
|
||||
public static int FileIndex(int squareIndex)
|
||||
{
|
||||
return squareIndex & 0b000111;
|
||||
}
|
||||
|
||||
public static int IndexFromCoord(int fileIndex, int rankIndex)
|
||||
{
|
||||
return rankIndex * 8 + fileIndex;
|
||||
}
|
||||
|
||||
public static int IndexFromCoord(Coord coord)
|
||||
{
|
||||
return IndexFromCoord(coord.fileIndex, coord.rankIndex);
|
||||
}
|
||||
|
||||
public static Coord CoordFromIndex(int squareIndex)
|
||||
{
|
||||
return new Coord(FileIndex(squareIndex), RankIndex(squareIndex));
|
||||
}
|
||||
|
||||
public static bool LightSquare(int fileIndex, int rankIndex)
|
||||
{
|
||||
return (fileIndex + rankIndex) % 2 != 0;
|
||||
}
|
||||
|
||||
public static string SquareNameFromCoordinate(int fileIndex, int rankIndex)
|
||||
{
|
||||
return fileNames[fileIndex] + "" + (rankIndex + 1);
|
||||
}
|
||||
|
||||
public static string SquareNameFromIndex(int squareIndex)
|
||||
{
|
||||
return SquareNameFromCoordinate(CoordFromIndex(squareIndex));
|
||||
}
|
||||
|
||||
public static string SquareNameFromCoordinate(Coord coord)
|
||||
{
|
||||
return SquareNameFromCoordinate(coord.fileIndex, coord.rankIndex);
|
||||
}
|
||||
|
||||
public static int SquareIndexFromName(string name)
|
||||
{
|
||||
char fileName = name[0];
|
||||
char rankName = name[1];
|
||||
int fileIndex = fileNames.IndexOf(fileName);
|
||||
int rankIndex = rankNames.IndexOf(rankName);
|
||||
return IndexFromCoord(fileIndex, rankIndex);
|
||||
}
|
||||
|
||||
public static bool IsValidCoordinate(int x, int y) => x >= 0 && x < 8 && y >= 0 && y < 8;
|
||||
|
||||
}
|
||||
}
|
294
Chess-Challenge/src/Framework/Chess/Helpers/FenUtility.cs
Normal file
294
Chess-Challenge/src/Framework/Chess/Helpers/FenUtility.cs
Normal file
|
@ -0,0 +1,294 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Helper class for dealing with FEN strings
|
||||
public static class FenUtility
|
||||
{
|
||||
public const string StartPositionFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
||||
|
||||
// Load position from fen string
|
||||
public static PositionInfo PositionFromFen(string fen)
|
||||
{
|
||||
|
||||
PositionInfo loadedPositionInfo = new PositionInfo();
|
||||
string[] sections = fen.Split(' ');
|
||||
|
||||
int file = 0;
|
||||
int rank = 7;
|
||||
|
||||
foreach (char symbol in sections[0])
|
||||
{
|
||||
if (symbol == '/')
|
||||
{
|
||||
file = 0;
|
||||
rank--;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (char.IsDigit(symbol))
|
||||
{
|
||||
file += (int)char.GetNumericValue(symbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
int pieceColour = (char.IsUpper(symbol)) ? PieceHelper.White : PieceHelper.Black;
|
||||
int pieceType = char.ToLower(symbol) switch
|
||||
{
|
||||
'k' => PieceHelper.King,
|
||||
'p' => PieceHelper.Pawn,
|
||||
'n' => PieceHelper.Knight,
|
||||
'b' => PieceHelper.Bishop,
|
||||
'r' => PieceHelper.Rook,
|
||||
'q' => PieceHelper.Queen,
|
||||
_ => PieceHelper.None
|
||||
};
|
||||
|
||||
loadedPositionInfo.squares[rank * 8 + file] = pieceType | pieceColour;
|
||||
file++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadedPositionInfo.whiteToMove = (sections[1] == "w");
|
||||
|
||||
string castlingRights = sections[2];
|
||||
loadedPositionInfo.whiteCastleKingside = castlingRights.Contains("K");
|
||||
loadedPositionInfo.whiteCastleQueenside = castlingRights.Contains("Q");
|
||||
loadedPositionInfo.blackCastleKingside = castlingRights.Contains("k");
|
||||
loadedPositionInfo.blackCastleQueenside = castlingRights.Contains("q");
|
||||
|
||||
if (sections.Length > 3)
|
||||
{
|
||||
string enPassantFileName = sections[3][0].ToString();
|
||||
if (BoardHelper.fileNames.Contains(enPassantFileName))
|
||||
{
|
||||
loadedPositionInfo.epFile = BoardHelper.fileNames.IndexOf(enPassantFileName) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Half-move clock
|
||||
if (sections.Length > 4)
|
||||
{
|
||||
int.TryParse(sections[4], out loadedPositionInfo.fiftyMovePlyCount);
|
||||
}
|
||||
// Full move number
|
||||
if (sections.Length > 5)
|
||||
{
|
||||
int.TryParse(sections[5], out loadedPositionInfo.moveCount);
|
||||
}
|
||||
return loadedPositionInfo;
|
||||
}
|
||||
|
||||
// Get the fen string of the current position
|
||||
public static string CurrentFen(Board board)
|
||||
{
|
||||
string fen = "";
|
||||
for (int rank = 7; rank >= 0; rank--)
|
||||
{
|
||||
int numEmptyFiles = 0;
|
||||
for (int file = 0; file < 8; file++)
|
||||
{
|
||||
int i = rank * 8 + file;
|
||||
int piece = board.Square[i];
|
||||
if (piece != 0)
|
||||
{
|
||||
if (numEmptyFiles != 0)
|
||||
{
|
||||
fen += numEmptyFiles;
|
||||
numEmptyFiles = 0;
|
||||
}
|
||||
bool isBlack = PieceHelper.IsColour(piece, PieceHelper.Black);
|
||||
int pieceType = PieceHelper.PieceType(piece);
|
||||
char pieceChar = ' ';
|
||||
switch (pieceType)
|
||||
{
|
||||
case PieceHelper.Rook:
|
||||
pieceChar = 'R';
|
||||
break;
|
||||
case PieceHelper.Knight:
|
||||
pieceChar = 'N';
|
||||
break;
|
||||
case PieceHelper.Bishop:
|
||||
pieceChar = 'B';
|
||||
break;
|
||||
case PieceHelper.Queen:
|
||||
pieceChar = 'Q';
|
||||
break;
|
||||
case PieceHelper.King:
|
||||
pieceChar = 'K';
|
||||
break;
|
||||
case PieceHelper.Pawn:
|
||||
pieceChar = 'P';
|
||||
break;
|
||||
}
|
||||
fen += (isBlack) ? pieceChar.ToString().ToLower() : pieceChar.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
numEmptyFiles++;
|
||||
}
|
||||
|
||||
}
|
||||
if (numEmptyFiles != 0)
|
||||
{
|
||||
fen += numEmptyFiles;
|
||||
}
|
||||
if (rank != 0)
|
||||
{
|
||||
fen += '/';
|
||||
}
|
||||
}
|
||||
|
||||
// Side to move
|
||||
fen += ' ';
|
||||
fen += (board.IsWhiteToMove) ? 'w' : 'b';
|
||||
|
||||
// Castling
|
||||
bool whiteKingside = (board.currentGameState.castlingRights & 1) == 1;
|
||||
bool whiteQueenside = (board.currentGameState.castlingRights >> 1 & 1) == 1;
|
||||
bool blackKingside = (board.currentGameState.castlingRights >> 2 & 1) == 1;
|
||||
bool blackQueenside = (board.currentGameState.castlingRights >> 3 & 1) == 1;
|
||||
fen += ' ';
|
||||
fen += (whiteKingside) ? "K" : "";
|
||||
fen += (whiteQueenside) ? "Q" : "";
|
||||
fen += (blackKingside) ? "k" : "";
|
||||
fen += (blackQueenside) ? "q" : "";
|
||||
fen += ((board.currentGameState.castlingRights) == 0) ? "-" : "";
|
||||
|
||||
// En-passant
|
||||
fen += ' ';
|
||||
int epFileIndex = board.currentGameState.enPassantFile - 1;
|
||||
int epRankIndex = (board.IsWhiteToMove) ? 5 : 2;
|
||||
|
||||
if (epFileIndex == -1 || !EnPassantCanBeCaptured(epFileIndex, epRankIndex, board))
|
||||
{
|
||||
fen += '-';
|
||||
}
|
||||
else
|
||||
{
|
||||
fen += BoardHelper.SquareNameFromCoordinate(epFileIndex, epRankIndex);
|
||||
}
|
||||
|
||||
// 50 move counter
|
||||
fen += ' ';
|
||||
fen += board.currentGameState.fiftyMoveCounter;
|
||||
|
||||
// Full-move count (should be one at start, and increase after each move by black)
|
||||
fen += ' ';
|
||||
fen += (board.plyCount / 2) + 1;
|
||||
|
||||
return fen;
|
||||
}
|
||||
|
||||
static bool EnPassantCanBeCaptured(int epFileIndex, int epRankIndex, Board board)
|
||||
{
|
||||
Coord captureFromA = new Coord(epFileIndex - 1, epRankIndex + (board.IsWhiteToMove ? -1 : 1));
|
||||
Coord captureFromB = new Coord(epFileIndex + 1, epRankIndex + (board.IsWhiteToMove ? -1 : 1));
|
||||
int epCaptureSquare = new Coord(epFileIndex, epRankIndex).SquareIndex;
|
||||
int friendlyPawn = PieceHelper.MakePiece(PieceHelper.Pawn, board.MoveColour);
|
||||
|
||||
|
||||
|
||||
return CanCapture(captureFromA) || CanCapture(captureFromB);
|
||||
|
||||
|
||||
bool CanCapture(Coord from)
|
||||
{
|
||||
bool isPawnOnSquare = board.Square[from.SquareIndex] == friendlyPawn;
|
||||
if (from.IsValidSquare() && isPawnOnSquare)
|
||||
{
|
||||
Move move = new Move(from.SquareIndex, epCaptureSquare, Move.EnPassantCaptureFlag);
|
||||
board.MakeMove(move);
|
||||
board.MakeNullMove();
|
||||
bool wasLegalMove = !board.CalculateInCheckState();
|
||||
|
||||
board.UnmakeNullMove();
|
||||
board.UndoMove(move);
|
||||
return wasLegalMove;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static string FlipFen(string fen)
|
||||
{
|
||||
string flippedFen = "";
|
||||
string[] sections = fen.Split(' ');
|
||||
|
||||
List<char> invertedFenChars = new();
|
||||
string[] fenRanks = sections[0].Split('/');
|
||||
|
||||
for (int i = fenRanks.Length - 1; i >= 0; i--)
|
||||
{
|
||||
string rank = fenRanks[i];
|
||||
foreach (char c in rank)
|
||||
{
|
||||
flippedFen += InvertCase(c);
|
||||
}
|
||||
if (i != 0)
|
||||
{
|
||||
flippedFen += '/';
|
||||
}
|
||||
}
|
||||
|
||||
flippedFen += " " + (sections[1][0] == 'w' ? 'b' : 'w');
|
||||
string castlingRights = sections[2];
|
||||
string flippedRights = "";
|
||||
foreach (char c in "kqKQ")
|
||||
{
|
||||
if (castlingRights.Contains(c))
|
||||
{
|
||||
flippedRights += InvertCase(c);
|
||||
}
|
||||
}
|
||||
flippedFen += " " + (flippedRights.Length == 0 ? "-" : flippedRights);
|
||||
|
||||
string ep = sections[3];
|
||||
string flippedEp = ep[0] + "";
|
||||
if (ep.Length > 1)
|
||||
{
|
||||
flippedEp += ep[1] == '6' ? '3' : '6';
|
||||
}
|
||||
flippedFen += " " + flippedEp;
|
||||
flippedFen += " " + sections[4] + " " + sections[5];
|
||||
|
||||
|
||||
return flippedFen;
|
||||
|
||||
char InvertCase(char c)
|
||||
{
|
||||
if (char.IsLower(c))
|
||||
{
|
||||
return char.ToUpper(c);
|
||||
}
|
||||
return char.ToLower(c);
|
||||
}
|
||||
}
|
||||
|
||||
public class PositionInfo
|
||||
{
|
||||
public int[] squares;
|
||||
// Castling rights
|
||||
public bool whiteCastleKingside;
|
||||
public bool whiteCastleQueenside;
|
||||
public bool blackCastleKingside;
|
||||
public bool blackCastleQueenside;
|
||||
// En passant file (1 is a-file, 8 is h-file, 0 means none)
|
||||
public int epFile;
|
||||
public bool whiteToMove;
|
||||
// Number of half-moves since last capture or pawn advance
|
||||
// (starts at 0 and increments after each player's move)
|
||||
public int fiftyMovePlyCount;
|
||||
// Total number of moves played in the game
|
||||
// (starts at 1 and increments after black's move)
|
||||
public int moveCount;
|
||||
|
||||
public PositionInfo()
|
||||
{
|
||||
squares = new int[64];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
364
Chess-Challenge/src/Framework/Chess/Helpers/MoveUtility.cs
Normal file
364
Chess-Challenge/src/Framework/Chess/Helpers/MoveUtility.cs
Normal file
|
@ -0,0 +1,364 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// Helper class for converting between various move representations:
|
||||
// UCI: move represented by string, e.g. "e2e4"
|
||||
// SAN: move represented in standard notation e.g. "Nxe7+"
|
||||
// Move: internal move representation
|
||||
public static class MoveUtility
|
||||
{
|
||||
// Converts a moveName into internal move representation
|
||||
// Name is expected in UCI format: "e2e4"
|
||||
// Promotions can be written with or without equals sign, for example: "e7e8=q" or "e7e8q"
|
||||
public static Move GetMoveFromUCIName(string moveName, Board board)
|
||||
{
|
||||
|
||||
int startSquare = BoardHelper.SquareIndexFromName(moveName.Substring(0, 2));
|
||||
int targetSquare = BoardHelper.SquareIndexFromName(moveName.Substring(2, 2));
|
||||
|
||||
int movedPieceType = PieceHelper.PieceType(board.Square[startSquare]);
|
||||
Coord startCoord = new Coord(startSquare);
|
||||
Coord targetCoord = new Coord(targetSquare);
|
||||
|
||||
// Figure out move flag
|
||||
int flag = Move.NoFlag;
|
||||
|
||||
if (movedPieceType == PieceHelper.Pawn)
|
||||
{
|
||||
// Promotion
|
||||
if (moveName.Length > 4)
|
||||
{
|
||||
flag = moveName[^1] switch
|
||||
{
|
||||
'q' => Move.PromoteToQueenFlag,
|
||||
'r' => Move.PromoteToRookFlag,
|
||||
'n' => Move.PromoteToKnightFlag,
|
||||
'b' => Move.PromoteToBishopFlag,
|
||||
_ => Move.NoFlag
|
||||
};
|
||||
}
|
||||
// Double pawn push
|
||||
else if (System.Math.Abs(targetCoord.rankIndex - startCoord.rankIndex) == 2)
|
||||
{
|
||||
flag = Move.PawnTwoUpFlag;
|
||||
}
|
||||
// En-passant
|
||||
else if (startCoord.fileIndex != targetCoord.fileIndex && board.Square[targetSquare] == PieceHelper.None)
|
||||
{
|
||||
flag = Move.EnPassantCaptureFlag;
|
||||
}
|
||||
}
|
||||
else if (movedPieceType == PieceHelper.King)
|
||||
{
|
||||
if (System.Math.Abs(startCoord.fileIndex - targetCoord.fileIndex) > 1)
|
||||
{
|
||||
flag = Move.CastleFlag;
|
||||
}
|
||||
}
|
||||
|
||||
return new Move(startSquare, targetSquare, flag);
|
||||
}
|
||||
|
||||
// Get name of move in UCI format
|
||||
// Examples: "e2e4", "e7e8q"
|
||||
public static string GetMoveNameUCI(Move move)
|
||||
{
|
||||
if (move.IsNull)
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
string startSquareName = BoardHelper.SquareNameFromIndex(move.StartSquareIndex);
|
||||
string endSquareName = BoardHelper.SquareNameFromIndex(move.TargetSquareIndex);
|
||||
string moveName = startSquareName + endSquareName;
|
||||
if (move.IsPromotion)
|
||||
{
|
||||
switch (move.MoveFlag)
|
||||
{
|
||||
case Move.PromoteToRookFlag:
|
||||
moveName += "r";
|
||||
break;
|
||||
case Move.PromoteToKnightFlag:
|
||||
moveName += "n";
|
||||
break;
|
||||
case Move.PromoteToBishopFlag:
|
||||
moveName += "b";
|
||||
break;
|
||||
case Move.PromoteToQueenFlag:
|
||||
moveName += "q";
|
||||
break;
|
||||
}
|
||||
}
|
||||
return moveName;
|
||||
}
|
||||
|
||||
// Get name of move in Standard Algebraic Notation (SAN)
|
||||
// Examples: "e4", "Bxf7+", "O-O", "Rh8#", "Nfd2"
|
||||
// Note, the move must not yet have been made on the board
|
||||
public static string GetMoveNameSAN(Move move, Board board)
|
||||
{
|
||||
if (move.IsNull)
|
||||
{
|
||||
return "Null";
|
||||
}
|
||||
int movePieceType = PieceHelper.PieceType(board.Square[move.StartSquareIndex]);
|
||||
int capturedPieceType = PieceHelper.PieceType(board.Square[move.TargetSquareIndex]);
|
||||
|
||||
if (move.MoveFlag == Move.CastleFlag)
|
||||
{
|
||||
int delta = move.TargetSquareIndex - move.StartSquareIndex;
|
||||
if (delta == 2)
|
||||
{
|
||||
return "O-O";
|
||||
}
|
||||
else if (delta == -2)
|
||||
{
|
||||
return "O-O-O";
|
||||
}
|
||||
}
|
||||
|
||||
MoveGenerator moveGen = new MoveGenerator();
|
||||
string moveNotation = GetSymbolFromPieceType(movePieceType);
|
||||
|
||||
// check if any ambiguity exists in notation (e.g if e2 can be reached via Nfe2 and Nbe2)
|
||||
if (movePieceType != PieceHelper.Pawn && movePieceType != PieceHelper.King)
|
||||
{
|
||||
var allMoves = moveGen.GenerateMoves(board);
|
||||
|
||||
foreach (Move altMove in allMoves)
|
||||
{
|
||||
|
||||
if (altMove.StartSquareIndex != move.StartSquareIndex && altMove.TargetSquareIndex == move.TargetSquareIndex)
|
||||
{ // if moving to same square from different square
|
||||
if (PieceHelper.PieceType(board.Square[altMove.StartSquareIndex]) == movePieceType)
|
||||
{ // same piece type
|
||||
int fromFileIndex = BoardHelper.FileIndex(move.StartSquareIndex);
|
||||
int alternateFromFileIndex = BoardHelper.FileIndex(altMove.StartSquareIndex);
|
||||
int fromRankIndex = BoardHelper.RankIndex(move.StartSquareIndex);
|
||||
int alternateFromRankIndex = BoardHelper.RankIndex(altMove.StartSquareIndex);
|
||||
|
||||
if (fromFileIndex != alternateFromFileIndex)
|
||||
{ // pieces on different files, thus ambiguity can be resolved by specifying file
|
||||
moveNotation += BoardHelper.fileNames[fromFileIndex];
|
||||
break; // ambiguity resolved
|
||||
}
|
||||
else if (fromRankIndex != alternateFromRankIndex)
|
||||
{
|
||||
moveNotation += BoardHelper.rankNames[fromRankIndex];
|
||||
break; // ambiguity resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (capturedPieceType != 0)
|
||||
{ // add 'x' to indicate capture
|
||||
if (movePieceType == PieceHelper.Pawn)
|
||||
{
|
||||
moveNotation += BoardHelper.fileNames[BoardHelper.FileIndex(move.StartSquareIndex)];
|
||||
}
|
||||
moveNotation += "x";
|
||||
}
|
||||
else
|
||||
{ // check if capturing ep
|
||||
if (move.MoveFlag == Move.EnPassantCaptureFlag)
|
||||
{
|
||||
moveNotation += BoardHelper.fileNames[BoardHelper.FileIndex(move.StartSquareIndex)] + "x";
|
||||
}
|
||||
}
|
||||
|
||||
moveNotation += BoardHelper.fileNames[BoardHelper.FileIndex(move.TargetSquareIndex)];
|
||||
moveNotation += BoardHelper.rankNames[BoardHelper.RankIndex(move.TargetSquareIndex)];
|
||||
|
||||
// add promotion piece
|
||||
if (move.IsPromotion)
|
||||
{
|
||||
int promotionPieceType = move.PromotionPieceType;
|
||||
moveNotation += "=" + GetSymbolFromPieceType(promotionPieceType);
|
||||
}
|
||||
|
||||
board.MakeMove(move, inSearch: true);
|
||||
var legalResponses = moveGen.GenerateMoves(board);
|
||||
// add check/mate symbol if applicable
|
||||
if (moveGen.InCheck())
|
||||
{
|
||||
if (legalResponses.Length == 0)
|
||||
{
|
||||
moveNotation += "#";
|
||||
}
|
||||
else
|
||||
{
|
||||
moveNotation += "+";
|
||||
}
|
||||
}
|
||||
board.UndoMove(move, inSearch: true);
|
||||
|
||||
return moveNotation;
|
||||
|
||||
string GetSymbolFromPieceType(int pieceType)
|
||||
{
|
||||
switch (pieceType)
|
||||
{
|
||||
case PieceHelper.Rook:
|
||||
return "R";
|
||||
case PieceHelper.Knight:
|
||||
return "N";
|
||||
case PieceHelper.Bishop:
|
||||
return "B";
|
||||
case PieceHelper.Queen:
|
||||
return "Q";
|
||||
case PieceHelper.King:
|
||||
return "K";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Move GetMoveFromSAN(Board board, string algebraicMove)
|
||||
{
|
||||
MoveGenerator moveGenerator = new MoveGenerator();
|
||||
|
||||
// Remove unrequired info from move string
|
||||
algebraicMove = algebraicMove.Replace("+", "").Replace("#", "").Replace("x", "").Replace("-", "");
|
||||
var allMoves = moveGenerator.GenerateMoves(board);
|
||||
|
||||
Move move = new Move();
|
||||
|
||||
foreach (Move moveToTest in allMoves)
|
||||
{
|
||||
move = moveToTest;
|
||||
|
||||
int moveFromIndex = move.StartSquareIndex;
|
||||
int moveToIndex = move.TargetSquareIndex;
|
||||
int movePieceType = PieceHelper.PieceType(board.Square[moveFromIndex]);
|
||||
Coord fromCoord = BoardHelper.CoordFromIndex(moveFromIndex);
|
||||
Coord toCoord = BoardHelper.CoordFromIndex(moveToIndex);
|
||||
if (algebraicMove == "OO")
|
||||
{ // castle kingside
|
||||
if (movePieceType == PieceHelper.King && moveToIndex - moveFromIndex == 2)
|
||||
{
|
||||
return move;
|
||||
}
|
||||
}
|
||||
else if (algebraicMove == "OOO")
|
||||
{ // castle queenside
|
||||
if (movePieceType == PieceHelper.King && moveToIndex - moveFromIndex == -2)
|
||||
{
|
||||
return move;
|
||||
}
|
||||
}
|
||||
// Is pawn move if starts with any file indicator (e.g. 'e'4. Note that uppercase B is used for bishops)
|
||||
else if (BoardHelper.fileNames.Contains(algebraicMove[0].ToString()))
|
||||
{
|
||||
if (movePieceType != PieceHelper.Pawn)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (BoardHelper.fileNames.IndexOf(algebraicMove[0]) == fromCoord.fileIndex)
|
||||
{ // correct starting file
|
||||
if (algebraicMove.Contains("="))
|
||||
{ // is promotion
|
||||
if (toCoord.rankIndex == 0 || toCoord.rankIndex == 7)
|
||||
{
|
||||
|
||||
if (algebraicMove.Length == 5) // pawn is capturing to promote
|
||||
{
|
||||
char targetFile = algebraicMove[1];
|
||||
if (BoardHelper.fileNames.IndexOf(targetFile) != toCoord.fileIndex)
|
||||
{
|
||||
// Skip if not moving to correct file
|
||||
continue;
|
||||
}
|
||||
}
|
||||
char promotionChar = algebraicMove[algebraicMove.Length - 1];
|
||||
|
||||
if (move.PromotionPieceType != GetPieceTypeFromSymbol(promotionChar))
|
||||
{
|
||||
continue; // skip this move, incorrect promotion type
|
||||
}
|
||||
|
||||
return move;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
char targetFile = algebraicMove[algebraicMove.Length - 2];
|
||||
char targetRank = algebraicMove[algebraicMove.Length - 1];
|
||||
|
||||
if (BoardHelper.fileNames.IndexOf(targetFile) == toCoord.fileIndex)
|
||||
{ // correct ending file
|
||||
if (targetRank.ToString() == (toCoord.rankIndex + 1).ToString())
|
||||
{ // correct ending rank
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // regular piece move
|
||||
|
||||
char movePieceChar = algebraicMove[0];
|
||||
if (GetPieceTypeFromSymbol(movePieceChar) != movePieceType)
|
||||
{
|
||||
continue; // skip this move, incorrect move piece type
|
||||
}
|
||||
|
||||
char targetFile = algebraicMove[algebraicMove.Length - 2];
|
||||
char targetRank = algebraicMove[algebraicMove.Length - 1];
|
||||
if (BoardHelper.fileNames.IndexOf(targetFile) == toCoord.fileIndex)
|
||||
{ // correct ending file
|
||||
if (targetRank.ToString() == (toCoord.rankIndex + 1).ToString())
|
||||
{ // correct ending rank
|
||||
|
||||
if (algebraicMove.Length == 4)
|
||||
{ // addition char present for disambiguation (e.g. Nbd7 or R7e2)
|
||||
char disambiguationChar = algebraicMove[1];
|
||||
|
||||
if (BoardHelper.fileNames.Contains(disambiguationChar.ToString()))
|
||||
{ // is file disambiguation
|
||||
if (BoardHelper.fileNames.IndexOf(disambiguationChar) != fromCoord.fileIndex)
|
||||
{ // incorrect starting file
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // is rank disambiguation
|
||||
if (disambiguationChar.ToString() != (fromCoord.rankIndex + 1).ToString())
|
||||
{ // incorrect starting rank
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return move;
|
||||
|
||||
int GetPieceTypeFromSymbol(char symbol)
|
||||
{
|
||||
switch (symbol)
|
||||
{
|
||||
case 'R':
|
||||
return PieceHelper.Rook;
|
||||
case 'N':
|
||||
return PieceHelper.Knight;
|
||||
case 'B':
|
||||
return PieceHelper.Bishop;
|
||||
case 'Q':
|
||||
return PieceHelper.Queen;
|
||||
case 'K':
|
||||
return PieceHelper.King;
|
||||
default:
|
||||
return PieceHelper.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
62
Chess-Challenge/src/Framework/Chess/Helpers/PGNCreator.cs
Normal file
62
Chess-Challenge/src/Framework/Chess/Helpers/PGNCreator.cs
Normal file
|
@ -0,0 +1,62 @@
|
|||
|
||||
using System.Text;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
|
||||
public static class PGNCreator
|
||||
{
|
||||
|
||||
public static string CreatePGN(Move[] moves)
|
||||
{
|
||||
return CreatePGN(moves, GameResult.InProgress, FenUtility.StartPositionFEN);
|
||||
}
|
||||
|
||||
public static string CreatePGN(Board board, GameResult result, string whiteName = "", string blackName = "")
|
||||
{
|
||||
return CreatePGN(board.AllGameMoves.ToArray(), result, board.GameStartFen, whiteName, blackName);
|
||||
}
|
||||
|
||||
public static string CreatePGN(Move[] moves, GameResult result, string startFen, string whiteName = "", string blackName = "")
|
||||
{
|
||||
startFen = startFen.Replace("\n", "").Replace("\r", "");
|
||||
|
||||
StringBuilder pgn = new();
|
||||
Board board = new Board();
|
||||
board.LoadPosition(startFen);
|
||||
// Headers
|
||||
if (!string.IsNullOrEmpty(whiteName))
|
||||
{
|
||||
pgn.AppendLine($"[White \"{whiteName}\"]");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(blackName))
|
||||
{
|
||||
pgn.AppendLine($"[Black \"{blackName}\"]");
|
||||
}
|
||||
|
||||
if (startFen != FenUtility.StartPositionFEN)
|
||||
{
|
||||
pgn.AppendLine($"[FEN \"{startFen}\"]");
|
||||
}
|
||||
if (result is not GameResult.NotStarted or GameResult.InProgress)
|
||||
{
|
||||
pgn.AppendLine($"[Result \"{result}\"]");
|
||||
}
|
||||
|
||||
for (int plyCount = 0; plyCount < moves.Length; plyCount++)
|
||||
{
|
||||
string moveString = MoveUtility.GetMoveNameSAN(moves[plyCount], board);
|
||||
board.MakeMove(moves[plyCount]);
|
||||
|
||||
if (plyCount % 2 == 0)
|
||||
{
|
||||
pgn.Append((plyCount / 2 + 1) + ". ");
|
||||
}
|
||||
pgn.Append(moveString + " ");
|
||||
}
|
||||
|
||||
return pgn.ToString();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
66
Chess-Challenge/src/Framework/Chess/Helpers/PGNLoader.cs
Normal file
66
Chess-Challenge/src/Framework/Chess/Helpers/PGNLoader.cs
Normal file
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public static class PGNLoader
|
||||
{
|
||||
|
||||
public static Move[] MovesFromPGN(string pgn, int maxPlyCount = int.MaxValue)
|
||||
{
|
||||
List<string> algebraicMoves = new List<string>();
|
||||
|
||||
string[] entries = pgn.Replace("\n", " ").Split(' ');
|
||||
for (int i = 0; i < entries.Length; i++)
|
||||
{
|
||||
// Reached move limit, so exit.
|
||||
// (This is used for example when creating book, where only interested in first n moves of game)
|
||||
if (algebraicMoves.Count == maxPlyCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
string entry = entries[i].Trim();
|
||||
|
||||
if (entry.Contains(".") || entry == "1/2-1/2" || entry == "1-0" || entry == "0-1")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(entry))
|
||||
{
|
||||
algebraicMoves.Add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return MovesFromAlgebraic(algebraicMoves.ToArray());
|
||||
}
|
||||
|
||||
static Move[] MovesFromAlgebraic(string[] algebraicMoves)
|
||||
{
|
||||
Board board = new Board();
|
||||
board.LoadStartPosition();
|
||||
var moves = new List<Move>();
|
||||
|
||||
for (int i = 0; i < algebraicMoves.Length; i++)
|
||||
{
|
||||
Move move = MoveUtility.GetMoveFromSAN(board, algebraicMoves[i].Trim());
|
||||
if (move.IsNull)
|
||||
{ // move is illegal; discard and return moves up to this point
|
||||
string pgn = "";
|
||||
foreach (string s in algebraicMoves)
|
||||
{
|
||||
pgn += s + " ";
|
||||
}
|
||||
moves.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
moves.Add(move);
|
||||
}
|
||||
board.MakeMove(move);
|
||||
}
|
||||
return moves.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
using System.Numerics;
|
||||
|
||||
public static class BitBoardUtility
|
||||
{
|
||||
|
||||
// Get index of least significant set bit in given 64bit value. Also clears the bit to zero.
|
||||
public static int PopLSB(ref ulong b)
|
||||
{
|
||||
int i = BitOperations.TrailingZeroCount(b);
|
||||
b &= (b - 1);
|
||||
return i;
|
||||
}
|
||||
|
||||
public static int PopCount(ulong x)
|
||||
{
|
||||
return BitOperations.PopCount(x);
|
||||
}
|
||||
|
||||
public static void SetSquare(ref ulong bitboard, int squareIndex)
|
||||
{
|
||||
bitboard |= 1ul << squareIndex;
|
||||
}
|
||||
|
||||
public static void ClearSquare(ref ulong bitboard, int squareIndex)
|
||||
{
|
||||
bitboard &= ~(1ul << squareIndex);
|
||||
}
|
||||
|
||||
|
||||
public static void ToggleSquare(ref ulong bitboard, int squareIndex)
|
||||
{
|
||||
bitboard ^= 1ul << squareIndex;
|
||||
}
|
||||
|
||||
public static void ToggleSquares(ref ulong bitboard, int squareA, int squareB)
|
||||
{
|
||||
bitboard ^= (1ul << squareA | 1ul << squareB);
|
||||
}
|
||||
|
||||
public static bool ContainsSquare(ulong bitboard, int square)
|
||||
{
|
||||
return ((bitboard >> square) & 1) != 0;
|
||||
}
|
||||
|
||||
public static ulong PawnAttacks(ulong pawnBitboard, bool isWhite)
|
||||
{
|
||||
// Pawn attacks are calculated like so: (example given with white to move)
|
||||
|
||||
// The first half of the attacks are calculated by shifting all pawns north-east: northEastAttacks = pawnBitboard << 9
|
||||
// Note that pawns on the h file will be wrapped around to the a file, so then mask out the a file: northEastAttacks &= notAFile
|
||||
// (Any pawns that were originally on the a file will have been shifted to the b file, so a file should be empty).
|
||||
|
||||
// The other half of the attacks are calculated by shifting all pawns north-west. This time the h file must be masked out.
|
||||
// Combine the two halves to get a bitboard with all the pawn attacks: northEastAttacks | northWestAttacks
|
||||
|
||||
if (isWhite)
|
||||
{
|
||||
return ((pawnBitboard << 9) & Bits.NotAFile) | ((pawnBitboard << 7) & Bits.NotHFile);
|
||||
}
|
||||
|
||||
return ((pawnBitboard >> 7) & Bits.NotAFile) | ((pawnBitboard >> 9) & Bits.NotHFile);
|
||||
}
|
||||
|
||||
|
||||
public static ulong Shift(ulong bitboard, int numSquaresToShift)
|
||||
{
|
||||
if (numSquaresToShift > 0)
|
||||
{
|
||||
return bitboard << numSquaresToShift;
|
||||
}
|
||||
else
|
||||
{
|
||||
return bitboard >> -numSquaresToShift;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static ulong ProtectedPawns(ulong pawns, bool isWhite)
|
||||
{
|
||||
ulong attacks = PawnAttacks(pawns, isWhite);
|
||||
return attacks & pawns;
|
||||
}
|
||||
|
||||
public static ulong LockedPawns(ulong whitePawns, ulong blackPawns)
|
||||
{
|
||||
ulong pushUp = whitePawns << 8;
|
||||
ulong pushDown = blackPawns >> 8;
|
||||
return (whitePawns & pushDown) | (blackPawns & pushUp);
|
||||
}
|
||||
|
||||
|
||||
static BitBoardUtility()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
using static System.Math;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
// A collection of precomputed bitboards for use during movegen, search, etc.
|
||||
public static class Bits
|
||||
{
|
||||
public const ulong FileA = 0x101010101010101;
|
||||
public const ulong FileH = FileA << 7;
|
||||
public const ulong NotAFile = ~FileA;
|
||||
public const ulong NotHFile = ~FileH;
|
||||
|
||||
public const ulong Rank1 = 0b11111111;
|
||||
public const ulong Rank2 = Rank1 << (8 * 1);
|
||||
public const ulong Rank3 = Rank1 << (8 * 2);
|
||||
public const ulong Rank4 = Rank1 << (8 * 3);
|
||||
public const ulong Rank5 = Rank1 << (8 * 4);
|
||||
public const ulong Rank6 = Rank1 << (8 * 5);
|
||||
public const ulong Rank7 = Rank1 << (8 * 6);
|
||||
public const ulong Rank8 = Rank1 << (8 * 7);
|
||||
|
||||
public const ulong WhiteKingsideMask = 1ul << BoardHelper.f1 | 1ul << BoardHelper.g1;
|
||||
public const ulong BlackKingsideMask = 1ul << BoardHelper.f8 | 1ul << BoardHelper.g8;
|
||||
|
||||
public const ulong WhiteQueensideMask2 = 1ul << BoardHelper.d1 | 1ul << BoardHelper.c1;
|
||||
public const ulong BlackQueensideMask2 = 1ul << BoardHelper.d8 | 1ul << BoardHelper.c8;
|
||||
|
||||
public const ulong WhiteQueensideMask = WhiteQueensideMask2 | 1ul << BoardHelper.b1;
|
||||
public const ulong BlackQueensideMask = BlackQueensideMask2 | 1ul << BoardHelper.b8;
|
||||
|
||||
public static readonly ulong[] WhitePassedPawnMask;
|
||||
public static readonly ulong[] BlackPassedPawnMask;
|
||||
|
||||
// A pawn on 'e4' for example, is considered supported by any pawn on
|
||||
// squares: d3, d4, f3, f4
|
||||
public static readonly ulong[] WhitePawnSupportMask;
|
||||
public static readonly ulong[] BlackPawnSupportMask;
|
||||
|
||||
public static readonly ulong[] FileMask;
|
||||
public static readonly ulong[] AdjacentFileMasks;
|
||||
|
||||
// 3x3 mask (except along edges of course)
|
||||
public static readonly ulong[] KingSafetyMask;
|
||||
|
||||
// Mask of 'forward' square. For example, from e4 the forward squares for white are: [e5, e6, e7, e8]
|
||||
public static readonly ulong[] WhiteForwardFileMask;
|
||||
public static readonly ulong[] BlackForwardFileMask;
|
||||
|
||||
// Mask of three consecutive files centred at given file index.
|
||||
// For example, given file '3', the mask would contains files [2,3,4].
|
||||
// Note that for edge files, such as file 0, it would contain files [0,1,2]
|
||||
public static readonly ulong[] TripleFileMask;
|
||||
|
||||
|
||||
public static readonly ulong[] KnightAttacks;
|
||||
public static readonly ulong[] KingMoves;
|
||||
public static readonly ulong[] WhitePawnAttacks;
|
||||
public static readonly ulong[] BlackPawnAttacks;
|
||||
|
||||
|
||||
static Bits()
|
||||
{
|
||||
FileMask = new ulong[8];
|
||||
AdjacentFileMasks = new ulong[8];
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
FileMask[i] = FileA << i;
|
||||
ulong left = i > 0 ? FileA << (i - 1) : 0;
|
||||
ulong right = i < 7 ? FileA << (i + 1) : 0;
|
||||
AdjacentFileMasks[i] = left | right;
|
||||
}
|
||||
|
||||
TripleFileMask = new ulong[8];
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
int clampedFile = System.Math.Clamp(i, 1, 6);
|
||||
TripleFileMask[i] = FileMask[clampedFile] | AdjacentFileMasks[clampedFile];
|
||||
}
|
||||
|
||||
WhitePassedPawnMask = new ulong[64];
|
||||
BlackPassedPawnMask = new ulong[64];
|
||||
WhitePawnSupportMask = new ulong[64];
|
||||
BlackPawnSupportMask = new ulong[64];
|
||||
WhiteForwardFileMask = new ulong[64];
|
||||
BlackForwardFileMask = new ulong[64];
|
||||
|
||||
for (int square = 0; square < 64; square++)
|
||||
{
|
||||
int file = BoardHelper.FileIndex(square);
|
||||
int rank = BoardHelper.RankIndex(square);
|
||||
ulong adjacentFiles = FileA << Max(0, file - 1) | FileA << Min(7, file + 1);
|
||||
// Passed pawn mask
|
||||
ulong whiteForwardMask = ~(ulong.MaxValue >> (64 - 8 * (rank + 1)));
|
||||
ulong blackForwardMask = ((1ul << 8 * rank) - 1);
|
||||
|
||||
WhitePassedPawnMask[square] = (FileA << file | adjacentFiles) & whiteForwardMask;
|
||||
BlackPassedPawnMask[square] = (FileA << file | adjacentFiles) & blackForwardMask;
|
||||
// Pawn support mask
|
||||
ulong adjacent = (1ul << (square - 1) | 1ul << (square + 1)) & adjacentFiles;
|
||||
WhitePawnSupportMask[square] = adjacent | BitBoardUtility.Shift(adjacent, -8);
|
||||
BlackPawnSupportMask[square] = adjacent | BitBoardUtility.Shift(adjacent, +8);
|
||||
|
||||
WhiteForwardFileMask[square] = whiteForwardMask & FileMask[file];
|
||||
BlackForwardFileMask[square] = blackForwardMask & FileMask[file];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
KnightAttacks = new ulong[64];
|
||||
KingMoves = new ulong[64];
|
||||
WhitePawnAttacks = new ulong[64];
|
||||
BlackPawnAttacks = new ulong[64];
|
||||
|
||||
(int x, int y)[] orthoDir = { (-1, 0), (0, 1), (1, 0), (0, -1) };
|
||||
(int x, int y)[] diagDir = { (-1, -1), (-1, 1), (1, 1), (1, -1) };
|
||||
(int x, int y)[] knightJumps = { (-2, -1), (-2, 1), (-1, 2), (1, 2), (2, 1), (2, -1), (1, -2), (-1, -2) };
|
||||
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
ProcessSquare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
KingSafetyMask = new ulong[64];
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
KingSafetyMask[i] = KingMoves[i] | (1ul << i);
|
||||
}
|
||||
|
||||
void ProcessSquare(int x, int y)
|
||||
{
|
||||
int squareIndex = y * 8 + x;
|
||||
|
||||
for (int dirIndex = 0; dirIndex < 4; dirIndex++)
|
||||
{
|
||||
// Orthogonal and diagonal directions
|
||||
for (int dst = 1; dst < 8; dst++)
|
||||
{
|
||||
int orthoX = x + orthoDir[dirIndex].x * dst;
|
||||
int orthoY = y + orthoDir[dirIndex].y * dst;
|
||||
int diagX = x + diagDir[dirIndex].x * dst;
|
||||
int diagY = y + diagDir[dirIndex].y * dst;
|
||||
|
||||
if (ValidSquareIndex(orthoX, orthoY, out int orthoTargetIndex))
|
||||
{
|
||||
if (dst == 1)
|
||||
{
|
||||
KingMoves[squareIndex] |= 1ul << orthoTargetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (ValidSquareIndex(diagX, diagY, out int diagTargetIndex))
|
||||
{
|
||||
if (dst == 1)
|
||||
{
|
||||
KingMoves[squareIndex] |= 1ul << diagTargetIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Knight jumps
|
||||
for (int i = 0; i < knightJumps.Length; i++)
|
||||
{
|
||||
int knightX = x + knightJumps[i].x;
|
||||
int knightY = y + knightJumps[i].y;
|
||||
if (ValidSquareIndex(knightX, knightY, out int knightTargetSquare))
|
||||
{
|
||||
KnightAttacks[squareIndex] |= 1ul << knightTargetSquare;
|
||||
}
|
||||
}
|
||||
|
||||
// Pawn attacks
|
||||
|
||||
if (ValidSquareIndex(x + 1, y + 1, out int whitePawnRight))
|
||||
{
|
||||
WhitePawnAttacks[squareIndex] |= 1ul << whitePawnRight;
|
||||
}
|
||||
if (ValidSquareIndex(x - 1, y + 1, out int whitePawnLeft))
|
||||
{
|
||||
WhitePawnAttacks[squareIndex] |= 1ul << whitePawnLeft;
|
||||
}
|
||||
|
||||
|
||||
if (ValidSquareIndex(x + 1, y - 1, out int blackPawnAttackRight))
|
||||
{
|
||||
BlackPawnAttacks[squareIndex] |= 1ul << blackPawnAttackRight;
|
||||
}
|
||||
if (ValidSquareIndex(x - 1, y - 1, out int blackPawnAttackLeft))
|
||||
{
|
||||
BlackPawnAttacks[squareIndex] |= 1ul << blackPawnAttackLeft;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ValidSquareIndex(int x, int y, out int index)
|
||||
{
|
||||
index = y * 8 + x;
|
||||
return x >= 0 && x < 8 && y >= 0 && y < 8;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
using static PrecomputedMagics;
|
||||
|
||||
// Helper class for magic bitboards.
|
||||
// This is a technique where bishop and rook moves are precomputed
|
||||
// for any configuration of origin square and blocking pieces.
|
||||
public static class Magic
|
||||
{
|
||||
// Rook and bishop mask bitboards for each origin square.
|
||||
// A mask is simply the legal moves available to the piece from the origin square
|
||||
// (on an empty board), except that the moves stop 1 square before the edge of the board.
|
||||
public static readonly ulong[] RookMask;
|
||||
public static readonly ulong[] BishopMask;
|
||||
|
||||
public static readonly ulong[][] RookAttacks;
|
||||
public static readonly ulong[][] BishopAttacks;
|
||||
|
||||
|
||||
public static ulong GetSliderAttacks(int square, ulong blockers, bool ortho)
|
||||
{
|
||||
return ortho ? GetRookAttacks(square, blockers) : GetBishopAttacks(square, blockers);
|
||||
}
|
||||
|
||||
public static ulong GetRookAttacks(int square, ulong blockers)
|
||||
{
|
||||
ulong key = ((blockers & RookMask[square]) * RookMagics[square]) >> RookShifts[square];
|
||||
return RookAttacks[square][key];
|
||||
}
|
||||
|
||||
public static ulong GetBishopAttacks(int square, ulong blockers)
|
||||
{
|
||||
ulong key = ((blockers & BishopMask[square]) * BishopMagics[square]) >> BishopShifts[square];
|
||||
return BishopAttacks[square][key];
|
||||
}
|
||||
|
||||
|
||||
static Magic()
|
||||
{
|
||||
RookMask = new ulong[64];
|
||||
BishopMask = new ulong[64];
|
||||
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
RookMask[squareIndex] = MagicHelper.CreateMovementMask(squareIndex, true);
|
||||
BishopMask[squareIndex] = MagicHelper.CreateMovementMask(squareIndex, false);
|
||||
}
|
||||
|
||||
RookAttacks = new ulong[64][];
|
||||
BishopAttacks = new ulong[64][];
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
RookAttacks[i] = CreateTable(i, true, RookMagics[i], RookShifts[i]);
|
||||
BishopAttacks[i] = CreateTable(i, false, BishopMagics[i], BishopShifts[i]);
|
||||
}
|
||||
|
||||
ulong[] CreateTable(int square, bool rook, ulong magic, int leftShift)
|
||||
{
|
||||
int numBits = 64 - leftShift;
|
||||
int lookupSize = 1 << numBits;
|
||||
ulong[] table = new ulong[lookupSize];
|
||||
|
||||
ulong movementMask = MagicHelper.CreateMovementMask(square, rook);
|
||||
ulong[] blockerPatterns = MagicHelper.CreateAllBlockerBitboards(movementMask);
|
||||
|
||||
foreach (ulong pattern in blockerPatterns)
|
||||
{
|
||||
ulong index = (pattern * magic) >> leftShift;
|
||||
ulong moves = MagicHelper.LegalMoveBitboardFromBlockers(square, pattern, rook);
|
||||
table[index] = moves;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public static class MagicHelper
|
||||
{
|
||||
public static ulong[] CreateAllBlockerBitboards(ulong movementMask)
|
||||
{
|
||||
// Create a list of the indices of the bits that are set in the movement mask
|
||||
List<int> moveSquareIndices = new();
|
||||
for (int i = 0; i < 64; i++)
|
||||
{
|
||||
if (((movementMask >> i) & 1) == 1)
|
||||
{
|
||||
moveSquareIndices.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total number of different bitboards (one for each possible arrangement of pieces)
|
||||
int numPatterns = 1 << moveSquareIndices.Count; // 2^n
|
||||
ulong[] blockerBitboards = new ulong[numPatterns];
|
||||
|
||||
// Create all bitboards
|
||||
for (int patternIndex = 0; patternIndex < numPatterns; patternIndex++)
|
||||
{
|
||||
for (int bitIndex = 0; bitIndex < moveSquareIndices.Count; bitIndex++)
|
||||
{
|
||||
int bit = (patternIndex >> bitIndex) & 1;
|
||||
blockerBitboards[patternIndex] |= (ulong)bit << moveSquareIndices[bitIndex];
|
||||
}
|
||||
}
|
||||
|
||||
return blockerBitboards;
|
||||
}
|
||||
|
||||
|
||||
public static ulong CreateMovementMask(int squareIndex, bool ortho)
|
||||
{
|
||||
ulong mask = 0;
|
||||
Coord[] directions = ortho ? BoardHelper.RookDirections : BoardHelper.BishopDirections;
|
||||
Coord startCoord = new Coord(squareIndex);
|
||||
|
||||
foreach (Coord dir in directions)
|
||||
{
|
||||
for (int dst = 1; dst < 8; dst++)
|
||||
{
|
||||
Coord coord = startCoord + dir * dst;
|
||||
Coord nextCoord = startCoord + dir * (dst + 1);
|
||||
|
||||
if (nextCoord.IsValidSquare())
|
||||
{
|
||||
BitBoardUtility.SetSquare(ref mask, coord.SquareIndex);
|
||||
}
|
||||
else { break; }
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
public static ulong LegalMoveBitboardFromBlockers(int startSquare, ulong blockerBitboard, bool ortho)
|
||||
{
|
||||
ulong bitboard = 0;
|
||||
|
||||
Coord[] directions = ortho ? BoardHelper.RookDirections : BoardHelper.BishopDirections;
|
||||
Coord startCoord = new Coord(startSquare);
|
||||
|
||||
foreach (Coord dir in directions)
|
||||
{
|
||||
for (int dst = 1; dst < 8; dst++)
|
||||
{
|
||||
Coord coord = startCoord + dir * dst;
|
||||
|
||||
if (coord.IsValidSquare())
|
||||
{
|
||||
BitBoardUtility.SetSquare(ref bitboard, coord.SquareIndex);
|
||||
if (BitBoardUtility.ContainsSquare(blockerBitboard, coord.SquareIndex))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else { break; }
|
||||
}
|
||||
}
|
||||
|
||||
return bitboard;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public static class PrecomputedMagics
|
||||
{
|
||||
public static readonly int[] RookShifts = { 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 53, 54, 53, 53, 54, 53, 53, 54, 54, 54, 53, 53, 54, 53, 53, 54, 53, 53, 54, 54, 54, 53, 52, 54, 53, 53, 53, 53, 54, 53, 52, 53, 54, 54, 53, 53, 54, 53, 53, 54, 54, 54, 53, 53, 54, 53, 52, 53, 53, 53, 53, 53, 53, 52 };
|
||||
public static readonly int[] BishopShifts = { 58, 60, 59, 59, 59, 59, 60, 58, 60, 59, 59, 59, 59, 59, 59, 60, 59, 59, 57, 57, 57, 57, 59, 59, 59, 59, 57, 55, 55, 57, 59, 59, 59, 59, 57, 55, 55, 57, 59, 59, 59, 59, 57, 57, 57, 57, 59, 59, 60, 60, 59, 59, 59, 59, 60, 60, 58, 60, 59, 59, 59, 59, 59, 58 };
|
||||
|
||||
public static readonly ulong[] RookMagics = { 468374916371625120, 18428729537625841661, 2531023729696186408, 6093370314119450896, 13830552789156493815, 16134110446239088507, 12677615322350354425, 5404321144167858432, 2111097758984580, 18428720740584907710, 17293734603602787839, 4938760079889530922, 7699325603589095390, 9078693890218258431, 578149610753690728, 9496543503900033792, 1155209038552629657, 9224076274589515780, 1835781998207181184, 509120063316431138, 16634043024132535807, 18446673631917146111, 9623686630121410312, 4648737361302392899, 738591182849868645, 1732936432546219272, 2400543327507449856, 5188164365601475096, 10414575345181196316, 1162492212166789136, 9396848738060210946, 622413200109881612, 7998357718131801918, 7719627227008073923, 16181433497662382080, 18441958655457754079, 1267153596645440, 18446726464209379263, 1214021438038606600, 4650128814733526084, 9656144899867951104, 18444421868610287615, 3695311799139303489, 10597006226145476632, 18436046904206950398, 18446726472933277663, 3458977943764860944, 39125045590687766, 9227453435446560384, 6476955465732358656, 1270314852531077632, 2882448553461416064, 11547238928203796481, 1856618300822323264, 2573991788166144, 4936544992551831040, 13690941749405253631, 15852669863439351807, 18302628748190527413, 12682135449552027479, 13830554446930287982, 18302628782487371519, 7924083509981736956, 4734295326018586370 };
|
||||
public static readonly ulong[] BishopMagics = { 16509839532542417919, 14391803910955204223, 1848771770702627364, 347925068195328958, 5189277761285652493, 3750937732777063343, 18429848470517967340, 17870072066711748607, 16715520087474960373, 2459353627279607168, 7061705824611107232, 8089129053103260512, 7414579821471224013, 9520647030890121554, 17142940634164625405, 9187037984654475102, 4933695867036173873, 3035992416931960321, 15052160563071165696, 5876081268917084809, 1153484746652717320, 6365855841584713735, 2463646859659644933, 1453259901463176960, 9808859429721908488, 2829141021535244552, 576619101540319252, 5804014844877275314, 4774660099383771136, 328785038479458864, 2360590652863023124, 569550314443282, 17563974527758635567, 11698101887533589556, 5764964460729992192, 6953579832080335136, 1318441160687747328, 8090717009753444376, 16751172641200572929, 5558033503209157252, 17100156536247493656, 7899286223048400564, 4845135427956654145, 2368485888099072, 2399033289953272320, 6976678428284034058, 3134241565013966284, 8661609558376259840, 17275805361393991679, 15391050065516657151, 11529206229534274423, 9876416274250600448, 16432792402597134585, 11975705497012863580, 11457135419348969979, 9763749252098620046, 16960553411078512574, 15563877356819111679, 14994736884583272463, 9441297368950544394, 14537646123432199168, 9888547162215157388, 18140215579194907366, 18374682062228545019 };
|
||||
}
|
||||
}
|
|
@ -0,0 +1,560 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
using System;
|
||||
using static PrecomputedMoveData;
|
||||
|
||||
public class MoveGenerator
|
||||
{
|
||||
public const int MaxMoves = 218;
|
||||
//public enum PromotionMode { All, QueenOnly, QueenAndKnight }
|
||||
public enum PromotionMode { All, QueenOnly, QueenAndKnight }
|
||||
|
||||
public PromotionMode promotionsToGenerate = PromotionMode.All;
|
||||
|
||||
// ---- Instance variables ----
|
||||
bool isWhiteToMove;
|
||||
int friendlyColour;
|
||||
int opponentColour;
|
||||
int friendlyKingSquare;
|
||||
int friendlyIndex;
|
||||
int enemyIndex;
|
||||
|
||||
bool inCheck;
|
||||
bool inDoubleCheck;
|
||||
|
||||
// If in check, this bitboard contains squares in line from checking piece up to king
|
||||
// If not in check, all bits are set to 1
|
||||
ulong checkRayBitmask;
|
||||
|
||||
ulong pinRays;
|
||||
ulong notPinRays;
|
||||
ulong opponentAttackMapNoPawns;
|
||||
public ulong opponentAttackMap;
|
||||
public ulong opponentPawnAttackMap;
|
||||
ulong opponentSlidingAttackMap;
|
||||
|
||||
bool generateQuietMoves;
|
||||
Board board;
|
||||
int currMoveIndex;
|
||||
|
||||
ulong enemyPieces;
|
||||
ulong friendlyPieces;
|
||||
ulong allPieces;
|
||||
ulong emptySquares;
|
||||
ulong emptyOrEnemySquares;
|
||||
// If only captures should be generated, this will have 1s only in positions of enemy pieces.
|
||||
// Otherwise it will have 1s everywhere.
|
||||
ulong moveTypeMask;
|
||||
|
||||
public System.Span<Move> GenerateMoves(Board board, bool includeQuietMoves = true)
|
||||
{
|
||||
System.Span<Move> moves = new Move[MaxMoves];
|
||||
return GenerateMoves(board, moves, includeQuietMoves);
|
||||
}
|
||||
|
||||
// Generates list of legal moves in current position.
|
||||
// Quiet moves (non captures) can optionally be excluded. This is used in quiescence search.
|
||||
public System.Span<Move> GenerateMoves(Board board, System.Span<Move> moves, bool includeQuietMoves = true)
|
||||
{
|
||||
this.board = board;
|
||||
generateQuietMoves = includeQuietMoves;
|
||||
|
||||
Init();
|
||||
|
||||
GenerateKingMoves(moves);
|
||||
|
||||
// Only king moves are valid in a double check position, so can return early.
|
||||
if (!inDoubleCheck)
|
||||
{
|
||||
GenerateSlidingMoves(moves);
|
||||
GenerateKnightMoves(moves);
|
||||
GeneratePawnMoves(moves);
|
||||
}
|
||||
|
||||
return moves.Slice(0, currMoveIndex);
|
||||
}
|
||||
|
||||
// Note, this will only return correct value after GenerateMoves() has been called in the current position
|
||||
public bool InCheck()
|
||||
{
|
||||
return inCheck;
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
// Reset state
|
||||
currMoveIndex = 0;
|
||||
inCheck = false;
|
||||
inDoubleCheck = false;
|
||||
checkRayBitmask = 0;
|
||||
pinRays = 0;
|
||||
|
||||
// Store some info for convenience
|
||||
isWhiteToMove = board.MoveColour == PieceHelper.White;
|
||||
friendlyColour = board.MoveColour;
|
||||
opponentColour = board.OpponentColour;
|
||||
friendlyKingSquare = board.KingSquare[board.MoveColourIndex];
|
||||
friendlyIndex = board.MoveColourIndex;
|
||||
enemyIndex = 1 - friendlyIndex;
|
||||
|
||||
// Store some bitboards for convenience
|
||||
enemyPieces = board.colourBitboards[enemyIndex];
|
||||
friendlyPieces = board.colourBitboards[friendlyIndex];
|
||||
allPieces = board.allPiecesBitboard;
|
||||
emptySquares = ~allPieces;
|
||||
emptyOrEnemySquares = emptySquares | enemyPieces;
|
||||
moveTypeMask = generateQuietMoves ? ulong.MaxValue : enemyPieces;
|
||||
|
||||
CalculateAttackData();
|
||||
}
|
||||
|
||||
void GenerateKingMoves(System.Span<Move> moves)
|
||||
{
|
||||
ulong legalMask = ~(opponentAttackMap | friendlyPieces);
|
||||
ulong kingMoves = Bits.KingMoves[friendlyKingSquare] & legalMask & moveTypeMask;
|
||||
while (kingMoves != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref kingMoves);
|
||||
moves[currMoveIndex++] = new Move(friendlyKingSquare, targetSquare);
|
||||
}
|
||||
|
||||
// Castling
|
||||
if (!inCheck && generateQuietMoves)
|
||||
{
|
||||
ulong castleBlockers = opponentAttackMap | board.allPiecesBitboard;
|
||||
if (board.currentGameState.HasKingsideCastleRight(board.IsWhiteToMove))
|
||||
{
|
||||
ulong castleMask = board.IsWhiteToMove ? Bits.WhiteKingsideMask : Bits.BlackKingsideMask;
|
||||
if ((castleMask & castleBlockers) == 0)
|
||||
{
|
||||
int targetSquare = board.IsWhiteToMove ? BoardHelper.g1 : BoardHelper.g8;
|
||||
moves[currMoveIndex++] = new Move(friendlyKingSquare, targetSquare, Move.CastleFlag);
|
||||
}
|
||||
}
|
||||
if (board.currentGameState.HasQueensideCastleRight(board.IsWhiteToMove))
|
||||
{
|
||||
ulong castleMask = board.IsWhiteToMove ? Bits.WhiteQueensideMask2 : Bits.BlackQueensideMask2;
|
||||
ulong castleBlockMask = board.IsWhiteToMove ? Bits.WhiteQueensideMask : Bits.BlackQueensideMask;
|
||||
if ((castleMask & castleBlockers) == 0 && (castleBlockMask & board.allPiecesBitboard) == 0)
|
||||
{
|
||||
int targetSquare = board.IsWhiteToMove ? BoardHelper.c1 : BoardHelper.c8;
|
||||
moves[currMoveIndex++] = new Move(friendlyKingSquare, targetSquare, Move.CastleFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateSlidingMoves(System.Span<Move> moves)
|
||||
{
|
||||
// Limit movement to empty or enemy squares, and must block check if king is in check.
|
||||
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
||||
|
||||
ulong othogonalSliders = board.FriendlyOrthogonalSliders;
|
||||
ulong diagonalSliders = board.FriendlyDiagonalSliders;
|
||||
|
||||
// Pinned pieces cannot move if king is in check
|
||||
if (inCheck)
|
||||
{
|
||||
othogonalSliders &= ~pinRays;
|
||||
diagonalSliders &= ~pinRays;
|
||||
}
|
||||
|
||||
// Ortho
|
||||
while (othogonalSliders != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref othogonalSliders);
|
||||
ulong moveSquares = Magic.GetRookAttacks(startSquare, allPieces) & moveMask;
|
||||
|
||||
// If piece is pinned, it can only move along the pin ray
|
||||
if (IsPinned(startSquare))
|
||||
{
|
||||
moveSquares &= alignMask[startSquare, friendlyKingSquare];
|
||||
}
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
// Diag
|
||||
while (diagonalSliders != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref diagonalSliders);
|
||||
ulong moveSquares = Magic.GetBishopAttacks(startSquare, allPieces) & moveMask;
|
||||
|
||||
// If piece is pinned, it can only move along the pin ray
|
||||
if (IsPinned(startSquare))
|
||||
{
|
||||
moveSquares &= alignMask[startSquare, friendlyKingSquare];
|
||||
}
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GenerateKnightMoves(System.Span<Move> moves)
|
||||
{
|
||||
int friendlyKnightPiece = PieceHelper.MakePiece(PieceHelper.Knight, board.MoveColour);
|
||||
// bitboard of all non-pinned knights
|
||||
ulong knights = board.pieceBitboards[friendlyKnightPiece] & notPinRays;
|
||||
ulong moveMask = emptyOrEnemySquares & checkRayBitmask & moveTypeMask;
|
||||
|
||||
while (knights != 0)
|
||||
{
|
||||
int knightSquare = BitBoardUtility.PopLSB(ref knights);
|
||||
ulong moveSquares = Bits.KnightAttacks[knightSquare] & moveMask;
|
||||
|
||||
while (moveSquares != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref moveSquares);
|
||||
moves[currMoveIndex++] = new Move(knightSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratePawnMoves(System.Span<Move> moves)
|
||||
{
|
||||
int pushDir = board.IsWhiteToMove ? 1 : -1;
|
||||
int pushOffset = pushDir * 8;
|
||||
|
||||
int friendlyPawnPiece = PieceHelper.MakePiece(PieceHelper.Pawn, board.MoveColour);
|
||||
ulong pawns = board.pieceBitboards[friendlyPawnPiece];
|
||||
|
||||
ulong promotionRankMask = board.IsWhiteToMove ? Bits.Rank8 : Bits.Rank1;
|
||||
|
||||
ulong singlePush = (BitBoardUtility.Shift(pawns, pushOffset)) & emptySquares;
|
||||
|
||||
ulong pushPromotions = singlePush & promotionRankMask & checkRayBitmask;
|
||||
|
||||
|
||||
ulong captureEdgeFileMask = board.IsWhiteToMove ? Bits.NotAFile : Bits.NotHFile;
|
||||
ulong captureEdgeFileMask2 = board.IsWhiteToMove ? Bits.NotHFile : Bits.NotAFile;
|
||||
ulong captureA = BitBoardUtility.Shift(pawns & captureEdgeFileMask, pushDir * 7) & enemyPieces;
|
||||
ulong captureB = BitBoardUtility.Shift(pawns & captureEdgeFileMask2, pushDir * 9) & enemyPieces;
|
||||
|
||||
ulong singlePushNoPromotions = singlePush & ~promotionRankMask & checkRayBitmask;
|
||||
|
||||
ulong capturePromotionsA = captureA & promotionRankMask & checkRayBitmask;
|
||||
ulong capturePromotionsB = captureB & promotionRankMask & checkRayBitmask;
|
||||
|
||||
captureA &= checkRayBitmask & ~promotionRankMask;
|
||||
captureB &= checkRayBitmask & ~promotionRankMask;
|
||||
|
||||
// Single / double push
|
||||
if (generateQuietMoves)
|
||||
{
|
||||
// Generate single pawn pushes
|
||||
while (singlePushNoPromotions != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref singlePushNoPromotions);
|
||||
int startSquare = targetSquare - pushOffset;
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate double pawn pushes
|
||||
ulong doublePushTargetRankMask = board.IsWhiteToMove ? Bits.Rank4 : Bits.Rank5;
|
||||
ulong doublePush = BitBoardUtility.Shift(singlePush, pushOffset) & emptySquares & doublePushTargetRankMask & checkRayBitmask;
|
||||
|
||||
while (doublePush != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref doublePush);
|
||||
int startSquare = targetSquare - pushOffset * 2;
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PawnTwoUpFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Captures
|
||||
while (captureA != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref captureA);
|
||||
int startSquare = targetSquare - pushDir * 7;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
while (captureB != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref captureB);
|
||||
int startSquare = targetSquare - pushDir * 9;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Promotions
|
||||
while (pushPromotions != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref pushPromotions);
|
||||
int startSquare = targetSquare - pushOffset;
|
||||
if (!IsPinned(startSquare))
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare, moves);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
while (capturePromotionsA != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref capturePromotionsA);
|
||||
int startSquare = targetSquare - pushDir * 7;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare, moves);
|
||||
}
|
||||
}
|
||||
|
||||
while (capturePromotionsB != 0)
|
||||
{
|
||||
int targetSquare = BitBoardUtility.PopLSB(ref capturePromotionsB);
|
||||
int startSquare = targetSquare - pushDir * 9;
|
||||
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
GeneratePromotions(startSquare, targetSquare, moves);
|
||||
}
|
||||
}
|
||||
|
||||
// En passant
|
||||
if (board.currentGameState.enPassantFile > 0)
|
||||
{
|
||||
int epFileIndex = board.currentGameState.enPassantFile - 1;
|
||||
int epRankIndex = board.IsWhiteToMove ? 5 : 2;
|
||||
int targetSquare = epRankIndex * 8 + epFileIndex;
|
||||
int capturedPawnSquare = targetSquare - pushOffset;
|
||||
|
||||
if (BitBoardUtility.ContainsSquare(checkRayBitmask, capturedPawnSquare))
|
||||
{
|
||||
ulong pawnsThatCanCaptureEp = pawns & BitBoardUtility.PawnAttacks(1ul << targetSquare, !board.IsWhiteToMove);
|
||||
|
||||
while (pawnsThatCanCaptureEp != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref pawnsThatCanCaptureEp);
|
||||
if (!IsPinned(startSquare) || alignMask[startSquare, friendlyKingSquare] == alignMask[targetSquare, friendlyKingSquare])
|
||||
{
|
||||
if (!InCheckAfterEnPassant(startSquare, targetSquare, capturedPawnSquare))
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.EnPassantCaptureFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GeneratePromotions(int startSquare, int targetSquare, Span<Move> moves)
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PromoteToQueenFlag);
|
||||
// Don't generate non-queen promotions in q-search
|
||||
if (generateQuietMoves)
|
||||
{
|
||||
if (promotionsToGenerate == MoveGenerator.PromotionMode.All)
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PromoteToKnightFlag);
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PromoteToRookFlag);
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PromoteToBishopFlag);
|
||||
}
|
||||
else if (promotionsToGenerate == MoveGenerator.PromotionMode.QueenAndKnight)
|
||||
{
|
||||
moves[currMoveIndex++] = new Move(startSquare, targetSquare, Move.PromoteToKnightFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPinned(int square)
|
||||
{
|
||||
return ((pinRays >> square) & 1) != 0;
|
||||
}
|
||||
|
||||
void GenSlidingAttackMap()
|
||||
{
|
||||
opponentSlidingAttackMap = 0;
|
||||
|
||||
UpdateSlideAttack(board.EnemyOrthogonalSliders, true);
|
||||
UpdateSlideAttack(board.EnemyDiagonalSliders, false);
|
||||
|
||||
void UpdateSlideAttack(ulong pieceBoard, bool ortho)
|
||||
{
|
||||
ulong blockers = board.allPiecesBitboard & ~(1ul << friendlyKingSquare);
|
||||
|
||||
while (pieceBoard != 0)
|
||||
{
|
||||
int startSquare = BitBoardUtility.PopLSB(ref pieceBoard);
|
||||
ulong moveBoard = Magic.GetSliderAttacks(startSquare, blockers, ortho);
|
||||
|
||||
opponentSlidingAttackMap |= moveBoard;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CalculateAttackData()
|
||||
{
|
||||
GenSlidingAttackMap();
|
||||
// Search squares in all directions around friendly king for checks/pins by enemy sliding pieces (queen, rook, bishop)
|
||||
int startDirIndex = 0;
|
||||
int endDirIndex = 8;
|
||||
|
||||
if (board.queens[enemyIndex].Count == 0)
|
||||
{
|
||||
startDirIndex = (board.rooks[enemyIndex].Count > 0) ? 0 : 4;
|
||||
endDirIndex = (board.bishops[enemyIndex].Count > 0) ? 8 : 4;
|
||||
}
|
||||
|
||||
for (int dir = startDirIndex; dir < endDirIndex; dir++)
|
||||
{
|
||||
bool isDiagonal = dir > 3;
|
||||
ulong slider = isDiagonal ? board.EnemyDiagonalSliders : board.EnemyOrthogonalSliders;
|
||||
if ((dirRayMask[dir, friendlyKingSquare] & slider) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int n = numSquaresToEdge[friendlyKingSquare][dir];
|
||||
int directionOffset = directionOffsets[dir];
|
||||
bool isFriendlyPieceAlongRay = false;
|
||||
ulong rayMask = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
int squareIndex = friendlyKingSquare + directionOffset * (i + 1);
|
||||
rayMask |= 1ul << squareIndex;
|
||||
int piece = board.Square[squareIndex];
|
||||
|
||||
// This square contains a piece
|
||||
if (piece != PieceHelper.None)
|
||||
{
|
||||
if (PieceHelper.IsColour(piece, friendlyColour))
|
||||
{
|
||||
// First friendly piece we have come across in this direction, so it might be pinned
|
||||
if (!isFriendlyPieceAlongRay)
|
||||
{
|
||||
isFriendlyPieceAlongRay = true;
|
||||
}
|
||||
// This is the second friendly piece we've found in this direction, therefore pin is not possible
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// This square contains an enemy piece
|
||||
else
|
||||
{
|
||||
int pieceType = PieceHelper.PieceType(piece);
|
||||
|
||||
// Check if piece is in bitmask of pieces able to move in current direction
|
||||
if (isDiagonal && PieceHelper.IsDiagonalSlider(pieceType) || !isDiagonal && PieceHelper.IsOrthogonalSlider(pieceType))
|
||||
{
|
||||
// Friendly piece blocks the check, so this is a pin
|
||||
if (isFriendlyPieceAlongRay)
|
||||
{
|
||||
pinRays |= rayMask;
|
||||
}
|
||||
// No friendly piece blocking the attack, so this is a check
|
||||
else
|
||||
{
|
||||
checkRayBitmask |= rayMask;
|
||||
inDoubleCheck = inCheck; // if already in check, then this is double check
|
||||
inCheck = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// This enemy piece is not able to move in the current direction, and so is blocking any checks/pins
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stop searching for pins if in double check, as the king is the only piece able to move in that case anyway
|
||||
if (inDoubleCheck)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
notPinRays = ~pinRays;
|
||||
|
||||
ulong opponentKnightAttacks = 0;
|
||||
ulong knights = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.Knight, board.OpponentColour)];
|
||||
ulong friendlyKingBoard = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.King, board.MoveColour)];
|
||||
|
||||
while (knights != 0)
|
||||
{
|
||||
int knightSquare = BitBoardUtility.PopLSB(ref knights);
|
||||
ulong knightAttacks = Bits.KnightAttacks[knightSquare];
|
||||
opponentKnightAttacks |= knightAttacks;
|
||||
|
||||
if ((knightAttacks & friendlyKingBoard) != 0)
|
||||
{
|
||||
inDoubleCheck = inCheck;
|
||||
inCheck = true;
|
||||
checkRayBitmask |= 1ul << knightSquare;
|
||||
}
|
||||
}
|
||||
|
||||
// Pawn attacks
|
||||
PieceList opponentPawns = board.pawns[enemyIndex];
|
||||
opponentPawnAttackMap = 0;
|
||||
|
||||
ulong opponentPawnsBoard = board.pieceBitboards[PieceHelper.MakePiece(PieceHelper.Pawn, board.OpponentColour)];
|
||||
opponentPawnAttackMap = BitBoardUtility.PawnAttacks(opponentPawnsBoard, !isWhiteToMove);
|
||||
if (BitBoardUtility.ContainsSquare(opponentPawnAttackMap, friendlyKingSquare))
|
||||
{
|
||||
inDoubleCheck = inCheck; // if already in check, then this is double check
|
||||
inCheck = true;
|
||||
ulong possiblePawnAttackOrigins = board.IsWhiteToMove ? Bits.WhitePawnAttacks[friendlyKingSquare] : Bits.BlackPawnAttacks[friendlyKingSquare];
|
||||
ulong pawnCheckMap = opponentPawnsBoard & possiblePawnAttackOrigins;
|
||||
checkRayBitmask |= pawnCheckMap;
|
||||
}
|
||||
|
||||
int enemyKingSquare = board.KingSquare[enemyIndex];
|
||||
|
||||
opponentAttackMapNoPawns = opponentSlidingAttackMap | opponentKnightAttacks | Bits.KingMoves[enemyKingSquare];
|
||||
opponentAttackMap = opponentAttackMapNoPawns | opponentPawnAttackMap;
|
||||
|
||||
if (!inCheck)
|
||||
{
|
||||
checkRayBitmask = ulong.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Test if capturing a pawn with en-passant reveals a sliding piece attack against the king
|
||||
// Note: this is only used for cases where pawn appears to not be pinned due to opponent pawn being on same rank
|
||||
// (therefore only need to check orthogonal sliders)
|
||||
bool InCheckAfterEnPassant(int startSquare, int targetSquare, int epCaptureSquare)
|
||||
{
|
||||
ulong enemyOrtho = board.EnemyOrthogonalSliders;
|
||||
|
||||
if (enemyOrtho != 0)
|
||||
{
|
||||
ulong maskedBlockers = (allPieces ^ (1ul << epCaptureSquare | 1ul << startSquare | 1ul << targetSquare));
|
||||
ulong rookAttacks = Magic.GetRookAttacks(friendlyKingSquare, maskedBlockers);
|
||||
return (rookAttacks & enemyOrtho) != 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using static System.Math;
|
||||
|
||||
public static class PrecomputedMoveData
|
||||
{
|
||||
|
||||
|
||||
public static readonly ulong[,] alignMask;
|
||||
public static readonly ulong[,] dirRayMask;
|
||||
|
||||
// First 4 are orthogonal, last 4 are diagonals (N, S, W, E, NW, SE, NE, SW)
|
||||
public static readonly int[] directionOffsets = { 8, -8, -1, 1, 7, -7, 9, -9 };
|
||||
|
||||
static readonly Coord[] dirOffsets2D =
|
||||
{
|
||||
new Coord(0, 1),
|
||||
new Coord(0, -1),
|
||||
new Coord(-1, 0),
|
||||
new Coord(1, 0),
|
||||
new Coord(-1, 1),
|
||||
new Coord(1, -1),
|
||||
new Coord(1, 1),
|
||||
new Coord(-1, -1)
|
||||
};
|
||||
|
||||
|
||||
// Stores number of moves available in each of the 8 directions for every square on the board
|
||||
// Order of directions is: N, S, W, E, NW, SE, NE, SW
|
||||
// So for example, if availableSquares[0][1] == 7...
|
||||
// that means that there are 7 squares to the north of b1 (the square with index 1 in board array)
|
||||
public static readonly int[][] numSquaresToEdge;
|
||||
|
||||
// Stores array of indices for each square a knight can land on from any square on the board
|
||||
// So for example, knightMoves[0] is equal to {10, 17}, meaning a knight on a1 can jump to c2 and b3
|
||||
public static readonly byte[][] knightMoves;
|
||||
public static readonly byte[][] kingMoves;
|
||||
|
||||
// Pawn attack directions for white and black (NW, NE; SW SE)
|
||||
public static readonly byte[][] pawnAttackDirections = {
|
||||
new byte[] { 4, 6 },
|
||||
new byte[] { 7, 5 }
|
||||
};
|
||||
|
||||
public static readonly int[][] pawnAttacksWhite;
|
||||
public static readonly int[][] pawnAttacksBlack;
|
||||
public static readonly int[] directionLookup;
|
||||
|
||||
public static readonly ulong[] kingAttackBitboards;
|
||||
public static readonly ulong[] knightAttackBitboards;
|
||||
public static readonly ulong[][] pawnAttackBitboards;
|
||||
|
||||
public static readonly ulong[] rookMoves;
|
||||
public static readonly ulong[] bishopMoves;
|
||||
public static readonly ulong[] queenMoves;
|
||||
|
||||
// Aka manhattan distance (answers how many moves for a rook to get from square a to square b)
|
||||
public static int[,] OrthogonalDistance;
|
||||
// Aka chebyshev distance (answers how many moves for a king to get from square a to square b)
|
||||
public static int[,] kingDistance;
|
||||
public static int[] CentreManhattanDistance;
|
||||
|
||||
public static int NumRookMovesToReachSquare(int startSquare, int targetSquare)
|
||||
{
|
||||
return OrthogonalDistance[startSquare, targetSquare];
|
||||
}
|
||||
|
||||
public static int NumKingMovesToReachSquare(int startSquare, int targetSquare)
|
||||
{
|
||||
return kingDistance[startSquare, targetSquare];
|
||||
}
|
||||
|
||||
// Initialize lookup data
|
||||
static PrecomputedMoveData()
|
||||
{
|
||||
pawnAttacksWhite = new int[64][];
|
||||
pawnAttacksBlack = new int[64][];
|
||||
numSquaresToEdge = new int[8][];
|
||||
knightMoves = new byte[64][];
|
||||
kingMoves = new byte[64][];
|
||||
numSquaresToEdge = new int[64][];
|
||||
|
||||
rookMoves = new ulong[64];
|
||||
bishopMoves = new ulong[64];
|
||||
queenMoves = new ulong[64];
|
||||
|
||||
// Calculate knight jumps and available squares for each square on the board.
|
||||
// See comments by variable definitions for more info.
|
||||
int[] allKnightJumps = { 15, 17, -17, -15, 10, -6, 6, -10 };
|
||||
knightAttackBitboards = new ulong[64];
|
||||
kingAttackBitboards = new ulong[64];
|
||||
pawnAttackBitboards = new ulong[64][];
|
||||
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
|
||||
int y = squareIndex / 8;
|
||||
int x = squareIndex - y * 8;
|
||||
|
||||
int north = 7 - y;
|
||||
int south = y;
|
||||
int west = x;
|
||||
int east = 7 - x;
|
||||
numSquaresToEdge[squareIndex] = new int[8];
|
||||
numSquaresToEdge[squareIndex][0] = north;
|
||||
numSquaresToEdge[squareIndex][1] = south;
|
||||
numSquaresToEdge[squareIndex][2] = west;
|
||||
numSquaresToEdge[squareIndex][3] = east;
|
||||
numSquaresToEdge[squareIndex][4] = System.Math.Min(north, west);
|
||||
numSquaresToEdge[squareIndex][5] = System.Math.Min(south, east);
|
||||
numSquaresToEdge[squareIndex][6] = System.Math.Min(north, east);
|
||||
numSquaresToEdge[squareIndex][7] = System.Math.Min(south, west);
|
||||
|
||||
// Calculate all squares knight can jump to from current square
|
||||
var legalKnightJumps = new List<byte>();
|
||||
ulong knightBitboard = 0;
|
||||
foreach (int knightJumpDelta in allKnightJumps)
|
||||
{
|
||||
int knightJumpSquare = squareIndex + knightJumpDelta;
|
||||
if (knightJumpSquare >= 0 && knightJumpSquare < 64)
|
||||
{
|
||||
int knightSquareY = knightJumpSquare / 8;
|
||||
int knightSquareX = knightJumpSquare - knightSquareY * 8;
|
||||
// Ensure knight has moved max of 2 squares on x/y axis (to reject indices that have wrapped around side of board)
|
||||
int maxCoordMoveDst = System.Math.Max(System.Math.Abs(x - knightSquareX), System.Math.Abs(y - knightSquareY));
|
||||
if (maxCoordMoveDst == 2)
|
||||
{
|
||||
legalKnightJumps.Add((byte)knightJumpSquare);
|
||||
knightBitboard |= 1ul << knightJumpSquare;
|
||||
}
|
||||
}
|
||||
}
|
||||
knightMoves[squareIndex] = legalKnightJumps.ToArray();
|
||||
knightAttackBitboards[squareIndex] = knightBitboard;
|
||||
|
||||
// Calculate all squares king can move to from current square (not including castling)
|
||||
var legalKingMoves = new List<byte>();
|
||||
foreach (int kingMoveDelta in directionOffsets)
|
||||
{
|
||||
int kingMoveSquare = squareIndex + kingMoveDelta;
|
||||
if (kingMoveSquare >= 0 && kingMoveSquare < 64)
|
||||
{
|
||||
int kingSquareY = kingMoveSquare / 8;
|
||||
int kingSquareX = kingMoveSquare - kingSquareY * 8;
|
||||
// Ensure king has moved max of 1 square on x/y axis (to reject indices that have wrapped around side of board)
|
||||
int maxCoordMoveDst = System.Math.Max(System.Math.Abs(x - kingSquareX), System.Math.Abs(y - kingSquareY));
|
||||
if (maxCoordMoveDst == 1)
|
||||
{
|
||||
legalKingMoves.Add((byte)kingMoveSquare);
|
||||
kingAttackBitboards[squareIndex] |= 1ul << kingMoveSquare;
|
||||
}
|
||||
}
|
||||
}
|
||||
kingMoves[squareIndex] = legalKingMoves.ToArray();
|
||||
|
||||
// Calculate legal pawn captures for white and black
|
||||
List<int> pawnCapturesWhite = new List<int>();
|
||||
List<int> pawnCapturesBlack = new List<int>();
|
||||
pawnAttackBitboards[squareIndex] = new ulong[2];
|
||||
if (x > 0)
|
||||
{
|
||||
if (y < 7)
|
||||
{
|
||||
pawnCapturesWhite.Add(squareIndex + 7);
|
||||
pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 7);
|
||||
}
|
||||
if (y > 0)
|
||||
{
|
||||
pawnCapturesBlack.Add(squareIndex - 9);
|
||||
pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 9);
|
||||
}
|
||||
}
|
||||
if (x < 7)
|
||||
{
|
||||
if (y < 7)
|
||||
{
|
||||
pawnCapturesWhite.Add(squareIndex + 9);
|
||||
pawnAttackBitboards[squareIndex][Board.WhiteIndex] |= 1ul << (squareIndex + 9);
|
||||
}
|
||||
if (y > 0)
|
||||
{
|
||||
pawnCapturesBlack.Add(squareIndex - 7);
|
||||
pawnAttackBitboards[squareIndex][Board.BlackIndex] |= 1ul << (squareIndex - 7);
|
||||
}
|
||||
}
|
||||
pawnAttacksWhite[squareIndex] = pawnCapturesWhite.ToArray();
|
||||
pawnAttacksBlack[squareIndex] = pawnCapturesBlack.ToArray();
|
||||
|
||||
// Rook moves
|
||||
for (int directionIndex = 0; directionIndex < 4; directionIndex++)
|
||||
{
|
||||
int currentDirOffset = directionOffsets[directionIndex];
|
||||
for (int n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++)
|
||||
{
|
||||
int targetSquare = squareIndex + currentDirOffset * (n + 1);
|
||||
rookMoves[squareIndex] |= 1ul << targetSquare;
|
||||
}
|
||||
}
|
||||
// Bishop moves
|
||||
for (int directionIndex = 4; directionIndex < 8; directionIndex++)
|
||||
{
|
||||
int currentDirOffset = directionOffsets[directionIndex];
|
||||
for (int n = 0; n < numSquaresToEdge[squareIndex][directionIndex]; n++)
|
||||
{
|
||||
int targetSquare = squareIndex + currentDirOffset * (n + 1);
|
||||
bishopMoves[squareIndex] |= 1ul << targetSquare;
|
||||
}
|
||||
}
|
||||
queenMoves[squareIndex] = rookMoves[squareIndex] | bishopMoves[squareIndex];
|
||||
}
|
||||
|
||||
directionLookup = new int[127];
|
||||
for (int i = 0; i < 127; i++)
|
||||
{
|
||||
int offset = i - 63;
|
||||
int absOffset = System.Math.Abs(offset);
|
||||
int absDir = 1;
|
||||
if (absOffset % 9 == 0)
|
||||
{
|
||||
absDir = 9;
|
||||
}
|
||||
else if (absOffset % 8 == 0)
|
||||
{
|
||||
absDir = 8;
|
||||
}
|
||||
else if (absOffset % 7 == 0)
|
||||
{
|
||||
absDir = 7;
|
||||
}
|
||||
|
||||
directionLookup[i] = absDir * System.Math.Sign(offset);
|
||||
}
|
||||
|
||||
// Distance lookup
|
||||
OrthogonalDistance = new int[64, 64];
|
||||
kingDistance = new int[64, 64];
|
||||
CentreManhattanDistance = new int[64];
|
||||
for (int squareA = 0; squareA < 64; squareA++)
|
||||
{
|
||||
Coord coordA = BoardHelper.CoordFromIndex(squareA);
|
||||
int fileDstFromCentre = Max(3 - coordA.fileIndex, coordA.fileIndex - 4);
|
||||
int rankDstFromCentre = Max(3 - coordA.rankIndex, coordA.rankIndex - 4);
|
||||
CentreManhattanDistance[squareA] = fileDstFromCentre + rankDstFromCentre;
|
||||
|
||||
for (int squareB = 0; squareB < 64; squareB++)
|
||||
{
|
||||
|
||||
Coord coordB = BoardHelper.CoordFromIndex(squareB);
|
||||
int rankDistance = Abs(coordA.rankIndex - coordB.rankIndex);
|
||||
int fileDistance = Abs(coordA.fileIndex - coordB.fileIndex);
|
||||
OrthogonalDistance[squareA, squareB] = fileDistance + rankDistance;
|
||||
kingDistance[squareA, squareB] = Max(fileDistance, rankDistance);
|
||||
}
|
||||
}
|
||||
|
||||
alignMask = new ulong[64, 64];
|
||||
for (int squareA = 0; squareA < 64; squareA++)
|
||||
{
|
||||
for (int squareB = 0; squareB < 64; squareB++)
|
||||
{
|
||||
Coord cA = BoardHelper.CoordFromIndex(squareA);
|
||||
Coord cB = BoardHelper.CoordFromIndex(squareB);
|
||||
Coord delta = cB - cA;
|
||||
Coord dir = new Coord(System.Math.Sign(delta.fileIndex), System.Math.Sign(delta.rankIndex));
|
||||
//Coord dirOffset = dirOffsets2D[dirIndex];
|
||||
|
||||
for (int i = -8; i < 8; i++)
|
||||
{
|
||||
Coord coord = BoardHelper.CoordFromIndex(squareA) + dir * i;
|
||||
if (coord.IsValidSquare())
|
||||
{
|
||||
alignMask[squareA, squareB] |= 1ul << (BoardHelper.IndexFromCoord(coord));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dirRayMask = new ulong[8, 64];
|
||||
for (int dirIndex = 0; dirIndex < dirOffsets2D.Length; dirIndex++)
|
||||
{
|
||||
for (int squareIndex = 0; squareIndex < 64; squareIndex++)
|
||||
{
|
||||
Coord square = BoardHelper.CoordFromIndex(squareIndex);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
Coord coord = square + dirOffsets2D[dirIndex] * i;
|
||||
if (coord.IsValidSquare())
|
||||
{
|
||||
dirRayMask[dirIndex, squareIndex] |= 1ul << (BoardHelper.IndexFromCoord(coord));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
Chess-Challenge/src/Framework/Chess/Result/Arbiter.cs
Normal file
106
Chess-Challenge/src/Framework/Chess/Result/Arbiter.cs
Normal file
|
@ -0,0 +1,106 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
using System.Linq;
|
||||
|
||||
public static class Arbiter
|
||||
{
|
||||
public static bool IsDrawResult(GameResult result)
|
||||
{
|
||||
return result is GameResult.DrawByArbiter or GameResult.FiftyMoveRule or
|
||||
GameResult.Repetition or GameResult.Stalemate or GameResult.InsufficientMaterial;
|
||||
}
|
||||
|
||||
public static bool IsWinResult(GameResult result)
|
||||
{
|
||||
return IsWhiteWinsResult(result) || IsBlackWinsResult(result);
|
||||
}
|
||||
|
||||
public static bool IsWhiteWinsResult(GameResult result)
|
||||
{
|
||||
return result is GameResult.BlackIsMated or GameResult.BlackTimeout or GameResult.BlackIllegalMove;
|
||||
}
|
||||
|
||||
public static bool IsBlackWinsResult(GameResult result)
|
||||
{
|
||||
return result is GameResult.WhiteIsMated or GameResult.WhiteTimeout or GameResult.WhiteIllegalMove;
|
||||
}
|
||||
|
||||
|
||||
public static GameResult GetGameState(Board board)
|
||||
{
|
||||
MoveGenerator moveGenerator = new MoveGenerator();
|
||||
var moves = moveGenerator.GenerateMoves(board);
|
||||
|
||||
// Look for mate/stalemate
|
||||
if (moves.Length == 0)
|
||||
{
|
||||
if (moveGenerator.InCheck())
|
||||
{
|
||||
return (board.IsWhiteToMove) ? GameResult.WhiteIsMated : GameResult.BlackIsMated;
|
||||
}
|
||||
return GameResult.Stalemate;
|
||||
}
|
||||
|
||||
// Fifty move rule
|
||||
if (board.currentGameState.fiftyMoveCounter >= 100)
|
||||
{
|
||||
return GameResult.FiftyMoveRule;
|
||||
}
|
||||
|
||||
// Threefold repetition
|
||||
int repCount = board.RepetitionPositionHistory.Count((x => x == board.currentGameState.zobristKey));
|
||||
if (repCount == 3)
|
||||
{
|
||||
return GameResult.Repetition;
|
||||
}
|
||||
|
||||
// Look for insufficient material
|
||||
if (InsufficentMaterial(board))
|
||||
{
|
||||
return GameResult.InsufficientMaterial;
|
||||
}
|
||||
return GameResult.InProgress;
|
||||
}
|
||||
|
||||
// Test for insufficient material (Note: not all cases are implemented)
|
||||
public static bool InsufficentMaterial(Board board)
|
||||
{
|
||||
// Can't have insufficient material with pawns on the board
|
||||
if (board.pawns[Board.WhiteIndex].Count > 0 || board.pawns[Board.BlackIndex].Count > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Can't have insufficient material with queens/rooks on the board
|
||||
if (board.FriendlyOrthogonalSliders != 0 || board.EnemyOrthogonalSliders != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If no pawns, queens, or rooks on the board, then consider knight and bishop cases
|
||||
int numWhiteBishops = board.bishops[Board.WhiteIndex].Count;
|
||||
int numBlackBishops = board.bishops[Board.BlackIndex].Count;
|
||||
int numWhiteKnights = board.knights[Board.WhiteIndex].Count;
|
||||
int numBlackKnights = board.knights[Board.BlackIndex].Count;
|
||||
int numWhiteMinors = numWhiteBishops + numWhiteKnights;
|
||||
int numBlackMinors = numBlackBishops + numBlackKnights;
|
||||
|
||||
// King v King
|
||||
if (numWhiteMinors == 0 && numBlackMinors == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Single minor piece vs lone king
|
||||
if ((numWhiteMinors == 1 && numBlackMinors == 0) || (numBlackMinors == 1 && numWhiteMinors == 0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
19
Chess-Challenge/src/Framework/Chess/Result/GameResult.cs
Normal file
19
Chess-Challenge/src/Framework/Chess/Result/GameResult.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
namespace ChessChallenge.Chess
|
||||
{
|
||||
public enum GameResult
|
||||
{
|
||||
NotStarted,
|
||||
InProgress,
|
||||
WhiteIsMated,
|
||||
BlackIsMated,
|
||||
Stalemate,
|
||||
Repetition,
|
||||
FiftyMoveRule,
|
||||
InsufficientMaterial,
|
||||
DrawByArbiter,
|
||||
WhiteTimeout,
|
||||
BlackTimeout,
|
||||
WhiteIllegalMove,
|
||||
BlackIllegalMove
|
||||
}
|
||||
}
|
10
Chess-Challenge/src/My Bot/MyBot.cs
Normal file
10
Chess-Challenge/src/My Bot/MyBot.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using ChessChallenge.API;
|
||||
|
||||
public class MyBot : IChessBot
|
||||
{
|
||||
public Move Think(Board board, Timer timer)
|
||||
{
|
||||
Move[] moves = board.GetLegalMoves();
|
||||
return moves[0];
|
||||
}
|
||||
}
|
51
README.md
Normal file
51
README.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
# Chess Coding Challenge (C#)
|
||||
Welcome to the [chess coding challenge](https://youtu.be/iScy18pVR58)! This is a friendly competition in which your goal is to create a small chess bot (in C#) using the framework provided in this repository.
|
||||
Once submissions close, these bots will battle it out to discover which bot is best!
|
||||
|
||||
I will then create a video exploring the implementations of the best and most unique/interesting bots.
|
||||
I also plan to make a small game that features these most interesting/challenging entries, so that everyone can try playing against them.
|
||||
|
||||
## Submission Due Date
|
||||
October 1st 2023
|
||||
You can submit your entry [here](https://forms.gle/6jjj8jxNQ5Ln53ie6).
|
||||
|
||||
## How to Participate
|
||||
* Install an an IDE such as [Visual Studio](https://visualstudio.microsoft.com/downloads/).
|
||||
* Install [.NET 6.0](https://dotnet.microsoft.com/en-us/download)
|
||||
* Download this repository and open the Chess-Challenge project in your IDE.
|
||||
* Try building and running the project.
|
||||
* If a window with a chess board appears — great!
|
||||
* If it doesn't work, please take a look at the [issues page](https://github.com/SebLague/Chess-Challenge/issues) to see if anyone is having a similar issue. If not, post about it there with any details such as error messages, operating system etc.
|
||||
* Open the MyBot.cs file _(located in src/MyBot)_ and write some code!
|
||||
* You might want to take a look at the [Documentation](https://seblague.github.io/chess-coding-challenge/documentation/) first, and the Rules too!
|
||||
* Build and run the program again to test your changes.
|
||||
* For testing, you have three options in the program:
|
||||
* You can play against the bot yourself (Human vs Bot)
|
||||
* The bot can play a match against itself (MyBot vs MyBot)
|
||||
* The bot can play a match against a simple example bot (MyBot vs EvilBot).<br>You could also replace the EvilBot code with your own code, to test two different versions of your bot against one another.
|
||||
* Once you're happy with your chess bot, head over to the [Submission Page](https://forms.gle/6jjj8jxNQ5Ln53ie6) to enter it into the competition.
|
||||
* You will be able to edit your entry up until the competition closes.
|
||||
|
||||
## Rules
|
||||
* You may participate alone, or in a group of any size.
|
||||
* Only the following namespaces are allowed:
|
||||
* ChessChallenge.API
|
||||
* System
|
||||
* System.Linq
|
||||
* System.Numerics
|
||||
* System.Collections.Generic
|
||||
* As implied by the allowed namespaces, you may not read data from a file or access the internet, nor may you create any new threads or tasks to run code in parallel/in the background.
|
||||
* You may not use the unsafe keyword.
|
||||
* All of your code/data must be contained within the _MyBot.cs_ file.
|
||||
* Note: you may create additional scripts for testing/training your bot, but only the _MyBot.cs_ file will be submitted, so it must be able to run without them.
|
||||
* You may not rename the _MyBot_ struct contained in the _MyBot.cs_ file.
|
||||
* The code in MyBot.cs may not exceed the _bot brain capacity_ of 1024 (see below).
|
||||
|
||||
## Bot Brain Capacity
|
||||
There is a size limit on the code you create called the _bot brain capacity_. This is measured in ‘tokens’ and may not exceed 1024. The number of tokens you have used so far is displayed on the bottom of the screen when running the program.
|
||||
|
||||
All names (variables, functions, etc.) are counted as a single token, regardless of length. This means that both lines of code: `bool a = true;` and `bool myObscenelyLongVariableName = true;` count the same. Additionally, the following things do not count towards the limit: white space, new lines, comments, access modifiers, commas, and semicolons.
|
||||
|
||||
## FAQ
|
||||
Nothing yet
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue