If I were a Professor of Computer Science

Discussion of chess software programming and technical issues.

Moderator: Ras

Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

Bo Persson wrote: Fri Dec 30, 2022 7:35 pm
Mike Sherwin wrote: Fri Dec 30, 2022 4:22 am

Code: Select all


  if (thread[i] == nullptr) {  // new returns nullptr on allocation failure.

new does not return at all on failure, but throws a bad_alloc exception. So this case never happens.
My bad memory at work again.

However; it can return a nullptr which is what I forgot to tell it.
By default, one of the versions of operator new is overloaded to accept a parameter of type nothrow_t (like nothrow). The value itself is not used, but that version of operator new shall return a null pointer in case of failure instead of throwing an exception.

The same applies to the new[] operator and function operator new[].

Example:

// nothrow example
#include <iostream> // std::cout
#include <new> // std::nothrow

int main () {
std::cout << "Attempting to allocate 1 MiB... ";
char* p = new (std::nothrow) char [1048576];

if (!p) { // null pointers are implicitly converted to false
std::cout << "Failed!\n";
}
else {
std::cout << "Succeeded!\n";
delete[] p;
}

return 0;
}
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

Bo Persson wrote: Fri Dec 30, 2022 7:44 pm Another bit about using comments to explain code:

Code: Select all

// The names of the squares
enum {
  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
};
What about using "enum square", and skip the comment? Then you could possibly also use "square" instead of "s08" elsewhere in the code, like turning "s08 fs;" into "square from;".
I knew of this possibility from my C++ primer but I never could quite grasp how to use it. Your explanation is better than the explanation in my C++ primer! I will make the change. 8-)

Keep them coming!! :D
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

I have a question though. In structure Move how does the compiler know that it should be a signed 8-bit value?

Code: Select all

enum square {
  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
};

struct Move {
  square fs;
  square ts;
  square ft;
  square tt;
  s32 sc;
};
Edit: This is too confusing right now so until I know how it works I'm going to leave it as it was. :(
JacquesRW
Posts: 127
Joined: Sat Jul 30, 2022 12:12 pm
Full name: Jamie Whiting

Re: If I were a Professor of Computer Science

Post by JacquesRW »

Mike Sherwin wrote: Fri Dec 30, 2022 9:35 pm I have a question though. In structure Move how does the compiler know that it should be a signed 8-bit value?
You can specify the type as below, otherwise it defaults to an int.

Code: Select all

enum square: <type> { ... };
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

JacquesRW wrote: Fri Dec 30, 2022 11:49 pm
Mike Sherwin wrote: Fri Dec 30, 2022 9:35 pm I have a question though. In structure Move how does the compiler know that it should be a signed 8-bit value?
You can specify the type as below, otherwise it defaults to an int.

Code: Select all

enum square: <type> { ... };
Thanks! 8-)
Sesse
Posts: 300
Joined: Mon Apr 30, 2018 11:51 pm

Re: If I were a Professor of Computer Science

Post by Sesse »

JacquesRW wrote: Fri Dec 30, 2022 11:49 pm You can specify the type as below, otherwise it defaults to an int.
Strictly speaking, the compiler can choose whatever type it wants as long as it can hold both enough bits to distinguish between the smallest and largest values. In particular, this means that if any of your enum values are larger than INT_MAX, the underlying enum type will be larger than int.
abulmo2
Posts: 465
Joined: Fri Dec 16, 2016 11:04 am
Location: France
Full name: Richard Delorme

Re: If I were a Professor of Computer Science

Post by abulmo2 »

Mike Sherwin wrote: Fri Dec 30, 2022 12:37 am Defines.cpp
Globals.cpp
Initialize.cpp
Defines.cpp will contain everything we define that helps us in the writing of the code.
Globals.cpp will be all the variables that can be accessed from anywhere in the program.
Initialize.cpp will contain all initialization code that almost all programs need to do.
IMHO, you can remove this three files, as it makes the code not modular. Moreover, globals are bad and should be avoided as much as possible. See https://en.wikipedia.org/wiki/Pure_function for some argumentations.

Code: Select all

// Quixotic.cpp

#include "Defines.cpp"
#include "Globals.cpp"
#include "Initialize.cpp"
#include "GetCommand.cpp"
#include "Move.cpp"
#include "Search.cpp"
#include "Main.cpp"
Link time optimization makes this approach useless. With MSVC you just need to use the /LTCG flag to enable it.

