// Othello for DOORS
/*
About Othello.
Although Othello can be played on any size board the standard game uses
a board with 64 squares (8x8).
The opening position has white discs at (3, 3) and (4, 4) and black
discs at (3, 4) and (4, 3).
Each othello disc is black on one side and white on the other.
Black plays first and places a disc, black side uppermost, on any square
on the board such that he "traps" at least one of his opponents pieces
between the disc he has just placed and any other of his colour already
on the board. Trapped discs are flipped to show the black side uppermost.
Play continues alternately. First black, then white. If at any time a
player does not have a legal move i.e. there is nowhere he can play that
flips one of his opponent's discs, he must pass and his opponent plays again.
It is possible to pass several times in succession. When neither player has a
legal move (usually when the board is full but not always) the game ends.
The winner is the player with the most pieces of his colour showing.
TBD:
Display scores during game.
Display flip count and rating on right-mouse click to assist
learner players.
Known issues:
Can't seem to prevent extra mouse clicks being acted upon
when it is not the user's turn.
Changes:
10-Jul-2007 Tony Goodman First working version.
*/
DB dbMain = null
DBE dbeCanvas = null
DBE dbeStart = null
int WHITE = 0 // white disc
int BLACK = 1 // black disc
int EMPTY = 2 // empty square
int STATES = 3 // square can be Black, White or empty
// colour for the discs and an empty square
int COLOURS[STATES] = { realColor_White, realColor_Black, realColor_Sea_Green }
// used for debug
string STRINGS[STATES] = { "WHITE", "BLACK", "EMPTY" }
// Human plays Black, computer plays White. Black always goes first.
int turn = BLACK
// prevent spurious mouse clicks
bool mouseActive = true
// playing board dimensions
int GRID_SIZE = 8 // number of squares across
int SQUARE_SIZE = 50 // pixels
/******************************************************************************
Data structures to hold state of play etc
******************************************************************************/
int BOARD_SIZE = GRID_SIZE * GRID_SIZE // 8 * 8 = 64 squares
int playerArray[BOARD_SIZE] // state of each square on the board
int valueArray[BOARD_SIZE + BOARD_SIZE] // value of each square to the player
int flipsArray[BOARD_SIZE + BOARD_SIZE] // flip count for each square to the player
int score[2] = { 0, 0 } // total score for each player
// forward declaration
void othello()
/******************************************************************************
getPlayer
Returns the state of the square at (x, y).
This will be WHITE, BLACK or EMPTY.
******************************************************************************/
int getPlayer(int x, int y)
{
return(playerArray[(y * GRID_SIZE) + x])
}
/******************************************************************************
setPlayer
Sets the state of the square at (x, y).
Allowable states are WHITE, BLACK or EMPTY.
******************************************************************************/
void setPlayer(int x, int y, int player)
{
playerArray[(y * GRID_SIZE) + x] = player
}
/******************************************************************************
getValue
Returns the value (rating) of the square at (x, y) for the player.
******************************************************************************/
int getValue(int x, int y, int player)
{
return(valueArray[(y * GRID_SIZE) + x + (player * BOARD_SIZE)])
}
/******************************************************************************
setValue
Sets the value (rating) of the square at (x, y) for the player.
******************************************************************************/
void setValue(int x, int y, int player, int val)
{
valueArray[(y * GRID_SIZE) + x + (player * BOARD_SIZE)] = val
}
/******************************************************************************
getFlips
Returns the flip count of the square at (x, y) for the player.
******************************************************************************/
int getFlips(int x, int y, int player)
{
return(flipsArray[(y * GRID_SIZE) + x + (player * BOARD_SIZE)])
}
/******************************************************************************
setFlips
Sets the flip count of the square at (x, y) for the player.
******************************************************************************/
void setFlips(int x, int y, int player, int flips)
{
flipsArray[(y * GRID_SIZE) + x + (player * BOARD_SIZE)] = flips
}
/******************************************************************************
otherPlayer
******************************************************************************/
int otherPlayer(int player)
{
return((player == WHITE) ? BLACK : WHITE)
}
/******************************************************************************
gameOver
Called when neither player can make a move.
Display the result of the game to the user.
******************************************************************************/
void gameOver()
{
infoBox("Game Over!\nWhite: " score[WHITE] "\nBlack: " score[BLACK] "")
}
/******************************************************************************
drawDisc
Draws a player's disc on the board in square (x, y).
******************************************************************************/
void drawDisc(int x, int y, int player)
{
realColor(dbeCanvas, COLOURS[player])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 4, 40, 40)
}
/******************************************************************************
drawTrans
Called when the other players discs are being flipped.
Displays an animated transition from one disc to another in square (x, y).
******************************************************************************/
void drawTrans(int x, int y, int fromPlayer, int toPlayer)
{
// empty the square...
realColor(dbeCanvas, COLOURS[EMPTY])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 4, 40, 40)
// ... and display a quarter-turned disc (ellipse) in old colour
realColor(dbeCanvas, COLOURS[fromPlayer])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 14, 40, 20)
// give user a chance to see it
sleep_(150)
// empty the square...
//realColor(dbeCanvas, COLOURS[EMPTY])
//ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 4, 40, 40)
// ... and draw disc on its edge (half-turned)
//realColor(dbeCanvas, COLOURS[fromPlayer])
//rectangle(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 22, 40, 3)
//realColor(dbeCanvas, COLOURS[toPlayer])
//rectangle(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 25, 40, 3)
// give user a chance to see it
//sleep_(100)
// empty the square...
realColor(dbeCanvas, COLOURS[EMPTY])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 4, 40, 40)
// ... and display quarter-turned disc (ellipse) in new colour
realColor(dbeCanvas, COLOURS[toPlayer])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 14, 40, 20)
// give user a chance to see it
sleep_(150)
// display a full disc (circle) in new colour
realColor(dbeCanvas, COLOURS[toPlayer])
ellipse(dbeCanvas, x * SQUARE_SIZE + 4, y * SQUARE_SIZE + 4, 40, 40)
}
/******************************************************************************
doDrawCanvas
Redraw the canvas.
******************************************************************************/
void doDrawCanvas(DBE dbeCanvas)
{
int x = 0
int y = 0
// fill background with empty square colour
realBackground(dbeCanvas, COLOURS[EMPTY])
// draw the grid of squares
for (y = 0; y < GRID_SIZE; y++)
{
for (x = 0; x < GRID_SIZE; x++)
{
realColor(dbeCanvas, realColor_Black)
box(dbeCanvas, x * SQUARE_SIZE, y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)
}
}
// draw player's discs
for (x = 0; x < GRID_SIZE; x++)
{
for (y = 0; y < GRID_SIZE; y++)
{
drawDisc(x, y, getPlayer(x, y))
}
}
}
/******************************************************************************
rawPutDisc
Place a disc on the board and update the players' scores.
If trans is TRUE, then the current player is flipping a disc and we show
the transition. If trans is false then the player is placing a disc on an
empty square so we can just draw it.
******************************************************************************/
void rawPutDisc(int x, int y, int player, bool trans)
{
// get other player
int other = otherPlayer(player)
// if the square is occupied by the other player, then if we are flipping
// the other player's disc, so reduce their score
if (getPlayer(x, y) == other)
{
score[other]--
}
// place this player's disc on the square
setPlayer(x, y, player)
// increase this player's score
score[player]++
// are we placing a disc or flipping discs?
if (!trans)
{
// this player is placing the disc in an empty square - just draw it
drawDisc(x, y, player)
}
else
{
// this player is flipping discs - show the transistion from the other
// player's colour to this player's colour.
drawTrans(x, y, otherPlayer(player), player)
}
}
/******************************************************************************
flipDiscs
******************************************************************************/
void flipDiscs(int x, int y, int player)
{
int deltax = 0
int deltay = 0
int distance = 0
int posx = 0
int posy = 0
// place the player's disc in the empty square at (x, y).
rawPutDisc(x, y, player, false)
// check surrounding squares for other player's discs that can be flipped
for (deltay = -1; deltay <= 1; deltay++)
{
for (deltax = -1; deltax <= 1; deltax++)
{
for (distance = 1;; distance++)
{
posx = x + (distance * deltax)
posy = y + (distance * deltay)
// stop if we go off the board
if (posx < 0 || posx >= GRID_SIZE || posy < 0 || posy >= GRID_SIZE)
{
break
}
// stop when we reach an empty square
if (getPlayer(posx, posy) == EMPTY)
{
break
}
if (getPlayer(posx, posy) == player)
{
// backtrack, flipping discs
for (distance--; distance > 0; distance--)
{
posx = x + (distance * deltax)
posy = y + (distance * deltay)
rawPutDisc(posx, posy, player, true)
}
break
}
}
}
}
}
/******************************************************************************
numFlips
******************************************************************************/
int numFlips(int x, int y, int player)
{
int deltax = 0
int deltay = 0
int distance = 0
int posx = 0
int posy = 0
int count = 0
for (deltay = -1; deltay <= 1; deltay++)
{
for (deltax = -1; deltax <= 1; deltax++)
{
for (distance = 1;; distance++)
{
posx = x + (distance * deltax)
posy = y + (distance * deltay)
//print("checking square (" posx ", " posy ")\n")
// stop if we go off the board
if (posx < 0 || posx >= GRID_SIZE || posy < 0 || posy >= GRID_SIZE)
{
//print("off board\n")
break
}
// stop when we reach an empty square
if (getPlayer(posx, posy) == EMPTY)
{
//print("empty")
break
}
// only update the flip count when we reach another of the
// player's discs
if (getPlayer(posx, posy) == player)
{
count += distance - 1;
break
}
}
}
}
//print("flip count = " count "\n")
return(count)
}
/******************************************************************************
anyMoves
******************************************************************************/
bool anyMoves(int turn)
{
int x = 0
int y = 0
for(y = 0; y < GRID_SIZE; y++)
{
for(x = 0; x < GRID_SIZE; x++)
{
if (getPlayer(x, y) != EMPTY)
{
continue
}
if (numFlips(x, y, turn) > 0)
{
return(true)
}
}
}
return(false)
}
/******************************************************************************
doneTurn
******************************************************************************/
void doneTurn()
{
bool moves = anyMoves(turn)
//print("done turn " turn "\n")
turn = otherPlayer(turn)
doDrawCanvas(dbeCanvas)
// check whether the other player has any moves
if (!anyMoves(turn))
{
if (!moves)
{
gameOver()
return
}
if (turn == BLACK)
{
infoBox("You cannot go - computer will take another turn.")
}
else
{
infoBox("Computer cannot go - please take another turn.")
mouseActive = true
}
// and switch players again
turn = otherPlayer(turn)
return
}
// computer takes its turn
if (turn != BLACK)
{
// make it look like we are thinking...
sleep_(1000)
othello()
}
else
{
//ready(dbMain)
mouseActive = true
}
}
/******************************************************************************
calcFlipCounts
Calculate the flip count for every square on the board for the player.
******************************************************************************/
void calcFlipCounts(int player)
{
int x = 0
int y = 0
for(y = 0; y < GRID_SIZE; y++)
{
for(x = 0; x < GRID_SIZE; x++)
{
// initialise flip count to zero
setFlips(x, y, player, 0)
// if the square is empty then there is no disc to flip
if (getPlayer(x, y) != EMPTY)
{
continue
}
// count and store the flip count for the square
setFlips(x, y, WHITE, numFlips(x, y, player))
}
}
}
/******************************************************************************
rate
******************************************************************************/
int rate(int x, int y, int player)
{
int rating = 0
// non-empty squares have a zero rating
if (getPlayer(x, y) != EMPTY)
{
return(0)
}
// get flip count for this square
rating = getFlips(x, y, player)
if (rating > 0)
{
// increase all non-zero weightings so we have room
// to wait 'less ideal' squares below the baseline
rating += 10
// raise edge ratings 4 points, corners are raised 8
if (x == 0 || x == GRID_SIZE - 1)
{
rating += 4
}
if (y == 0 || y == GRID_SIZE - 1)
{
rating += 4
}
// lower next-to-edge ratings by 5 points, next-to-corner by 10
if (x == 1 || x == GRID_SIZE - 2)
{
rating -= 5
}
if (y == 1 || y == GRID_SIZE - 2)
{
rating -= 5
}
// we cannot rule out a move because of bad location; we must
// always go somewhere
if (rating < 1)
{
rating = 1
}
}
return(rating)
}
/******************************************************************************
othello
This routine is called when it is the computer's turn.
******************************************************************************/
void othello()
{
int x = 0
int y = 0
int best = 0
int numbest = 0
int rating = 0
int pick = 0
int count = 0
// rank each position on the board by the potential flip count
calcFlipCounts(WHITE)
// apply rating to each square and count the number of "best" squares
for(y = 0; y < GRID_SIZE; y++)
{
for(x = 0; x < GRID_SIZE; x++)
{
rating = rate(x, y, WHITE)
// store the rating back into the board
setValue(x, y, WHITE, rating)
if (rating == best)
{
// this square is equal to the best so far
numbest++
}
else if (rating > best)
{
// this is the best square so far
best = rating
numbest = 1
}
}
}
while (numbest > 0)
{
// if there were more than one "best" squares then we
// will need to pick one of these at random.
pick = random(numbest)
count = 0
// loop through all squares again
for (y = 0; y < GRID_SIZE; y++)
{
for(x = 0; x < GRID_SIZE; x++)
{
// read it's rating.
rating = getValue(x, y, WHITE)
// if this square's rating is equal to the "best"
if (rating == best)
{
// only choose this square if it was randomly selected earlier
if (count == pick)
{
// make the move
flipDiscs(x, y, WHITE)
doneTurn()
return
}
else
{
count++
}
}
}
}
}
// if we make it here, then there was nowhere to go
doneTurn()
}
/******************************************************************************
putDisc
This is only ever called when Black (human) is playing a turn.
Check that the user can put a disc on square (x, y).
If the user can put a disc here then call flipDiscs to make the move.
******************************************************************************/
void putDisc(int x, int y)
{
int flipCount = 0
// check that it is the human player's turn
if (turn != BLACK)
{
//print("not your turn\n")
return
}
// check that the squarte is empty
// user is allowed to click another square
if (getPlayer(x, y) != EMPTY)
{
//print("square not empty\n")
mouseActive = true
return
}
// get the flip count. i.e. how many of the opponent's discs
// will be flipped by moving here.
flipCount = numFlips(x, y, BLACK)
// must flip at least once disc for a legal move
// user is allowed to click another square
if (flipCount == 0)
{
mouseActive = true
return
}
// make the move
flipDiscs(x, y, BLACK)
// refresh the display
doDrawCanvas(dbeCanvas)
// end of turn
doneTurn()
}
/******************************************************************************
initialiseGrid
Initialise the data structures ready for a new game.
******************************************************************************/
void initialiseGrid()
{
int x = 0
int y = 0
// empty the board and set flips and values to zero.
for (x = 0; x < GRID_SIZE; x++)
{
for (y = 0; y < GRID_SIZE; y++)
{
setPlayer(x, y, EMPTY)
setValue(x, y, WHITE, 0)
setValue(x, y, BLACK, 0)
setFlips(x, y, WHITE, 0)
setFlips(x, y, BLACK, 0)
}
}
// initial placements of discs
setPlayer(3, 3, WHITE)
setPlayer(4, 4, WHITE)
setPlayer(3, 4, BLACK)
setPlayer(4, 3, BLACK)
// initial scores
score[WHITE] = 2
score[BLACK] = 2
}
/******************************************************************************
doMouse
Callback from user mouse click on the canvas.
******************************************************************************/
void doMouse(DBE dbeCanvas, int but, bool ctrl, int xPixels, int yPixels)
{
const int LEFT_MOUSE = 1
const int RIGHT_MOUSE = 3
int x = 0
int y = 0
if (!mouseActive)
{
//print "inactive "
return
}
//print "active "
// prevent user making any more mouse clicks until turn is restored.
mouseActive = false
//busy(dbMain)
if (turn != BLACK)
{
//print("ignore\n")
return
}
// only react to a left mouse click
if (but != LEFT_MOUSE)
{
mouseActive = true
return
}
x = xPixels / SQUARE_SIZE
y = yPixels / SQUARE_SIZE
//print("(" x "," y ") ")
putDisc(x, y)
//drawDisc(x, y, WHITE)
}
/******************************************************************************
doInitialise
******************************************************************************/
void doInitialise(DB db)
{
initialiseGrid()
doDrawCanvas(dbeCanvas)
mouseActive = true
turn = BLACK
}
/******************************************************************************
doCloseAction
Clean up data structures when closing the dialog.
******************************************************************************/
void doCloseAction(DB db)
{
hide(dbMain)
destroy(dbMain)
dbMain = null
}
/******************************************************************************
MAIN
******************************************************************************/
initialiseGrid()
dbMain = create("Othello", styleCentered | styleFixed)
dbeCanvas = canvas(dbMain,
(GRID_SIZE * (SQUARE_SIZE + 2) - 5),
(GRID_SIZE * (SQUARE_SIZE + 2) - 5),
doDrawCanvas)
dbeStart = apply(dbMain, "New Game", doInitialise)
close(dbMain, true, doCloseAction)
realize dbMain
set(dbeCanvas, doMouse)
doDrawCanvas(dbeCanvas)
show dbMain
sitemap