+ All Categories
Home > Documents > Ch1 Combine

Ch1 Combine

Date post: 26-Oct-2014
Category:
Upload: shubhamgarg2222
View: 182 times
Download: 13 times
Share this document with a friend
Popular Tags:
88
Winglet forum Winboard forum Talkchess forum OpenChess forum References A chess program that beats most human players 01 Introduction 02 Anatomy of a chess program 03 Choices - What if your system is different? 04 Preparations - Setting up Microsoft Visual C++ 2010 Express - Setting up Winboard 05 First steps with C++ 001 your new friend - My C++ header philosophy - Targeting for 64-bit platforms 06 Reading user commands 002 wingletx.cpp 003 defines.h 004 protos.h 005 command.cpp 006 globals.h 007 extglobals.h 07 Internal representation of the chess board - bitboards - Bitwise setting and testing 008 board.h 08 Displaying the position 009 globals.h 010 extglobals.h 011 defines.h 012 bitops.cpp 013 protos.h 014 wingletx.cpp 015 command.cpp 016 data.cpp 017 board.cpp 09 Reading a FEN string 018 readfen.cpp 019 protos.h 020 command.cpp 10 Setting up the board manually 021 setup.cpp 022 command.cpp 023 protos.h 11 The move generator - The move structure 024 move.h 025 move.cpp - Move generation for Knights - Move generation for sliding pieces - 6 bits only - Calculation of a 6-bit occupancy state index - Rank occupancy - File occupancy - magic multipliers Writing a chess program in 99 steps A step-by-step guide for writing a bitboard chess engine, winglet copyright full source code 01 Introduction This is a systematic guide to write a chess engine (connected to the Winboard chess interface). The targeted audience are chess programming enthusiasts. You will only need a PC with Windows installed, because everything else is free. I do assume however that you know how to navigate in Windows, install programs, how to play chess, and how to program (but not necessarily in C++). The program that will be developed is called winglet (because it is loosely derived from wing, my private engine). Winglet is free and open source. You can redistribute it and/or modify it under the terms of the GNU General Public License. All code is going to be built from scratch. You will see code snippets that list complete (but simple) functions. Followed later by code snippets to improve, or enhance, existing functions. In the left side of this page you will see an overview of all code snippets. The functionality of the code will grow 'organically', allowing you to follow my thinking, coding and testing (and hopefully without the bugs). In 99 steps? well, it's going to be slightly more than that, I just thought 99 would be a nice goal. The reason why this is a step-by-step guide is to allow you to go through, and think through, all phases and concepts involved when writing a chess engine from scratch, in a more or less natural and logical order. You will be able to build, run, test and experiment with the program as its functionality increases. Alternatively, you may just want to copy & paste the snippets, ending up with a complete chess program, and skip reading the explanations. That's fine, but you will not learn anything from that. At the other end of the creativity scale, you can treat this as a rough guideline, program your own engine, use some of the idea's, copy code parts, or use nothing at all because you can do it better! Another reason is that, for beginning chess programmers, it's not easy to decide where to start. There is a lot of information on the internet on specific chess programming topics, but not a lot of guidance on where and how to start, what comes next, when to start worrying about the interface (at the end!). As Robert Hyatt has put it (and I fully agree): the evaluation and search functions are the heart & soul of a chess engine, it is mainly from these two components that an engine gets its 'personality', and mainly these two components make one engine stronger than another. Some components of the engine may not be very engine specific, or they may not affect playing strength at all. For instance, you will find fast and similarly looking bitboard move generators in many programs. Winglet will provide you with a basis for experimenting and improving evaluation, search and move sorting in particular. They are the three most underdeveloped functions in winglet, and this is done intentionally, to invite you to improve and develop an engine with its own personality / strength, without having to go through the hassle of writing the backbone from scratch. Once you start adding your own thoughts into the evaluation and search code, it will become your engine, with its own 'personality', and then you should give it it's own name too! Some of you will find my programming style odd, or just plain wrong. I am OK with that. I like to keep things simple, understandable, readable, straightforward. So that ten years from now you will still be able to read and understand the code. If you are looking for compact or advanced C++ programming techniques, you will not find them here (unless required for speed, and then I would just copy whatever is available from the internet, after testing). This engine is going to be sufficiently fast, but relatively simple, providing you a backbone that you can expand upon, change and tweak. The implementation of bitboards should be comprehensible to all. This website will get you to the point that you have written a basic bitboard chess engine, playing reasonable chess, as a console program, or using the Winboard interface. If you want to discuss this project, found a bug, or have an idea for improvement, you can send me a mail, or post in one of the chess programming forums listed above. winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm 1 of 5 15-07-2012 01:27
Transcript
Page 1: Ch1 Combine

Winglet forumWinboard forumTalkchess forum

OpenChess forumReferences

A chess program that beats

most human players

01 Introduction

02 Anatomy of a chess

program

03 Choices

- What if your system is

different?

04 Preparations

- Setting up Microsoft

Visual C++ 2010 Express

- Setting up Winboard

05 First steps with C++

001 your new friend

- My C++ header

philosophy

- Targeting for 64-bit

platforms

06 Reading user commands

002 wingletx.cpp

003 defines.h

004 protos.h

005 command.cpp

006 globals.h

007 extglobals.h

07 Internal representation

of the chess board - bitboards

- Bitwise setting and

testing

008 board.h

08 Displaying the position

009 globals.h

010 extglobals.h

011 defines.h

012 bitops.cpp

013 protos.h

014 wingletx.cpp

015 command.cpp

016 data.cpp

017 board.cpp

09 Reading a FEN string

018 readfen.cpp

019 protos.h

020 command.cpp

10 Setting up the board

manually

021 setup.cpp

022 command.cpp

023 protos.h

11 The move generator

- The move structure

024 move.h

025 move.cpp

- Move generation for

Knights

- Move generation for

sliding pieces

- 6 bits only

- Calculation of a 6-bitoccupancy state index

- Rank occupancy

- File occupancy - magicmultipliers

Writing a chess program in 99 steps

A step-by-step guide for writing a bitboard chess engine, wingletcopyrightfull source code

01 Introduction

This is a systematic guide to write a chess engine (connected to the Winboard chess interface). Thetargeted audience are chess programming enthusiasts.

You will only need a PC with Windows installed, because everything else is free. I do assume however thatyou know how to navigate in Windows, install programs, how to play chess, and how to program (but notnecessarily in C++).

The program that will be developed is called winglet (because it is loosely derived from wing, my privateengine). Winglet is free and open source. You can redistribute it and/or modify it under the terms of the GNUGeneral Public License.

All code is going to be built from scratch. You will see code snippets that list complete (but simple) functions.Followed later by code snippets to improve, or enhance, existing functions. In the left side of this page youwill see an overview of all code snippets. The functionality of the code will grow 'organically', allowing you tofollow my thinking, coding and testing (and hopefully without the bugs). In 99 steps? well, it's going to beslightly more than that, I just thought 99 would be a nice goal.

The reason why this is a step-by-step guide is to allow you to go through, and think through, all phases andconcepts involved when writing a chess engine from scratch, in a more or less natural and logical order. Youwill be able to build, run, test and experiment with the program as its functionality increases. Alternatively,you may just want to copy & paste the snippets, ending up with a complete chess program, and skip readingthe explanations. That's fine, but you will not learn anything from that. At the other end of the creativityscale, you can treat this as a rough guideline, program your own engine, use some of the idea's, copy codeparts, or use nothing at all because you can do it better!

Another reason is that, for beginning chess programmers, it's not easy to decide where to start. There is alot of information on the internet on specific chess programming topics, but not a lot of guidance on whereand how to start, what comes next, when to start worrying about the interface (at the end!).

As Robert Hyatt has put it (and I fully agree): the evaluation and search functions are the heart & soul of achess engine, it is mainly from these two components that an engine gets its 'personality', and mainly thesetwo components make one engine stronger than another. Some components of the engine may not be veryengine specific, or they may not affect playing strength at all. For instance, you will find fast and similarlylooking bitboard move generators in many programs. Winglet will provide you with a basis forexperimenting and improving evaluation, search and move sorting in particular. They are the three mostunderdeveloped functions in winglet, and this is done intentionally, to invite you to improve and develop anengine with its own personality / strength, without having to go through the hassle of writing the backbonefrom scratch. Once you start adding your own thoughts into the evaluation and search code, it will becomeyour engine, with its own 'personality', and then you should give it it's own name too!

Some of you will find my programming style odd, or just plain wrong. I am OK with that. I like to keep thingssimple, understandable, readable, straightforward. So that ten years from now you will still be able to readand understand the code. If you are looking for compact or advanced C++ programming techniques, you willnot find them here (unless required for speed, and then I would just copy whatever is available from theinternet, after testing). This engine is going to be sufficiently fast, but relatively simple, providing you abackbone that you can expand upon, change and tweak. The implementation of bitboards should becomprehensible to all.

This website will get you to the point that you have written a basic bitboard chess engine, playingreasonable chess, as a console program, or using the Winboard interface.If you want to discuss this project, found a bug, or have an idea for improvement, you can send me a mail,or post in one of the chess programming forums listed above.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm

1 of 5 15-07-2012 01:27

Page 2: Ch1 Combine

- A8H1 diagonal occupancy -magic multipliers

- A1H8 diagonal occupancy -magic multipliers

- Generalized sliding attack

026 board.h

027 data.cpp

028 movegen.cpp

- isAttacked

029 command.cpp

- Display the list of

generated moves

030 displaymove.cpp

031 protos.h

032 globals.h

033 extglobals.h

034 defines.h

12 Making the moves

- How moves are stored

035 gameline.h

036 board.h

- Adding timers

037 timer.h

038 timer.cpp

- Perft - measuring speed andverify correctness

039 perft.cpp

040 makemove.cpp

041 command.cpp

042 defines.h

043 protos.h

13 The evaluation function

044 eval.cpp

045 board.h

046 board.cpp

047 defines.h

048 wingletx.cpp

049 perft.cpp

050 command.cpp

051 data.cpp

052 globals.h

053 extglobals.h

14 Search

- Minimax

- Alpha-beta

- Principal variation search

- Collecting the principalvariation

054 globals.h

055 extglobals.h

056 board.h

057 search.cpp

058 command.h

15 Mate and draw detection

- Move disambiguation

059 globals.h

060 extglobals.h

061 protos.h

062 board.h

063 command.cpp

064 data.cpp

065 displaymove.h

066 search.cpp

16 Repetition detection -

Zobrist keys

- Zobrist keys

067 hash.h

068 hash.cpp

069 globals.h

070 extglobals.h

071 data.cpp

072 board.h

I learned chess programming from reading internet articles and open source code. Mostly internet articlesfrom Robert Hyatt (author of Crafty) and Bruce Moreland, and of course studying the code of other opensource programs. Others invented everything I write in this guide, not me, and in some cases I even do notknow who to give credit. Thanks to others, who made their thoughts, research and inventions availablethrough publications, discussion forums, websites, and open source code, I was able to start programmingmy private chess engine, wing. Check out the list of references if you want to read the same stuff I did.

√ you will see this text box at the bottom of some pages.√ it means that, at this point, the source code is ready to compile, link & run for you to try out the newfunctionality

02 Anatomy of a chess program

Chess programs determine the best move by traversing down a tree of possible moves and opponentmoves, up to a certain depth, and then evaluate the resulting positions at the tree tips (leafs, at the end ofthe search branches) to see which move will lead to the best result, assuming that the opponent will alsoplay the best moves. Note that this tree of variations is usually shown 'upside-down', i.e. with the root at thetop and branches at the bottom:

The chess search tree is very wide (on average there are 35 legal moves for a chess position), and verydeep (the average chess game is 40 moves, or 80 half moves or plies). Not all possible variations can be

evaluated (note that 3580 is approximately 10123 ; by comparison, the estimated number of atoms in the

universe is 1080). The search function is implemented as a recursive function (a function that makes a callto itself); at every chess position (node), the move generator returns a list of moves to make (and unmake,when the search traverses back up to the root of the tree to pass the result of the search to the callingfunction).

The evaluation of chess positions is done by considering many static aspects, starting of course with thematerial balance, and considering pawn structure, king safety, piece mobility, and many other aspects of theposition. The score is usually expressed in centipawns (a pawn equals 100).

Speed is important, and there are many techniques to improve the search speed of a chess program(including using bitboards and hash tables). Some chess engines will display their search speed in knods/s(1000 nodes per second).

Another important area of optimization is the search algorithm. The main objective here is to minimize thenumber of moves to search, by skipping moves that are proven to be, or seem to be, 'unattractive'.Techniques to improve and speed up the search include the alpha-beta algorithm, pvs (principal variationsearch), move ordering, null-move, search extensions, and aspiration windows. There is of course anintrinsic danger in omitting moves that seem unattractive (at small search depths), because some of themcould lead to a decisive advantage as the game progresses (at greater search depths). Alpha-beta, pvs

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm

2 of 5 15-07-2012 01:27

Page 3: Ch1 Combine

073 board.cpp

074 makemove.cpp

075 command.cpp

076 search.cpp

17 Iterative deepening and

move ordering

077 board.h

078 sortmoves.cpp

079 search.cpp

18 Quiescence search and

SEE

- SEE

080 board.h

081 search.cpp

082 qsearch.cpp

083 see.cpp

084 movegen.cpp

085 globals.h

086 extglobals.h

087 data.cpp

088 command.cpp

089 defines.h

090 wingletx.cpp

19 Null move pruning

091 globals.h

092 extglobals.h

093 board.h

094 search.cpp

20 Time control and running

test suites

- Running test suites

095 protos.h

096 board.h

097 globals.h

098 extglobals.h

099 command.cpp

100 peek.cpp

101 data.cpp

102 test.cpp

103 search.cpp

104 qsearch.cpp

21 Connecting to Winboard

- Specifying and reading aninitialization file as commandline option

- Think, analyze, ponder

- Peeking for input duringsearch

- Time control in matches

- Setting up Winboard

105 globals.h

106 extglobals.h

107 winglet.cpp

108 defines.h

109 data.cpp

110 commands.cpp

111 search.cpp

112 peek.cpp

and move ordering increase the search speed without affecting accuracy. Other techniques can severelydeteriorate accuracy, if not implemented correctly, or when using too aggressive pruning parameters.

By far the best investment you can make to produce a strong program is to improve the evaluation function. Speed is still important in the evaluation function. However, if you can improve the evaluation function, atthe cost of adding more code lines and hence making your program somewhat slower, go for it! Same isvalid for the search algorithm, adding more code lines will make the code slower, but can make the searchfaster (by visiting less nodes).

As a rule of thumb, the strongest chess programs have fast move generators, use data structures that allowthem to implement fast makemove and unmakemove algorithms, they search a relatively small number ofmoves only (using aggressive forward pruning rules, allowing them to search deeper), and have extensiveevaluation functions that capture a significant amount of chess knowledge.

03 Choices

Winglet is written on a platform running Windows 7 Home Premium, SP1 (64-bit), and coding is done withMicrosoft Visual C++ 2010 Express, version 4.

What if your system is different?

If your operating system or software development environment (SDE) is different, that does notautomatically mean that you cannot follow this guide. It only means that some steps or implementationsmight be somewhat different, depending on how your particular system is set up.

I suppose you can also follow this guide in case you want to write a UCI engine under Linux (skipping theparts for Winboard and coding the UCI parts yourself). Some coding might be machine and/or OSdependent, I will gladly leave that as a complimentary challenge for you to figure out. I choose to focus onthe chess programming part.

04 Preparations

Setting up Microsoft Visual C++ 2010 Express

Download and install Microsoft Visual C++ 2010 Express (it is free, but you will need to register touse it). It can be found here, or use a search engine to locate it. There might be additional Service Packsavailable for installation, or Windows updates related to Visual Studio. Some are required for MicrosoftVisual C++ 2010 Express to function properly.

After installation of VC++, check your Windows Updater – it will tell you which other updates and/or servicepacks you might need.

Setting up Winboard

Note that you do not need Winboard right away, winglet will initially be developed as a console program,with user input from the keyboard. Winboard support and details on setting up Winboard is discussed insection 21.

You can download and install the latest Winboard version, from here (version 4.5.2) or use a search engineto locate it.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm

3 of 5 15-07-2012 01:27

Page 4: Ch1 Combine

05 First steps with Visual Studio C++

Start Visual studio C++, select New Project..., select Win32, select Win32 ConsoleApplication…,enter wingletx as the project name, choose a suitable location, and click OK:

Now, navigate through the Win32 Application Wizard, select Win32 console application, makesure to uncheck precompiled header. You will end up with an empty template for a C++ program.

Note that, in Visual studio C++, you have complete control over the layout of the working space and positionof windows. You might want to spend some time to get familiar with the interface, my working space usuallylooks like this:

Check if you can copy this into your code:

step 1: your new friend

int _tmain(int argc, _TCHAR* argv[])

{

printf("hello, I am your new friend!\n");

return 0;

}

Build the executabe (hit F7). Run the executable (hit Ctrl-F5). This should open a console window and display the message:

You are ready to go if you've gotten this far. I am not going to explain a lot more than this on working withVisual C++. That is not the intention of this guide; Again, I choose to focus on the chess programming part.

My C++ header philosophy

C++ programs are usually split up into multiple smaller source code files (suffix .cpp). Splitting up chunks ofcode into separate files allows programmers to separate certain elements of a program's source code intomanageable and reusable files. C++ header files (suffix .h) normally contain declarations of structures,functions, variables, and other identifiers. Identifiers that are declared in a header file can be included inother source files.

In my programs, the order of header file inclusion is important (which is considered bad coding practice). Some structure definitions, or global variables, might be using non-standard types that were defined indefines.hSo “defines.h” needs to be included first, before any structure definition headers. I find this approachquite practical for limited sized programs that are not intended to be sharing any code with other programs.

The winglet C++ header files are set up to contain the following definitions (and you always should#include them in this particular order to prevent compilation problems):1) defines.h: contains compiler directives (#define), type definitions (typedef), and any machinedependent definitions. This header can be included by any source file that needs it.2) protos.h: contains the prototypes of all functions of the winglet program, sorted alphabetically. This

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm

4 of 5 15-07-2012 01:27

Page 5: Ch1 Combine

header can be included by any source file that needs it.3) globals.h: contains all global variable declarations. This header is included ONLY in wingletx.cpp(the file that contains main())4) extglobals.h: the same list of global variables as in globals.h, and in the same order, but nowdeclared as external variables. This header can be included by any source file that needs it, EXCEPT inwingletx.cpp.

Then there are a number of dedicated header files that are used by one, or a limited number, of sourcefiles. Usually, the prefix of the header file name will indicate its intention and to which source file that headerbelongs. Examples are structure definitions.

Targeting for 64-Bit Platforms

Visual Studio C++ Express does not come with 64-bit compilers. If you want to build your executablefor a 64-bit platform, then I suggest searching the internet for "how to configure Visual C++ Projects toTarget 64-Bit Platforms". This will lead you to Microsoft's site with more information.

You will have to download and install Microsoft Windows SDK and .NET Framework 4 (which is alsofree software), define your 64-bit platform in Visual C++, and select Windows7.1SDK as Platform Toolset.

In case Microsoft Windows SDK cannot install the X64 or IA64 compilers, then you might have touninstall Visual Studio 2010 Service Pack 1, and reinstall (not repair) Microsoft Windows SDK. Seethis link for more info on this known bug.

If you're not sure how many CPU's your system has, then you can use following program to find out (this willwork in Windows-based systems only):

#include <Windows.h>

#include <iostream>

int main(int argc, char *argv[])

{

SYSTEM_INFO sysinfo;

GetSystemInfo(&sysinfo);

std::cout << "Number of processors: " << sysinfo.dwNumberOfProcessors << std::endl;

if (sysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)

std::cout << "Architecture: INTEL - x86" << std::endl;

else if (sysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)

std::cout << "Architecture: IA64 - Intel Itanium-based" << std::endl;

else if (sysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64)

std::cout << "Architecture: AMD64 - x64 (AMD or Intel)" << std::endl;

else std::cout << "Architecture: Unknown architecture." << std::endl;

return 0;

}

This will probably get you the right number of processors, but I noticed that my machine thinks it's anArchitecture: INTEL - x86 if I compile it for 32-bit, and it thinks it's an Architecture: AMD64 - x64 (AMD or Intel)if I compile it for 64-bit (!)... inscrutable Microsoft logic.

last update: Friday 09 September 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/index.htm

5 of 5 15-07-2012 01:27

Page 6: Ch1 Combine

Writing a chess program in99 steps

Generalized sliding attack

Now we have defined how the occupancy of any rank, file or diagonal is transformed into a6-bit address range ( 0..63), and we can look up the moves in attack bitboards by this typeof construct:

RANK_MOVES = RANK_ATTACKS[from][((board.occupiedSquares & someMASK[from]) *someMAGIC[from]) >> 57] & targetBitmap;

Actually, we only need 7 macro's to define all lookup's of sliding moves; (a) is thefrom-field:

