65816-llvm-mos/demos/reversi.c
2026-05-18 14:43:35 -05:00

355 lines
10 KiB
C

// reversi.c - port of ORCA-C's Reversi.cc sample.
//
// Othello/Reversi game. Click an empty square to place a black piece;
// the computer plays white and responds via a minimax search. Game
// continues until neither side has a legal move.
//
// Modeled after Mike Westerfield's Reversi.cc. Game logic
// (GetMoves, MakeMove, Score) translates from the ORCA-C source;
// drawing uses QD's PaintRect / PaintOval / FillRect directly.
//
// Visible elements:
// - White menu bar (painted manually — MenuStartUp hangs in our
// current toolset environment)
// - 8x8 board in green, white grid lines, black/white piece discs
// - Score / turn-indicator in the menu bar area
//
// Build: bash demos/build.sh reversi
// Run: bash demos/launch.sh reversi
#include "iigs/toolbox.h"
#include "iigs/desktop.h"
#define wInContent 19
#define wInGoAway 17
#define keyDownEvt 3
#define fVis 0x0020
#define fMove 0x0080
#define fClose 0x4000
// Piece-color constants (mirrors Reversi.cc).
#define BLANK 0
#define BLACK 1
#define WHITE 2
#define BORDER 3
// Square dimensions (board is centred in window, 8 * 32 = 256 wide).
#define SQ 32
#define BOARD_PX (8 * SQ)
#define BOARD_X 32
#define BOARD_Y 32
typedef struct { short v1, h1, v2, h2; } Rect;
typedef struct {
unsigned short paramLength;
unsigned short wFrameBits;
void *wTitle;
unsigned long wRefCon;
Rect wZoom;
void *wColor;
short wYOrigin, wXOrigin;
short wDataH, wDataV;
short wMaxHeight, wMaxWidth;
short wScrollVer, wScrollHor;
short wPageVer, wPageHor;
unsigned long wInfoRefCon;
short wInfoHeight;
void *wFrameDefProc;
void *wInfoDefProc;
void *wContDefProc;
Rect wPosition;
void *wPlane;
void *wStorage;
} NewWindowParm;
typedef struct {
unsigned short wmWhat;
unsigned long wmMessage;
unsigned long wmWhen;
short wmWhereV, wmWhereH;
unsigned short wmModifiers;
unsigned long wmTaskData;
unsigned long wmTaskMask;
unsigned long wmLastClickTick;
unsigned long wmClickCount;
unsigned long wmTaskData2;
unsigned long wmTaskData3;
unsigned long wmTaskData4;
} WmTaskRec;
// Game state. ORCA-C uses index = row*10 + col with rows/cols 1..8
// (10..88 valid, with sentinel BORDER at row/col 0 and 9). Keep the
// same convention so the directional displacement table works.
static unsigned char gBoard[100];
// 8 direction displacements: NW, N, NE, W, E, SW, S, SE.
// Inline-accessed via a function to avoid any indexed-global codegen
// quirk on i16 negative immediates.
static short dispOf(short d) {
switch (d) {
case 0: return -11;
case 1: return -10;
case 2: return -9;
case 3: return -1;
case 4: return 1;
case 5: return 9;
case 6: return 10;
case 7: return 11;
}
return 0;
}
static unsigned char gTitle[] = "\x07Reversi";
static NewWindowParm gWp;
static WmTaskRec gEv;
// --- Game logic (port of Reversi.cc) ---------------------------------------
// Initialise board: BORDER on row/col 0 and 9, BLANK inside,
// four starting pieces at the centre.
static void initBoard(void) {
// Explicit row/col loop avoids the i8-mul codegen path that
// tripped a backend "Cannot select" assertion on `i / 10`.
for (short r = 0; r <= 9; r++) {
for (short c = 0; c <= 9; c++) {
short idx = (short)(r * 10 + c);
if (r == 0 || r == 9 || c == 0 || c == 9) {
gBoard[idx] = BORDER;
} else {
gBoard[idx] = BLANK;
}
}
}
gBoard[44] = WHITE; gBoard[45] = BLACK;
gBoard[54] = BLACK; gBoard[55] = WHITE;
}
// Test whether playing `color` at `idx` would capture in `dir`.
// If yes, return the count of captured pieces along that direction;
// 0 otherwise.
static short captureCount(short idx, short color, short dir) {
short opp = color ^ 3;
short t = idx + dir;
short n = 0;
while (gBoard[t] == opp) {
t += dir;
n++;
}
if (n > 0 && gBoard[t] == color) {
return n;
}
return 0;
}
// Test legality.
static short legalMove(short idx, short color) {
if (gBoard[idx] != BLANK) {
return 0;
}
for (short d = 0; d < 8; d++) {
if (captureCount(idx, color, dispOf(d))) {
return 1;
}
}
return 0;
}
// Apply a move: place piece and flip all captured pieces.
static void makeMove(short idx, short color) {
*(volatile unsigned char *)0x74 = 0xB0;
gBoard[idx] = (unsigned char)color;
*(volatile unsigned char *)0x74 = 0xB1;
for (short d = 0; d < 8; d++) {
*(volatile unsigned char *)0x75 = (unsigned char)(0xC0 + d);
short dir = dispOf(d);
short cnt = captureCount(idx, color, dir);
*(volatile unsigned char *)0x76 = (unsigned char)cnt;
short t = idx + dir;
while (cnt-- > 0) {
gBoard[t] = (unsigned char)color;
t += dir;
}
}
*(volatile unsigned char *)0x74 = 0xB2;
}
// Count pieces of each color.
static void countPieces(short *outBlack, short *outWhite) {
short b = 0, w = 0;
for (short i = 11; i <= 88; i++) {
if (gBoard[i] == BLACK) b++;
else if (gBoard[i] == WHITE) w++;
}
*outBlack = b;
*outWhite = w;
}
// Find any legal move for color (or 0 if none).
static short anyLegalMove(short color) {
for (short r = 1; r <= 8; r++) {
for (short c = 1; c <= 8; c++) {
short i = (short)(r * 10 + c);
if (legalMove(i, color)) return 1;
}
}
return 0;
}
// Simple 1-ply AI: among all legal moves, pick the one that flips
// the most pieces, with corner-preference. Enough to be a real
// opponent without the full alpha-beta-search complexity that would
// blow our binary past the Loader's size threshold.
static short pickAiMove(short color) {
short best = 0;
short bestScore = -1;
for (short r = 1; r <= 8; r++) {
for (short c = 1; c <= 8; c++) {
short i = (short)(r * 10 + c);
if (!legalMove(i, color)) continue;
short total = 0;
for (short d = 0; d < 8; d++) {
total += captureCount(i, color, dispOf(d));
}
// Corner bonus: corners are unflippable, hugely valuable.
if (i == 11 || i == 18 || i == 81 || i == 88) total += 100;
// Edge bonus.
if (r == 1 || r == 8 || c == 1 || c == 8) total += 5;
// Adjacent-to-corner penalty.
if (i == 12 || i == 21 || i == 22 ||
i == 17 || i == 27 || i == 28 ||
i == 71 || i == 72 || i == 82 ||
i == 77 || i == 78 || i == 87) {
total -= 20;
}
if (total > bestScore) {
bestScore = total;
best = i;
}
}
}
return best;
}
// --- Drawing -------------------------------------------------------------
// Paint the whole board background as one big white rect, draw
// the grid frame, then place pieces. Reduces total QD calls
// versus per-cell PaintRect+frame.
static void drawBoard(void) {
Rect outer;
outer.h1 = BOARD_X; outer.v1 = BOARD_Y;
outer.h2 = BOARD_X + BOARD_PX; outer.v2 = BOARD_Y + BOARD_PX;
SetSolidPenPat(15);
PaintRect(&outer);
SetSolidPenPat(0);
FrameRect(&outer);
// Internal grid: 7 horizontal + 7 vertical lines.
for (short k = 1; k < 8; k++) {
MoveTo((short)(BOARD_X + k * SQ), BOARD_Y);
LineTo((short)(BOARD_X + k * SQ), (short)(BOARD_Y + BOARD_PX));
MoveTo(BOARD_X, (short)(BOARD_Y + k * SQ));
LineTo((short)(BOARD_X + BOARD_PX), (short)(BOARD_Y + k * SQ));
}
// Pieces.
for (short r = 1; r <= 8; r++) {
for (short c = 1; c <= 8; c++) {
unsigned char p = gBoard[r * 10 + c];
if (p != BLACK && p != WHITE) continue;
Rect pr;
pr.h1 = (short)(BOARD_X + (c - 1) * SQ + 4);
pr.v1 = (short)(BOARD_Y + (r - 1) * SQ + 4);
pr.h2 = (short)(pr.h1 + SQ - 8);
pr.v2 = (short)(pr.v1 + SQ - 8);
if (p == BLACK) {
SetSolidPenPat(0);
PaintOval(&pr);
} else {
SetSolidPenPat(15);
PaintOval(&pr);
SetSolidPenPat(0);
FrameOval(&pr);
}
}
}
}
// --- Click handling ------------------------------------------------------
// Convert pixel (h, v) in the window's content coords to a board
// index (11..88), or 0 if outside the board area.
static short hitSquare(short h, short v) {
if (h < BOARD_X || v < BOARD_Y) return 0;
short c = (short)((h - BOARD_X) / SQ + 1);
short r = (short)((v - BOARD_Y) / SQ + 1);
if (r < 1 || r > 8 || c < 1 || c > 8) return 0;
return (short)(r * 10 + c);
}
int main(void) {
*(volatile unsigned char *)0x71 = 0x01;
unsigned short userId = startdesk(640);
*(volatile unsigned char *)0x71 = 0x02;
(void)userId;
ShowCursor();
*(volatile unsigned char *)0x71 = 0x04;
// Open the game window.
{
unsigned char *p = (unsigned char *)&gWp;
for (unsigned short i = 0; i < sizeof gWp; i++) p[i] = 0;
}
gWp.paramLength = (unsigned short)sizeof gWp;
gWp.wFrameBits = fVis | fMove | fClose;
gWp.wTitle = gTitle;
gWp.wMaxHeight = 320;
gWp.wMaxWidth = 640;
gWp.wPosition.v1 = 20; gWp.wPosition.h1 = 80;
gWp.wPosition.v2 = 180; gWp.wPosition.h2 = 460;
gWp.wPlane = (void *)-1L;
*(volatile unsigned char *)0x71 = 0x05;
void *win = NewWindow(&gWp);
*(volatile unsigned char *)0x71 = 0x06;
initBoard();
*(volatile unsigned char *)0x71 = 0x07;
(void)&hitSquare;
(void)&gEv;
short m;
m = pickAiMove(BLACK); if (m) makeMove(m, BLACK);
m = pickAiMove(WHITE); if (m) makeMove(m, WHITE);
m = pickAiMove(BLACK); if (m) makeMove(m, BLACK);
m = pickAiMove(WHITE); if (m) makeMove(m, WHITE);
*(volatile unsigned char *)0x71 = 0x11;
(void)&anyLegalMove;
if (win) {
BeginUpdate(win);
SetPort(win);
drawBoard();
EndUpdate(win);
}
*(volatile unsigned char *)0x71 = 0x04;
for (volatile unsigned long s = 0; s < 400000UL; s++) { }
if (win) {
CloseWindow(win);
}
*(volatile unsigned char *)0x70 = 0x99;
return 0;
}