Starting a new project - Rommie

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

Starting a new project - Rommie

Post by Mike Sherwin »

Working on Bricabrac much was learned. And going back and looking at what was done in RomiChess I realized that some of what was done in RomiChess is better. So Rommie will have the best of both. RomiChess in the starting position searches 7,325k nodes per second while Bricabrac searches about 10,000k nodes per second. I can do better than that.

A note on how RomiChess got its name. On the TV show Andromeda Ascendant Rommie (Lexa Doig) was the starship Andromeda Ascendants' android AI avatar. In one episode Rommie was playing chess against captain Hunt (Kevin Sorbo). So I thought it would make a good name for a chess engine. Well back then I thought her name was spelled Romi. It was much later that I found out that the correct spelling was Rommie. :shock:

RomiChess is single threaded. Rommie will be multi threaded! :D Another change will be a main search that at remaining depth < 4 will call a search optimized for a remaining depth of < 4. It will save a lot of "if" statements to test for remaining depth of < 4! And there are quite a few other changes as well. Also Rommie will be a multiple file program like RomiChess. When I get a little more code written I'll start posting some code, explaining what is being done.

Lexa Doig as Rommie.
https://static.wikia.nocookie.net/headh ... 1204040248

:D
User avatar
mvanthoor
Posts: 1784
Joined: Wed Jul 03, 2019 4:42 pm
Location: Netherlands
Full name: Marcel Vanthoor

Re: Starting a new project - Rommie

Post by mvanthoor »

Mike Sherwin wrote: Thu Mar 03, 2022 11:08 am When I get a little more code written I'll start posting some code, explaining what is being done.
Good luck.

Personally I like "Romi" better than "Rommie", to be honest.

Maybe for the next project you could make spaghetti-code and call it "RamenChess" :lol:
Author of Rustic, an engine written in Rust.
Releases | Code | Docs | Progress | CCRL
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Starting a new project - Rommie

Post by lithander »

Awesome news!

I really look forward to playing against this new engine of yours with Leorik! And I appreciate the background info about how it got the name. I could write a short essay about why Leorik is called Leorik and how exactly the Logo (which exists only as scribles at this point) is composed. There should be a place where these non-obvious details about engines are collected. Maybe Günther's RWBC chronology needs a Trivia collumn! ;)

The picture you linked was broken for me however. I had to manually edit the link to see it.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
eligolf
Posts: 114
Joined: Sat Nov 14, 2020 12:49 pm
Full name: Elias Nilsson

Re: Starting a new project - Rommie

Post by eligolf »

Cool, looking forward of seeing the progress! :D
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Starting a new project - Rommie

Post by Mike Sherwin »

mvanthoor wrote: Thu Mar 03, 2022 11:49 am
Mike Sherwin wrote: Thu Mar 03, 2022 11:08 am When I get a little more code written I'll start posting some code, explaining what is being done.
Good luck.

Personally I like "Romi" better than "Rommie", to be honest.

Maybe for the next project you could make spaghetti-code and call it "RamenChess" :lol:
:lol: Too much pasta is not good for healthy living!
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Starting a new project - Rommie

Post by Mike Sherwin »

lithander wrote: Thu Mar 03, 2022 11:59 am Awesome news!

I really look forward to playing against this new engine of yours with Leorik! And I appreciate the background info about how it got the name. I could write a short essay about why Leorik is called Leorik and how exactly the Logo (which exists only as scribles at this point) is composed. There should be a place where these non-obvious details about engines are collected. Maybe Günther's RWBC chronology needs a Trivia collumn! ;)