#define RANKMOVES(a) (RANK_ATTACKS[(a)][((board.occupiedSquares & RANKMASK[(a)]) >>RANKSHIFT[(a)])] & targetBitmap)#define FILEMOVES(a) (FILE_ATTACKS[(a)][((board.occupiedSquares & FILEMASK[(a)]) *FILEMAGIC[(a)]) >> 57] & targetBitmap)#define SLIDEA8H1MOVES(a) (DIAGA8H1_ATTACKS[(a)][((board.occupiedSquares &DIAGA8H1MASK[(a)]) * DIAGA8H1MAGIC[(a)]) >> 57] & targetBitmap)#define SLIDEA1H8MOVES(a) (DIAGA1H8_ATTACKS[(a)][((board.occupiedSquares &DIAGA1H8MASK[(a)]) * DIAGA1H8MAGIC[(a)]) >> 57] & targetBitmap)#define ROOKMOVES(a) (RANKMOVES(a) | FILEMOVES(a))#define BISHOPMOVES(a) (SLIDEA8H1MOVES(a) | SLIDEA1H8MOVES(a))#define QUEENMOVES(a) (BISHOPMOVES(a) | ROOKMOVES(a))

The next step is to populate the attack[from][occupancy]-bitboards, at program startup. Tomake this somewhat easier, we first initialize an array GEN_SLIDING_ATTACKS:

unsigned char GEN_SLIDING_ATTACKS[8][64];

This arrays contains all possible sliding attack combinations (stored as unsigned char, so 8bits), the first address index is the relative location of the attacker on the file, rank ordiagonal (so 0..8), and the second address index is the occupancy state (0..63).

Example: GEN_SLIDING_ATTACKS[5][19] = 220, why?An occupancy of 00100110 translates to an index = 19, because we remove the first andlast bit, ending up with 010011 (decimal 19).

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

1 of 13 16-07-2012 10:52

Page 7: Ch1 Combine

An attacker on bit number 5 (00100000, counting from right to left, and starting from 0), will have the following attack bits set: 1101100 (decimal 220). Hence: GEN_SLIDING_ATTACKS[5][19] = 220

Using GEN_SLIDING_ATTACKS[8][64] is a convenient way to populate all theRANK_ATTACKS[64][64], FILE_ATTACKS[64][64], DIAGA8H1_ATTACKS[64][64] andDIAGA1H8_ATTACKS[64][64] bitboards.This is done in void dataInit(), at program start-up.

That's all there is to bitboard move generation, using magic multiplication numbers. This rather lengthy explanation is not covering every little detail, but it should be sufficient tounderstand the code. All relevant code snippets follow (some are long!):

step 26: board.h

#ifndef WINGLET_BOARD_H_#define WINGLET_BOARD_H_ #include "defines.h"#include "move.h" struct Board{ BitMap whiteKing, whiteQueens, whiteRooks, whiteBishops, whiteKnights,whitePawns; BitMap blackKing, blackQueens, blackRooks, blackBishops, blackKnights,blackPawns; BitMap whitePieces, blackPieces, occupiedSquares; unsigned char nextMove; // WHITE_MOVE or BLACK_MOVE unsigned char castleWhite; // White's castle status, CANCASTLEOO = 1,CANCASTLEOOO = 2 unsigned char castleBlack; // Black's castle status, CANCASTLEOO = 1,CANCASTLEOOO = 2 int epSquare; // En-passant target square after doublepawn move int fiftyMove; // Moves since the last pawnmove or capture // additional variables: int Material; // incrementally updated, total material onboard, // in centipawns, from white’s side of view int square[64]; // incrementally updated, this array isusefull if we want to // probe what kind ofpiece is on a particular square. BOOLTYPE viewRotated; // only used for displaying the board. TRUEor FALSE. // storing moves: Move moveBuffer[MAX_MOV_BUFF]; // all generated moves of the currentsearch tree are stored in this array. int moveBufLen[MAX_PLY]; // this arrays keeps track of which movesbelong to which ply void init(); void initFromSquares(int input[64], unsigned char next, int fiftyM, int

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

2 of 13 16-07-2012 10:52

Page 8: Ch1 Combine

castleW, int castleB, int epSq); void initFromFen(char fen[], char fencolor[], char fencastling[], charfenenpassant[], int fenhalfmoveclock, int fenfullmovenumber); void display();}; #endif

step 27: data.cpp

#include <iostream>#include <iomanip>#include "defines.h"#include "protos.h"#include "extglobals.h" void dataInit(){ unsigned char CHARBITSET[8]; int i, square, rank, file, arank, afile, state, slide, diaga1h8, diaga8h1,attackbit; unsigned char state6Bit, state8Bit, attack8Bit; Move move; // ===========================================================================// BITSET has only one bit set:// =========================================================================== BITSET[0] = 0x1; for (i = 1; i < 64 ; i++) { BITSET[i] = BITSET[i-1] << 1; } // ===========================================================================// BOARDINDEX is used to translate [file][rank] to [square],// Note that file is from 1..8 and rank from 1..8 (not starting from 0)// =========================================================================== for (rank = 0 ; rank < 9; rank++) { for (file = 0 ; file < 9; file++) { BOARDINDEX[file][rank] = (rank-1) * 8 + file - 1; } } // ===========================================================================// Initialize the board// =========================================================================== board.init(); // ===========================================================================// Initialize MS1BTABLE, used in lastOne (see bitops.cpp)

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

3 of 13 16-07-2012 10:52

Page 9: Ch1 Combine

// =========================================================================== for (i = 0; i < 256; i++) { MS1BTABLE[i] = ( (i > 127) ? 7 : (i > 63) ? 6 : (i > 31) ? 5 : (i > 15) ? 4 : (i > 7) ? 3 : (i > 3) ? 2 : (i > 1) ? 1 : 0 ); } // ===========================================================================// Initialize rank, file and diagonal 6-bit masking bitmaps, to get the// occupancy state, used in the movegenerator (see movegen.ccp)// =========================================================================== for (square = 0; square < 64; square++) { RANKMASK[square] = 0x0; FILEMASK[square] = 0x0; DIAGA8H1MASK[square] = 0x0; DIAGA1H8MASK[square] = 0x0; FILEMAGIC[square] = 0x0; DIAGA8H1MAGIC[square] = 0x0; DIAGA1H8MAGIC[square] = 0x0; } for (file = 1; file < 9; file++) { for (rank = 1; rank < 9; rank++) {// ===========================================================================// initialize 6-bit rank mask, used in the movegenerator (seemovegen.ccp)// =========================================================================== RANKMASK[BOARDINDEX[file][rank]] = BITSET[BOARDINDEX[2][rank]] | BITSET[BOARDINDEX[3][rank]] | BITSET[BOARDINDEX[4][rank]] ; RANKMASK[BOARDINDEX[file][rank]] |= BITSET[BOARDINDEX[5][rank]] | BITSET[BOARDINDEX[6][rank]] | BITSET[BOARDINDEX[7][rank]] ; // ===========================================================================// initialize 6-bit file mask, used in the movegenerator (seemovegen.ccp)// =========================================================================== FILEMASK[BOARDINDEX[file][rank]] =BITSET[BOARDINDEX[file][2]] | BITSET[BOARDINDEX[file][3]] |BITSET[BOARDINDEX[file][4]] ; FILEMASK[BOARDINDEX[file][rank]] |=BITSET[BOARDINDEX[file][5]] | BITSET[BOARDINDEX[file][6]] |BITSET[BOARDINDEX[file][7]] ; // ===========================================================================// Initialize diagonal magic multiplication numbers, used in the

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

4 of 13 16-07-2012 10:52

Page 10: Ch1 Combine

movegenerator (see movegen.ccp)// =========================================================================== diaga8h1 = file + rank; // from 2 to 16, longest diagonal =9 DIAGA8H1MAGIC[BOARDINDEX[file][rank]] =_DIAGA8H1MAGICS[diaga8h1 - 2]; // ===========================================================================// Initialize 6-bit diagonal mask, used in the movegenerator (seemovegen.ccp)// =========================================================================== DIAGA8H1MASK[BOARDINDEX[file][rank]] = 0x0; if (diaga8h1 < 10) // lower half, diagonals 2 to 9 { for (square = 2 ; square < diaga8h1-1 ; square ++) { DIAGA8H1MASK[BOARDINDEX[file][rank]] |=BITSET[BOARDINDEX[square][diaga8h1-square]]; } } else // upper half, diagonals 10 to 16 { for (square = 2 ; square < 17 - diaga8h1 ; square ++) { DIAGA8H1MASK[BOARDINDEX[file][rank]] |=BITSET[BOARDINDEX[diaga8h1+square-9][9-square]]; } } // ===========================================================================// Initialize diagonal magic multiplication numbers, used in themovegenerator (see movegen.ccp)// =========================================================================== diaga1h8 = file - rank; // from -7 to +7, longest diagonal =0 DIAGA1H8MAGIC[BOARDINDEX[file][rank]] =_DIAGA1H8MAGICS[diaga1h8+7]; // ===========================================================================// Initialize 6-bit diagonal mask, used in the movegenerator (seemovegen.ccp)// =========================================================================== DIAGA1H8MASK[BOARDINDEX[file][rank]] = 0x0; if (diaga1h8 > -1) // lower half, diagonals 0 to 7 { for (square = 2 ; square < 8 - diaga1h8 ; square ++) { DIAGA1H8MASK[BOARDINDEX[file][rank]] |=BITSET[BOARDINDEX[diaga1h8 + square][square]]; } } else { for (square = 2 ; square < 8 + diaga1h8 ; square ++) { DIAGA1H8MASK[BOARDINDEX[file][rank]] |=BITSET[BOARDINDEX[square][square - diaga1h8]];

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

5 of 13 16-07-2012 10:52

Page 11: Ch1 Combine

} } // ===========================================================================// Initialize file magic multiplication numbers, used in themovegenerator (see movegen.ccp)// =========================================================================== FILEMAGIC[BOARDINDEX[file][rank]] = _FILEMAGICS[file-1]; } } // ===========================================================================// Now initialize the GEN_SLIDING_ATTACKS array, used to generate the sliding// attack bitboards.// unsigned char GEN_SLIDING_ATTACKS[8 squares][64 states] holds the attacks// for any file, rank or diagonal - it is going to be usefull when generatingthe// RANK_ATTACKS[64][64], FILE_ATTACKS[64][64], DIAGA8H1_ATTACKS[64][64] and// DIAGA1H8_ATTACKS[64][64] arrays// =========================================================================== // initialize CHARBITSET, this array is equivalant to BITSET forbitboards: // 8 chars, each with only 1 bit set. CHARBITSET[0] = 1; for (square = 1; square <= 7; square++) { CHARBITSET[square] = CHARBITSET[square-1] << 1; } // loop over rank, file or diagonal squares: for (square = 0; square <= 7; square++) { // loop of occupancy states // state6Bit represents the 64 possible occupancy states of a rank, // except the 2 end-bits, because they don't matter for calculatingattacks for (state6Bit = 0; state6Bit < 64; state6Bit++) { state8Bit = state6Bit << 1; // create an 8-bit occupancystate attack8Bit = 0; if (square < 7) { attack8Bit |= CHARBITSET[square + 1]; } slide = square + 2; while (slide <= 7) // slide in '+' direction { if ((~state8Bit) & (CHARBITSET[slide - 1])) { attack8Bit |= CHARBITSET[slide]; } else break; slide++; } if (square > 0) { attack8Bit |= CHARBITSET[square - 1];

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

6 of 13 16-07-2012 10:52

Page 12: Ch1 Combine

} slide = square - 2; while (slide >= 0) // slide in '-' direction { if ((~state8Bit) & (CHARBITSET[slide + 1])) { attack8Bit |= CHARBITSET[slide]; } else break; slide--; } GEN_SLIDING_ATTACKS[square][state6Bit] = attack8Bit; } } // ===========================================================================// Initialize all attack bitmaps, used in the movegenerator (see movegen.ccp)// =========================================================================== for (square = 0; square < 64; square++) { KNIGHT_ATTACKS[square] = 0x0; KING_ATTACKS[square] = 0x0; WHITE_PAWN_ATTACKS[square] = 0x0; WHITE_PAWN_MOVES[square] = 0x0; WHITE_PAWN_DOUBLE_MOVES[square] = 0x0; BLACK_PAWN_ATTACKS[square] = 0x0; BLACK_PAWN_MOVES[square] = 0x0; BLACK_PAWN_DOUBLE_MOVES[square] = 0x0; for (state = 0; state < 64; state++) { RANK_ATTACKS[square][state] = 0x0; FILE_ATTACKS[square][state] = 0x0; DIAGA8H1_ATTACKS[square][state] = 0x0; DIAGA1H8_ATTACKS[square][state] = 0x0; } } // WHITE_PAWN_ATTACKS for (square = 0; square < 64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file - 1; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) WHITE_PAWN_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) WHITE_PAWN_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; } // WHITE_PAWN_MOVES for (square = 0; square <64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) WHITE_PAWN_MOVES[square] |= BITSET[BOARDINDEX[afile][arank]]; if (rank == 2) {

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

7 of 13 16-07-2012 10:52

Page 13: Ch1 Combine

afile = file; arank = rank + 2; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <=8)) WHITE_PAWN_DOUBLE_MOVES[square] |=BITSET[BOARDINDEX[afile][arank]]; } } // BLACK_PAWN_ATTACKS for (square = 0; square < 64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file - 1; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) BLACK_PAWN_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) BLACK_PAWN_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; } // BLACK_PAWN_MOVES for (square = 0; square < 64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) BLACK_PAWN_MOVES[square] |= BITSET[BOARDINDEX[afile][arank]]; if (rank == 7) { afile = file; arank = rank - 2; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <=8)) BLACK_PAWN_DOUBLE_MOVES[square] |=BITSET[BOARDINDEX[afile][arank]]; } } // KNIGHT attacks; for (square = 0; square < 64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file - 2; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file - 1; arank = rank + 2; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank + 2; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 2; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 2; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank - 2; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file - 1; arank = rank - 2;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

8 of 13 16-07-2012 10:52

Page 14: Ch1 Combine

if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file - 2; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KNIGHT_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; } // KING attacks; for (square = 0; square < 64; square++) { file = FILES[square]; rank = RANKS[square]; afile = file - 1; arank = rank; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file - 1; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank + 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file + 1; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; afile = file - 1; arank = rank - 1; if ((afile >= 1) & (afile <= 8) & (arank >= 1) & (arank <= 8)) KING_ATTACKS[square] |= BITSET[BOARDINDEX[afile][arank]]; } // RANK attacks (ROOKS and QUEENS): // use unsigned char GEN_SLIDING_ATTACKS[8 squares] [64 states] // to initialize BitMap RANK_ATTACKS [64 squares][64 states] // for (square = 0; square < 64; square++) { for (state6Bit = 0; state6Bit < 64; state6Bit++) { RANK_ATTACKS[square][state6Bit] = 0; RANK_ATTACKS[square][state6Bit] |= BitMap(GEN_SLIDING_ATTACKS[FILES[square]-1][state6Bit]) << (RANKSHIFT[square] - 1); } } // FILE attacks (ROOKS and QUEENS): // use unsigned char GEN_SLIDING_ATTACKS[8 squares] [64 states] // to initialize BitMap FILE_ATTACKS [64 squares][64 states] // // Occupancy transformation is as follows: // // occupancy state bits of the file: occupancy state bitsin GEN_SLIDING_ATTACKS: // // . . . . . . . . MSB LSB MSB // . . . . . A . . => A B C D E F ..

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

9 of 13 16-07-2012 10:52

Page 15: Ch1 Combine

// . . . . . B . . // . . . . . C . . // . . . . . D . . // . . . . . E . . // . . . . . F . . // LSB . . . . . . . . // // The reverse transformation is as follows: // // attack bits in GEN_SLIDING_ATTACKS: attack bits in thefile: // // LSB MSB . . . . . m . .MSB // m n o p q r s t => . . . . . n . . // . . . . . o . . // . . . . . p . . // . . . . . q . . // . . . . . r . . // . . . . . s . . // LSB . . . . . t . . // for (square = 0; square < 64; square++) { for (state6Bit = 0; state6Bit < 64; state6Bit++) { FILE_ATTACKS[square][state6Bit] = 0x0; // check to see if attackbit'-th bit is set inGEN_SLIDING_ATTACKS, for this combination of square/occupancy state for (attackbit = 0; attackbit < 8; attackbit++) // from LSBto MSB { // conversion from 64 board squares to the 8corresponding positions in the GEN_SLIDING_ATTACKS array: "8-RANKS[square]" if (GEN_SLIDING_ATTACKS[8-RANKS[square]][state6Bit] &CHARBITSET[attackbit]) { // the bit is set, so we need to updateFILE_ATTACKS accordingly: // conversion of square/attackbit to thecorresponding 64 board FILE: FILES[square] // conversion of square/attackbit to thecorresponding 64 board RANK: 8-attackbit file = FILES[square]; rank = 8 - attackbit; FILE_ATTACKS[square][state6Bit] |= BITSET[BOARDINDEX[file][rank]]; } } } } // DIAGA8H1_ATTACKS attacks (BISHOPS and QUEENS): for (square = 0; square < 64; square++) { for (state6Bit = 0; state6Bit < 64; state6Bit++) { DIAGA8H1_ATTACKS[square][state6Bit] = 0x0; for (attackbit = 0; attackbit < 8; attackbit++) // from LSBto MSB { // conversion from 64 board squares to the 8corresponding positions in the GEN_SLIDING_ATTACKS array: MIN((8-RANKS[square]),

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

10 of 13 16-07-2012 10:52

Page 16: Ch1 Combine

(FILES[square]-1)) if (GEN_SLIDING_ATTACKS[(8-RANKS[square]) <(FILES[square]-1) ? (8-RANKS[square]) : (FILES[square]-1)][state6Bit] &CHARBITSET[attackbit]) { // the bit is set, so we need to updateFILE_ATTACKS accordingly: // conversion of square/attackbit to thecorresponding 64 board file and rank: diaga8h1 = FILES[square] + RANKS[square]; //from 2 to 16, longest diagonal = 9 if (diaga8h1 < 10) { file = attackbit + 1; rank = diaga8h1 - file; } else { rank = 8 - attackbit; file = diaga8h1 - rank; } if ((file > 0) && (file < 9) && (rank > 0) &&(rank < 9)) { DIAGA8H1_ATTACKS[square][state6Bit] |= BITSET[BOARDINDEX[file][rank]]; } } } } } // DIAGA1H8_ATTACKS attacks (BISHOPS and QUEENS): for (square = 0; square < 64; square++) { for (state6Bit = 0; state6Bit < 64; state6Bit++) { DIAGA1H8_ATTACKS[square][state6Bit] = 0x0; for (attackbit = 0; attackbit < 8; attackbit++) // from LSBto MSB { // conversion from 64 board squares to the 8corresponding positions in the GEN_SLIDING_ATTACKS array: MIN((8-RANKS[square]),(FILES[square]-1)) if (GEN_SLIDING_ATTACKS[(RANKS[square]-1) <(FILES[square]-1) ? (RANKS[square]-1) : (FILES[square]-1)][state6Bit] &CHARBITSET[attackbit]) { // the bit is set, so we need to updateFILE_ATTACKS accordingly: // conversion of square/attackbit to thecorresponding 64 board file and rank: diaga1h8 = FILES[square] - RANKS[square]; //from -7 to 7, longest diagonal = 0 if (diaga1h8 < 0) { file = attackbit + 1; rank = file - diaga1h8; } else { rank = attackbit + 1; file = diaga1h8 + rank; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

11 of 13 16-07-2012 10:52

Page 17: Ch1 Combine

if ((file > 0) && (file < 9) && (rank > 0) &&(rank < 9)) { DIAGA1H8_ATTACKS[square][state6Bit] |= BITSET[BOARDINDEX[file][rank]]; } } } } } // ===========================================================================// Masks for castling, index 0 is for white, 1 is for black// =========================================================================== maskEG[0] = BITSET[E1] | BITSET[F1] | BITSET[G1]; maskEG[1] = BITSET[E8] | BITSET[F8] | BITSET[G8]; maskFG[0] = BITSET[F1] | BITSET[G1]; maskFG[1] = BITSET[F8] | BITSET[G8]; maskBD[0] = BITSET[B1] | BITSET[C1] | BITSET[D1]; maskBD[1] = BITSET[B8] | BITSET[C8] | BITSET[D8]; maskCE[0] = BITSET[C1] | BITSET[D1] | BITSET[E1]; maskCE[1] = BITSET[C8] | BITSET[D8] | BITSET[E8]; // ===========================================================================// The 4 castling moves can be predefined:// =========================================================================== move.clear(); move.setCapt(EMPTY); move.setPiec(WHITE_KING); move.setProm(WHITE_KING); move.setFrom(E1); move.setTosq(G1); WHITE_OO_CASTL = move.moveInt; move.setTosq(C1); WHITE_OOO_CASTL = move.moveInt; move.setPiec(BLACK_KING); move.setProm(BLACK_KING); move.setFrom(E8); move.setTosq(G8); BLACK_OO_CASTL = move.moveInt; move.setTosq(C8); BLACK_OOO_CASTL = move.moveInt; return;} void info(){ // your playground... display variables - meant for testing/verificationpurposes only std::cout << std::endl << "============ info start ==============" <<std::endl;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

12 of 13 16-07-2012 10:52

Page 18: Ch1 Combine

