834 lines
24 KiB
C
834 lines
24 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.
|
||
|
||
#include "iigs/toolbox.h"
|
||
#include "iigs/desktop.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
|
||
|
||
#define norml 0
|
||
#define stop 1
|
||
#define note 2
|
||
#define caution 3
|
||
|
||
#define buttonItem 10
|
||
#define statText 136
|
||
#define itemDisable 0x8000
|
||
|
||
#define topMost ((void *)-1L)
|
||
|
||
|
||
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 {
|
||
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 {
|
||
short itemID;
|
||
short itemRectV1, itemRectH1, itemRectV2, itemRectH2;
|
||
unsigned short itemType;
|
||
void *itemDescr;
|
||
short itemValue;
|
||
short itemFlag;
|
||
void *itemColor;
|
||
} ItemTemplate;
|
||
|
||
typedef struct {
|
||
short atRectV1, atRectH1, atRectV2, atRectH2;
|
||
short atBtnHorz;
|
||
short atBeep0, atBeep1, atBeep2, atBeep3;
|
||
void *atSound;
|
||
void *atResv1;
|
||
void *atResv2;
|
||
void *atItemList[8];
|
||
} AlertTemplate;
|
||
|
||
|
||
typedef struct {
|
||
short num;
|
||
unsigned char moves[60];
|
||
} MoveList;
|
||
|
||
|
||
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: just one phase, much smaller than the
|
||
// original's 300-entry / 3-phase bSc. Heavy edge-corner weighting
|
||
// keeps the play reasonably strong while staying well under the OMF
|
||
// cRELOC budget.
|
||
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
|
||
};
|
||
|
||
|
||
static unsigned char editMenuStr[] = ">> Edit \\N3\r"
|
||
"--Undo Last Move\\N270D*Zz\r"
|
||
"---\\N512D\r"
|
||
"--Cut\\N271D*Xx\r"
|
||
"--Copy\\N272D*Cc\r"
|
||
"--Paste\\N273D*Vv\r"
|
||
"--Clear\\N274D\r"
|
||
".\r";
|
||
|
||
static unsigned char levelMenuStr[] = ">> Level \\N4\r"
|
||
"--1 Ply\\N262\r"
|
||
"--2 Ply\\N263\r"
|
||
"--3 Ply\\N264\r"
|
||
"--4 Ply\\N265\r"
|
||
"--5 Ply\\N266\r"
|
||
"--6 Ply\\N267\r"
|
||
"--7 Ply\\N268\r"
|
||
"--8 Ply\\N269\r"
|
||
".\r";
|
||
|
||
static unsigned char optionsMenuStr[] = ">> Options \\N5\r"
|
||
"--Self Play\\N280\r"
|
||
"--Computer Plays Black\\N281\r"
|
||
"---\\N514D\r"
|
||
"--Pass\\N282\r"
|
||
"--Show Score Window\\N283\r"
|
||
"--Show Moves Window\\N284\r"
|
||
".\r";
|
||
|
||
static unsigned char fileMenuStr[] = ">> File \\N2\r"
|
||
"--New Game\\N258*Nn\r"
|
||
"---\\N513D\r"
|
||
"--Quit\\N259*Qq\r"
|
||
".\r";
|
||
|
||
static unsigned char appleMenuStr[] = ">>@\\XN1\r"
|
||
"--About Reversi\\N257\r"
|
||
".\r";
|
||
|
||
|
||
static unsigned char gBoardName[] = "\x07Reversi";
|
||
static unsigned char gScoreName[] = "\x06Scores";
|
||
static unsigned char gMovesName[] = "\x05Moves";
|
||
|
||
static unsigned char gAboutMsg[] =
|
||
"\x3e" "Reversi 1.0\r"
|
||
"Copyright 1989\r"
|
||
"Byte Works, Inc.\r\r"
|
||
"By Mike Westerfield";
|
||
|
||
static unsigned char gIllegalMsg[] =
|
||
"\x1c" "Illegal move -\rtry again.";
|
||
static unsigned char gPassMsg[] =
|
||
"\x22" "I cannot move, so I\rmust pass.\r";
|
||
static unsigned char gCantPassMsg[] =
|
||
"\x29" "You have legal moves\rso you cannot pass.\r";
|
||
static unsigned char gDrawMsg[] =
|
||
"\x21" "The game is over. It\ris a draw.";
|
||
static unsigned char gWhiteWinsMsg[] =
|
||
"\x18" "White wins the game.";
|
||
static unsigned char gBlackWinsMsg[] =
|
||
"\x18" "Black wins the game.";
|
||
|
||
|
||
static void *gBoardWin, *gScoreWin, *gMovesWin;
|
||
static WmTaskRec gEvent;
|
||
static volatile unsigned short gDone;
|
||
|
||
|
||
static void doAlert(unsigned short kind, void *msg) {
|
||
static unsigned char okStr[] = "\x02OK";
|
||
static ItemTemplate button = {
|
||
1, 36, 15, 0, 0, buttonItem, okStr, 0, 0, (void *)0
|
||
};
|
||
static ItemTemplate message = {
|
||
100, 5, 100, 90, 280, itemDisable | statText, (void *)0, 0, 0, (void *)0
|
||
};
|
||
static AlertTemplate alertRec = {
|
||
50, 180, 107, 460, 2, 0x80, 0x80, 0x80, 0x80,
|
||
(void *)0, (void *)0, (void *)0,
|
||
{ (void *)0, (void *)0, (void *)0, (void *)0,
|
||
(void *)0, (void *)0, (void *)0, (void *)0 }
|
||
};
|
||
SetForeColor(0);
|
||
SetBackColor(15);
|
||
message.itemDescr = msg;
|
||
alertRec.atItemList[0] = (void *)&button;
|
||
alertRec.atItemList[1] = (void *)&message;
|
||
alertRec.atItemList[2] = (void *)0;
|
||
switch (kind) {
|
||
case norml: (void)Alert(&alertRec, (void *)0); break;
|
||
case stop: (void)StopAlert(&alertRec, (void *)0); break;
|
||
case note: (void)NoteAlert(&alertRec, (void *)0); break;
|
||
case caution: (void)CautionAlert(&alertRec, (void *)0); break;
|
||
default: break;
|
||
}
|
||
}
|
||
|
||
|
||
// --- 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;
|
||
}
|
||
|
||
|
||
// Apply move `index` of color `col` to local board copy and return
|
||
// the resulting flips applied (board mutated).
|
||
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;
|
||
}
|
||
|
||
|
||
// Forward declarations for drawing helpers.
|
||
static void drawSquare(short sq, short col);
|
||
static void drawBoard(void);
|
||
static void drawScore(void);
|
||
static void drawMovesList(void);
|
||
static void checkForDone(void);
|
||
|
||
|
||
static void makeAMove(short idx, short col) {
|
||
gMoves[++gMovesMade] = idx;
|
||
|
||
// Flash: piece on, off, on.
|
||
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);
|
||
// Repaint captured squares too.
|
||
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) {
|
||
doAlert(note, gPassMsg);
|
||
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); // white square (no green in our B/W
|
||
PaintRect(&r); // palette; keeps both piece colors visible)
|
||
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]);
|
||
}
|
||
}
|
||
|
||
|
||
// Tiny 5x7 digit glyphs in a 16-byte (8 row × 2 bytes) bitmap so we
|
||
// don't need to wire snprintf to a window port. Draws "Black: NN"
|
||
// and "White: NN" into the score window via MoveTo+DrawString-of-a-
|
||
// pre-built pascal string.
|
||
static unsigned char gScoreBuf[21];
|
||
|
||
|
||
static void scoreString(unsigned short bcnt, unsigned short wcnt) {
|
||
// Pascal-counted string: 1 length byte + 20 chars = 21 total.
|
||
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, 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);
|
||
}
|
||
|
||
|
||
// Convert move index (11..88) to "A1".."H8" pascal string.
|
||
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);
|
||
// Show up to the most recent 20 moves in a vertical column.
|
||
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, wcnt = 0;
|
||
for (short i = 11; i < 90; i++) {
|
||
if (gBoard[i] == blackPiece) bcnt++;
|
||
else if (gBoard[i] == whitePiece) wcnt++;
|
||
}
|
||
if (wcnt == bcnt) doAlert(note, gDrawMsg);
|
||
else if (wcnt > bcnt) doAlert(note, gWhiteWinsMsg);
|
||
else doAlert(note, gBlackWinsMsg);
|
||
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 {
|
||
doAlert(stop, gIllegalMsg);
|
||
}
|
||
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 {
|
||
doAlert(stop, gCantPassMsg);
|
||
}
|
||
}
|
||
|
||
|
||
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) {
|
||
doAlert(note, gAboutMsg);
|
||
}
|
||
|
||
|
||
static void handleMenu(unsigned short menuNum) {
|
||
switch (menuNum) {
|
||
case apple_AboutReversi: menuAbout(); break;
|
||
case file_NewGame: newGame(); break;
|
||
case file_Quit: gDone = 1; break;
|
||
case level_1Ply: case level_2Ply: case level_3Ply: case level_4Ply:
|
||
case level_5Ply: case level_6Ply: case level_7Ply: case level_8Ply:
|
||
menuSetPly((short)menuNum);
|
||
break;
|
||
case options_SelfPlay: menuSelfPlay(); break;
|
||
case options_ComputerPlaysWhite: menuColor(); break;
|
||
case options_Pass: menuPass(); break;
|
||
default: break;
|
||
}
|
||
HiliteMenu(0, (unsigned short)(gEvent.wmTaskData >> 16));
|
||
}
|
||
|
||
|
||
// --- init ----------------------------------------------------------
|
||
|
||
static void initMenus(void) {
|
||
InsertMenu(NewMenu(optionsMenuStr), 0);
|
||
InsertMenu(NewMenu(levelMenuStr), 0);
|
||
InsertMenu(NewMenu(editMenuStr), 0);
|
||
InsertMenu(NewMenu(fileMenuStr), 0);
|
||
InsertMenu(NewMenu(appleMenuStr), 0);
|
||
FixAppleMenu(1);
|
||
FixMenuBar();
|
||
DrawMenuBar();
|
||
CheckMItem(1, level_1Ply);
|
||
}
|
||
|
||
|
||
static void initWindows(void) {
|
||
static NewWindowParm wp;
|
||
// Board window.
|
||
unsigned char *p = (unsigned char *)℘
|
||
for (unsigned short k = 0; k < sizeof wp; k++) p[k] = 0;
|
||
wp.paramLength = (unsigned short)sizeof wp;
|
||
wp.wFrameBits = 0x80E4;
|
||
wp.wTitle = gBoardName;
|
||
wp.wMaxHeight = squareHeight * 8;
|
||
wp.wMaxWidth = squareWidth * 8;
|
||
wp.wDataV = squareHeight * 8;
|
||
wp.wDataH = squareWidth * 8;
|
||
wp.wPosition.v1 = 32;
|
||
wp.wPosition.h1 = 32;
|
||
wp.wPosition.v2 = (short)(32 + squareHeight * 8);
|
||
wp.wPosition.h2 = (short)(32 + squareWidth * 8);
|
||
wp.wPlane = topMost;
|
||
gBoardWin = NewWindow(&wp);
|
||
|
||
// Score window.
|
||
for (unsigned short k = 0; k < sizeof wp; k++) p[k] = 0;
|
||
wp.paramLength = (unsigned short)sizeof wp;
|
||
wp.wFrameBits = 0xC0C4;
|
||
wp.wTitle = gScoreName;
|
||
wp.wMaxHeight = 29;
|
||
wp.wMaxWidth = 200;
|
||
wp.wDataV = 29;
|
||
wp.wDataH = 200;
|
||
wp.wPosition.v1 = 32;
|
||
wp.wPosition.h1 = (short)(640 - 32 - 200);
|
||
wp.wPosition.v2 = 61;
|
||
wp.wPosition.h2 = (short)(640 - 32);
|
||
wp.wPlane = topMost;
|
||
gScoreWin = NewWindow(&wp);
|
||
|
||
// Moves window.
|
||
for (unsigned short k = 0; k < sizeof wp; k++) p[k] = 0;
|
||
wp.paramLength = (unsigned short)sizeof wp;
|
||
wp.wFrameBits = 0xC0C4;
|
||
wp.wTitle = gMovesName;
|
||
wp.wMaxHeight = 112;
|
||
wp.wMaxWidth = 100;
|
||
wp.wDataV = 112;
|
||
wp.wDataH = 100;
|
||
wp.wPosition.v1 = 80;
|
||
wp.wPosition.h1 = (short)(640 - 32 - 100);
|
||
wp.wPosition.v2 = 192;
|
||
wp.wPosition.h2 = (short)(640 - 32);
|
||
wp.wPlane = topMost;
|
||
gMovesWin = NewWindow(&wp);
|
||
|
||
SelectWindow(gBoardWin);
|
||
}
|
||
|
||
|
||
int main(void) {
|
||
unsigned short userId = startdesk(640);
|
||
(void)userId;
|
||
|
||
paintDesktopBackdrop();
|
||
initMenus();
|
||
initWindows();
|
||
newGame();
|
||
gEvent.wmTaskMask = 0x13FFL;
|
||
ShowCursor();
|
||
|
||
// Marker: init complete and we're entering the event loop. The
|
||
// headless test reads $00:0070 to confirm the demo got this far.
|
||
// Interactive runs continue to the TaskMaster loop below.
|
||
*(volatile unsigned char *)0x70 = 0x99;
|
||
|
||
gDone = 0;
|
||
unsigned short watchdog = 0;
|
||
do {
|
||
unsigned short event = TaskMaster(0x074E, &gEvent);
|
||
switch (event) {
|
||
case wInSpecial:
|
||
case wInMenuBar:
|
||
handleMenu((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;
|
||
}
|