The picture you linked was broken for me however. I had to manually edit the link to see it.
I'm on a mission now so progress should be quick. A lot of code can be copied and pasted. :D
The link works fine for me, so I would not know how to fix it. :(
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Starting a new project - Rommie

Post by Mike Sherwin »

eligolf wrote: Thu Mar 03, 2022 12:21 pm Cool, looking forward of seeing the progress! :D
Thanks, I'll do my best!
User avatar
lithander
Posts: 915
Joined: Sun Dec 27, 2020 2:40 am
Location: Bremen, Germany
Full name: Thomas Jahn

Re: Starting a new project - Rommie

Post by lithander »

Mike Sherwin wrote: Thu Mar 03, 2022 12:26 pm The link works fine for me, so I would not know how to fix it. :(
I had to remove everything after the ".jpg" from the URL to get it to work.
Minimal Chess (simple, open source, C#) - Youtube & Github
Leorik (competitive, in active development, C#) - Github & Lichess
Mike Sherwin
Posts: 965
Joined: Fri Aug 21, 2020 1:25 am
Location: Planet Earth, Sol system
Full name: Michael J Sherwin

Re: Starting a new project - Rommie

Post by Mike Sherwin »

I have a nice skeleton started.

Code: Select all

// Rommie.cpp
// A Chess Engine
// By Michael Sherwin

#include <bit>

#include "Defines.cpp"
#include "Variables.cpp"
#include "Move.cpp"
#include "GenMoves.cpp"
#include "Search.cpp"
#include "GetCommand.cpp"
#include "Initialize.cpp"
#include "Main.cpp"

Code: Select all

// Defines.cpp

typedef char s08;
typedef unsigned char u08;
typedef int s32;
typedef unsigned long long u64;

enum { EXIT, GETCMD, ROMMIE, MOVE };

enum { BLACK, WHITE };

enum { 
  EMPTY,
  WP2, WP3, WP4, WP5, WP6, WP7, WN, WB, WRC, WR, WQ, WKC, WK, WCS, WCL,
  BP7, BP6, BP5, BP4, BP3, BP2, BN, BB, BRC, BR, BQ, BKC, BK, BCS, BCL 
};

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

enum { FILE1, FILE2, FILE3, FILE4, FILE5, FILE6, FILE7, FILE8 };
enum { RANK1, RANK2, RANK3, RANK4, RANK5, RANK6, RANK7, RANK8 };

struct Rays {
  u64 rayNW;
  u64 rayNN;
  u64 rayNE;
  u64 rayEE;
  u64 raySE;
  u64 raySS;
  u64 raySW;
  u64 rayWW;
  u64 rwsNW;
  u64 rwsNN;
  u64 rwsNE;
  u64 rwsEE;
  u64 rwsSE;
  u64 rwsSS;
  u64 rwsSW;
  u64 rwsWW;
};

struct sMove {
  u08 fs;
  u08 ts;
  u08 ft;
  u08 tt;
  s32 sc;
};

union uMove {
  sMove s;
  u64 m;
};

struct Thread {
  u64 pieceSquareBits[2];
  s32 board[64];
  s32 stm;
  s32 ply;
};

#define pieceSquareBits t->pieceSquareBits
#define board t->board
#define stm t->stm
#define ply t->ply

Code: Select all

// Variables.cpp

s32 mode;

Rays ray[65];

u64 bob[64];
u64 rob[64];
u64 qob[64];

Thread* thread[32];
Thread* t;

Code: Select all

// Main.cpp

s32 main() {

  Initialize();

  while (mode != EXIT) {
	if (mode == GETCMD) GetCommand();
	if (mode == ROMMIE) RLSearch(t);
	if (mode == MOVE) GameMove();
  }

  return 0;
}

Code: Select all

// Initialize.cpp

void NewGame() {

}

void Initialize() {
  int sq, ts, file, rank, c, maxThreads;

  mode = GETCMD;

  maxThreads = 32;

  Thread* Rommie = new Thread[maxThreads];
  for (c = 0; c < maxThreads; c++) {
	thread[c] = Rommie + c;
  }

  t = thread[0];

  ray[64].rayNW = 0;
  ray[64].rayNN = 0;
  ray[64].rayNE = 0;
  ray[64].rayEE = 0;
  ray[64].raySE = 0;
  ray[64].raySS = 0;
  ray[64].raySW = 0;
  ray[64].rayWW = 0;

  for (sq = A1; sq <= H8; sq++) {
	file = sq & 7;
	rank = sq >> 3;
	bob[sq] = 0;
	rob[sq] = 0;
	qob[sq] = 0;

	// Northwest
	ray[sq].rayNW = 0;
	for (c = 1, ts = sq + 7; file - c >= FILE1 && rank + c <= RANK8; c++, ts += 7) ray[sq].rayNW |= 1ull << ts;
	ray[sq].rwsNW |= ray[sq].rayNW & 0x8000000000000001;
	bob[sq] |= ray[sq].rayNW;

	// Northeast
	ray[sq].rayNE = 0;
	for (c = 1, ts = sq + 9; file + c <= FILE8 && rank + c <= RANK8; c++, ts += 9) ray[sq].rayNE |= 1ull << ts;
	ray[sq].rwsNE |= ray[sq].rayNE & 0x8000000000000001;
	bob[sq] |= ray[sq].rayNE;

	// Southeast
	ray[sq].raySE = 0;
	for (c = 1, ts = sq - 7; file + c <= FILE8 && rank - c >= RANK1; c++, ts -= 7) ray[sq].raySE |= 1ull << ts;
	ray[sq].rwsSE |= ray[sq].raySE & 0x8000000000000001;
	bob[sq] |= ray[sq].raySE;

	// Southwest
	ray[sq].raySW = 0;
	for (c = 1, ts = sq - 9; file - c >= FILE1 && rank - c >= RANK1; c++, ts -= 9) ray[sq].raySW |= 1ull << ts;
	ray[sq].rwsSW |= ray[sq].raySW & 0x8000000000000001;
	bob[sq] |= ray[sq].raySW;

	// North
	ray[sq].rayNN = 0;
	for (c = 1, ts = sq + 8; rank + c <= RANK8; c++, ts += 8) ray[sq].rayNN |= 1ull << ts;
	ray[sq].rwsNN |= ray[sq].rayNN & 0x8000000000000001;
	rob[sq] |= ray[sq].rayNN;

	// East
	ray[sq].rayEE = 0;
	for (c = 1, ts = sq + 1; file + c <= FILE8; c++, ts += 1) ray[sq].rayEE |= 1ull << ts;
	ray[sq].rwsEE |= ray[sq].rayEE & 0x8000000000000001;
	rob[sq] |= ray[sq].rayEE;

	// South
	ray[sq].raySS = 0;
	for (c = 1, ts = sq - 8; rank - c >= RANK1; c++, ts -= 8) ray[sq].raySS |= 1ull << ts;
	ray[sq].rwsSS |= ray[sq].raySS & 0x8000000000000001;
	rob[sq] |= ray[sq].raySS;

	// West
	ray[sq].rayWW = 0;
	for (c = 1, ts = sq - 1; file - c >= FILE1; c++, ts -= 1) ray[sq].rayWW |= 1ull << ts;
	ray[sq].rwsWW |= ray[sq].rayWW & 0x8000000000000001;
	rob[sq] |= ray[sq].rayWW;

	qob[sq] = bob[sq] | rob[sq];
  }
}

Code: Select all

// Search.cpp

void RootSearch(Thread* t, s32 depth) {

}

void IterativeDeepeningSearch(Thread* t) {
  s32 depth;

  for (depth = 1; ; depth++) {
	RootSearch(t, depth);
  }

}

// reinforcement learning in real time
void RLSearch(Thread* t) {
  // RL code
  IterativeDeepeningSearch(t);
}
The code so far is just to support writing the GenMoves function. I haven't got very far yet but it will come together in the next few days. Speed is of course is the goal. Like in RomiChess only the bitboards will be generated as they are very efficient for finding moves for staged move generation and removing the corresponding bit when a move is found. It is also efficient for determining legality instead of calling Incheck() all the time. On newer CPUs there is plenty of instruction cache so having more code than say on a 80386 is no problem. Some of the case statements can be combined like I used to do but that type of code is not pretty code in my opinion. The pawns by Rank are all different pseudo types, to save time, as it is known for each pseudo type what to do. The rook and king also have a pseudo types depending if castling is still possible or not. Once castling moves are found and stored MakeMove and TakeBack have the pseudo types WCS, WCL, BCS, BCL to speed up that processing. And when castling is not possible then no more clock cycles will be wasted. By itself it makes little difference but everything adds up. :D

Code: Select all

// GenMoves.cpp

s32 GenMoves(Thread* t) {
  u64 fromSquares, bb, notMe, occ, captures;
  u08 fs;
  s32 ft;

  notMe = pieceSquareBits[1 - stm];

  fromSquares = pieceSquareBits[stm];

  occ = fromSquares | notMe;

  do {
	fs = std::countr_zero(fromSquares);
	fromSquares ^= 1ull << fs;
	ft = board[fs];
	switch (ft) {
	case EMPTY:
	  // can't get here
	  break;
	case WP2:

	  break;
	case WP3:

	  break;
	case WP4:

	  break;
	case WP5:

	  break;
	case WP6:

	  break;
	case WP7:

	  break;
	case WN:

	  break;
	case WB:

	  break;
	case WRC:
	case WR:

	  break;
	case WQ:
	  occ |= 0x8000000000000001;
	  bb = ray[std::countr_zero(ray[fs].rwsNW & occ)].rayNW
		| ray[std::countr_zero(ray[fs].rwsNN & occ)].rayNN
		| ray[std::countr_zero(ray[fs].rwsNE & occ)].rayNE
		| ray[std::countr_zero(ray[fs].rwsEE & occ)].rayEE
		| ray[63 - std::countl_zero(ray[fs].rwsSE & occ)].raySE
		| ray[63 - std::countl_zero(ray[fs].rwsSS & occ)].raySS
		| ray[63 - std::countl_zero(ray[fs].rwsSW & occ)].raySW
		| ray[63 - std::countl_zero(ray[fs].rwsWW & occ)].rayWW
		^ qob[fs]
		& notMe;
	  break;
	case WKC:

	case WK:

	  break;
	case WCS:
	case WCL:
	  // can't get here
	  break;
	case BP7:

	  break;
	case BP6:

	  break;
	case BP5:

	  break;
	case BP4:

	  break;
	case BP3:

	  break;
	case BP2:

	  break;
	case BN:

	  break;
	case BB:

	  break;
	case BRC:
	case BR:

	  break;
	case BQ:
	  occ |= 0x8000000000000001;
	  bb = ray[std::countr_zero(ray[fs].rwsNW & occ)].rayNW
		| ray[std::countr_zero(ray[fs].rwsNN & occ)].rayNN
		| ray[std::countr_zero(ray[fs].rwsNE & occ)].rayNE
		| ray[std::countr_zero(ray[fs].rwsEE & occ)].rayEE
		| ray[63 - std::countl_zero(ray[fs].rwsSE & occ)].raySE
		| ray[63 - std::countl_zero(ray[fs].rwsSS & occ)].raySS
		| ray[63 - std::countl_zero(ray[fs].rwsSW & occ)].raySW
		| ray[63 - std::countl_zero(ray[fs].rwsWW & occ)].rayWW
		^ qob[fs]
		& notMe;
	  break;
	case BKC:

	case BK:

	  break;
	}

  } while (fromSquares);

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

Re: Starting a new project - Rommie

Post by Mike Sherwin »

If I understand new correctly I could write

int* intArray = new int[10];

to get a pointer to an array of 10 int pointers. Is that correct? If so then

*(intArray + 3) = 6;
*(intArray + 6) = 3;

int AddTwoInts(int* y, int* z) {
return *y + *z;
}

int a = *(intArray + 3);
int b = *(intArray + 6);

int x = AddTwoInts(&a, &b); // returns 9 (6 + 3) into x

So if this is all correct then

Thread* thread = new Thread[32];

should be a pointer to an array of 32 Thread pointers? However, the number of threads is not known beforehand so thread cannot be initialized on creation. Therefore we only declare thread as just a Thread pointer.

Thread* thread;

Then in initialization we first find maxThreads somehow. Then we write

thread = new Thread[maxThreads];

and now we (should) have a pointer to an array of maxThreads Thread pointers. Which can be accessed like

Thread* t;

t = *(thread + n);

Or can it be written

t = thread[n]; ?

Is it that easy or am I missing something? Maybe t needs to be declared as a pointer to a pointer

Thread **t;

because that is what t is, a pointer to a pointer?

Are there any C++ pointer grandmasters (a C++ pointer expert would also do :))) out there that can get me started correctly on this? :D