std::cout << "size of board, in bytes = " << sizeof(board) << std::endl; std::cout << "Material value = " << board.Material <<std::endl; std::cout << "White castling rights = " << int(board.castleWhite) <<std::endl; std::cout << "Black castling rights = " << int(board.castleBlack) <<std::endl; std::cout << "En-passant square = " << board.epSquare <<std::endl; std::cout << "Fifty move count = " << board.fiftyMove <<std::endl; std::cout << "============ info end ================" << std::endl <<std::endl; return;}

last update: Friday 10 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen04.htm

13 of 13 16-07-2012 10:52

Page 19: Ch1 Combine

Writing a chess program in99 steps

step 28: movegen.cpp

#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h"#include "move.h" // Macro's to define sliding attacks:#define RANKMOVES(a) (RANK_ATTACKS[(a)][((board.occupiedSquares &RANKMASK[(a)]) >> RANKSHIFT[(a)])] & targetBitmap)#define FILEMOVES(a) (FILE_ATTACKS[(a)][((board.occupiedSquares &FILEMASK[(a)]) * FILEMAGIC[(a)]) >> 57] & targetBitmap)#define SLIDEA8H1MOVES(a) (DIAGA8H1_ATTACKS[(a)][((board.occupiedSquares &DIAGA8H1MASK[(a)]) * DIAGA8H1MAGIC[(a)]) >> 57] & targetBitmap)#define SLIDEA1H8MOVES(a) (DIAGA1H8_ATTACKS[(a)][((board.occupiedSquares &DIAGA1H8MASK[(a)]) * DIAGA1H8MAGIC[(a)]) >> 57] & targetBitmap)#define ROOKMOVES(a) (RANKMOVES(a) | FILEMOVES(a))#define BISHOPMOVES(a) (SLIDEA8H1MOVES(a) | SLIDEA1H8MOVES(a))#define QUEENMOVES(a) (BISHOPMOVES(a) | ROOKMOVES(a)) int movegen(int index){ // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++// This is winglet's pseudo-legal bitmap move generator,// using magic multiplication instead of rotated bitboards.// There is no check if a move leaves the king in check// The first free location in moveBuffer[] is supplied in index,// and the new first free location is returned// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ unsigned char opponentSide; unsigned int from, to; BitMap tempPiece, tempMove; BitMap targetBitmap, freeSquares; Move move; move.clear(); opponentSide = !board.nextMove; freeSquares = ~board.occupiedSquares; // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black to move // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

1 of 10 16-07-2012 10:55

Page 20: Ch1 Combine

if (board.nextMove) // black to move { targetBitmap = ~board.blackPieces; // we cannot capture one of ourown pieces! // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black Pawns // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_PAWN); tempPiece = board.blackPawns; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = BLACK_PAWN_MOVES[from] &freeSquares; // normal moves if (RANKS[from] == 7 && tempMove) tempMove |= (BLACK_PAWN_DOUBLE_MOVES[from] &freeSquares); // add double moves tempMove |= BLACK_PAWN_ATTACKS[from] &board.whitePieces; // add captures while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); if ((RANKS[to]) ==1) // add promotions { move.setProm(BLACK_QUEEN); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(BLACK_ROOK); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(BLACK_BISHOP); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(BLACK_KNIGHT); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(EMPTY); } else { board.moveBuffer[index++].moveInt =move.moveInt; } tempMove ^= BITSET[to]; } // add en-passantcaptures: if (board.epSquare) // do a quick check first { if (BLACK_PAWN_ATTACKS[from] & BITSET[board.epSquare]) { move.setProm(BLACK_PAWN); move.setCapt(WHITE_PAWN); move.setTosq(board.epSquare); board.moveBuffer[index++].moveInt =move.moveInt; } } tempPiece ^= BITSET[from]; move.setProm(EMPTY);

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

2 of 10 16-07-2012 10:55

Page 21: Ch1 Combine

} // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black Knights // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_KNIGHT); tempPiece = board.blackKnights; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = KNIGHT_ATTACKS[from] & targetBitmap; while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black Bishops // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_BISHOP); tempPiece = board.blackBishops; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = BISHOPMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black Rooks // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_ROOK); tempPiece = board.blackRooks; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = ROOKMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

3 of 10 16-07-2012 10:55

Page 22: Ch1 Combine

// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black Queens // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_QUEEN); tempPiece = board.blackQueens; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = QUEENMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // Black King // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(BLACK_KING); tempPiece = board.blackKing; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = KING_ATTACKS[from] & targetBitmap; while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } // Black 0-0 Castling: if (board.castleBlack & CANCASTLEOO) { if (!(maskFG[1] & board.occupiedSquares)) { if (!isAttacked(maskEG[BLACK_MOVE],WHITE_MOVE)) { board.moveBuffer[index++].moveInt =BLACK_OO_CASTL; // predefined unsigned int } } } // Black 0-0-0 Castling: if (board.castleBlack & CANCASTLEOOO) { if (!(maskBD[1] & board.occupiedSquares)) { if (!isAttacked(maskCE[BLACK_MOVE],WHITE_MOVE)) { board.moveBuffer[index++].moveInt =BLACK_OOO_CASTL; // predefined unsigned int

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

4 of 10 16-07-2012 10:55

Page 23: Ch1 Combine

} } } tempPiece ^= BITSET[from]; move.setProm(EMPTY); } } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White to move // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ else { targetBitmap = ~board.whitePieces; // we cannot capture one of ourown pieces! // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White Pawns // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_PAWN); tempPiece = board.whitePawns; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = WHITE_PAWN_MOVES[from] &freeSquares; // normal moves if (RANKS[from] == 2 && tempMove) tempMove |= (WHITE_PAWN_DOUBLE_MOVES[from] &freeSquares); // add double moves tempMove |= WHITE_PAWN_ATTACKS[from] &board.blackPieces; // add captures while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); if ((RANKS[to]) ==8) // add promotions { move.setProm(WHITE_QUEEN); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(WHITE_ROOK); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(WHITE_BISHOP); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(WHITE_KNIGHT); board.moveBuffer[index++].moveInt = move.moveInt; move.setProm(EMPTY); } else { board.moveBuffer[index++].moveInt =move.moveInt; } tempMove ^= BITSET[to]; } // add en-passantcaptures: if (board.epSquare) // do a quick check first {

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

5 of 10 16-07-2012 10:55

Page 24: Ch1 Combine

if (WHITE_PAWN_ATTACKS[from] & BITSET[board.epSquare]) { move.setProm(WHITE_PAWN); move.setCapt(BLACK_PAWN); move.setTosq(board.epSquare); board.moveBuffer[index++].moveInt =move.moveInt; } } tempPiece ^= BITSET[from]; move.setProm(EMPTY); } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White Knights // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_KNIGHT); tempPiece = board.whiteKnights; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = KNIGHT_ATTACKS[from] & targetBitmap; while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White Bishops // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_BISHOP); tempPiece = board.whiteBishops; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = BISHOPMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White Rooks // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_ROOK); tempPiece = board.whiteRooks; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from);

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

6 of 10 16-07-2012 10:55

Page 25: Ch1 Combine

tempMove = ROOKMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White Queens // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_QUEEN); tempPiece = board.whiteQueens; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = QUEENMOVES(from); // see Macro's while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } tempPiece ^= BITSET[from]; } // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ // White king // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ move.setPiec(WHITE_KING); tempPiece = board.whiteKing; while (tempPiece) { from = firstOne(tempPiece); move.setFrom(from); tempMove = KING_ATTACKS[from] & targetBitmap; while (tempMove) { to = firstOne(tempMove); move.setTosq(to); move.setCapt(board.square[to]); board.moveBuffer[index++].moveInt = move.moveInt; tempMove ^= BITSET[to]; } // White 0-0 Castling: if (board.castleWhite & CANCASTLEOO) { if (!(maskFG[0] & board.occupiedSquares)) { if (!isAttacked(maskEG[WHITE_MOVE],BLACK_MOVE)) { board.moveBuffer[index++].moveInt =WHITE_OO_CASTL; // predefined unsigned int } }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

7 of 10 16-07-2012 10:55

Page 26: Ch1 Combine

} // White 0-0-0 Castling: if (board.castleWhite & CANCASTLEOOO) { if (!(maskBD[0] & board.occupiedSquares)) { if (!isAttacked(maskCE[WHITE_MOVE],BLACK_MOVE)) { board.moveBuffer[index++].moveInt =WHITE_OOO_CASTL; // predefined unsigned int } } } tempPiece ^= BITSET[from]; move.setProm(EMPTY); } } return index;} BOOLTYPE isAttacked(BitMap &targetBitmap, const unsigned char &fromSide){ // ===========================================================================// isAttacked is used mainly as a move legality test to see if targetBitmapis// attacked by white or black.// Returns true at the first attack found, and returns false if no attack isfound.// It can be used for:// - check detection, and// - castling legality: test to see if the king passes through, or ends up on,// a square that is attacked// =========================================================================== BitMap tempTarget; BitMap slidingAttackers; int to; tempTarget = targetBitmap; if (fromSide) // test for attacks from BLACK to targetBitmap { while (tempTarget) { to = firstOne(tempTarget); if (board.blackPawns & WHITE_PAWN_ATTACKS[to]) return true; if (board.blackKnights & KNIGHT_ATTACKS[to]) return true; if (board.blackKing & KING_ATTACKS[to]) return true; // file / rank attacks slidingAttackers = board.blackQueens | board.blackRooks; if (slidingAttackers) { if (RANK_ATTACKS[to][((board.occupiedSquares &RANKMASK[to]) >> RANKSHIFT[to])] & slidingAttackers) return true; if (FILE_ATTACKS[to][((board.occupiedSquares &FILEMASK[to]) * FILEMAGIC[to]) >> 57] & slidingAttackers) return true; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

8 of 10 16-07-2012 10:55

Page 27: Ch1 Combine

// diagonals slidingAttackers = board.blackQueens | board.blackBishops; if (slidingAttackers) { if (DIAGA8H1_ATTACKS[to][((board.occupiedSquares &DIAGA8H1MASK[to]) * DIAGA8H1MAGIC[to]) >> 57] & slidingAttackers) return true; if (DIAGA1H8_ATTACKS[to][((board.occupiedSquares &DIAGA1H8MASK[to]) * DIAGA1H8MAGIC[to]) >> 57] & slidingAttackers) return true; } tempTarget ^= BITSET[to]; } } else // test for attacks from WHITE to targetBitmap { while (tempTarget) { to = firstOne(tempTarget); if (board.whitePawns & BLACK_PAWN_ATTACKS[to]) return true; if (board.whiteKnights & KNIGHT_ATTACKS[to]) return true; if (board.whiteKing & KING_ATTACKS[to]) return true; // file / rank attacks slidingAttackers = board.whiteQueens | board.whiteRooks; if (slidingAttackers) { if (RANK_ATTACKS[to][((board.occupiedSquares &RANKMASK[to]) >> RANKSHIFT[to])] & slidingAttackers) return true; if (FILE_ATTACKS[to][((board.occupiedSquares &FILEMASK[to]) * FILEMAGIC[to]) >> 57] & slidingAttackers) return true; } // diagonals: slidingAttackers = board.whiteQueens | board.whiteBishops; if (slidingAttackers) { if (DIAGA8H1_ATTACKS[to][((board.occupiedSquares &DIAGA8H1MASK[to]) * DIAGA8H1MAGIC[to]) >> 57] & slidingAttackers) return true; if (DIAGA1H8_ATTACKS[to][((board.occupiedSquares &DIAGA1H8MASK[to]) * DIAGA1H8MAGIC[to]) >> 57] & slidingAttackers) return true; } tempTarget ^= BITSET[to]; } } return false;}

isAttacked

Since the move generator will also generate moves that leave the king in check, we need totest if our king is left in check after actually having made the move. This is how mostengines treat move legality, it's faster than building the test into the move generator. One ofthe reasons is that a lot of moves that were generates will never be made in the search,due to cutting of search tree branches. So making a move will always have to beaccompanied by this test:

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

9 of 10 16-07-2012 10:55

Page 28: Ch1 Combine

makeMove(board.moveBuffer[i]);if (isOtherKingAttacked()){ unmakeMove(board.moveBuffer[i]);}

There is a generic function isAttacked, that does an attack test on specified fields. This testis not very difficult to do, because we can use the bitboards and attack lookup tables thatare already used in the move generator. However, the move generator is working from theperspective of the moving piece (the attacker), but to test if a field is being attacked, wereverse the perspective (because it is faster): for example, in order to see if square b3 isattacked by a black knight, we place a (hypothetical) white knight on square b3 and see ifthis white knight can capture a black knight. If the answer is yes, then the black knight isalso attacking the white knight's field.

For sliding pieces, we generate the rank, file and diagonal attack bitboards AS IF there wasa white rook, queen or bishop on the square to be checked, and check if any of theseattacks can capture a black rook, queen or bishop. Same technique is applied for pawnsand kings.The example below is showing a check of the to field for attacks from black, along files andranks (attacks from black rooks and queens). It's surprisingly simple (compare this to themacro's that are used in the move generator):

slidingAttackers = board.blackQueens | board.blackRooks;if (slidingAttackers){ if (FILE_ATTACKS[to][((board.occupiedSquares & FILEMASK[to]) *FILEMAGIC[to]) >> 57] & slidingAttackers) return true; if (RANK_ATTACKS[to][((board.occupiedSquares & RANKMASK[to]) >>RANKSHIFT[to])] & slidingAttackers) return true;}

isAttacked is also called from the move generator to check if castlings can be done, i.e. ifthere are no fields attacked by the opponent that would prevent castling.

last update: Saturday 11 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen05.htm

10 of 10 16-07-2012 10:55

Page 29: Ch1 Combine

Writing a chess program in 99steps

We add a new command: moves, to display all generated pseudo-legal moves:

step 29: command.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE#define _CRT_SECURE_NO_DEPRECATE 1#endif#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h"#include "board.h" void readCommands(){ int nextc; if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); // ===========================================================================// Read a command and call doCommand:// =========================================================================== while ((nextc = getc(stdin)) != EOF) { if (nextc == '\n') { CMD_BUFF[CMD_BUFF_COUNT] = '\0'; while (CMD_BUFF_COUNT) { if (!doCommand(CMD_BUFF)) return; } if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); } else { if (CMD_BUFF_COUNT >= MAX_CMD_BUFF-1) { std::cout << "Warning: command buffer full !! " << std::endl; CMD_BUFF_COUNT = 0; } CMD_BUFF[CMD_BUFF_COUNT++] = nextc; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

1 of 11 16-07-2012 10:57

Page 30: Ch1 Combine

}} BOOLTYPE doCommand(const char *buf){ char userinput[80]; int i, number; // =================================================================// return when command buffer is empty// ================================================================= if (!strcmp(buf, "")) { CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// help, h, or ?: show this help// ================================================================= if ((!strcmp(buf, "help")) || (!strcmp(buf, "h")) || (!strcmp(buf, "?"))) { std::cout << std::endl << "help:" << std::endl; std::cout << "black : BLACK to move" << std::endl; std::cout << "cc : play computer-to-computer " << std::endl; std::cout << "d : display board " << std::endl; std::cout << "exit : exit program " << std::endl; std::cout << "eval : show static evaluation of this position" <<std::endl; std::cout << "game : show game moves " << std::endl; std::cout << "go : computer next move " << std::endl; std::cout << "help, h, or ? : show this help " << std::endl; std::cout << "info : display variables (for testing purposes)" <<std::endl; std::cout << "move e2e4, or h7h8q : enter a move (use this format)" << std::endl; std::cout << "moves : show all legal moves" << std::endl; std::cout << "new : start new game" << std::endl; std::cout << "perf : benchmark a number of key functions" <<std::endl; std::cout << "perft n : calculate raw number of nodes from here, depth n" << std::endl; std::cout << "quit : exit program " << std::endl; std::cout << "r : rotate board " << std::endl; std::cout << "readfen filename n : reads #-th FEN position from filename" <<std::endl; std::cout << "sd n : set the search depth to n" << std::endl; std::cout << "setup : setup board... " << std::endl; std::cout << "undo : take back last move" << std::endl; std::cout << "white : WHITE to move" << std::endl; std::cout << std::endl; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// black: black to move// ================================================================= if (!strcmp(buf, "black")) { board.nextMove = BLACK_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// d: display board// ================================================================= if (!strcmp(buf, "d")) { board.display(); CMD_BUFF_COUNT = '\0'; return true; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

2 of 11 16-07-2012 10:57

Page 31: Ch1 Combine

// =================================================================// exit or quit: exit program// ================================================================= if ((!strcmp(buf, "exit")) || (!strcmp(buf, "quit"))) { CMD_BUFF_COUNT = '\0'; return false; } // =================================================================// info: display variables (for testing purposes)// ================================================================= if (!strcmp(buf, "info")) { info(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// moves: show all legal moves// ================================================================= if (!strcmp(buf, "moves")) { board.moveBufLen[0] = 0; board.moveBufLen[1] = movegen(board.moveBufLen[0]); std::cout << std::endl << "pseudo-legal moves from this position:" << std::endl; for (i = board.moveBufLen[0]; i < board.moveBufLen[1]; i++) { std::cout << i+1 << ". " ; displayMove(board.moveBuffer[i]); std::cout << std::endl; } CMD_BUFF_COUNT = '\0'; return true; }

// =================================================================// new: start new game// ================================================================= if (!strcmp(buf, "new")) { dataInit(); board.init(); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// r: rotate board// ================================================================= if (!strcmp(buf, "r")) { board.viewRotated = !board.viewRotated; board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// readfen filename n : reads #-th FEN position from filename// ================================================================= if (!strncmp(buf, "readfen", 7)) { sscanf(buf+7,"%s %d", userinput, &number); board.init(); readFen(userinput, number); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

3 of 11 16-07-2012 10:57

Page 32: Ch1 Combine

// setup : setup board...// ================================================================= if (!strncmp(buf, "setup", 5)) { setup(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// white: white to move// ================================================================= if (!strcmp(buf, "white")) { board.nextMove = WHITE_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// unknown command// ================================================================= std::cout << " command not implemented: " << buf << ", type 'help' for more info" <<std::endl; CMD_BUFF_COUNT = '\0'; return true;}

Display the list of generated moves

Now is the time to add the function that displays one move at a time. Since only one move is passed, thisfunction cannot do any disambiguation in the way the move is displayed. Later we will add more fancy movedisplaying functions, including disambiguation. At least for now this gives us a simple way of checking themove generator.

step 30: displaymove.cpp

#include <iostream>#include "defines.h"#include "extglobals.h" void displayMove(Move &move){ // displays a single move on the console, no disambiguation if ((move.getPiec() == WHITE_KING) && (move.isCastleOO())) { std::cout << "O-O"; return; }; if ((move.getPiec() == WHITE_KING) && (move.isCastleOOO())) { std::cout << "O-O-O"; return; }; if ((move.getPiec() == BLACK_KING) && (move.isCastleOO())) { std::cout << "O-O"; return; }; if ((move.getPiec() == BLACK_KING) && (move.isCastleOOO())) { std::cout << "O-O-O"; return; }; if ((move.getPiec() == WHITE_ROOK) || (move.getPiec() == BLACK_ROOK)) std::cout << "R"; if ((move.getPiec() == WHITE_BISHOP) || (move.getPiec() == BLACK_BISHOP)) std::cout << "B";

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

4 of 11 16-07-2012 10:57

Page 33: Ch1 Combine

if ((move.getPiec() == WHITE_KNIGHT) || (move.getPiec() == BLACK_KNIGHT)) std::cout << "N"; if ((move.getPiec() == WHITE_KING) || (move.getPiec() == BLACK_KING)) std::cout << "K"; if ((move.getPiec() == WHITE_QUEEN) || (move.getPiec() == BLACK_QUEEN)) std::cout << "Q"; if (((move.getPiec() == WHITE_PAWN) || (move.getPiec() == BLACK_PAWN)) &&move.isCapture()) { if (FILES[move.getFrom()] == 1) std::cout << "a"; if (FILES[move.getFrom()] == 2) std::cout << "b"; if (FILES[move.getFrom()] == 3) std::cout << "c"; if (FILES[move.getFrom()] == 4) std::cout << "d"; if (FILES[move.getFrom()] == 5) std::cout << "e"; if (FILES[move.getFrom()] == 6) std::cout << "f"; if (FILES[move.getFrom()] == 7) std::cout << "g"; if (FILES[move.getFrom()] == 8) std::cout << "h"; } if (move.isCapture()) std::cout << "x" ; if (FILES[move.getTosq()] == 1) std::cout << "a"; if (FILES[move.getTosq()] == 2) std::cout << "b"; if (FILES[move.getTosq()] == 3) std::cout << "c"; if (FILES[move.getTosq()] == 4) std::cout << "d"; if (FILES[move.getTosq()] == 5) std::cout << "e"; if (FILES[move.getTosq()] == 6) std::cout << "f"; if (FILES[move.getTosq()] == 7) std::cout << "g"; if (FILES[move.getTosq()] == 8) std::cout << "h"; std::cout << RANKS[move.getTosq()]; if (move.isPromotion()) { if ((move.getProm() == WHITE_ROOK) || (move.getProm() == BLACK_ROOK)) std::cout<< "=R"; if ((move.getProm() == WHITE_BISHOP) || (move.getProm() == BLACK_BISHOP)) std::cout<< "=B"; if ((move.getProm() == WHITE_KNIGHT) || (move.getProm() == BLACK_KNIGHT)) std::cout<< "=N"; if ((move.getProm() == WHITE_KING) || (move.getProm() == BLACK_KING)) std::cout<< "=K"; if ((move.getProm() == WHITE_QUEEN) || (move.getProm() == BLACK_QUEEN)) std::cout<< "=Q"; } std::cout.flush(); return;}