Code: Select all

// Type defines are used, by the professor's prerogative, to give alternate names to internal variable types
typedef char s08;
In C or C++, char can be signed or unsigned you should have written typedef signed char s08;

Code: Select all

// A thread structure to allow for a multi-threaded engine
struct Thread {
  s32 stm; // side to move
  s32 ply; // a counter to denote how deep we are in the search
  s08 board[64]; // The chessboard
};
To me the above looks like a chessboard structure, not a thread structure.

Code: Select all

//Globals.cpp

// Global variables
s32 mode; // tells the engine what mode it is in

Code: Select all

// Initialize.cpp

// Initialization code
void Initialize() {
  mode = GETCMD; // start the engine off in get command mode
}

Code: Select all

// GetCommand.cpp

// Tell the engine what to do
void GetCommand() {
   mode = Search; // temporary code to test program flow
}

Code: Select all

Move.cpp

// Performs a move in the game being played
void GameMove() {
  mode = EXIT; // temporary code to test program flow
}

Code: Select all

// Search.cpp

// Beginning of the code that will produce a move to be played
void StartSearch() {
  mode = MOVE; // temporary code to test program flow
}

Code: Select all

// Main.cpp

// Where the code starts running and controls the flow of the engine
s32 main() {

  Initialize();

  while (mode != EXIT) {
	if (mode == GETCMD) GetCommand();
	if (mode == SEARCH) StartSearch();
	if (mode == MOVE) GameMove();
  }

  return 0;
}
Having

Code: Select all

mode
as a global variable that can be changed anywhere is a bad practice I think.
Richard Delorme
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

abulmo2 wrote: Sat Dec 31, 2022 3:44 pm
Mike Sherwin wrote: Fri Dec 30, 2022 12:37 am Defines.cpp
Globals.cpp
Initialize.cpp
Defines.cpp will contain everything we define that helps us in the writing of the code.
Globals.cpp will be all the variables that can be accessed from anywhere in the program.
Initialize.cpp will contain all initialization code that almost all programs need to do.
IMHO, you can remove this three files, as it makes the code not modular. Moreover, globals are bad and should be avoided as much as possible. See https://en.wikipedia.org/wiki/Pure_function for some argumentations.

Code: Select all

// Quixotic.cpp

#include "Defines.cpp"
#include "Globals.cpp"
#include "Initialize.cpp"
#include "GetCommand.cpp"
#include "Move.cpp"
#include "Search.cpp"
#include "Main.cpp"
Link time optimization makes this approach useless. With MSVC you just need to use the /LTCG flag to enable it.

Code: Select all

// Type defines are used, by the professor's prerogative, to give alternate names to internal variable types
typedef char s08;
In C or C++, char can be signed or unsigned you should have written typedef signed char s08;

Code: Select all

// A thread structure to allow for a multi-threaded engine
struct Thread {
  s32 stm; // side to move
  s32 ply; // a counter to denote how deep we are in the search
  s08 board[64]; // The chessboard
};
To me the above looks like a chessboard structure, not a thread structure.

Code: Select all

//Globals.cpp

// Global variables
s32 mode; // tells the engine what mode it is in

Code: Select all

// Initialize.cpp

// Initialization code
void Initialize() {
  mode = GETCMD; // start the engine off in get command mode
}

Code: Select all

// GetCommand.cpp

// Tell the engine what to do
void GetCommand() {
   mode = Search; // temporary code to test program flow
}

Code: Select all

Move.cpp

// Performs a move in the game being played
void GameMove() {
  mode = EXIT; // temporary code to test program flow
}

Code: Select all

// Search.cpp

// Beginning of the code that will produce a move to be played
void StartSearch() {
  mode = MOVE; // temporary code to test program flow
}

Code: Select all

// Main.cpp

// Where the code starts running and controls the flow of the engine
s32 main() {

  Initialize();

  while (mode != EXIT) {
	if (mode == GETCMD) GetCommand();
	if (mode == SEARCH) StartSearch();
	if (mode == MOVE) GameMove();
  }

  return 0;
}
Having

Code: Select all

mode
as a global variable that can be changed anywhere is a bad practice I think.
In 1983 I bought an Atari 800 because I wanted to write a chess Program. Atari 800 Basic was carp so I got a 90KB floppy drive for it and learned how to program the boot sector in 6502 assembler. I managed to write a chess GUI also in 6502 assembler. So now I had a chessboard with pieces that I could move the pieces with the joystick. This took too long and before I could write the chess playing code the Atari 800 was a completely dead platform.

