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