Three more functions to add here, movegen, isAttacked and displayMove:

step 31: protos.h

#ifndef WINGLET_PROTOS_H#define WINGLET_PROTOS_H #include "move.h" unsigned int bitCnt(BitMap);void dataInit();void displayBitmap(BitMap);void displayMove(Move &);BOOLTYPE doCommand(const char *);unsigned int firstOne(BitMap);void info();BOOLTYPE isAttacked(BitMap &, const unsigned char &);unsigned int lastOne(BitMap);int movegen(int);void readCommands();BOOLTYPE readFen(char *, int);void setup();void setupFen(char *, char *, char *, char *, int , int ); #endif

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

5 of 11 16-07-2012 10:57

Page 34: Ch1 Combine

The list of global variables is significantly increased, there are a number of new global bitmaps that areused for move generation:

step 32: globals.h

#ifndef WINGLET_GLOBALS_H#define WINGLET_GLOBALS_H #include "defines.h"#include "board.h" char CMD_BUFF[MAX_CMD_BUFF];int CMD_BUFF_COUNT = 0; Board board; extern const int A8 = 56; extern const int B8 = 57; extern const int C8 = 58; extern const int D8= 59;extern const int E8 = 60; extern const int F8 = 61; extern const int G8 = 62; extern const int H8= 63;extern const int A7 = 48; extern const int B7 = 49; extern const int C7 = 50; extern const int D7= 51;extern const int E7 = 52; extern const int F7 = 53; extern const int G7 = 54; extern const int H7= 55;extern const int A6 = 40; extern const int B6 = 41; extern const int C6 = 42; extern const int D6= 43;extern const int E6 = 44; extern const int F6 = 45; extern const int G6 = 46; extern const int H6= 47;extern const int A5 = 32; extern const int B5 = 33; extern const int C5 = 34; extern const int D5= 35;extern const int E5 = 36; extern const int F5 = 37; extern const int G5 = 38; extern const int H5= 39;extern const int A4 = 24; extern const int B4 = 25; extern const int C4 = 26; extern const int D4= 27;extern const int E4 = 28; extern const int F4 = 29; extern const int G4 = 30; extern const int H4= 31;extern const int A3 = 16; extern const int B3 = 17; extern const int C3 = 18; extern const int D3= 19;extern const int E3 = 20; extern const int F3 = 21; extern const int G3 = 22; extern const int H3= 23;extern const int A2 = 8; extern const int B2 = 9; extern const int C2 = 10; extern const int D2= 11;extern const int E2 = 12; extern const int F2 = 13; extern const int G2 = 14; extern const int H2= 15;extern const int A1 = 0; extern const int B1 = 1; extern const int C1 = 2; extern const int D1= 3;extern const int E1 = 4; extern const int F1 = 5; extern const int G1 = 6; extern const int H1= 7; const char* SQUARENAME[64] = {"a1","b1","c1","d1","e1","f1","g1","h1", "a2","b2","c2","d2","e2","f2","g2","h2", "a3","b3","c3","d3","e3","f3","g3","h3", "a4","b4","c4","d4","e4","f4","g4","h4", "a5","b5","c5","d5","e5","f5","g5","h5", "a6","b6","c6","d6","e6","f6","g6","h6", "a7","b7","c7","d7","e7","f7","g7","h7", "a8","b8","c8","d8","e8","f8","g8","h8"}; extern const int FILES[64] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; extern const int RANKS[64] = { 1, 1, 1, 1, 1, 1, 1, 1,

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

6 of 11 16-07-2012 10:57

Page 35: Ch1 Combine

2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8}; // Identifier of next move:extern const unsigned char WHITE_MOVE = 0; extern const unsigned char BLACK_MOVE = 1; // Piece identifiers, 4 bits each.// Usefull bitwise properties of this numbering scheme:// white = 0..., black = 1..., sliding = .1.., nonsliding = .0..// rank/file sliding pieces = .11., diagonally sliding pieces = .1.1// pawns and kings (without color bits), are < 3// major pieces (without color bits set), are > 5// minor and major pieces (without color bits set), are > 2extern const unsigned char EMPTY = 0; // 0000extern const unsigned char WHITE_PAWN = 1; // 0001extern const unsigned char WHITE_KING = 2; // 0010extern const unsigned char WHITE_KNIGHT = 3; // 0011extern const unsigned char WHITE_BISHOP = 5; // 0101extern const unsigned char WHITE_ROOK = 6; // 0110extern const unsigned char WHITE_QUEEN = 7; // 0111extern const unsigned char BLACK_PAWN = 9; // 1001extern const unsigned char BLACK_KING = 10; // 1010extern const unsigned char BLACK_KNIGHT = 11; // 1011extern const unsigned char BLACK_BISHOP = 13; // 1101extern const unsigned char BLACK_ROOK = 14; // 1110extern const unsigned char BLACK_QUEEN = 15; // 1111 const char* PIECENAMES[16] = {" ","P ","K ","N "," ","B ","R ","Q ", " ","P*","K*","N*"," ","B*","R*","Q*"}; BitMap BITSET[64];int BOARDINDEX[9][9]; // index 0 is not used, only 1..8. // Value of material, in centipawns:extern const int PAWN_VALUE = 100;extern const int KNIGHT_VALUE = 300;extern const int BISHOP_VALUE = 300;extern const int ROOK_VALUE = 500;extern const int QUEEN_VALUE = 900;extern const int KING_VALUE = 9999;extern const int CHECK_MATE = KING_VALUE; // used in Eugene Nalimov's bitScanReverseint MS1BTABLE[256]; // Attack tables:BitMap WHITE_PAWN_ATTACKS[64];BitMap WHITE_PAWN_MOVES[64];BitMap WHITE_PAWN_DOUBLE_MOVES[64];BitMap BLACK_PAWN_ATTACKS[64];BitMap BLACK_PAWN_MOVES[64];BitMap BLACK_PAWN_DOUBLE_MOVES[64];BitMap KNIGHT_ATTACKS[64];BitMap KING_ATTACKS[64];BitMap RANK_ATTACKS[64][64]; // 32KBBitMap FILE_ATTACKS[64][64]; // 32KBBitMap DIAGA8H1_ATTACKS[64][64]; // 32KBBitMap DIAGA1H8_ATTACKS[64][64]; // 32KB // Move generator shift for ranks:extern const int RANKSHIFT[64] = { 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 17, 17, 17, 17, 17, 17, 17, 17, 25, 25, 25, 25, 25, 25, 25, 25, 33, 33, 33, 33, 33, 33, 33, 33, 41, 41, 41, 41, 41, 41, 41, 41, 49, 49, 49, 49, 49, 49, 49, 49,

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

7 of 11 16-07-2012 10:57

Page 36: Ch1 Combine

57, 57, 57, 57, 57, 57, 57, 57}; // Move generator magic multiplication numbers for files:extern const BitMap _FILEMAGICS[8] = { 0x8040201008040200, 0x4020100804020100, 0x2010080402010080, 0x1008040201008040, 0x0804020100804020, 0x0402010080402010, 0x0201008040201008, 0x0100804020100804}; // Move generator magic multiplication numbers for diagonals:extern const BitMap _DIAGA8H1MAGICS[15] = { 0x0, 0x0, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0080808080808080, 0x0040404040404040, 0x0020202020202020, 0x0010101010101010, 0x0008080808080808, 0x0, 0x0}; // Move generator magic multiplication numbers for diagonals:extern const BitMap _DIAGA1H8MAGICS[15] = { 0x0, 0x0, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x0101010101010100, 0x8080808080808000, 0x4040404040400000, 0x2020202020000000, 0x1010101000000000, 0x0808080000000000, 0x0, 0x0}; // Move generator 6-bit masking and magic multiplication numbers:BitMap RANKMASK[64];BitMap FILEMASK[64];BitMap FILEMAGIC[64];BitMap DIAGA8H1MASK[64];BitMap DIAGA8H1MAGIC[64];BitMap DIAGA1H8MASK[64];BitMap DIAGA1H8MAGIC[64]; // We use one generalized sliding attacks array: [8 squares][64 states]// the unsigned char (=8 bits) contains the attacks for a rank, file or diagonalunsigned char GEN_SLIDING_ATTACKS[8][64]; // Used for castling:unsigned char CANCASTLEOO = 1;unsigned char CANCASTLEOOO = 2;BitMap maskEG[2];BitMap maskFG[2];BitMap maskBD[2];BitMap maskCE[2];unsigned int WHITE_OOO_CASTL;unsigned int BLACK_OOO_CASTL;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

8 of 11 16-07-2012 10:57

Page 37: Ch1 Combine

unsigned int WHITE_OO_CASTL;unsigned int BLACK_OO_CASTL;

int ICAPT;int IEP;int IPROM;int ICASTLOO;int ICASTLOOO;int ICHECK;

#endif

Extglobals.h is updated accordingly, as usual:

step 33: extglobals.h

#ifndef WINGLET_EXTGLOBALS_H#define WINGLET_EXTGLOBALS_H #include "defines.h"#include "board.h" extern char CMD_BUFF[];extern int CMD_BUFF_COUNT; extern Board board; extern const int A8; extern const int B8; extern const int C8; extern const int D8;extern const int E8; extern const int F8; extern const int G8; extern const int H8;extern const int A7; extern const int B7; extern const int C7; extern const int D7;extern const int E7; extern const int F7; extern const int G7; extern const int H7;extern const int A6; extern const int B6; extern const int C6; extern const int D6;extern const int E6; extern const int F6; extern const int G6; extern const int H6;extern const int A5; extern const int B5; extern const int C5; extern const int D5;extern const int E5; extern const int F5; extern const int G5; extern const int H5;extern const int A4; extern const int B4; extern const int C4; extern const int D4;extern const int E4; extern const int F4; extern const int G4; extern const int H4;extern const int A3; extern const int B3; extern const int C3; extern const int D3;extern const int E3; extern const int F3; extern const int G3; extern const int H3;extern const int A2; extern const int B2; extern const int C2; extern const int D2;extern const int E2; extern const int F2; extern const int G2; extern const int H2;extern const int A1; extern const int B1; extern const int C1; extern const int D1;extern const int E1; extern const int F1; extern const int G1; extern const int H1; extern const char* SQUARENAME[]; extern const int FILES[];extern const int RANKS[]; extern const unsigned char WHITE_MOVE; extern const unsigned char BLACK_MOVE; extern const unsigned char EMPTY;extern const unsigned char WHITE_KNIGHT;extern const unsigned char WHITE_PAWN;extern const unsigned char WHITE_KING;extern const unsigned char WHITE_BISHOP;extern const unsigned char WHITE_ROOK;extern const unsigned char WHITE_QUEEN;extern const unsigned char BLACK_KNIGHT;extern const unsigned char BLACK_PAWN;extern const unsigned char BLACK_KING;extern const unsigned char BLACK_BISHOP;extern const unsigned char BLACK_ROOK;extern const unsigned char BLACK_QUEEN; extern const char* PIECENAMES[]; extern BitMap BITSET[];extern int BOARDINDEX[9][9]; extern const int PAWN_VALUE;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

9 of 11 16-07-2012 10:57

Page 38: Ch1 Combine

extern const int KNIGHT_VALUE;extern const int BISHOP_VALUE;extern const int ROOK_VALUE;extern const int QUEEN_VALUE;extern const int KING_VALUE;extern const int CHECK_MATE; extern int MS1BTABLE[]; extern BitMap WHITE_PAWN_ATTACKS[];extern BitMap WHITE_PAWN_MOVES[];extern BitMap WHITE_PAWN_DOUBLE_MOVES[];extern BitMap BLACK_PAWN_ATTACKS[];extern BitMap BLACK_PAWN_MOVES[];extern BitMap BLACK_PAWN_DOUBLE_MOVES[];extern BitMap KNIGHT_ATTACKS[];extern BitMap KING_ATTACKS[];extern BitMap RANK_ATTACKS[64][64];extern BitMap FILE_ATTACKS[64][64];extern BitMap DIAGA8H1_ATTACKS[64][64];extern BitMap DIAGA1H8_ATTACKS[64][64]; extern const int RANKSHIFT[];extern const BitMap _FILEMAGICS[];extern const BitMap _DIAGA8H1MAGICS[];extern const BitMap _DIAGA1H8MAGICS[]; extern BitMap RANKMASK[];extern BitMap FILEMAGIC[];extern BitMap FILEMASK[];extern BitMap DIAGA8H1MASK[];extern BitMap DIAGA8H1MAGIC[];extern BitMap DIAGA1H8MASK[];extern BitMap DIAGA1H8MAGIC[]; extern unsigned char GEN_SLIDING_ATTACKS[8][64]; extern unsigned char CANCASTLEOO;extern unsigned char CANCASTLEOOO;extern BitMap maskEG[];extern BitMap maskFG[];extern BitMap maskBD[];extern BitMap maskCE[];extern unsigned int WHITE_OOO_CASTL;extern unsigned int BLACK_OOO_CASTL;extern unsigned int WHITE_OO_CASTL;extern unsigned int BLACK_OO_CASTL; int ICAPT;int IEP;int IPROM;int ICASTLOO;int ICASTLOOO;int ICHECK;

#endif

Finally, we need to define the length of the move buffer array, MAX_MOVE_BUFF:

step 34: defines.h

#ifndef WINGLET_DEFINES_H#define WINGLET_DEFINES_H #define WINGLET_PROG_VERSION "winglet 0.0, Copyright (C) 2011, Stef Luijten" #define MAX_CMD_BUFF 256 // Console command input buffer#define MAX_MOV_BUFF 4096 // Number of moves that we can store (all plies)#define MAX_PLY 64 // Search depth typedef unsigned long long U64;typedef unsigned long long BitMap;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

10 of 11 16-07-2012 10:57

Page 39: Ch1 Combine

typedef short SHORTINT;typedef unsigned short USHORTINT;typedef int BOOLTYPE; #endif

√ read positions from BT2450.pgn, or set up a position manually, and then type "moves" to see allpseudo-legal moves.√ change the side to move, to see the opponent's moves in this position.√ try to set up a position that allows for an en-passant capture, or a pawn promotion, or a castling, andthen type "moves" to see if that move shows up in the list.√ note that illegal moves are also shown, like capturing a king, or leaving a king in check, or castlingwhen the king is in check, or castling when the king passes through, or ends up on, a square that isattacked.

last update: Friday 10 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen06.htm

11 of 11 16-07-2012 10:57

Page 40: Ch1 Combine

Writing a chess program in99 steps

06 Reading user commands

Before we continue, let's get rid of some files we do not need. Right-click onstdafx.cpp in the solution explorer, and remove that file (and choose delete topermanently delete). Do the same with targetver.h , stdafx.h , and ReadMe.txt.

