355 lines
10 KiB
C
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;
|
|
}
|