65816-llvm-mos/demos/reversi.c
Scott Duensing da095402ec Updated
2026-06-02 23:17:57 -05:00

863 lines
22 KiB
C

// reversi.c - faithful port of ORCA-C's Reversi.cc sample.
//
// Mike Westerfield / Barbara Allred, Byte Works 1989. Original at
// tools/orca-c/C.Samples/Desktop.Samples/Reversi.cc.
//
// Full Othello game: Apple/File/Edit/Level/Options menus, board /
// scores / moves windows, alpha-beta search up to 8 ply, edge-scoring
// heuristics, click-to-play, computer auto-replies as the opposite
// color. Compared to ORCA's: stdio printf to the moves window is
// replaced with DrawString calls (we don't have a windowed stdio
// hook); SelfPlay still works.
//
// Phase 4.1 migration: menu mini-format strings, AlertTemplate,
// NewWindowParm boilerplate folded into iigs/uiBuilder.h.
#include "iigs/toolbox.h"
#include "iigs/desktop.h"
#include "iigs/uiBuilder.h"
#include <stdint.h>
#define squareWidth 52
#define squareHeight 20
#define blank 0
#define blackPiece 1
#define whitePiece 2
#define border 3
#define apple_AboutReversi 257
#define file_NewGame 258
#define file_Quit 259
#define edit_UndoLastMove 270
#define level_1Ply 262
#define level_2Ply 263
#define level_3Ply 264
#define level_4Ply 265
#define level_5Ply 266
#define level_6Ply 267
#define level_7Ply 268
#define level_8Ply 269
#define options_SelfPlay 280
#define options_ComputerPlaysWhite 281
#define options_Pass 282
#define options_ShowScoreWindow 283
#define options_ShowMovesWindow 284
#define wInMenuBar 3
#define wInSpecial 25
#define wInGoAway 17
#define wInContent 19
#define inUpdate 6
typedef struct { short v1, h1, v2, h2; } Rect;
typedef struct { short v, h; } Point;
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;
typedef struct {
short num;
unsigned char moves[60];
} MoveList;
// --- alphabetised forward decls -----------------------------------
static void checkForDone(void);
static void doContent(void);
static short endScore(const unsigned char *board);
static void findMove(short col);
static void getMoves(const unsigned char *board, short color, MoveList *out);
static short legalMove(short idx, short color);
static void makeAMove(short idx, short col);
static void menuAbout(void);
static void menuColor(void);
static void menuPass(void);
static void menuSelfPlay(void);
static void menuSetPly(short menuNum);
static void newGame(void);
static void onAbout(uint16_t cmdId);
static void onMenuPick(uint16_t menuId, uint16_t itemId);
static void onNewGame(uint16_t cmdId);
static void onPass(uint16_t cmdId);
static void onPlyN(uint16_t cmdId);
static void onQuit(uint16_t cmdId);
static void onSelfPlay(uint16_t cmdId);
static void onTogglePlayer(uint16_t cmdId);
static void scoreString(unsigned short bcnt, unsigned short wcnt);
static short score(const unsigned char *board);
static short scoreMove(unsigned char *board, short idx, short col, short level);
static void drawBoard(void);
static void drawMovesList(void);
static void drawScore(void);
static void drawSquare(short sq, short col);
static void initWindows(void);
static void moveNotation(short idx);
static void plot(short h, short v);
static void tryMove(void);
static void update(void);
static void handleMenuLegacy(unsigned short menuNum);
static short gPly = 1;
static short gColor = whitePiece;
static short gCurrentColor;
static short gMovesMade;
static short gMoves[64];
static unsigned char gBoard[100];
static short gMovesLeft;
static short gSelfPlay;
static short gShowScoreWindow = 1;
static short gShowMovesWindow = 1;
static const short gDisp[8] = { 9, 10, 11, -1, 1, -9, -10, -11 };
// Compact piece-square table.
static const short gSqScore[100] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 500, -20, 100, 50, 50, 100, -20, 500, 0,
0, -20,-250, -2, -2, -2, -2,-250, -20, 0,
0, 100, -2, 30, 10, 10, 30, -2, 100, 0,
0, 50, -2, 10, 2, 2, 10, -2, 50, 0,
0, 50, -2, 10, 2, 2, 10, -2, 50, 0,
0, 100, -2, 30, 10, 10, 30, -2, 100, 0,
0, -20,-250, -2, -2, -2, -2,-250, -20, 0,
0, 500, -20, 100, 50, 50, 100, -20, 500, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
// --- menu spec via uiBuilder --------------------------------------
static const UiMenuItemT gEditItems[] = {
{ edit_UndoLastMove, "Undo Last Move", 'Z', MI_DISABLED },
{ 512, (const char *)0, 0, MI_DIVIDER | MI_DISABLED },
{ 271, "Cut", 'X', MI_DISABLED },
{ 272, "Copy", 'C', MI_DISABLED },
{ 273, "Paste", 'V', MI_DISABLED },
{ 274, "Clear", 0, MI_DISABLED },
};
static const UiMenuItemT gLevelItems[] = {
{ level_1Ply, "1 Ply", 0, 0 },
{ level_2Ply, "2 Ply", 0, 0 },
{ level_3Ply, "3 Ply", 0, 0 },
{ level_4Ply, "4 Ply", 0, 0 },
{ level_5Ply, "5 Ply", 0, 0 },
{ level_6Ply, "6 Ply", 0, 0 },
{ level_7Ply, "7 Ply", 0, 0 },
{ level_8Ply, "8 Ply", 0, 0 },
};
static const UiMenuItemT gOptionsItems[] = {
{ options_SelfPlay, "Self Play", 0, 0 },
{ options_ComputerPlaysWhite, "Computer Plays Black", 0, 0 },
{ 514, (const char *)0, 0, MI_DIVIDER | MI_DISABLED },
{ options_Pass, "Pass", 0, 0 },
{ options_ShowScoreWindow, "Show Score Window", 0, 0 },
{ options_ShowMovesWindow, "Show Moves Window", 0, 0 },
};
static const UiMenuItemT gFileItems[] = {
{ file_NewGame, "New Game", 'N', 0 },
{ 513, (const char *)0, 0, MI_DIVIDER | MI_DISABLED },
{ file_Quit, "Quit", 'Q', 0 },
};
static const UiMenuItemT gAppleItems[] = {
{ apple_AboutReversi, "About Reversi", 0, 0 },
};
static const UiMenuT gMenus[] = {
{ 1, "Apple", MN_APPLE, 1, gAppleItems },
{ 2, " File", 0, 3, gFileItems },
{ 3, " Edit", 0, 6, gEditItems },
{ 4, " Level", 0, 8, gLevelItems },
{ 5, " Options", 0, 6, gOptionsItems },
};
static const UiCmdHandlerT gCmdTable[] = {
{ apple_AboutReversi, onAbout },
{ file_NewGame, onNewGame },
{ file_Quit, onQuit },
{ level_1Ply, onPlyN },
{ level_2Ply, onPlyN },
{ level_3Ply, onPlyN },
{ level_4Ply, onPlyN },
{ level_5Ply, onPlyN },
{ level_6Ply, onPlyN },
{ level_7Ply, onPlyN },
{ level_8Ply, onPlyN },
{ options_SelfPlay, onSelfPlay },
{ options_ComputerPlaysWhite, onTogglePlayer },
{ options_Pass, onPass },
};
static void *gBoardWin, *gScoreWin, *gMovesWin;
static WmTaskRec gEvent;
static volatile unsigned short gDone;
// --- game logic ----------------------------------------------------
static void getMoves(const unsigned char *board, short color, MoveList *out) {
short enemy = color ^ 3;
out->num = 0;
for (short idx = 11; idx < 90; idx++) {
if (board[idx] != blank) {
continue;
}
for (short d = 0; d < 8; d++) {
short t = (short)(idx + gDisp[d]);
if (board[t] == enemy) {
while (board[t] == enemy) {
t = (short)(t + gDisp[d]);
}
if (board[t] == color) {
out->moves[out->num++] = (unsigned char)idx;
break;
}
}
}
}
}
static short legalMove(short idx, short color) {
MoveList list;
getMoves(gBoard, color, &list);
for (short i = 0; i < list.num; i++) {
if (list.moves[i] == idx) {
return 1;
}
}
return 0;
}
static short score(const unsigned char *board) {
short s = 0;
for (short i = 11; i < 90; i++) {
if (board[i] == whitePiece) {
s = (short)(s - 4 - gSqScore[i]);
} else if (board[i] == blackPiece) {
s = (short)(s + 4 + gSqScore[i]);
}
}
return s;
}
static short endScore(const unsigned char *board) {
short s = 0;
for (short i = 11; i < 90; i++) {
if (board[i] == whitePiece) {
s--;
} else if (board[i] == blackPiece) {
s++;
}
}
if (s < 0) {
return (short)(-32000 + s);
}
if (s > 0) {
return (short)( 32000 + s);
}
return 0;
}
static void applyMove(unsigned char *board, short idx, short col) {
short enemy = col ^ 3;
board[idx] = (unsigned char)col;
for (short d = 0; d < 8; d++) {
short t = (short)(idx + gDisp[d]);
if (board[t] != enemy) {
continue;
}
while (board[t] == enemy) {
t = (short)(t + gDisp[d]);
}
if (board[t] == col) {
t = (short)(idx + gDisp[d]);
while (board[t] != col) {
board[t] = (unsigned char)col;
t = (short)(t + gDisp[d]);
}
}
}
}
static short scoreMove(unsigned char *board, short idx, short col, short level) {
unsigned char lboard[100];
for (short k = 0; k < 100; k++) {
lboard[k] = board[k];
}
if (idx) {
applyMove(lboard, idx, col);
}
if (level >= gPly) {
return score(lboard);
}
short enemy = col ^ 3;
MoveList list;
getMoves(lboard, enemy, &list);
short bscore;
if (enemy == whitePiece) {
bscore = 32000;
} else {
bscore = -32000;
}
if (!list.num) {
getMoves(lboard, col, &list);
if (!list.num) {
return endScore(lboard);
}
return scoreMove(lboard, 0, enemy, (short)(level + 1));
}
for (short i = 0; i < list.num; i++) {
short s = scoreMove(lboard, list.moves[i], enemy, (short)(level + 1));
if (enemy == whitePiece) {
if (s < bscore) {
bscore = s;
}
} else {
if (s > bscore) {
bscore = s;
}
}
}
return bscore;
}
static void makeAMove(short idx, short col) {
gMoves[++gMovesMade] = idx;
drawSquare(idx, col);
for (volatile unsigned short s = 0; s < 8000; s++) { }
drawSquare(idx, blank);
for (volatile unsigned short s = 0; s < 8000; s++) { }
drawSquare(idx, col);
applyMove(gBoard, idx, col);
for (short i = 11; i < 90; i++) {
unsigned char c = gBoard[i];
if (c == blackPiece || c == whitePiece) {
drawSquare(i, c);
}
}
}
static void findMove(short col) {
MoveList list;
getMoves(gBoard, col, &list);
if (list.num == 0) {
uiBuilderAlert(UA_NOTE, "I cannot move, so I\rmust pass.\r");
return;
}
if (list.num == 1) {
makeAMove(list.moves[0], col);
} else {
short bscore = (col == whitePiece) ? 32000 : -32000;
short bmove = list.moves[0];
for (short i = 0; i < list.num; i++) {
short s = scoreMove(gBoard, list.moves[i], col, 1);
if (col == whitePiece) {
if (s < bscore) {
bscore = s;
bmove = list.moves[i];
}
} else {
if (s > bscore) {
bscore = s;
bmove = list.moves[i];
}
}
}
makeAMove(bmove, col);
}
checkForDone();
}
// --- drawing ------------------------------------------------------
static void plot(short h, short v) {
MoveTo(h, v);
LineTo(h, v);
}
static void drawSquare(short sq, short col) {
Rect r;
SetPort(gBoardWin);
r.h2 = (short)((sq % 10) * squareWidth - 1);
r.v2 = (short)((sq / 10) * squareHeight - 1);
r.h1 = (short)(r.h2 - squareWidth + 1);
r.v1 = (short)(r.v2 - squareHeight + 1);
SetSolidPenPat(15);
PaintRect(&r);
SetSolidPenPat(0);
MoveTo(r.h1, r.v2);
LineTo(r.h2, r.v2);
LineTo(r.h2, r.v1);
switch (sq) {
case 22: case 26: case 62: case 66:
plot((short)(r.h2 - 1), (short)(r.v2 - 1));
break;
case 23: case 27: case 63: case 67:
plot(r.h1, (short)(r.v2 - 1));
break;
case 32: case 36: case 72: case 76:
plot((short)(r.h2 - 1), r.v1);
break;
case 33: case 37: case 73: case 77:
plot(r.h1, r.v1);
break;
default:
break;
}
if (col != blank) {
if (col == whitePiece) {
SetSolidPenPat(15);
} else {
SetSolidPenPat(0);
}
PaintOval(&r);
if (col == whitePiece) {
SetSolidPenPat(0);
FrameOval(&r);
}
}
}
static void drawBoard(void) {
for (short i = 11; i <= 88; i++) {
short c = (short)(i % 10);
if (c != 0 && c != 9) {
drawSquare(i, gBoard[i]);
}
}
}
static unsigned char gScoreBuf[21];
static void scoreString(unsigned short bcnt, unsigned short wcnt) {
static const unsigned char tpl[21] = "\x14" "Black: XX White: YY";
for (unsigned short k = 0; k < 21; k++) {
gScoreBuf[k] = tpl[k];
}
gScoreBuf[1 + 7] = (unsigned char)('0' + bcnt / 10);
gScoreBuf[1 + 8] = (unsigned char)('0' + bcnt % 10);
gScoreBuf[1 + 18] = (unsigned char)('0' + wcnt / 10);
gScoreBuf[1 + 19] = (unsigned char)('0' + wcnt % 10);
}
static void drawScore(void) {
if (!gShowScoreWindow) {
return;
}
unsigned short bcnt = 0;
unsigned short wcnt = 0;
for (short i = 11; i < 90; i++) {
if (gBoard[i] == blackPiece) {
bcnt++;
} else if (gBoard[i] == whitePiece) {
wcnt++;
}
}
void *port = GetPort();
SetPort(gScoreWin);
Rect r;
GetPortRect(&r);
SetSolidPenPat(15);
PaintRect(&r);
SetForeColor(0);
SetBackColor(15);
scoreString(bcnt, wcnt);
MoveTo(4, 14);
DrawString(gScoreBuf);
SetPort(port);
}
static unsigned char gMoveNotation[4];
static void moveNotation(short idx) {
char col = (char)('A' + (idx % 10) - 1);
char row = (char)('0' + 9 - (idx / 10));
gMoveNotation[0] = 3;
gMoveNotation[1] = (unsigned char)col;
gMoveNotation[2] = (unsigned char)row;
gMoveNotation[3] = ' ';
}
static void drawMovesList(void) {
if (!gShowMovesWindow) {
return;
}
void *port = GetPort();
SetPort(gMovesWin);
Rect r;
GetPortRect(&r);
SetSolidPenPat(15);
PaintRect(&r);
SetForeColor(0);
SetBackColor(15);
short start = (short)(gMovesMade - 19);
if (start < 1) {
start = 1;
}
short y = 12;
for (short i = start; i <= gMovesMade; i++) {
MoveTo(4, y);
moveNotation(gMoves[i]);
DrawString(gMoveNotation);
y = (short)(y + 10);
}
SetPort(port);
}
static void checkForDone(void) {
MoveList ml;
getMoves(gBoard, whitePiece, &ml);
if (ml.num) {
return;
}
getMoves(gBoard, blackPiece, &ml);
if (ml.num) {
return;
}
unsigned short bcnt = 0;
unsigned short wcnt = 0;
for (short i = 11; i < 90; i++) {
if (gBoard[i] == blackPiece) {
bcnt++;
} else if (gBoard[i] == whitePiece) {
wcnt++;
}
}
if (wcnt == bcnt) {
uiBuilderAlert(UA_NOTE, "The game is over. It\ris a draw.");
} else if (wcnt > bcnt) {
uiBuilderAlert(UA_NOTE, "White wins the game.");
} else {
uiBuilderAlert(UA_NOTE, "Black wins the game.");
}
gMovesLeft = 0;
}
static void newGame(void) {
for (short i = 0; i < 100; i++) {
short col = (short)(i % 10);
short row = (short)(i / 10);
if (row == 0 || row == 9 || col == 0 || col == 9) {
gBoard[i] = border;
} else {
gBoard[i] = blank;
}
}
gBoard[44] = whitePiece; gBoard[55] = whitePiece;
gBoard[45] = blackPiece; gBoard[54] = blackPiece;
gCurrentColor = blackPiece;
gMovesLeft = 1;
gMovesMade = 0;
drawBoard();
drawScore();
drawMovesList();
}
// --- click handling -----------------------------------------------
static void tryMove(void) {
if (!gMovesLeft) {
return;
}
SetPort(gBoardWin);
Point p;
p.h = gEvent.wmWhereH;
p.v = gEvent.wmWhereV;
GlobalToLocal(&p);
short col = (short)(p.h / squareWidth + 1);
short row = (short)(p.v / squareHeight + 1);
if (row < 1 || row > 8 || col < 1 || col > 8) {
return;
}
short idx = (short)(row * 10 + col);
if (legalMove(idx, gCurrentColor)) {
makeAMove(idx, gCurrentColor);
gCurrentColor ^= 3;
} else {
uiBuilderAlert(UA_STOP, "Illegal move -\rtry again.");
}
checkForDone();
drawScore();
drawMovesList();
}
static void doContent(void) {
void *fw = FrontWindow();
if ((void *)gEvent.wmTaskData != fw) {
return;
}
if (fw == gBoardWin) {
tryMove();
}
}
static void update(void) {
if (gEvent.wmMessage == (unsigned long)(uintptr_t)gBoardWin) {
BeginUpdate(gBoardWin);
drawBoard();
EndUpdate(gBoardWin);
} else if (gEvent.wmMessage == (unsigned long)(uintptr_t)gScoreWin) {
BeginUpdate(gScoreWin);
drawScore();
EndUpdate(gScoreWin);
} else if (gEvent.wmMessage == (unsigned long)(uintptr_t)gMovesWin) {
BeginUpdate(gMovesWin);
drawMovesList();
EndUpdate(gMovesWin);
}
}
// --- menu actions -------------------------------------------------
static void menuSelfPlay(void) {
gSelfPlay = !gSelfPlay;
}
static void menuColor(void) {
gColor = (gColor == whitePiece) ? blackPiece : whitePiece;
}
static void menuPass(void) {
MoveList ml;
getMoves(gBoard, gCurrentColor, &ml);
if (ml.num == 0) {
gCurrentColor ^= 3;
} else {
uiBuilderAlert(UA_STOP, "You have legal moves\rso you cannot pass.\r");
}
}
static void menuSetPly(short menuNum) {
CheckMItem(0, (unsigned short)(gPly + level_1Ply - 1));
CheckMItem(1, (unsigned short)menuNum);
gPly = (short)(menuNum - level_1Ply + 1);
}
static void menuAbout(void) {
uiBuilderAlert(UA_NOTE,
"Reversi 1.0\r"
"Copyright 1989\r"
"Byte Works, Inc.\r\r"
"By Mike Westerfield");
}
static void onAbout(uint16_t cmdId) {
(void)cmdId;
menuAbout();
}
static void onNewGame(uint16_t cmdId) {
(void)cmdId;
newGame();
}
static void onQuit(uint16_t cmdId) {
(void)cmdId;
gDone = 1;
}
static void onPlyN(uint16_t cmdId) {
menuSetPly((short)cmdId);
}
static void onSelfPlay(uint16_t cmdId) {
(void)cmdId;
menuSelfPlay();
}
static void onTogglePlayer(uint16_t cmdId) {
(void)cmdId;
menuColor();
}
static void onPass(uint16_t cmdId) {
(void)cmdId;
menuPass();
}
static void onMenuPick(uint16_t menuId, uint16_t itemId) {
(void)menuId;
uiBuilderDispatch(itemId, gCmdTable, (uint16_t)(sizeof gCmdTable / sizeof gCmdTable[0]));
HiliteMenu(0, (unsigned short)(gEvent.wmTaskData >> 16));
}
static void handleMenuLegacy(unsigned short menuNum) {
onMenuPick(0, (uint16_t)menuNum);
}
// --- init ----------------------------------------------------------
static void initWindows(void) {
UiWindowT spec;
// Board window: 0x80E4 = fTitle | fVis | fMove | fInfo + fPage
spec.title = "Reversi";
spec.frameBits = 0x80E4;
spec.position.v1 = 32;
spec.position.h1 = 32;
spec.position.v2 = (int16_t)(32 + squareHeight * 8);
spec.position.h2 = (int16_t)(32 + squareWidth * 8);
spec.maxHeight = (int16_t)(squareHeight * 8);
spec.maxWidth = (int16_t)(squareWidth * 8);
spec.refCon = 0;
spec.contentDefProc = (void *)0;
gBoardWin = uiBuilderOpenWindow(&spec);
// Score window: 0xC0C4 = fTitle | fClose | fVis | fMove | fInfo
spec.title = "Scores";
spec.frameBits = 0xC0C4;
spec.position.v1 = 32;
spec.position.h1 = (int16_t)(640 - 32 - 200);
spec.position.v2 = 61;
spec.position.h2 = (int16_t)(640 - 32);
spec.maxHeight = 29;
spec.maxWidth = 200;
gScoreWin = uiBuilderOpenWindow(&spec);
// Moves window.
spec.title = "Moves";
spec.frameBits = 0xC0C4;
spec.position.v1 = 80;
spec.position.h1 = (int16_t)(640 - 32 - 100);
spec.position.v2 = 192;
spec.position.h2 = (int16_t)(640 - 32);
spec.maxHeight = 112;
spec.maxWidth = 100;
gMovesWin = uiBuilderOpenWindow(&spec);
SelectWindow(gBoardWin);
}
int main(void) {
unsigned short userId = startdesk(640);
(void)userId;
paintDesktopBackdrop();
uiBuilderInstallMenuBar(gMenus, (uint16_t)(sizeof gMenus / sizeof gMenus[0]));
CheckMItem(1, level_1Ply);
initWindows();
newGame();
gEvent.wmTaskMask = 0x13FFL;
ShowCursor();
*(volatile unsigned char *)0x70 = 0x99;
gDone = 0;
unsigned short watchdog = 0;
do {
unsigned short event = TaskMaster(0x074E, (void *)&gEvent);
switch (event) {
case wInSpecial:
case wInMenuBar:
handleMenuLegacy((unsigned short)gEvent.wmTaskData);
watchdog = 0;
break;
case inUpdate:
update();
watchdog = 0;
break;
case wInContent:
doContent();
watchdog = 0;
break;
case wInGoAway:
gDone = 1;
break;
default:
break;
}
if (gMovesLeft) {
if (gSelfPlay) {
findMove(gCurrentColor);
gCurrentColor ^= 3;
drawScore();
drawMovesList();
} else if (gColor == gCurrentColor) {
findMove(gColor);
gCurrentColor ^= 3;
drawScore();
drawMovesList();
}
}
watchdog++;
} while (!gDone && watchdog < 1000);
*(volatile unsigned char *)0x70 = 0x99;
return 0;
}