Our entry point, main(), is going to be quite short initially. All it will do is callreadCommand(), to read user input (see snippet below. So the first step is to delete allcontents of wingletx.cpp and make it look exactly like this:

step 2: wingletx.cpp

#include <iostream>#include "defines.h"#include "protos.h"#include "globals.h" int main(int argc, char *argv[]){ std::cout << WINGLET_PROG_VERSION << std::endl; readCommands(); return 0;}

You will notice that there are new header files to add: In the Solution Explorer,right-click on Header Files and add them. defines.h holds type definitions and constants:

step 3: defines.h

#ifndef WINGLET_DEFINES_H#define WINGLET_DEFINES_H #define WINGLET_PROG_VERSION "winglet 0.0, Copyright (C) 2011, Stef Luijten" #define MAX_CMD_BUFF 256 // Console command input buffer typedef int BOOLTYPE;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/06commands01.htm

1 of 5 15-07-2012 01:28

Page 41: Ch1 Combine

#endif

protos.h contains the prototypes of all functions (we'll keep them sorted alphabetically). This ensures that the compiler already knows how a function looks like, before actuallyhaving seen the code. We'll start with prototyping the two first functions:

step 4: protos.h

#ifndef WINGLET_PROTOS_H#define WINGLET_PROTOS_H BOOLTYPE doCommand(const char *buf);void readCommands(); #endif

The way user input is being read and processed might seem a bit peculiar at first sight.Later, when we connect the engine to Winboard, this method will prove handy, becausewe only have to add more (Winboard) commands without the need to change commandprocessing. The two functions will be inserted in a new source code file, commands.cpp.

The 'help'-section in doCommand shows the user commands that we intend to implementinitially. The coding that actually executes these commands is not there yet, it will followlater.

You will notice that the prompt is fixed now, "wt> ". Later, when we have an internal boardrepresentation, the prompt is going to switch back and forth between "wt> " and "bl> ",depending on which side is to move next.

step 5: command.cpp

#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h" void readCommands(){ int nextc; { std::cout << "wt> "; } std::cout.flush(); // ===========================================================================// Read a command and call doCommand:// =========================================================================== while ((nextc = getc(stdin)) != EOF)

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/06commands01.htm

2 of 5 15-07-2012 01:28

Page 42: Ch1 Combine

{ if (nextc == '\n') { CMD_BUFF[CMD_BUFF_COUNT] = '\0'; while (CMD_BUFF_COUNT) { if (!doCommand(CMD_BUFF)) return; } { std::cout << "wt> "; } std::cout.flush(); } else { if (CMD_BUFF_COUNT >= MAX_CMD_BUFF-1) { std::cout << "Warning: command buffer full !! " <<std::endl; CMD_BUFF_COUNT = 0; } CMD_BUFF[CMD_BUFF_COUNT++] = nextc; } }} BOOLTYPE doCommand(const char *buf){ // =================================================================// return when command buffer is empty// ================================================================= if (!strcmp(buf, "")) { CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// help, h, or ?: show this help// ================================================================= if ((!strcmp(buf, "help")) || (!strcmp(buf, "h")) || (!strcmp(buf, "?"))) { std::cout << std::endl << "help:" << std::endl; std::cout << "black : BLACK to move" << std::endl; std::cout << "cc : play computer-to-computer " <<std::endl; std::cout << "d : display board " << std::endl; std::cout << "exit : exit program " << std::endl; std::cout << "eval : show static evaluation of thisposition" << std::endl; std::cout << "game : show game moves " << std::endl; std::cout << "go : computer next move " <<std::endl; std::cout << "help, h, or ? : show this help " << std::endl; std::cout << "info : display variables (for testingpurposes)" << std::endl; std::cout << "move e2e4, or h7h8q : enter a move (use this format)"<< std::endl; std::cout << "moves : show all legal moves" <<std::endl;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/06commands01.htm

3 of 5 15-07-2012 01:28

Page 43: Ch1 Combine

std::cout << "new : start new game" << std::endl; std::cout << "perf : benchmark a number of keyfunctions" << std::endl; std::cout << "perft n : calculate raw number of nodesfrom here, depth n " << std::endl; std::cout << "quit : exit program " << std::endl; std::cout << "r : rotate board " << std::endl; std::cout << "readfen filename n : reads #-th FEN position fromfilename" << std::endl; std::cout << "sd n : set the search depth to n" <<std::endl; std::cout << "setup : setup board... " << std::endl; std::cout << "undo : take back last move" <<std::endl; std::cout << "white : WHITE to move" << std::endl; std::cout << std::endl; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// exit or quit: exit program// ================================================================= if ((!strcmp(buf, "exit")) || (!strcmp(buf, "quit"))) { CMD_BUFF_COUNT = '\0'; return false; } // =================================================================// unknown command// ================================================================= std::cout << " command not implemented: " << buf << ", type 'help' formore info" << std::endl; CMD_BUFF_COUNT = '\0'; return true;}

step 6: globals.h

#ifndef WINGLET_GLOBALS_H#define WINGLET_GLOBALS_H #include "defines.h" char CMD_BUFF[MAX_CMD_BUFF];int CMD_BUFF_COUNT = 0; #endif

step 7: extglobals.h

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/06commands01.htm

4 of 5 15-07-2012 01:28

Page 44: Ch1 Combine

#ifndef WINGLET_EXTGLOBALS_H#define WINGLET_EXTGLOBALS_H #include "defines.h" extern char CMD_BUFF[];extern int CMD_BUFF_COUNT; #endif

At this point, the program will read user input, and displays help, or some informativemessage. The next task is to display the chessboard's start position, which means that wehave to implement the internal board representation.

√ there's not much to try out yet, the response is always the same...

last update: Monday 30 May 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/06commands01.htm

5 of 5 15-07-2012 01:28

Page 45: Ch1 Combine

Writing a chess program in99 steps

07 Internal representation of the chessboard - bitboards

My implementation of bitboards is based on articles from Robert Hyatt. The concept isquite simple: a 64-bit word is used to store the positions of e.g. all black pawns, or all whiterooks. Every square is represented by one bit in the 64-bit word. And since there are 12different pieces in chess, we need 12 bitboards to store a chess position:

whiteKing bitboard with the position bit of the white king set to 1 (and other bits setto 0).

1.

whiteQueens bitboard with the position bits of all white queens set to 1 (and otherbits set to 0), etc

2.

whiteRooks3.whiteBishops4.whiteKnights5.whitePawns6.blackKing7.blackQueens8.blackRooks9.blackBishops10.blackKnights11.blackPawns12.

We will also need to introduce a bit-numbering convention for our bitboards: bit #0 will be the rightmost bit in the 64-bit word (least significant bit, or LSB), andbit #63 will be the leftmost bit in the 64-bit word (most significant bit, or MSB). Furthermore, square a1 = bit #0, h1 = bit #7, a8 = bit #56 and h8 = bit #63, see figurebelow:

RANKS: 8 | 56 57 58 59 60 61 62 63 (MSB,

7 | 48 49 50 51 52 53 54 55 left) 6 | 40 41 42 43 44 45 46 47 5 | 32 33 34 35 36 37 38 39 4 | 24 25 26 27 28 29 30 31

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/07boardrep01.htm

1 of 5 15-07-2012 01:29

Page 46: Ch1 Combine

3 | 16 17 18 19 20 21 22 23 2 | 8 9 10 11 12 13 14 15 1 | (LSB, 0 1 2 3 4 5 6 7

right) ------------------------------------------- FILES: a b c d e f g h

If FILE and RANK are numbered from 1..8, then SQUARE (0..63) can be calculated asfollows: SQUARE = 8 * RANK + FILE - 9;

There are some fast bit-wise operations that we can perform on bitboards. For instance, ifwe want to have a bitboard of all white pieces, then we can get this by using the bit-wiseOR operator:

whitePieces = whiteKing | whiteQueens | whiteRooks |whiteBishops | whiteKnights | whitePawns;

Or all occupied squares:

occupiedSquares = whitePieces | blackPieces;

There are six bitwise operators in C++, that are used in combination with bitboards:& the AND operator, compares two values, and returns a value that has its bits set if, andonly if, the two values being compared both have their bits set.| the OR operator, compares two values, and returns a value that has its bits set if one orthe other value, or both, have their bits set.^ the XOR operator, compares two values, and returns a value that has its bits set if oneor the other value has its corresponding bits set, but not both.~ the Ones Complement, or Inversion, operator acts only on one value and inverts it,turning all ones into zeros, and all zeros into ones.>> the Right Shift operator, shifts the bits from the high bit (MSB) to the low bit (LSB), thenumber of bit positions specified.<< the Left Shift operator, shifts the bits from the low bit (LSB) to the high bit (MSB), thenumber of bit positions specified.

Bitwise setting & testing operations

Following is an overview of frequently used bitwise setting & testing operations:

Bitwise set bits:Mask has all bits setthat need to be set:Result |= Mask;

Bitwise set bits:

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/07boardrep01.htm

2 of 5 15-07-2012 01:29

Page 47: Ch1 Combine

Mask has all bitscleared that need to beset:Result |= ~Mask;

Bitwise clear bits:Mask has all bits setthat need to be cleared:Result &= ~Mask;

Bitwise clear bits:Mask has all bitscleared that need to becleared:Result &= Mask;

Logical test for bits set:Mask has bits set thatneed to be tested for all1:if (Test & Mask)== Mask)

1 = all Test bits are 1

0 = not all Test bits are1

Logical test for bits set:Mask has bits clearedthat need to be testedfor all 1:if ((Test & ~Mask)== ~Mask)

1 = all Test bits are 1

0 = not all Test bits are1

Logical test for bitscleared:Mask has bits set thatneed to be tested for all0:if ((~Test & Mask)== Mask)

1 = all Test bits are 0

0 = not all Test bits are0

Logical test for bitscleared:

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/07boardrep01.htm

3 of 5 15-07-2012 01:29

Page 48: Ch1 Combine

Mask has bits clearedthat need to be testedfor all 0:if ((~Test &~Mask) == ~Mask)

1 = all Test bits are 0

0 = not all Test bits are0

Bitboards offer speed advantages in two parts of the program. First (and this is the mainreason for using them) the move generator is going to be very fast. There are no loops toslide a piece down a diagonal, or rank/file as you will see later in the section on the movegenerator.

Second, bitboards offer speed improvements in the evaluation function. For example, wecan check if the white pawn on square e4 is passed with a single bitboard operation, thereis no need to loop over the squares d5, d6, d7, e5, e6, e7, f5, f6 and f7 to see if any squareis occupied by black pawns. The Board structure will contain the 12 bitboards, togetherwith additional variables for e.g. castling rights.

Some variables are going to be computed incrementally. "Incremental updating" means thata variable is updated during makeMove and unmakeMove. For instance, int Materialholds the total material balance on the board (pawn = 100, knight = 300, etc, using positivenumbers for white and negative numbers for black). If a piece is captured or promoted,Material will be updated accordingly. This information can be readily used in theevaluation function. Incrementally updating the material balance in makeMove may beadvantageous in case we want to know the material balance while traversing down thesearch tree, it's always kept up to date without having to do a full evaluation of the position.Another convenient array to introduce is int square[64]. This array will keep track ofthe contents of each square, and is useful if we want to know e.g. what piece is on squaree2.

Now we have a data structure that allows us to display the chess board, set up the board,and read a position from a FEN string, so this is all I am going to explain about bitboards;There will be more information on working with bitboards later.

step 8: board.h

#ifndef WINGLET_BOARD_H_#define WINGLET_BOARD_H_ #include "defines.h" struct Board{ BitMap whiteKing, whiteQueens, whiteRooks, whiteBishops, whiteKnights,whitePawns; BitMap blackKing, blackQueens, blackRooks, blackBishops, blackKnights,blackPawns; BitMap whitePieces, blackPieces, occupiedSquares;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/07boardrep01.htm

4 of 5 15-07-2012 01:29

Page 49: Ch1 Combine

unsigned char nextMove; // WHITE_MOVE or BLACK_MOVE unsigned char castleWhite; // White's castle status, CANCASTLEOO = 1,CANCASTLEOOO = 2 unsigned char castleBlack; // Black's castle status, CANCASTLEOO = 1,CANCASTLEOOO = 2 int epSquare; // En-passant target square after doublepawn move int fiftyMove; // Moves since the last pawn move orcapture // additional variables: int Material; // incrementally updated, total material onboard, // in centipawns, from white’s side of view int square[64]; // incrementally updated, this array isusefull if we want to // probe what kind of piece is on aparticular square. BOOLTYPE viewRotated; // only used for displaying the board. TRUEor FALSE. void init(); void initFromSquares(int input[64], unsigned char next, int fiftyM, intcastleW, int castleB, int epSq); void initFromFen(char fen[], char fencolor[], char fencastling[], charfenenpassant[], int fenhalfmoveclock, int fenfullmovenumber); void display();}; #endif

last update: Saturday 04 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/07boardrep01.htm

5 of 5 15-07-2012 01:29

Page 50: Ch1 Combine

Writing a chess program in 99 steps

08 Displaying the position

Once the board structure is defined, it becomes almost trivial to display it on the console. We introduce a number ofnew global variables, most are just meant to keep the code understandable:

step 9: globals.h

#ifndef WINGLET_GLOBALS_H#define WINGLET_GLOBALS_H #include "defines.h"#include "board.h" char CMD_BUFF[MAX_CMD_BUFF];int CMD_BUFF_COUNT = 0; Board board; extern const int A8 = 56; extern const int B8 = 57; extern const int C8 = 58; extern const int D8 = 59;extern const int E8 = 60; extern const int F8 = 61; extern const int G8 = 62; extern const int H8 = 63;extern const int A7 = 48; extern const int B7 = 49; extern const int C7 = 50; extern const int D7 = 51;extern const int E7 = 52; extern const int F7 = 53; extern const int G7 = 54; extern const int H7 = 55;extern const int A6 = 40; extern const int B6 = 41; extern const int C6 = 42; extern const int D6 = 43;extern const int E6 = 44; extern const int F6 = 45; extern const int G6 = 46; extern const int H6 = 47;extern const int A5 = 32; extern const int B5 = 33; extern const int C5 = 34; extern const int D5 = 35;extern const int E5 = 36; extern const int F5 = 37; extern const int G5 = 38; extern const int H5 = 39;extern const int A4 = 24; extern const int B4 = 25; extern const int C4 = 26; extern const int D4 = 27;extern const int E4 = 28; extern const int F4 = 29; extern const int G4 = 30; extern const int H4 = 31;extern const int A3 = 16; extern const int B3 = 17; extern const int C3 = 18; extern const int D3 = 19;extern const int E3 = 20; extern const int F3 = 21; extern const int G3 = 22; extern const int H3 = 23;extern const int A2 = 8; extern const int B2 = 9; extern const int C2 = 10; extern const int D2 = 11;extern const int E2 = 12; extern const int F2 = 13; extern const int G2 = 14; extern const int H2 = 15;extern const int A1 = 0; extern const int B1 = 1; extern const int C1 = 2; extern const int D1 = 3;extern const int E1 = 4; extern const int F1 = 5; extern const int G1 = 6; extern const int H1 = 7; const char* SQUARENAME[64] = {"a1","b1","c1","d1","e1","f1","g1","h1", "a2","b2","c2","d2","e2","f2","g2","h2", "a3","b3","c3","d3","e3","f3","g3","h3", "a4","b4","c4","d4","e4","f4","g4","h4", "a5","b5","c5","d5","e5","f5","g5","h5", "a6","b6","c6","d6","e6","f6","g6","h6", "a7","b7","c7","d7","e7","f7","g7","h7", "a8","b8","c8","d8","e8","f8","g8","h8"}; extern const int FILES[64] = { 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}; extern const int RANKS[64] = { 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8};

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

1 of 11 15-07-2012 01:29

Page 51: Ch1 Combine

// Identifier of next move:extern const unsigned char WHITE_MOVE = 0; extern const unsigned char BLACK_MOVE = 1; // Piece identifiers, 4 bits each.// Usefull bitwise properties of this numbering scheme:// white = 0..., black = 1..., sliding = .1.., nonsliding = .0..// rank/file sliding pieces = .11., diagonally sliding pieces = .1.1// pawns and kings (without color bits), are < 3// major pieces (without color bits set), are > 5// minor and major pieces (without color bits set), are > 2extern const unsigned char EMPTY = 0; // 0000extern const unsigned char WHITE_PAWN = 1; // 0001extern const unsigned char WHITE_KING = 2; // 0010extern const unsigned char WHITE_KNIGHT = 3; // 0011extern const unsigned char WHITE_BISHOP = 5; // 0101extern const unsigned char WHITE_ROOK = 6; // 0110extern const unsigned char WHITE_QUEEN = 7; // 0111extern const unsigned char BLACK_PAWN = 9; // 1001extern const unsigned char BLACK_KING = 10; // 1010extern const unsigned char BLACK_KNIGHT = 11; // 1011extern const unsigned char BLACK_BISHOP = 13; // 1101extern const unsigned char BLACK_ROOK = 14; // 1110extern const unsigned char BLACK_QUEEN = 15; // 1111 const char* PIECENAMES[16] = {" ","P ","K ","N "," ","B ","R ","Q ", " ","P*","K*","N*"," ","B*","R*","Q*"}; BitMap BITSET[64];int BOARDINDEX[9][9]; // index 0 is not used, only 1..8. // Value of material, in centipawns:extern const int PAWN_VALUE = 100;extern const int KNIGHT_VALUE = 300;extern const int BISHOP_VALUE = 300;extern const int ROOK_VALUE = 500;extern const int QUEEN_VALUE = 900;extern const int KING_VALUE = 9999;extern const int CHECK_MATE = KING_VALUE; // used in Eugene Nalimov's bitScanReverseint MS1BTABLE[256]; // Used for castling:unsigned char CANCASTLEOO = 1;unsigned char CANCASTLEOOO = 2;unsigned int WHITE_OOO_CASTL;unsigned int BLACK_OOO_CASTL;unsigned int WHITE_OO_CASTL;unsigned int BLACK_OO_CASTL; #endif

So now we need to update extglobals.h accordingly:

step 10: extglobals.h

#ifndef WINGLET_EXTGLOBALS_H#define WINGLET_EXTGLOBALS_H #include "defines.h"#include "board.h" extern char CMD_BUFF[];extern int CMD_BUFF_COUNT; extern Board board; extern const int A8; extern const int B8; extern const int C8; extern const int D8;extern const int E8; extern const int F8; extern const int G8; extern const int H8;extern const int A7; extern const int B7; extern const int C7; extern const int D7;extern const int E7; extern const int F7; extern const int G7; extern const int H7;extern const int A6; extern const int B6; extern const int C6; extern const int D6;extern const int E6; extern const int F6; extern const int G6; extern const int H6;extern const int A5; extern const int B5; extern const int C5; extern const int D5;extern const int E5; extern const int F5; extern const int G5; extern const int H5;extern const int A4; extern const int B4; extern const int C4; extern const int D4;extern const int E4; extern const int F4; extern const int G4; extern const int H4;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

2 of 11 15-07-2012 01:29

Page 52: Ch1 Combine

extern const int A3; extern const int B3; extern const int C3; extern const int D3;extern const int E3; extern const int F3; extern const int G3; extern const int H3;extern const int A2; extern const int B2; extern const int C2; extern const int D2;extern const int E2; extern const int F2; extern const int G2; extern const int H2;extern const int A1; extern const int B1; extern const int C1; extern const int D1;extern const int E1; extern const int F1; extern const int G1; extern const int H1; extern const char* SQUARENAME[]; extern const int FILES[];extern const int RANKS[]; extern const unsigned char WHITE_MOVE; extern const unsigned char BLACK_MOVE; extern const unsigned char EMPTY;extern const unsigned char WHITE_KNIGHT;extern const unsigned char WHITE_PAWN;extern const unsigned char WHITE_KING;extern const unsigned char WHITE_BISHOP;extern const unsigned char WHITE_ROOK;extern const unsigned char WHITE_QUEEN;extern const unsigned char BLACK_KNIGHT;extern const unsigned char BLACK_PAWN;extern const unsigned char BLACK_KING;extern const unsigned char BLACK_BISHOP;extern const unsigned char BLACK_ROOK;extern const unsigned char BLACK_QUEEN; extern const char* PIECENAMES[]; extern BitMap BITSET[];extern int BOARDINDEX[9][9]; extern const int PAWN_VALUE;extern const int KNIGHT_VALUE;extern const int BISHOP_VALUE;extern const int ROOK_VALUE;extern const int QUEEN_VALUE;extern const int KING_VALUE;extern const int CHECK_MATE; extern int MS1BTABLE[]; extern unsigned char CANCASTLEOO;extern unsigned char CANCASTLEOOO;extern unsigned int WHITE_OOO_CASTL;extern unsigned int BLACK_OOO_CASTL;extern unsigned int WHITE_OO_CASTL;extern unsigned int BLACK_OO_CASTL; #endif

Also, some new types are defined, to accommodate working with 64-bit unsigned words and bitboards:

step 11: defines.h

#ifndef WINGLET_DEFINES_H#define WINGLET_DEFINES_H #define WINGLET_PROG_VERSION "winglet 0.0, Copyright (C) 2011, Stef Luijten" #define MAX_CMD_BUFF 256 // Console command input buffer typedef unsigned long long U64;typedef unsigned long long BitMap;typedef short SHORTINT;typedef unsigned short USHORTINT;typedef int BOOLTYPE; #endif

Next code snippet is a new source file, bitops.cpp. This file contains bit-oriented operations, like counting thenumber of bits set (bitCnt), or searching the first, least significant, bit set (firstOne) or the last, most significant, bitset (lastOne). These are the sort of operations we need to do with bitboards, mainly in the move generator and theevaluation function.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

3 of 11 15-07-2012 01:29

Page 53: Ch1 Combine

Though, even to just display the bitboards as a chess position that is understandable for humans, we need to havethese bit-operations available. I will not explain how they exactly work, these functions are advanced bit programmingtechniques that I just copied from the internet (after testing!). In case you are interested in the mathematicalbackground of the algorithms, there are links and references provided in the source code. displayBitmap is an add-on that enables you to display a bitmap as a chessboard on the console window, in caseyou want to examine some of the results of bitmap operations.

step 12: bitops.cpp

#include <iostream>#include <iomanip>#include "defines.h"#include "extglobals.h" unsigned int bitCnt(U64 bitmap){ // MIT HAKMEM algorithm, see http://graphics.stanford.edu/~seander/bithacks.html static const U64 M1 = 0x5555555555555555; // 1 zero, 1 one ... static const U64 M2 = 0x3333333333333333; // 2 zeros, 2 ones ... static const U64 M4 = 0x0f0f0f0f0f0f0f0f; // 4 zeros, 4 ones ... static const U64 M8 = 0x00ff00ff00ff00ff; // 8 zeros, 8 ones ... static const U64 M16 = 0x0000ffff0000ffff; // 16 zeros, 16 ones ... static const U64 M32 = 0x00000000ffffffff; // 32 zeros, 32 ones bitmap = (bitmap & M1 ) + ((bitmap >> 1) & M1 ); //put count of each 2 bits into those 2 bits bitmap = (bitmap & M2 ) + ((bitmap >> 2) & M2 ); //put count of each 4 bits into those 4 bits bitmap = (bitmap & M4 ) + ((bitmap >> 4) & M4 ); //put count of each 8 bits into those 8 bits bitmap = (bitmap & M8 ) + ((bitmap >> 8) & M8 ); //put count of each 16 bits into those 16 bits bitmap = (bitmap & M16) + ((bitmap >> 16) & M16); //put count of each 32 bits into those 32 bits bitmap = (bitmap & M32) + ((bitmap >> 32) & M32); //put count of each 64 bits into those 64 bits return (int)bitmap;} unsigned int firstOne(U64 bitmap){ // De Bruijn Multiplication, see http://chessprogramming.wikispaces.com/BitScan // don't use this if bitmap = 0 static const int INDEX64[64] = { 63, 0, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31, 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5 }; static const U64 DEBRUIJN64 = U64(0x07EDD5E59A4E28C2); // here you would get a warming: "unary minus operator applied to unsigned type", // that's intended and OK so I'll disable it#pragma warning (disable: 4146) return INDEX64[((bitmap & -bitmap) * DEBRUIJN64) >> 58]; } unsigned int lastOne(U64 bitmap){ // this is Eugene Nalimov's bitScanReverse // use firstOne if you can, it is faster than lastOne. // don't use this if bitmap = 0 int result = 0; if (bitmap > 0xFFFFFFFF) { bitmap >>= 32; result = 32; } if (bitmap > 0xFFFF) { bitmap >>= 16; result += 16; } if (bitmap > 0xFF) {

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

4 of 11 15-07-2012 01:29

Page 54: Ch1 Combine

bitmap >>= 8; result += 8; } return result + MS1BTABLE[bitmap];} void displayBitmap(BitMap in){ int i, rank, file; char boardc[64]; for (i = 0 ; i < 64 ; i++) { if (in & BITSET[i]) boardc[i] = '1'; else boardc[i] = '.'; } std::cout << std::endl << "as binary integer:" << std::endl; for (i = 63 ; i >= 0 ; i--) std::cout << boardc[i]; std::cout << std::endl << " firstOne = " << firstOne(in) << ", lastOne = " << lastOne(in) << ",bitCnt = " << bitCnt(in) << std::endl; std::cout << std::endl << std::endl; if (board.viewRotated) { std::cout << " hgfedcba" << std::endl << std::endl; for (rank = 1 ; rank <= 8; rank++) { std::cout << " "; for (file = 8 ; file >= 1; file--) { std::cout << boardc[BOARDINDEX[file][rank]]; } std::cout << " " << rank << std::endl; } } else { for (rank = 8 ; rank >= 1; rank--) { std::cout << " " << rank << " "; for (file = 1 ; file <= 8; file++) { std::cout << boardc[BOARDINDEX[file][rank]]; } std::cout << std::endl; } std::cout << std::endl << " abcdefgh" << std::endl; } std::cout << std::endl; return;}

We need to update protos.h with the new functions:

step 13: protos.h

#ifndef WINGLET_PROTOS_H#define WINGLET_PROTOS_H unsigned int bitCnt(BitMap);void dataInit();void displayBitmap(BitMap);BOOLTYPE doCommand(const char *);unsigned int firstOne(BitMap);void info();unsigned int lastOne(BitMap);void readCommands(); #endif

And, to initialize the global constants, our main function will now call dataInit, a new function:

step 14: wingletx.cpp

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

5 of 11 15-07-2012 01:29

Page 55: Ch1 Combine

#include <iostream>#include "defines.h"#include "protos.h"#include "globals.h" int main(int argc, char *argv[]){ std::cout << WINGLET_PROG_VERSION << std::endl; dataInit(); readCommands(); return 0;}

We have a board, so we can have the user prompt switch back and forth between 'wt> ' and 'bk> ', depending onwhich side is to move next. There a number of new commands to be added in doCommand(), mainly related to theintroduction of the Board structure:

step 15: command.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE#define _CRT_SECURE_NO_DEPRECATE 1#endif#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h"#include "board.h" void readCommands(){ int nextc; if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); // ===========================================================================// Read a command and call doCommand:// =========================================================================== while ((nextc = getc(stdin)) != EOF) { if (nextc == '\n') { CMD_BUFF[CMD_BUFF_COUNT] = '\0'; while (CMD_BUFF_COUNT) { if (!doCommand(CMD_BUFF)) return; } if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); } else { if (CMD_BUFF_COUNT >= MAX_CMD_BUFF-1) { std::cout << "Warning: command buffer full !! " << std::endl; CMD_BUFF_COUNT = 0; } CMD_BUFF[CMD_BUFF_COUNT++] = nextc; } }} BOOLTYPE doCommand(const char *buf){

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

