Chess Challenge Project

This commit is contained in:
Sebastian Lague 2023-07-21 14:16:37 +02:00
parent 29e522df30
commit a268bc3cad
60 changed files with 7316 additions and 0 deletions

6
.editorconfig Normal file
View 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
View 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

View 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>

View 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

Binary file not shown.

View 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View 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);
}
}
}

View 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;
}
}

View file

@ -0,0 +1,8 @@

namespace ChessChallenge.API
{
public interface IChessBot
{
Move Think(Board board, Timer timer);
}
}

View 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;
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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
}
}

View 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();
}
}

View 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();
}
}
}

View 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;
}
}
}

View file

@ -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();
}
}
}

View 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");
}
}
}

View 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
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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;
}
}
}
}
}

View 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();
}
}
}

View file

@ -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;
}
}
}

View 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);
}
}
}
}

View 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]);
}
}
}

View file

@ -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;
}
}
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}
}

View file

@ -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);
}
}
}

View 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;
}
}
}
}
}

View 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;
}
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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;
}
}

View 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;
}
}

View 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];
}
}

View 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);
}
}
}

View 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;
}
}

View 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];
}
}
}
}

View 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;
}
}
}
}
}

View 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();
}
}
}

View 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();
}
}
}

View file

@ -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()
{
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}
}

View file

@ -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;
}
}
}

View file

@ -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 };
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}
}
}
}
}

View 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;
}
}
}

View file

@ -0,0 +1,19 @@
namespace ChessChallenge.Chess
{
public enum GameResult
{
NotStarted,
InProgress,
WhiteIsMated,
BlackIsMated,
Stalemate,
Repetition,
FiftyMoveRule,
InsufficientMaterial,
DrawByArbiter,
WhiteTimeout,
BlackTimeout,
WhiteIllegalMove,
BlackIllegalMove
}
}

View 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
View 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