mirror of
https://github.com/StewBC/cc65-Chess.git
synced 2026-10-25 01:02:25 +00:00
The old engine was weak for one structural reason. cpu_ScorePieceSubTree
looked like minimax but had a branching factor of one: at each level it
followed only the single reply a static heuristic ranked first, so it
evaluated one predicted line per root move and never saw a refutation the
heuristic missed. Two things compounded it - the root considered only one
move per piece, and the scoring functions returned move desirability rather
than position value, which is not a quantity minimax can validly accumulate
across plies.
What replaces it:
engine.c 0x88 board, make/unmake against a per-ply state stack, legal
move generation, and attack queries by ray-cast. The stored
attack database is gone - eng_IsAttacked walks outwards from a
square instead of rebuilding 64 squares' worth of state after
every trial move.
eval.c material and piece-square tables as a pure position value,
kept as a running total updated incrementally by make/unmake.
search.c iterative-deepening negamax with alpha-beta, MVV-LVA and killer
ordering, and a quiescence search over captures.
board.c becomes the adapter between engine and display; plat.h and the 0-63
tile numbering are untouched, so no platform file needed editing - including
the three that cannot be built here.
Verified: perft exact on all five reference positions to depth 5, and to
depth 6 on the two that publish one. Against the engine it replaces, at that
engine's strongest setting: 6 wins, 0 losses.
Smaller than what it replaces despite doing far more - 31129 bytes total on
the c64 at optspeed against 31543 for the two engines' worth of machinery it
supersedes, and 514 bytes less RAM. All five buildable targets fit.
tests/ carries the harness this was developed against: perft, a game fuzzer,
castling scenarios, an internal match runner, on-target C64 benchmarks, and
a UCI adapter for playing external engines. It stops being gitignored here -
the exclusion was scaffolding added during the rework to keep git status
quiet, and the harness is the evidence for everything above.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
116 lines
2.4 KiB
C
116 lines
2.4 KiB
C
/*
|
|
* testutil.c
|
|
* cc65 Chess - test support
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include "types.h"
|
|
#include "globals.h"
|
|
#include "engine.h"
|
|
#include "testutil.h"
|
|
|
|
static const char sc_pieceChar[] = { '.','R','N','B','Q','K','P' };
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
char test_Square(const char *algebraic)
|
|
{
|
|
// tile 0 is a8, so rank 8 maps to row 0
|
|
return (char)((('8' - algebraic[1]) * 8) + (algebraic[0] - 'a'));
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
void test_TileName(char tile, char *out3)
|
|
{
|
|
out3[0] = 'a' + (tile & 7);
|
|
out3[1] = '8' - (tile / 8);
|
|
out3[2] = '\0';
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
// The fullmove number is always 1: nothing that reads these cares, and the
|
|
// engine does not track it
|
|
void test_EngineGetFEN(char side, char *out)
|
|
{
|
|
char row, file, *p = out;
|
|
|
|
for(row = 0; row < 8; ++row)
|
|
{
|
|
char empty = 0;
|
|
|
|
for(file = 0; file < 8; ++file)
|
|
{
|
|
char piece = geBoard[(row << 4) | file];
|
|
char type = piece & PIECE_DATA;
|
|
|
|
if(NONE == type)
|
|
{
|
|
++empty;
|
|
continue;
|
|
}
|
|
if(empty)
|
|
{
|
|
*p++ = '0' + empty;
|
|
empty = 0;
|
|
}
|
|
*p++ = (piece & PIECE_WHITE) ? sc_pieceChar[type]
|
|
: sc_pieceChar[type] - 'A' + 'a';
|
|
}
|
|
if(empty)
|
|
*p++ = '0' + empty;
|
|
if(row < 7)
|
|
*p++ = '/';
|
|
}
|
|
|
|
*p++ = ' ';
|
|
*p++ = (SIDE_WHITE == side) ? 'w' : 'b';
|
|
*p++ = ' ';
|
|
|
|
if(!geCastle)
|
|
*p++ = '-';
|
|
else
|
|
{
|
|
if(geCastle & ENG_CASTLE_WK) *p++ = 'K';
|
|
if(geCastle & ENG_CASTLE_WQ) *p++ = 'Q';
|
|
if(geCastle & ENG_CASTLE_BK) *p++ = 'k';
|
|
if(geCastle & ENG_CASTLE_BQ) *p++ = 'q';
|
|
}
|
|
|
|
*p++ = ' ';
|
|
if(ENG_NO_SQUARE == geEP)
|
|
*p++ = '-';
|
|
else
|
|
{
|
|
char name[3];
|
|
test_TileName(ENG_TO_TILE(geEP), name);
|
|
*p++ = name[0];
|
|
*p++ = name[1];
|
|
}
|
|
|
|
sprintf(p, " %d 1", geHalfmove);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
void test_DumpBoard(const char *tag)
|
|
{
|
|
char row, file;
|
|
|
|
printf("--- %s (kings b=%02x w=%02x, ep=%02x, castle=%x, halfmove=%d)\n",
|
|
tag, geKing[0], geKing[1], geEP, geCastle, geHalfmove);
|
|
|
|
for(row = 0; row < 8; ++row)
|
|
{
|
|
printf(" %d ", 8 - row);
|
|
for(file = 0; file < 8; ++file)
|
|
{
|
|
char piece = geBoard[(row << 4) | file];
|
|
char c = sc_pieceChar[piece & PIECE_DATA];
|
|
|
|
if(!(piece & PIECE_WHITE) && (piece & PIECE_DATA))
|
|
c = c - 'A' + 'a';
|
|
printf("%c", (piece & PIECE_DATA) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
printf(" abcdefgh\n");
|
|
}
|