6 of 11 15-07-2012 01:29

Page 56: Ch1 Combine

// =================================================================// return when command buffer is empty// ================================================================= if (!strcmp(buf, "")) { CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// help, h, or ?: show this help// ================================================================= if ((!strcmp(buf, "help")) || (!strcmp(buf, "h")) || (!strcmp(buf, "?"))) { std::cout << std::endl << "help:" << std::endl; std::cout << "black : BLACK to move" << std::endl; std::cout << "cc : play computer-to-computer " << std::endl; std::cout << "d : display board " << std::endl; std::cout << "exit : exit program " << std::endl; std::cout << "eval : show static evaluation of this position" << std::endl; std::cout << "game : show game moves " << std::endl; std::cout << "go : computer next move " << std::endl; std::cout << "help, h, or ? : show this help " << std::endl; std::cout << "info : display variables (for testing purposes)" << std::endl; std::cout << "move e2e4, or h7h8q : enter a move (use this format)" << std::endl; std::cout << "moves : show all legal moves" << std::endl; std::cout << "new : start new game" << std::endl; std::cout << "perf : benchmark a number of key functions" << std::endl; std::cout << "perft n : calculate raw number of nodes from here, depth n " <<std::endl; std::cout << "quit : exit program " << std::endl; std::cout << "r : rotate board " << std::endl; std::cout << "readfen filename n : reads #-th FEN position from filename" << std::endl; std::cout << "sd n : set the search depth to n" << std::endl; std::cout << "setup : setup board... " << std::endl; std::cout << "undo : take back last move" << std::endl; std::cout << "white : WHITE to move" << std::endl; std::cout << std::endl; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// black: black to move// ================================================================= if (!strcmp(buf, "black")) { board.nextMove = BLACK_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// d: display board// ================================================================= if (!strcmp(buf, "d")) { board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// exit or quit: exit program// ================================================================= if ((!strcmp(buf, "exit")) || (!strcmp(buf, "quit"))) { CMD_BUFF_COUNT = '\0'; return false; } // =================================================================// info: display variables (for testing purposes)// ================================================================= if (!strcmp(buf, "info")) { info(); CMD_BUFF_COUNT = '\0'; return true; }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

7 of 11 15-07-2012 01:29

Page 57: Ch1 Combine

// =================================================================// new: start new game// ================================================================= if (!strcmp(buf, "new")) { dataInit(); board.init(); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// r: rotate board// ================================================================= if (!strcmp(buf, "r")) { board.viewRotated = !board.viewRotated; board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// white: white to move// ================================================================= if (!strcmp(buf, "white")) { board.nextMove = WHITE_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// unknown command// ================================================================= std::cout << " command not implemented: " << buf << ", type 'help' for more info" << std::endl; CMD_BUFF_COUNT = '\0'; return true;}

Another new source file, data.cpp, will initialize global constants. You can freely change and play around withinfo(), it doesn't serve any other function than testing or verify parts of the program. So go ahead in case you want tocheck out e.g. how some of the bitboards look like, the source code below will display(board.blackKnights | board.whitePawns) :

step 16: data.cpp

#include <iostream>#include <iomanip>#include "defines.h"#include "protos.h"#include "extglobals.h" void dataInit(){ int i, rank, file; // ===========================================================================// BITSET has only one bit set:// =========================================================================== BITSET[0] = 0x1; for (i = 1; i < 64 ; i++) { BITSET[i] = BITSET[i-1] << 1; } // ===========================================================================// BOARDINDEX is used to translate [file][rank] to [square],// Note that file is from 1..8 and rank from 1..8 (not starting from 0)// =========================================================================== for (rank = 0 ; rank < 9; rank++) { for (file = 0 ; file < 9; file++) { BOARDINDEX[file][rank] = (rank-1) * 8 + file - 1; } }

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

8 of 11 15-07-2012 01:29

Page 58: Ch1 Combine

// ===========================================================================// Initialize the board// =========================================================================== board.init(); // ===========================================================================// Initialize MS1BTABLE, used in lastOne (see bitops.cpp)// =========================================================================== for (i = 0; i < 256; i++) { MS1BTABLE[i] = ( (i > 127) ? 7 : (i > 63) ? 6 : (i > 31) ? 5 : (i > 15) ? 4 : (i > 7) ? 3 : (i > 3) ? 2 : (i > 1) ? 1 : 0 ); } return;} void info(){ // your playground... display variables - meant for testing/verification purposes only std::cout << std::endl << "============ info start ==============" << std::endl; std::cout << "size of board, in bytes = " << sizeof(board) << std::endl; std::cout << "Material value = " << board.Material << std::endl; std::cout << "White castling rights = " << int(board.castleWhite) << std::endl; std::cout << "Black castling rights = " << int(board.castleBlack) << std::endl; std::cout << "En-passant square = " << board.epSquare << std::endl; std::cout << "Fifty move count = " << board.fiftyMove << std::endl; std::cout << "bitCnt of white pawns = " << bitCnt(board.whitePawns) << std::endl; std::cout << std::endl << "bitmap of blackKnights | board.whitePawns:" << std::endl; displayBitmap(board.blackKnights | board.whitePawns); std::cout << "============ info end ================" << std::endl << std::endl; return;}

Last but not least, we add board.cpp , the implementation of functions that belong to the board structure, likeinitialization and displaying the board on the console window:

step 17: board.cpp

#include <iostream>#include <iomanip>#include "defines.h"#include "protos.h"#include "extglobals.h"#include "board.h" void Board::init(){ viewRotated = false; for (int i = 0; i < 64; i++) square[i] = EMPTY; square[E1] = WHITE_KING; square[D1] = WHITE_QUEEN; square[A1] = WHITE_ROOK; square[H1] = WHITE_ROOK; square[B1] = WHITE_KNIGHT; square[G1] = WHITE_KNIGHT; square[C1] = WHITE_BISHOP; square[F1] = WHITE_BISHOP; square[A2] = WHITE_PAWN; square[B2] = WHITE_PAWN; square[C2] = WHITE_PAWN; square[D2] = WHITE_PAWN; square[E2] = WHITE_PAWN; square[F2] = WHITE_PAWN; square[G2] = WHITE_PAWN; square[H2] = WHITE_PAWN;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

9 of 11 15-07-2012 01:29

Page 59: Ch1 Combine

square[E8] = BLACK_KING; square[D8] = BLACK_QUEEN; square[A8] = BLACK_ROOK; square[H8] = BLACK_ROOK; square[B8] = BLACK_KNIGHT; square[G8] = BLACK_KNIGHT; square[C8] = BLACK_BISHOP; square[F8] = BLACK_BISHOP; square[A7] = BLACK_PAWN; square[B7] = BLACK_PAWN; square[C7] = BLACK_PAWN; square[D7] = BLACK_PAWN; square[E7] = BLACK_PAWN; square[F7] = BLACK_PAWN; square[G7] = BLACK_PAWN; square[H7] = BLACK_PAWN; initFromSquares(square, WHITE_MOVE, 0, CANCASTLEOO + CANCASTLEOOO, CANCASTLEOO + CANCASTLEOOO, 0); return;} void Board::initFromSquares(int input[64], unsigned char next, int fiftyM, int castleW, int castleB, intepSq){ // sets up the board variables according to the information found in // the input[64] array // All board & game initializations are done through this function (including readfen and setup). int i; // bitboards whiteKing = 0; whiteQueens = 0; whiteRooks = 0; whiteBishops = 0; whiteKnights = 0; whitePawns = 0; blackKing = 0; blackQueens = 0; blackRooks = 0; blackBishops = 0; blackKnights = 0; blackPawns = 0; whitePieces = 0; blackPieces = 0; occupiedSquares = 0; // populate the 12 bitboard: for (i = 0; i < 64; i++) { square[i] = input[i]; if (square[i] == WHITE_KING) whiteKing = whiteKing | BITSET[i]; if (square[i] == WHITE_QUEEN) whiteQueens = whiteQueens | BITSET[i]; if (square[i] == WHITE_ROOK) whiteRooks = whiteRooks | BITSET[i]; if (square[i] == WHITE_BISHOP) whiteBishops = whiteBishops | BITSET[i]; if (square[i] == WHITE_KNIGHT) whiteKnights = whiteKnights | BITSET[i]; if (square[i] == WHITE_PAWN) whitePawns = whitePawns | BITSET[i]; if (square[i] == BLACK_KING) blackKing = blackKing | BITSET[i]; if (square[i] == BLACK_QUEEN) blackQueens = blackQueens | BITSET[i]; if (square[i] == BLACK_ROOK) blackRooks = blackRooks | BITSET[i]; if (square[i] == BLACK_BISHOP) blackBishops = blackBishops | BITSET[i]; if (square[i] == BLACK_KNIGHT) blackKnights = blackKnights | BITSET[i]; if (square[i] == BLACK_PAWN) blackPawns = blackPawns | BITSET[i]; } whitePieces = whiteKing | whiteQueens | whiteRooks | whiteBishops | whiteKnights | whitePawns; blackPieces = blackKing | blackQueens | blackRooks | blackBishops | blackKnights | blackPawns; occupiedSquares = whitePieces | blackPieces; nextMove = next; castleWhite = castleW; castleBlack = castleB; epSquare = epSq; fiftyMove = fiftyM; Material = bitCnt(whitePawns) * PAWN_VALUE + bitCnt(whiteKnights) * KNIGHT_VALUE + bitCnt(whiteBishops) * BISHOP_VALUE + bitCnt(whiteRooks) * ROOK_VALUE + bitCnt(whiteQueens) * QUEEN_VALUE; Material -= (bitCnt(blackPawns) * PAWN_VALUE + bitCnt(blackKnights) * KNIGHT_VALUE +

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

10 of 11 15-07-2012 01:29

Page 60: Ch1 Combine

bitCnt(blackBishops) * BISHOP_VALUE + bitCnt(blackRooks) * ROOK_VALUE + bitCnt(blackQueens) * QUEEN_VALUE); return;} void Board::display(){ int rank, file; std::cout << std::endl; { if (!viewRotated) { for (rank = 8; rank >= 1; rank--) { std::cout << " +---+---+---+---+---+---+---+---+" << std::endl; std::cout << std::setw(3) << rank << " |"; for (file = 1; file <= 8; file++) { std::cout << " " << PIECENAMES[square[BOARDINDEX[file][rank]]] << "|"; } std::cout << std::endl; } std::cout << " +---+---+---+---+---+---+---+---+" << std::endl; std::cout << " a b c d e f g h" << std::endl << std::endl; } else { std::cout << " h g f e d c b a" << std::endl; for (rank = 1; rank <= 8; rank++) { std::cout << " +---+---+---+---+---+---+---+---+" << std::endl; std::cout << " |"; for (file = 8; file >= 1; file--) { std::cout << " " << PIECENAMES[square[BOARDINDEX[file][rank]]] << "|"; } std::cout << std::setw(3) << rank << std::endl; } std::cout << " +---+---+---+---+---+---+---+---+" << std::endl << std::endl; } } return;}

√ try "d" to display the board, and "r" to rotate the board√ try "black", or "white", changing the side to move and the command prompt will change accordingly

last update: Monday 30 May 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/08display01.htm

11 of 11 15-07-2012 01:29

Page 61: Ch1 Combine

Writing a chess program in 99steps

09 Reading a FEN string

At this point it's good to have a function to read a chess position from a pgn-file. When we have a movegenerator, being able to read and setup positions will make the testing of the move generator a lot easier.

The code below opens a file, reads the numberth chess position found, puts that information in theboard.square-array and calls board.initFromSquares to initialize the board.

step 18: readfen.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE // suppress MS security warnings#define _CRT_SECURE_NO_DEPRECATE 1#endif#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h" BOOLTYPE readFen(char *filename, int number){ int numberf; char s[180]; char fenwhite[80]; char fenblack[80]; char fen[100]; char fencolor[2]; char fencastling[5]; char fenenpassant[3]; char temp[80]; int fenhalfmoveclock; int fenfullmovenumber; BOOLTYPE returnValue; FILE * fp; returnValue = false; if (number <= 0) return returnValue; // open the file for read and scan through until we find the number-th position: fp=fopen(filename, "rt"); if (fp != NULL) { numberf = 0; while (fscanf(fp, "%s", s) != EOF) { if (!strcmp(s, "[White")) { fscanf(fp, "%s", fenwhite); // remove first (") and last two characters ("]) from fenwhite: strcpy(temp, ""); strncat(temp, fenwhite, strlen(fenwhite)-2); strcpy(temp, temp+1); strcpy(fenwhite, temp); } if (!strcmp(s, "[Black")) { fscanf(fp, "%s", fenblack);

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

1 of 8 15-07-2012 01:30

Page 62: Ch1 Combine

// remove first (") and last two characters ("]) from fenblack: strcpy(temp, ""); strncat(temp, fenblack, strlen(fenblack)-2); strcpy(temp, temp+1); strcpy(fenblack, temp); } if (!strcmp(s, "[FEN")) { // position found, so increment numberf. // we already have fenwhite and fenblack. numberf++; if (numberf == number) { fscanf(fp, "%s", fen); fscanf(fp, "%s", fencolor); // b or w fscanf(fp, "%s", fencastling); // -, or KQkq fscanf(fp, "%s", fenenpassant); // -, or e3, or b6, etc fscanf(fp, "%d", &fenhalfmoveclock); // int, used for the fiftymove draw rule fscanf(fp, "%d", &fenfullmovenumber); // int. start with 1, Itis incremented after move by Black std::cout << std::endl << "winglet> fen #" << numberf << " in "<< filename << ":" << std::endl << std::endl; std::cout << " White: " << fenwhite << std::endl; std::cout << " Black: " << fenblack << std::endl; std::cout << " " << &fen[1] << std::endl; if (fencolor[0] == 'w') { std::cout << " wt to move next" << std::endl; } else { std::cout << " bl to move next" << std::endl; } std::cout << " Castling: " << fencastling << std::endl; std::cout << " EP square: " << fenenpassant << std::endl; std::cout << " Fifty move count: " << fenhalfmoveclock <<std::endl; std::cout << " Move number: " << fenfullmovenumber << std::endl<< std::endl; } } } if (numberf < number) { printf("winglet> only %d fens present in %s, fen #%d not found\n", numberf, filename, number); returnValue = false; } else { setupFen(fen, fencolor, fencastling, fenenpassant, fenhalfmoveclock,fenfullmovenumber); returnValue = true; } fclose(fp); } else { printf("winglet> error opening file: %s\n", filename); returnValue = false; } return returnValue;} void setupFen(char *fen, char *fencolor, char *fencastling, char *fenenpassant, intfenhalfmoveclock, int fenfullmovenumber){ int i, file, rank, counter, piece; int whiteCastle, blackCastle, next, epsq; piece = 0; for (i = 0; i < 64; i++)

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

2 of 8 15-07-2012 01:30

Page 63: Ch1 Combine

{ board.square[i] = EMPTY; } // loop over the FEN string characters, and populate board.square[] // i is used as index for the FEN string // counter is the index for board.square[], 0..63 // file and rank relate to the position on the chess board, 1..8 // There is no error/legality checking on the FEN string!! file = 1; rank = 8; i = 0; counter = 0; while ((counter < 64) && (fen[i] != '\0')) { // '1' through '8': if (((int) fen[i] > 48) && ((int) fen[i] < 57)) { file+= (int) fen[i] - 48; counter+= (int) fen[i] - 48; } else // other characters: { switch (fen[i]) { case '/': rank--; file = 1; break; case 'P': board.square[BOARDINDEX[file][rank]] = WHITE_PAWN; file += 1; counter += 1; break; case 'N': board.square[BOARDINDEX[file][rank]] = WHITE_KNIGHT; file += 1; counter += 1; break; case 'B': board.square[BOARDINDEX[file][rank]] = WHITE_BISHOP; file += 1; counter += 1; break; case 'R': board.square[BOARDINDEX[file][rank]] = WHITE_ROOK; file += 1; counter += 1; break; case 'Q': board.square[BOARDINDEX[file][rank]] = WHITE_QUEEN; file += 1; counter += 1; break; case 'K': board.square[BOARDINDEX[file][rank]] = WHITE_KING; file += 1; counter += 1; break; case 'p': board.square[BOARDINDEX[file][rank]] = BLACK_PAWN; file += 1; counter += 1; break; case 'n': board.square[BOARDINDEX[file][rank]] = BLACK_KNIGHT;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

3 of 8 15-07-2012 01:30

Page 64: Ch1 Combine

