Programming Game Boards

In most games, players move pieces or characters around some space, like a game board or an imaginary room. For these types of games, we need to keep track of what piece (or character or object) is in what space. In most cases, all we really care about is the floor (or board) space, which is two-dimensional.

One very efficient way we can do this is to partition the space into a grid. Then all we need to do is keep track of what is inside each grid cell (space). Although this is the obvious solution for game boards that already have grids on them (like tic-tac-toe or checkers), this also works well for mazes and adventure games.

We maintain a record (struct in C, property list in Lingo) for each grid cell, indicating what it contains. For example, in tic-tac-toe, we want to know whether a space is blank, has an 'X', or has an 'O' in it. We can also keep track of other properties of the grid cell in this record, such as the grid cell's position or dimensions. We then store all of these records in an array (linear list in Lingo). It is important to arrange the records so that the position of the record in the array corresponds to the location of the grid cell on the game board. That way, we can use simple formulas to determine which record corresponds to which space, and also find the records for neighboring spaces.

Consider the grid in the above figure. It has 4 columns and 3 rows. Each grid cell is assigned a unique index value, starting at 1 and increasing left to right, top to bottom.* Notice that in row 1, the index value is equal to the column number. In row 2, the index value is equal to the column number plus 4, which is the total number of columns in the grid. We can generalize this with the following formula, which allows us to find an index given a row and a column:

index = ( ( row - 1 ) * ( number of columns ) ) + column

So, for the square with index 11, the formula is:

( ( 3 - 1 ) * ( 4 ) ) + 3

* We start at 1 because that is the index of the first element in a Lingo list.


Tic-Tac-Toe

Click here to play the java-exported version of this game.

The tic-tac-toe board is represented by 9 blank squares arranged in a 3x3 grid. These blank squares occupy sprites numbered 1..9, so that the sprite number corresponds to the index number for each square. A corresponding list, boardState, keeps track of the state of each grid square. A custom handler, getIndex, will convert a row and column to an index.

The first frame of the movie represents the start of the game. Upon exiting this frame, a call is made to handler initBoard, which initializes boardState and the Stage. It also initializes some global variables. The variable turn keeps track of whose turn it is (X or O). The variable count keeps track of how many turns have been taken; because there are only 9 squares, there can only be 9 turns. If no one has won at that point, then the game is a draw.

The next frame loops infinitely, waiting for the players to click on the squares. When a square is clicked on, the program first checks to make sure that this square is blank (by checking the corresponding value in boardState). If the square is blank, the appropriate mark is made and boardState is updated accordingly. The program then checks to see if the current player (whose turn it is) has won. The player has won if:

If the current player has won, the program indicates this and then jumps to the "game over" frame. There, the players can choose to play again. If no one has won, but 9 turns have been taken, the program indicates that it is a draw, and also jumps to the "game over" frame. Otherwise, it becomes the next player's turn.


Intitializing the Board:

on initBoard

-- boardState is a linear list containing 1 property list
-- (indicating row, column, and object) for each square

global boardState
set boardState = [ [#row:1,#column:1,#object:"blank"],\
[#row:1,#column:2,#object:"blank"],\
[#row:1,#column:3,#object:"blank"],\
[#row:2,#column:1,#object:"blank"],\
[#row:2,#column:2,#object:"blank"],\
[#row:2,#column:3,#object:"blank"],\
[#row:3,#column:1,#object:"blank"],\
[#row:3,#column:2,#object:"blank"],\
[#row:3,#column:3,#object:"blank"] ]

-- set corresponding sprites = blank

repeat with i = 1 to 9
set the member of sprite i = "blank"
end repeat

-- turn keeps track of which player goes next;
-- the name of the player is the same as the name of the cast member

global turn
set turn = "X"
put "It's X's turn" into field "turninfo"

-- count the number of turns taken; not to exceed 9

global count
set count = 0
end-- getIndex returns an index (item number) for boardState
-- given a row and colum on the board
on getIndex row, column
return (row-1) * 3 + column
end


Marking the Squares:

-- When player clicks on a square,
-- mark the square (if possible)
-- and see if that player has won
on mouseUp
-- global variables initialized in initBoard
global boardState
global turn
global count

-- sprite number corresponds to index into boardState
set spriteNumber = the clickOn
set squarePosition = getAt(boardState, spriteNumber)

-- can move here only if the square is blank
if (getProp(squarePosition, #object) = "blank") then

-- update count of turns taken
count = count + 1

-- put the appropriate mark in the square, and update boardState
set the memberNum of sprite spriteNumber = the number of member turn
setProp squarePosition, #object, turn
setAt boardState, spriteNumber, squarePosition

-- the square selected is in row i and column j
set i = getProp (squarePosition, #row)
set j = getProp (squarePosition, #column)

-- assume no one has won the game (for now)
set wins = FALSE

-- look for 3 in a row horizontally
if (getProp (getAt (boardState, getIndex(i,1)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(i,2)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(i,3)), #object) = turn) then
set wins = TRUE

-- look for 3 in a row vertically
else if (getProp (getAt (boardState, getIndex(1,j)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(2,j)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(3,j)), #object) = turn) then
set wins = TRUE

-- look for 3 in a row diagonally
else if (getProp (getAt (boardState, getIndex(1,1)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(2,2)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(3,3)), #object) = turn) then
set wins = TRUE
else if (getProp (getAt (boardState, getIndex(1,3)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(2,2)), #object) = turn) and\
(getProp (getAt (boardState, getIndex(3,1)), #object) = turn) then
set wins = TRUE
end if

-- write the appropriate message in field "turninfo"
if wins then
set the text of field "turninfo" = turn & " wins!"
go to frame "game over"
else if (count >= 9) then
set the text of field "turninfo" = "It's a Draw"
go to frame "game over"
else
if turn = "X" then
set turn = "O"
put "It's O's turn" into field "turninfo"
else
set turn = "X"
put "It's X's turn" into field "turninfo"
end if
end if
end if
end