My problem is I have a learning/memory disability and everything programming wise takes me too long. I did not try again to write a chess program until I got an 80386 computer. I also got the Borland C compiler as well as the Borland assembler. Programming in C was too difficult for me so I wrote a dos console chess program in 32 bit assembler. It lacked castling, ep and promotion. It was not very useful but I setup positions to avoid castling and played by hand some games against CM5000. My chess program held its own against CM5000 and even got some winning positions before the limitations got in the way. My chess engine was basically a material only searcher with a caveat. It counted beta cutoffs for each root move and played the best material move with the highest number of favorable beta cutoffs for the engine. The trick that made it work (that I had forgotten until recently) was that each root move was searched with an open window (-inf, inf) so the cutoff count for each root move was accurate. But I still could not program in C.

Sometime around 2003/4 I started writing Carnivor (still for dos, hence no e at the end). I was still having trouble with C so I wrote the MoveGen, MakeMove and TakeBack functions in assembler. But eventually I was able to write all of Carnivor in C. Then I downloaded Crafty and learned about bitboards. I could not understand Rotated Bitboards so I created my own bitboard move generator. Well recreated is probably a more accurate word because later I found out that my bitboards were very similar to Chess4.5 classic bitboards. I was not confident that they would work so first I put my bitboards into TSCP1.81 to test them. They worked perfectly so I sent my bitboard version of TSCP1.81 to Tom Kerrigan and he put it up on his site for download. Then I started writing RomiChess that was released in June of 2005 to Leo Dicksman's site.

So it took me from 1983 to 2005 to write a successful chess engine. That is 22 years it took me. I don't have another 22 years to master all the complicated nuances of modern C/C++ before I create something useful in this thread. So I'm just going to do it as I know how and leave it up to the target audience, the novice wantabe, to learn from all the wise council offered in this thread. Thanks!
JohnWoe
Posts: 529
Joined: Sat Mar 02, 2013 11:31 pm

Re: If I were a Professor of Computer Science

Post by JohnWoe »

typedef int s32; is simply std::int32_t
But int could be whatever in C++.
Using exceptions instead of crashing is the way to. Even if it costs 1 Elo.
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: If I were a Professor of Computer Science

Post by Mike Sherwin »

After an eventful weekend we are back in class uncovering the mysteries of programming a computer to play chess. Our in class tutorial engine Quixotic needs to be able to generate sliding piece moves efficiently. And we would like it to be something new so the class can gain the experience of such an exercise. After long consideration during the weekend I decided upon combining two different established methods to form a new method that is hopefully better than the originals. The first method was created by yours truly and is named SISSY bitboards. Okay class enough of the giggling. SISSY is all in capitals and stands for Split Index Super Set Yielding Bitboards. The second method is Kindergarten Bitboards by Gerd Isenburg. The new combined method is called Kindergarten Super SISSY Bitboards. More giggling? I'll wait until you get it out of your system. Everyone good? Okay let's begin.

Code: Select all

u64 RookAttacks(sq, occ) {
    u64 vBlockers = occ & (VERTICLE_FILE_MASK << (sq & 7));
    u64 hBlockers = occ & (HORIZONTAL_RANK_MASK << (sq >> 3);
    u64 vIndex = (vBlockers >> (sq & 7) * MAIN_DIAGONAL) >> 57;
    u64 hIndex = hBlockers >> ((sq >> 3) + 1;
    return vSuperSet[sq][vIndex] & hSuperSet[sq][hIndex];

As Gerd said, "Wasn't that simple? That is why it is called kindergarten bitboards" as it uses a simple multiplication!
And the name SISSY is because of two indexes (Split Index) Yielding two Super Sets!

Code: Select all

u64 BishopAttacks(sq, occ) {
    u64 dBlockers = (occ & diagonalMask[sq]);
    u64 aBlockers = (occ & antidiagMaskEx[sq]); 
    u64 dIndex = (dBlockers * 0x0202020202020202) >> 58;
    u64 aIndex = (aBlockers * 0x0202020202020202) >> 58;
    return dSuperSet[sq][dIndex] & aSuperSet[sq][aIndex];
}
Tonight's assignment is to form an opinion of its performance and why.
Those with an accurate assessment will receive one extra credit point on the next exam!
In the next class we will write the initialization code.
Class dismissed. :D