file += 1; counter += 1; break; case 'b': board.square[BOARDINDEX[file][rank]] = BLACK_BISHOP; file += 1; counter += 1; break; case 'r': board.square[BOARDINDEX[file][rank]] = BLACK_ROOK; file += 1; counter += 1; break; case 'q': board.square[BOARDINDEX[file][rank]] = BLACK_QUEEN; file += 1; counter += 1; break; case 'k': board.square[BOARDINDEX[file][rank]] = BLACK_KING; file += 1; counter += 1; break; default: break; } } i++; } next = WHITE_MOVE; if (fencolor[0] == 'b') next = BLACK_MOVE; whiteCastle = 0; blackCastle = 0; if (strstr(fencastling, "K")) whiteCastle += CANCASTLEOO; if (strstr(fencastling, "Q")) whiteCastle += CANCASTLEOOO; if (strstr(fencastling, "k")) blackCastle += CANCASTLEOO; if (strstr(fencastling, "q")) blackCastle += CANCASTLEOOO; if (strstr(fenenpassant, "-")) { epsq = 0; } else { // translate a square coordinate (as string) to int (eg 'e3' to 20): epsq = ((int) fenenpassant[0] - 96) + 8 * ((int) fenenpassant[1] - 48) - 9; } board.initFromSquares(board.square, next, fenhalfmoveclock, whiteCastle , blackCastle ,epsq); }

readFen and setupFen need to be added in protos.h

step 19: protos.h

#ifndef WINGLET_PROTOS_H#define WINGLET_PROTOS_H unsigned int bitCnt(BitMap);void dataInit();void displayBitmap(BitMap);BOOLTYPE doCommand(const char *);unsigned int firstOne(BitMap);void info();unsigned int lastOne(BitMap);void readCommands();

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

4 of 8 15-07-2012 01:30

Page 65: Ch1 Combine

BOOLTYPE readFen(char *, int);void setupFen(char *, char *, char *, char *, int , int ); #endif

There's some changes that we need to make in commands.cpp. First, add the_CRT_SECURE_NODEPRECATE definition at the beginning of the file. It will suppress security warnings thatdon't make sense. Apparently, Microsoft's intention is to have you use their (non-portable) versions offunctions.

The call to readFen is also added in doCommand:

step 20: command.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE#define _CRT_SECURE_NO_DEPRECATE 1#endif#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h"#include "board.h" void readCommands(){ int nextc; if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); // ===========================================================================// Read a command and call doCommand:// =========================================================================== while ((nextc = getc(stdin)) != EOF) { if (nextc == '\n') { CMD_BUFF[CMD_BUFF_COUNT] = '\0'; while (CMD_BUFF_COUNT) { if (!doCommand(CMD_BUFF)) return; } if (board.nextMove == WHITE_MOVE) { std::cout << "wt> "; } else { std::cout << "bl> "; } std::cout.flush(); } else { if (CMD_BUFF_COUNT >= MAX_CMD_BUFF-1) { std::cout << "Warning: command buffer full !! " << std::endl; CMD_BUFF_COUNT = 0; } CMD_BUFF[CMD_BUFF_COUNT++] = nextc; } }}

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

5 of 8 15-07-2012 01:30

Page 66: Ch1 Combine

BOOLTYPE doCommand(const char *buf){ char userinput[80]; int number; // =================================================================// return when command buffer is empty// ================================================================= if (!strcmp(buf, "")) { CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// help, h, or ?: show this help// ================================================================= if ((!strcmp(buf, "help")) || (!strcmp(buf, "h")) || (!strcmp(buf, "?"))) { std::cout << std::endl << "help:" << std::endl; std::cout << "black : BLACK to move" << std::endl; std::cout << "cc : play computer-to-computer " << std::endl; std::cout << "d : display board " << std::endl; std::cout << "exit : exit program " << std::endl; std::cout << "eval : show static evaluation of this position" <<std::endl; std::cout << "game : show game moves " << std::endl; std::cout << "go : computer next move " << std::endl; std::cout << "help, h, or ? : show this help " << std::endl; std::cout << "info : display variables (for testing purposes)" <<std::endl; std::cout << "move e2e4, or h7h8q : enter a move (use this format)" << std::endl; std::cout << "moves : show all legal moves" << std::endl; std::cout << "new : start new game" << std::endl; std::cout << "perf : benchmark a number of key functions" <<std::endl; std::cout << "perft n : calculate raw number of nodes from here, depth n" << std::endl; std::cout << "quit : exit program " << std::endl; std::cout << "r : rotate board " << std::endl; std::cout << "readfen filename n : reads #-th FEN position from filename" <<std::endl; std::cout << "sd n : set the search depth to n" << std::endl; std::cout << "setup : setup board... " << std::endl; std::cout << "undo : take back last move" << std::endl; std::cout << "white : WHITE to move" << std::endl; std::cout << std::endl; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// black: black to move// ================================================================= if (!strcmp(buf, "black")) { board.nextMove = BLACK_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// d: display board// ================================================================= if (!strcmp(buf, "d")) { board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

6 of 8 15-07-2012 01:30

Page 67: Ch1 Combine

// exit or quit: exit program// ================================================================= if ((!strcmp(buf, "exit")) || (!strcmp(buf, "quit"))) { CMD_BUFF_COUNT = '\0'; return false; } // =================================================================// info: display variables (for testing purposes)// ================================================================= if (!strcmp(buf, "info")) { info(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// new: start new game// ================================================================= if (!strcmp(buf, "new")) { dataInit(); board.init(); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// r: rotate board// ================================================================= if (!strcmp(buf, "r")) { board.viewRotated = !board.viewRotated; board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// readfen filename n : reads #-th FEN position from filename// ================================================================= if (!strncmp(buf, "readfen", 7)) { sscanf(buf+7,"%s %d", userinput, &number); board.init(); readFen(userinput, number); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// white: white to move// ================================================================= if (!strcmp(buf, "white")) { board.nextMove = WHITE_MOVE; CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// unknown command// ================================================================= std::cout << " command not implemented: " << buf << ", type 'help' for more info" <<std::endl; CMD_BUFF_COUNT = '\0'; return true;}

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

7 of 8 15-07-2012 01:30

Page 68: Ch1 Combine

The program can now read FEN strings. Later we will be using BT2450.pgn as a test suite to estimatewinglet's chess playing strength (ELO-rating). This suite was developed by Hubert Bednorz and FredToennissen to measure the tactical capability of chess engines, as opposed to strategic/positional strength.

How does that work? A chess engine is given 15 minutes (900 seconds) to analyze each position. If aposition is solved, the solution time is recorded in seconds. It doesn't count as a solution if the engine findsthe move and then changes its mind. If the engine finds the move, changes its mind then finds the move

again, that 2nd time is used. Any solution that is not found scores as 900 seconds. The 30 times areaveraged and subtracted from 2450 to give the estimated ELO rating. So, if no solution is found, theestimated ELO rating will be 2450-900=1550. If the average time is 8 minutes (480 seconds), then theestimated ELO rating is 2450-480=1970.

The suite has 30 test positions. You can download the file to test the new FEN string reading functionality.Note that this file must be placed in the correct folder in order for winglet to find it, which is your main projectfolder (one level up from the Debug or Release folder).

√ browse through BT2450.pgn, to see the positions.The command syntax is "readfen BT2450.pgn 1" for the first position, etc.

last update: Tuesday 31 May 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/09readfen01.htm

8 of 8 15-07-2012 01:30

Page 69: Ch1 Combine

Writing a chess program in 99steps

10 Setting up the board manually

Apart from being able to read FEN positions, we also want to set up the board manually. The manual setupprocedure has some overlap with reading a FEN string. You can provide a FEN string for the completeposition (64 squares), or define a single rank by a FEN string for that rank only (8 squares). Again, there isno error checking on validity of the supplied position. A new source file is created for this purpose,setup.cpp:

How to copy text to the console window - using your mouse:1. Right-click in the window, and then click Paste.

How to copy text from the console window - using your mouse:1. Right-click on the title bar of the console window, point to Edit, and then click Mark.2. Click the beginning of the text you want to copy.3. Press and hold down the SHIFT key, and then click the end of the text you want to copy (or you can clickand drag the cursor to select the text).4. Right-click the title bar, point to Edit, and then click Copy.

step 21: setup.cpp

#ifndef _CRT_SECURE_NO_DEPRECATE // suppress MS security warnings#define _CRT_SECURE_NO_DEPRECATE 1#endif#include <iostream>#include "defines.h"#include "protos.h"#include "extglobals.h" void setup(){ // interactively setup the chess board int file, rank; int whiteCastle; int blackCastle; unsigned char next; int halfmoves; int epsq, i; char s[80], epsqc[80], castle[80], fenrank[80]; char fen[100]; char fencolor[2]; char fencastling[5]; char fenenpassant[3]; int fenhalfmoveclock; int fenfullmovenumber; whiteCastle = board.castleWhite; blackCastle = board.castleBlack; next = board.nextMove; halfmoves = board.fiftyMove; epsq = board.epSquare;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

1 of 6 15-07-2012 01:30

Page 70: Ch1 Combine

if (board.nextMove == WHITE_MOVE) { std::cout << "wt> setup> type 'help' for more info" << std::endl; } else { std::cout << "bl> setup> type 'help' for more info" << std::endl; } // infinite loop - user input: for (;;) { if (board.nextMove == WHITE_MOVE) { std::cout << "wt> setup> "; } else { std::cout << "bl> setup> "; } std::cout.flush(); std::cin >> s; if ((!strcmp(s, "help")) || (!strcmp(s, "h")) || (!strcmp(s, "?"))) { std::cout << std::endl << "setup help:" << std::endl; std::cout << "black : BLACK to move" << std::endl; std::cout << "castle cccc : castling rights, using FEN-style.Example: 'castle KQkq'" << std::endl; std::cout << "clear : clear the board" << std::endl; std::cout << "d : display board" << std::endl; std::cout << "epsq cc : set en-passant target square. Example:'epsq e3'" << std::endl; std::cout << "exit : exit setup" << std::endl; std::cout << "fen fenstring : sets up the board with a FEN-string (6elements)," << std::endl; std::cout << " for instance: n1n5/PPPk4/8/8/8/8/4Kppp/5N1N b - - 0 1" << std::endl; std::cout << " you can paste a string into the windowsconsole" << std::endl; std::cout << " by using your mouse and right-clickpaste" << std::endl; std::cout << "fifty n : n = half moves since last capture or pawnmove" << std::endl; std::cout << "new : new game" << std::endl; std::cout << "r : rotate board" << std::endl; std::cout << "rank n fenrank : piece placement for rank n (from white'sperspective)" << std::endl; std::cout << " fenrank defines the contents of eachsquare, from left to" << std::endl; std::cout << " right (file a through file h). fenrankuses FEN-style:" << std::endl; std::cout << " pieces are identified by a single letter(pawn=P," << std::endl; std::cout << " knight=N, etc), using upper-case lettersfor white pieces" << std::endl; std::cout << " and lowercase letters for black pieces."<< std::endl; std::cout << " Blank squares are noted using digits 1through 8 (the " << std::endl; std::cout << " number of blank squares)." << std::endl; std::cout << " Examples: 'rank 1 R1BQKBNR' or 'rank 63p2p1'" << std::endl; std::cout << "white : WHITE to move" << std::endl << std::endl; } else if (!strcmp(s, "black")) { next = BLACK_MOVE; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else if (!strncmp(s, "castle", 5)) {

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

2 of 6 15-07-2012 01:30

Page 71: Ch1 Combine

std::cin >> castle; whiteCastle = 0; blackCastle = 0; if (strstr(castle, "K")) whiteCastle += CANCASTLEOO; if (strstr(castle, "Q")) whiteCastle += CANCASTLEOOO; if (strstr(castle, "k")) blackCastle += CANCASTLEOO; if (strstr(castle, "q")) blackCastle += CANCASTLEOOO; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else if (!strcmp(s, "clear")) { for (i = 0; i < 64; i++) { board.square[i] = EMPTY; } next = WHITE_MOVE; halfmoves = 0; whiteCastle = 0; blackCastle = 0; epsq = 0; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else if (!strcmp(s, "d")) { board.display(); std::cout << " castleWhite = " << (int) board.castleWhite << " castleBlack =" << (int) board.castleBlack << " epSquare = " << board.epSquare << " fiftyMove = " << board.fiftyMove << std::endl <<std::endl; } else if (!strncmp(s, "epsq", 4)) { std::cin >> epsqc; epsq = ((int) epsqc[0] - 96) + 8 * ((int) epsqc[1] - 48) - 9; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else if (!strcmp(s, "exit")) { std::cout.flush(); std::cin.clear(); return; } else if (!strncmp(s, "fen", 3)) { std::cin >> fen; std::cin >> fencolor; std::cin >> fencastling; std::cin >> fenenpassant; std::cin >> fenhalfmoveclock; std::cin >> fenfullmovenumber; setupFen(fen, fencolor, fencastling, fenenpassant, fenhalfmoveclock,fenfullmovenumber); } else if (!strncmp(s, "fifty", 5)) { std::cin >> halfmoves; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else if (!strcmp(s, "new")) { board.init(); } else if (!strcmp(s, "rank"))

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

3 of 6 15-07-2012 01:30

Page 72: Ch1 Combine

{ std::cin >> rank; if ((rank > 0) & (rank < 9)) { std::cin >> fenrank; // clear the file for (file = 1; file < 9; file++) { board.square[BOARDINDEX[file][rank]] = EMPTY; } file = 1; // chess board, file i = 0; // location in string while ((fenrank[i] != '\0') && (file < 9)) { // '1' through '8': if (((int) fenrank[i] > 48) && ((int) fenrank[i] < 57)) { file+= (int) fenrank[i] - 48; } else // other characters: { switch (fenrank[i]) { case 'P': board.square[BOARDINDEX[file][rank]] = WHITE_PAWN; file += 1; break; case 'N': board.square[BOARDINDEX[file][rank]] = WHITE_KNIGHT; file += 1; break; case 'B': board.square[BOARDINDEX[file][rank]] = WHITE_BISHOP; file += 1; break; case 'R': board.square[BOARDINDEX[file][rank]] = WHITE_ROOK; file += 1; break; case 'Q': board.square[BOARDINDEX[file][rank]] = WHITE_QUEEN; file += 1; break; case 'K': board.square[BOARDINDEX[file][rank]] = WHITE_KING; file += 1; break; case 'p': board.square[BOARDINDEX[file][rank]] = BLACK_PAWN; file += 1; break; case 'n': board.square[BOARDINDEX[file][rank]] = BLACK_KNIGHT; file += 1; break; case 'b': board.square[BOARDINDEX[file][rank]] = BLACK_BISHOP;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

4 of 6 15-07-2012 01:30

Page 73: Ch1 Combine

file += 1; break; case 'r': board.square[BOARDINDEX[file][rank]] = BLACK_ROOK; file += 1; break; case 'q': board.square[BOARDINDEX[file][rank]] = BLACK_QUEEN; file += 1; break; case 'k': board.square[BOARDINDEX[file][rank]] = BLACK_KING; file += 1; break; default: break; } } i++; } board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } } else if (!strcmp(s, "r")) { board.viewRotated = !board.viewRotated; } else if (!strcmp(s, "white")) { next = BLACK_MOVE; board.initFromSquares(board.square, next, halfmoves, whiteCastle ,blackCastle , epsq); } else { std::cout << " command not implemented: " << s << ", type 'help' for moreinfo" << std::endl; std::cin.clear(); } }}

Next, we add the new command in command.cpp:

step 22: command.cpp

(...)// =================================================================// readfen filename n : reads #-th FEN position from filename// ================================================================= if (!strncmp(buf, "readfen", 7)) { sscanf(buf+7,"%s %d", userinput, &number); board.init(); readFen(userinput, number); board.display(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

5 of 6 15-07-2012 01:30

Page 74: Ch1 Combine

// setup : setup board...// ================================================================= if (!strncmp(buf, "setup", 5)) { setup(); CMD_BUFF_COUNT = '\0'; return true; } // =================================================================// white: white to move// =================================================================

(...)

And protos.h needs to be updated:

step 23: protos.h

#ifndef WINGLET_PROTOS_H#define WINGLET_PROTOS_H unsigned int bitCnt(BitMap);void dataInit();void displayBitmap(BitMap);BOOLTYPE doCommand(const char *);unsigned int firstOne(BitMap);void info();unsigned int lastOne(BitMap);void readCommands();BOOLTYPE readFen(char *, int);void setup();void setupFen(char *, char *, char *, char *, int , int ); #endif

√ type "setup" and then "help" to see all board setup commands. Any position can be defined on theboard (even illegal ones!)√ using the "fen" command, you can copy a fen position (string) from internet and paste it to winglet(mouse right-click to paste a string into the console). Note that "fen" expects 6 elements.

last update: Wednesday 15 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/10setup01.htm

6 of 6 15-07-2012 01:30

Page 75: Ch1 Combine

Writing a chess program in 99steps

11 The move generator

This section is probably going to be the steepest part of your chess programming learning curve. Takeyour time, you might have to read this (and other articles on the internet) many times, before the ideassink in and before you come to appreciate how bitboard move generation works; I did too. The basic ideafor bitboard move generation is that moves are not calculated, but looked up in a table. There are manyvariations, but all methods are trying to hit some optimum between speed and memory, depending on theintended use of the chess engine. Engines that are meant for handheld devices will have animplementation with very compact tables, at the cost of more computations to calculate the table lookupaddress. Winglet is at the other end of the scale. Computations are kept to a minimum, making the codereadable, at the cost of a significant memory footprint (total memory allocation for winglet's sliding attacktables is 128kB).

Before we can write a move generator, we need to define the internal representation of a chess move. Let's see if we can use a 32-bit unsigned integer to store a move. Why 32-bit? because using less than32 bits would probably not give significant speed benefits, most computers today are based on a 32-bit or64-bit architecture. More than 32 bits could lead to some speed penalties on 32-bit systems (not on 64-bitsystems).

The move structure

A chess move can be represented by the from (from) and to (tosq) field (two positive integers, from0..63, so 6 bits each. Since we also need to be able to unmake (reverse) a move, captured pieces needto be stored as part of the move. Our piece identifiers are numbers between 0..15, for this we need

(24=16) 4 bits (stored in a 4-bit bitfield: capt ).In case of a pawn promotion (a white pawn reaches rank 8, or black pawn reaches rank 1), we alsoneed to store the promotion piece (queen, rook , bishop, or knight). One more 4-bit bitfield is added:prom . Our total is 20 bits, which is still less than 32. We might as well store the moving piece, it's only taking 4additional bits and could prove useful later, let's call this bitfield piec .So, we use 24 bits of a 32-bit unsigned integer to store a move.We already talked about pawn promotions, how about other 'special' moves, like en-passant captures,and castling? In order to make the move reversible, we need to flag an en-passant move, or else acaptured opponent's pawn would be misplaced when reversing the move. We'll use the existing 4-bitprom field to identify an en-passant capture: if the promotion bit-field equals WHITE_PAWN, it will meanthat this is en-passant capture by a white pawn. There really is no need to do anything special for castlings. If the king moves more than one field, it mustbe a castling move. However, just for ease of identifying castlings, we'll use the Prom field again: if itequals WHITE_KING, or BLACK_KING, then it means this was a castling move involving one of the rooks.

The move structure and member functions are defined below. I kept the header (.h) and actual

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

1 of 6 15-07-2012 01:30

Page 76: Ch1 Combine

implementation coding (.cpp) separate, even if the functions are very short. This will make it easier to testdifferent methods of storing a move, in terms of computational speed.

step 24: move.h

#ifndef WINGLET_MOVE_H_#define WINGLET_MOVE_H_ #include "defines.h" // There are at least 3 different ways to store a move in max 32 bits// 1) using shift & rank in an unsigned int // 2) using 4 unsigned chars, union-ed with an unsigned int// 3) using C++ bitfields, union-ed with an unsigned int // this is 1) using shift & rank in an unsigned int (32 bit):struct Move { // from (6 bits) // tosq (6 bits) // piec (4 bits) // capt (4 bits) // prom (4 bits) unsigned int moveInt; void clear(); void setFrom(unsigned int from); void setTosq(unsigned int tosq); void setPiec(unsigned int piec); void setCapt(unsigned int capt); void setProm(unsigned int prom); unsigned int getFrom(); unsigned int getTosq(); unsigned int getPiec(); unsigned int getCapt(); unsigned int getProm(); BOOLTYPE isWhitemove(); BOOLTYPE isBlackmove(); BOOLTYPE isCapture(); BOOLTYPE isKingcaptured(); BOOLTYPE isRookmove(); BOOLTYPE isRookcaptured(); BOOLTYPE isKingmove(); BOOLTYPE isPawnmove(); BOOLTYPE isPawnDoublemove(); BOOLTYPE isEnpassant(); BOOLTYPE isPromotion(); BOOLTYPE isCastle(); BOOLTYPE isCastleOO(); BOOLTYPE isCastleOOO();}; #endif

The implementation of the member functions below looks quite long, for just a single chess move. I'llexplain:

step 25: move.cpp

#include "move.h" void Move::clear(){ moveInt = 0;} void Move::setFrom(unsigned int from) { // bits 0.. 5

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

2 of 6 15-07-2012 01:30

Page 77: Ch1 Combine

moveInt &= 0xffffffc0; moveInt |= (from & 0x0000003f);} void Move::setTosq(unsigned int tosq) { // bits 6..11 moveInt &= 0xfffff03f; moveInt |= (tosq & 0x0000003f) << 6;} void Move::setPiec(unsigned int piec) { // bits 12..15 moveInt &= 0xffff0fff; moveInt |= (piec & 0x0000000f) << 12;} void Move::setCapt(unsigned int capt) { // bits 16..19 moveInt &= 0xfff0ffff; moveInt |= (capt & 0x0000000f) << 16;} void Move::setProm(unsigned int prom) { // bits 20..23 moveInt &= 0xff0fffff; moveInt |= (prom & 0x0000000f) << 20;} // read move information:// first shift right, then mask to get the info unsigned int Move::getFrom() { // 6 bits (value 0..63), position 0.. 5 return (moveInt & 0x0000003f);} unsigned int Move::getTosq() { // 6 bits (value 0..63), position 6..11 return (moveInt >> 6) & 0x0000003f; } unsigned int Move::getPiec() { // 4 bits (value 0..15), position 12..15 return (moveInt >> 12) & 0x0000000f; } unsigned int Move::getCapt() { // 4 bits (value 0..15), position 16..19 return (moveInt >> 16) & 0x0000000f; } unsigned int Move::getProm() { // 4 bits (value 0..15), position 20..23 return (moveInt >> 20) & 0x0000000f; } // boolean checks for some types of moves.// first mask, then compare// Note that we are using the bit-wise properties of piece identifiers, so we cannot just changethem anymore ! BOOLTYPE Move::isWhitemove() { // piec is white: bit 15 must be 0 return (~moveInt & 0x00008000) == 0x00008000;} BOOLTYPE Move::isBlackmove() { // piec is black: bit 15 must be 1 return ( moveInt & 0x00008000) == 0x00008000;} BOOLTYPE Move::isCapture() { // capt is nonzero, bits 16 to 19 must be nonzero return ( moveInt & 0x000f0000) != 0x00000000;} BOOLTYPE Move::isKingcaptured(){ // bits 17 to 19 must be 010 return ( moveInt & 0x00070000) == 0x00020000;

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

3 of 6 15-07-2012 01:30

Page 78: Ch1 Combine

} BOOLTYPE Move::isRookmove(){ // bits 13 to 15 must be 110 return ( moveInt & 0x00007000) == 0x00006000;} BOOLTYPE Move::isRookcaptured(){ // bits 17 to 19 must be 110 return ( moveInt & 0x00070000) == 0x00060000;} BOOLTYPE Move::isKingmove(){ // bits 13 to 15 must be 010 return ( moveInt & 0x00007000) == 0x00002000;} BOOLTYPE Move::isPawnmove(){ // bits 13 to 15 must be 001 return ( moveInt & 0x00007000) == 0x00001000;} BOOLTYPE Move::isPawnDoublemove(){ // bits 13 to 15 must be 001 & // bits 4 to 6 must be 001 (from rank 2) & bits 10 to 12 must be 011 (to rank 4) // OR: bits 4 to 6 must be 110 (from rank 7) & bits 10 to 12 must be 100 (to rank 5) return ((( moveInt & 0x00007000) == 0x00001000) && (((( moveInt & 0x00000038) ==0x00000008) && ((( moveInt & 0x00000e00) == 0x00000600))) || ((( moveInt & 0x00000038) ==0x00000030) && ((( moveInt & 0x00000e00) == 0x00000800)))));} BOOLTYPE Move::isEnpassant() { // prom is a pawn, bits 21 to 23 must be 001 return ( moveInt & 0x00700000) == 0x00100000;} BOOLTYPE Move::isPromotion() { // prom (with color bit removed), .xxx > 2 (not king or pawn) return ( moveInt & 0x00700000) > 0x00200000;} BOOLTYPE Move::isCastle() { // prom is a king, bits 21 to 23 must be 010 return ( moveInt & 0x00700000) == 0x00200000;} BOOLTYPE Move::isCastleOO() { // prom is a king and tosq is on the g-file return ( moveInt & 0x007001c0) == 0x00200180;} BOOLTYPE Move::isCastleOOO() { // prom is a king and tosq is on the c-file return ( moveInt & 0x007001c0) == 0x00200080;}

What's all that 0xfffff03f about? These are numbers, constants, in this case using the hexadecimalsystem (using digits from 0 to f ). A hexadecimal number in C++ starts with 0x , to signal the compilerthat what follows is to be interpreted as a hexadecimal number. Using hexadecimals is a compact way ofwriting binary numbers, because 4 bits can be replaced by 1 hexadecimal digit (see table below), and a32-bit number can be written as a hexadecimal number using just 8 digits (a bitboard is 16 hexadecimaldigits).

decimal binary hexadecimal0 0000 0

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

4 of 6 15-07-2012 01:30

Page 79: Ch1 Combine

1 0001 1

2 0010 2

3 0011 3

4 0100 4

5 0101 5

6 0110 6

7 0111 7

8 1000 8

9 1001 9

10 1010 a

11 1011 b

12 1100 c

13 1101 d

14 1110 e

15 1111 f

Let's look how a move like 'pawn from e2 to e4' is going to be stored in a 32-bit word:We start by calling move.clear() , to clear any garbage from a previous use of this memory location,then we call move.setFrom(E2) , move.setTosq(E4) and finally move.setPiec(WHITE_PAWN) .The numerical values are: E2=12, E4=28 and WHITEPAWN=1.

move.setFrom(E2) will be executed as folllows (with the hexadecimal constants written as 32 bits): moveInt &= 11111111111111111111111111000000 ; // this will clear therightmost 6 bits, the 'from' bitfieldmoveInt != (from & 00000000000000000000000000111111 ); // this will set therightmost 6 'from' bits to the value of // 'from': e2 (which isequal to 12), binary 001100

move.setTosq(E4) will be executed as folllows: moveInt &= 11111111111111111111000000111111 ; // this willclear the 'to' bitfieldmoveInt != (tosq & 00000000000000000000000000111111 ) << 6 ; // this will setthe 'to' bitfield to the value of // 'tosq': e4(which is equal to 24), binary 011000

move.setPiec(WHITE_PAWN) will be executed as folllows: moveInt &= 11111111111111110000111111111111 ; // this willclear the 'piec' bitfieldmoveInt != (piec & 00000000000000000000000000001111 ) << 12; // this will setthe 'piece' bitfield to the value of // 'piec':WHITE_PAWN (which is equal to 1), binary 0001

So we end up with the following 32-bit word for e2-e4 (the bitfields are separated for clarity):00000000 0000 0000 0001 011000 001100 prom capt piec tosq from

Phew, not sure if I'm going to do much more examples like that, don't want my keyboard 1's and 0's todrop out. I suppose you get the idea.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

5 of 6 15-07-2012 01:30

Page 80: Ch1 Combine

Next we introduce a move buffer, this is just an array of type Move, sufficiently long to store the generatedmoves (complete code snippets will follow later, no need to copy & paste now)

last update: Friday 10 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen01.htm

6 of 6 15-07-2012 01:30

Page 81: Ch1 Combine

Writing a chess program in99 steps

Our move generator is going to generate pseudo-legal moves, this means that there will beno test against leaving our own king in check. This test will be carried out when we actuallymake the move. Most generated moves will never made (due to cut-offs), so it's faster todo legality testing when we actually make the move, rather than doing these tests in themove generator.

Move generation for Knights

Bitboard move generation for knights is the easiest to explain, so we'll start with that.

The basic idea for bitboard move generation is that all moves are pre-computed and storedin a lookup table, and the move generator simply looks up the moves in this table. I'llexplain the code for white knights step by step, it's only 17 lines (complete code will follow):

01 targetBitmap = ~board.whitePieces;

02 move.setPiec(WHITE_KNIGHT);

03 tempPiece = board.whiteKnights;

04 while (tempPiece)

05 {

06 from = firstOne(tempPiece);

07 tempMove = KNIGHT_ATTACKS[from] & targetBitmap;

08 while (tempMove)

09 {

10 to = firstOne(tempMove);

11 move.setTosq(to);

12 move.setCapt(board.square[to]);

13 moveBuffer[moveBufLen++].moveInt = move.moveInt;

14 tempMove ^= BITSET[to];

15 }

16 tempPiece ^= BITSET[from];

17 }

line 01: targetBitmap is a bitboard with all possible target fields, basically all fieldsexcept the ones that are occupied by white pieces (because we cannot capture our ownpiece).line 02: we already know what piece is going to move, so store the Piec bitfield in themove (it is a white knight).line 03: tempPiece is a local bitboard, it holds all white knights. We're going to loop over

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen02.htm

1 of 4 15-07-2012 01:32

Page 82: Ch1 Combine

all white knights and generate all white knight moves.line 04: start of the loop over all white knights (see also line 16). If there is no white knightleft, then we're ready.line 06: here we detect the from bitfield of the current white knight (using firstOne).line 07: this is the essence of bitboard move generation: we do a bitwise AND of the targetbitboard with the attack bitboard that belongs to the piece/from-field. This attack bitboard isa pre-computed lookup table.

An example of a 'piece/from attack bitboard': for a white knight (piece) on e4(from), the attack bitboard KNIGHT_ATTACKS[E4] looks like this:

KNIGHT_ATTACKS[E4] bitboard ('x' and '.' are zeroes):

. . . . . . . .

. . . . . . . .

. . . 1 . 1 . .

. . 1 . . . 1 .

. . . . x . . .

. . 1 . . . 1 .

. . . 1 . 1 . .

. . . . . . . .

There are 64 KNIGHT_ATTACKS bitboards, one for each from-field. These are just 64pre-computed lookup bitboards for knight moves. After AND-ing the attackbitboard with our targetBitmap, we end up with all target squares for a whiteknight on e4. So all the moves for a knight on e4 are generated in one go ('bit-parallel', if you like). We don't need a loop to calculate the target squares,because they are already pre-computed and stored in the attack bitboards. Thistechnique of move generation is one of the main advantages of using bitboards.

line 08: this is a loop over all the target fields that we found, every target field represents adifferent knight move. See also line 14. If there is no target field left (tempMove = 0), then we're ready.line 10: detect the current target field (again using firstOne).lines 11 and line 12: store the tosq and capt bitfields of the move. Now you'll understand why we introduced the square array in the Board struct. This is a very convenient way of looking up if we captured an opponent's piece, ormoved the knight to an empty field. If we only had bitboards to represent the position, this detection would be muchmore expensive (requiring a loop over all 6 bitboards of black pieces).line 13: copy the move into the moveBuffer, and increment moveBuflen.line 14: remove the current target square from tempMove, and go back to line 08 for thenext move.line 16: remove the current white knight from tempPiece, and go back to line 04 for thenext white knight.

In summary, this position shows the move generation for the white knight on c3, with the 3

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen02.htm

2 of 4 15-07-2012 01:32

Page 83: Ch1 Combine

relevant bitboards:

Relevant move generation bitboards for the white knight on c3:

targetBitmap =~whitePieces

KNIGHT_ATTACKS[c3](simple table lookup)

knight moves:KNIGHT_ATTACKS[c3] &

targetBitmap

1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1

1 1 1 1 1 . 1 1

1 1 1 1 1 1 1 1

1 1 1 . 1 1 1 1

1 . . 1 1 . 1 .

1 . 1 1 1 . . 1

. 1 . . . 1 . 1

. . . . . . . .

. . . . . . . .

. . . . . . . .

. 1 . 1 . . . .

1 . . . 1 . . .

. . . . . . . .

1 . . . 1 . . .

. 1 . 1 . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. 1 . 1 . . . .

1 . . . 1 . . .

. . . . . . . .

1 . . . 1 . . .

. 1 . . . . . .

Move generation for the kings is very similar to this, we first generate the target bitboard,then 'AND' this with the king attack bitboard, which simply has all 8 bits around the kingsquare set. Castling is dealt with separately, because we have to check if the side to movestill has the appropriate castling rights (as stored in the board structure), and also if thefields between king and rook are empty. For instance, for white's king-side castling, we testif squares f1 and g1 are free. This is easy using bitboards, and the test looks as follows:

if (!(maskF1G1 & ~occupiedSquares))

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen02.htm

3 of 4 15-07-2012 01:32

Page 84: Ch1 Combine

Pawn moves are also generated this way, but need more work. Normal moves(non-captures) and captures have separate attack bitboards and need different conditionsregarding occupancy of the target fields. En-passant captures also need to be dealt withseparately, because these are captures with an empty target field. The target field foren-passant moves is stored in the board structure. If a pawn reaches the 8th or 1st rank,we have to store the 4 possible pawn promotions (to knight, bishop, rook and queen) asseparate moves. Finally, white and black pawns cannot share the same attack bitboards,because they move in different directions. Many differences if compared with knight moves,but the basic ideas are the same.

last update: Thursday 23 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen02.htm

4 of 4 15-07-2012 01:32

Page 85: Ch1 Combine

Writing a chess program in 99 steps

Move generation for sliding pieces

Personally I found bitboard move generation for sliding pieces to be one of the most difficult parts to understand in chess programming (together withalpha-beta search). The main difference between sliding and non-sliding pieces is that the attack bitboards for sliding pieces not only depend on thefrom-square (as with non-sliding pieces), but they also depend on the occupancy ('state') of the files, ranks and diagonals. Non-sliding attack bitboardscan be stored in one-dimensional lookup tables (arrays), but sliding attack bitboards need to be stored in two-dimensional lookup tables, the seconddimension relates to the occupancy, or state, of the rank, file, or diagonal.

Sliding pieces will attack a rank, file or diagonal, up to and including the first piece that they encounter (irrespective of the color of that piece). So in termsof move generation, a sliding piece will also attack pieces of the same color. This is no problem, because equal-color attacks will be 'AND'-ed away bythe targetBitmap = ~whitePieces bitboard (same as with non-sliding pieces), and this effectively removes all equal-color captures. The conceptmight need some time to sink in, but it is totally consistent with how we generated the white knight moves: the knight attack bitboards have no check tosee what the contents of an attacked field is, there might be a black piece on the attacked field, or a white piece, or it may be empty.

To clarify the idea a bit further, let's take a look at a chess position with the relevant bitboards for move generation, and look how the moves for the whiterook on f1 are generated:

Relevant move generation bitboards for the rook on f1 (compare with the knight move generation):

targetBitmap =~whitePieces

ROOKMOVES =RANK_ATTACKS[f1][rankstate] |FILE_ATTACKS[f1][filestate]

rook moves:ROOKMOVES &

targetBitmap

1 1 1 1 . 1 1 1

1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1

1 1 1 1 1 1 1 1

1 1 1 1 . 1 1 1

. 1 . 1 . 1 1 1

1 . . 1 . 1 . .

. 1 1 . 1 . . 1

. . . . . . . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . 1 1 . 1 .

. . . . . . . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . . 1 . .

. . . . 1 . . .

Notice how the rook attacks stop when they encounter a piece (any piece), and also how file and rank attacks are combined (using a bitwise OR).In the example above, the first rank occupancy state could be represented as, going from a1 to h1: 10010110

For this state, a rook attack (from f1) along the 1st rank will look like: 00011010. The 6th file occupancy state can be represented as (from f1 to f8):

10000011, and the rook attack (from f1) along the 6th file will look like: 01111110.

6 bits only

At this point it is important to understand that the rook attack on the first rank does not depend on the contents of fields a1 or h1 (the first and last bits).Whether these fields are attacked only depends on b1 and g1 respectively, not on the contents of a1 and h1 themselves.

If there is NO piece on b1, then a1's attack status will just be 'inherited' from b1, irrespective if there is a piece on a1 or not. If there IS a piece on b1, thena1 will NOT be attacked, irrespective if there is a piece on a1 or not.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen03.htm

1 of 4 15-07-2012 02:24

Page 86: Ch1 Combine

In any case, the presence of a piece on a1 does not matter for generating the rank attacks. Same goes for h1 (the attack depends on g1, not on h1). Andthis reasoning is equally valid for ranks, files and diagonals.

It means that, for sliding move generation purposes, we can omit the 2 outer bits and only need the 6 inner bits to define the occupancy state of a file,rank or diagonal. As a consequence, the sliding rank attack bitboards are declared as: BitMap RANK_ATTACKS[64][64], using 32KB of memory. The firstindex is the from field, the second index is the rank occupancy state (6 bits, 0..63). There are ways to minimize the memory use for diagonal attackbitmaps, because most diagonals have less than 8 fields. But for the sake of speed and simplicity I'll not get into this, it will prevent additional coding tocheck for the length of the diagonal, at the cost of a little more memory use.

Calculation of a 6-bit occupancy state index

Calculation of the table lookup index (from the occupancy state) is basically a two-step process:First, we extract the occupancy bitboard of a single file/rank or diagonal. This is done by taking occupiedSquares, and masking it with an appropriatemasking bitboard that leaves us with only the appropriate bits of one file, rank, or diagonal (the 2 outer bits are not set in the masking bitboards). Theexample below demonstrates this step to get the file occupancy of field e4:

occupiedSquares FILEMASK[e4]occupiedSquares &DIAGA1H8MASK[e4]

1 1 1 1 . 1 1 1

1 . . . 1 . 1 1

. 1 . . 1 . . .

. 1 1 . 1 . . .

. . . . 1 . . .

. 1 . . . 1 1 1

1 . . 1 . 1 . .

. 1 1 . 1 . . 1

. . . . . . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . . . . .

. . . . . . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . 1 . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

Or, for the 'a1-h8-diagonal' of field e4:

occupiedSquares DIAGA1H8MASK[e4]occupiedSquares &DIAGA1H8MASK[e4]

1 1 1 1 . 1 1 1

1 . 1 . 1 . 1 1

. 1 . 1 . . 1 .

. . . . . 1 . .

. 1 1 . 1 . . .

. . . 1 . . 1 .

1 . . 1 . 1 . 1

. 1 1 . 1 . 1 .

. . . . . . . .

. . . . . . . .

. . . . . . 1 .

. . . . . 1 . .

. . . . 1 . . .

. . . 1 . . . .

. . 1 . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . 1 .

. . . . . 1 . .

. . . . 1 . . .

. . . 1 . . . .

. . . . . . . .

. . . . . . . .

Second, this 64-bit bitboard (with maximum 6 bits set, all on a single file, rank or diagonal) is transformed to a 6-bit (0..63) lookup index.This transformation is done differently for files, ranks, a1-h8 diagonals and a8-h1 diagonals:

Rank occupancy

For ranks, the occupancy index is simply calculated as follows:

6bitstate = (occupiedSquares & RANKMASK[from]) >> RANKSHIFT[from]

The example below demonstrates how the 6-bit rank occupancy index (in red below) for field d3 is calculated (RANKSHIFT[d3] = 17):

occupiedSquares RANKMASK[d3]occupiedSquares &RANKMASK[d3]

occupiedSquares &RANKMASK[d3] >> 17

1 . 1 1 . . 1 1

1 1 . 1 1 1 1 1

. . 1 . 1 . 1 .

. . 1 . . . . .

. . . . . . . .

. 1 . 1 . 1 1 1

1 . . 1 . 1 . .

. 1 1 . 1 . . 1

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. 1 1 1 1 1 1 .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. 1 . 1 . 1 1 .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

1 . 1 . 1 1 . .

File occupancy - magic multipliers

For files, the lookup index calculation is as follows:

6bitstate = (occupiedSquares & FILEMASK[from]) * FILEMAGIC[from] >> 57

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen03.htm

2 of 4 15-07-2012 02:24

Page 87: Ch1 Combine

Compared to ranks, there is one step added: a 64-bit multiplication with FILEMAGIC[from]. These 'magic' multipliers are chosen such that theytransform a single file to the last rank, and at the same time retain the bit order, so we only have to shift the result to end up with the 6-bit occupancy stateof that file. Let's take the 3rd file as an example (bit locations are identified by A-F, they can either be 0 or 1, all dots are 0):

3rd file: magic number for 3rdfile:0x2010080402010080

3rd file * magic numberfor 3rd file:

3rd file * magic number >> 57:

. . . . . . . .

. . A . . . . .

. . B . . . . .

. . C . . . . .

. . D . . . . .

. . E . . . . .

. . F . . . . .

. . . . . . . .

x

. . . . . 1 . .

. . . . 1 . . .

. . . 1 . . . .

. . 1 . . . . .

. 1 . . . . . .

1 . . . . . . .

. . . . . . . .

. . . . . . . 1

=

. A B C D E F .

. A B C D E . .

. A B C D . . .

. A B C . . . .

. A B . . . . .

. A . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

A B C D E F . .

There is a magic multiplier for every file:

file: magic:

a-file: 0x8040201008040200

b-file: 0x4020100804020100

c-file: 0x2010080402010080d-file: 0x1008040201008040

e-file: 0x0804020100804020

f-file: 0x0402010080402010g-file: 0x0201008040201008

h-file: 0x0100804020100804

How did I get to these magic numbers? Well, basically by trial and error. If you know a magic number for one file, or diagonal (for instance fromwikispaces), then it's not hard to work out the magic numbers for the other files or diagonals. Keep in mind that two files only differ by a shift (left or right),if you apply the opposite shift to the magic number, you'll end up with the same multiplication result.

A8H1 diagonal occupancy - magic multipliers

For a8h1 diagonals ('a8h1' is indicating the direction), calculating the lookup index (or: diagonal occupancy) is very similar to what we did for files:

6bitstate = (occupiedSquares & DIAGA8H1MASK[from]) * DIAGA8H1MAGIC[from] >> 57

Let's define the diagonal numbering: diaga8h1 = file + rank - 1There are 15 diagonals, numbered from 1 to 15, diagonal 8 being the longest diagonal (from a8 to h1), and let's take diaga8h1 = 9 as an example:

diaga8h1[9]: magic number fordiaga8h1[9]:0x0080808080808080

diaga8h1[9] *0x0080808080808080:

diaga8h1[9] * 0x0080808080808080 >>57:

. . . . . . . .

. . A . . . . .

. . . B . . . .

. . . . C . . .

. . . . . D . .

. . . . . . E .

. . . . . . . .

. . . . . . . .

x

. . . . . . . .

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

=

. A B C D E . .

. . B C D E . .

. . . C D E . .

. . . . D E . .

. . . . . E . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

A B C D E . . .

Note that this particular diagonal only needs 5 addressing bits. For simplicity, we will just treat it as if it has 6 bits (with the last bit being irrelevant), suchthat all diagonal attack bitboards will have the same addressing range.

Again, there is a magic multiplier for every a8h1 diagonal (and some diagonals share the same multiplier):

diagonal: magic:

diaga8h1 = 1, a1: 0x0

diaga8h1 = 2, a2-b1: 0x0

diaga8h1 = 3, a3-c1: 0x0101010101010100diaga8h1 = 4, a4-d1: 0x0101010101010100

diaga8h1 = 5, a5-e1: 0x0101010101010100

diaga8h1 = 6, a6-f1: 0x0101010101010100diaga8h1 = 7, a7-g1: 0x0101010101010100

diaga8h1 = 8, a8-h1: 0x0101010101010100

diaga8h1 = 9, b8-h2: 0x0080808080808080

diaga8h1 = 10, c8-h3: 0x0040404040404040diaga8h1 = 11, d8-h4: 0x0020202020202020

diaga8h1 = 12, e8-h5: 0x0010101010101010

diaga8h1 = 13, f8-h6: 0x0008080808080808diaga8h1 = 14, g8-h7: 0x0

diaga8h1 = 15, h8: 0x0

The first two and last two diagonals don't need magic numbers, because they consist of only 1 or 2 squares, so the attacks along these diagonals don'tdepend on their occupancy.

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen03.htm

3 of 4 15-07-2012 02:24

Page 88: Ch1 Combine

A1H8 diagonal occupancy - magic multipliers

a1h8 diagonal occupancy lookup indices are calculated the same way:

6bitstate = (occupiedSquares & DIAGA1H8MASK[from]) * DIAGA1H8MAGIC[from] >> 57

We define a1h8 diagonal numbers as follows: diaga1h8 = file - rank + 8, so numbered from 1 to 15, and 8 is the longest diagonal. Take diaga1h8 = 9 asan example:

diaga1h8[9] magic number fordiaga8h1[9]:0x8080808080808000

diaga8h1[9] *0x0080808080808080:

diaga8h1[9] * 0x0080808080808080 >>57:

. . . . . . . .

. . . . . . . .

. . . . . . E .

. . . . . D . .

. . . . C . . .

. . . B . . . .

. . A . . . . .

. . . . . . . .

x

. . . . . . . .

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

. . . . . . . 1

=

. A B C D E . .

. A B C D E . .

. A B C D . . .

. A B C . . . .

. A B . . . . .

. A . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

A B C D E . . .

Again, there is a magic multiplier for every a8h1 diagonal (and again, some diagonals have the same multiplier):

diagonal: magic:diaga1h8 = 1, a8: 0x0

diaga1h8 = 2, a7-b8: 0x0

diaga1h8 = 3, a6-c8: 0x0101010101010100diaga1h8 = 4, a5-d8: 0x0101010101010100

diaga1h8 = 5, a4-e8: 0x0101010101010100

diaga1h8 = 6, a3-f8: 0x0101010101010100diaga1h8 = 7, a2-g8: 0x0101010101010100

diaga1h8 = 8, a1-h8: 0x0101010101010100

diaga1h8 = 9, b1-h7: 0x8080808080808000

diaga1h8 = 10, c1-h6: 0x4040404040400000diaga1h8 = 11, d1-h5: 0x2020202020000000

diaga1h8 = 12, e1-h4: 0x1010101000000000

diaga1h8 = 13, f1-h3: 0x0808080000000000diaga1h8 = 14, g1-h2: 0x0

diaga1h8 = 15, h1: 0x0

last update: Thursday 02 June 2011

winglet, writing a chess program in 99 steps http://www.sluijten.com/winglet/11movegen03.htm

4 of 4 15-07-2012 02:24


Recommended