Skip to main content

Bonus exercise: Make your Tic-Tac-Toe object-oriented

A possible solution is at the end of this page.

Try it yourself

Can you refactor your Tic Tac Toe to be Object-Oriented? How many classes would you introduce?

Possible solution

The original course had no solution for this exercise, so this one was written for this book. It refactors the Tic-Tac-Toe solution TicTacToe.bbj into four files. Board holds the nine cells and the rules, so the game logic has no window code. Player holds a name and a mark, so a later version can add a computer player without touching the board. GameWindow owns the controls and callbacks and asks Board for every decision. PlayTicTacToe.bbj only starts the game. Keep the four files in one folder and run PlayTicTacToe.bbj. You find them in the folder exercises/oo-tic-tac-toe/ of the exercises.zip link.

Board.bbj
rem Board.bbj
rem Part of the possible solution for the bonus exercise Make your Tic-Tac-Toe object-oriented, written for the Introduction to BBj Development book
rem Board holds the state and the rules of the game: which cell has which mark, whether a move is allowed and who has won. It knows nothing about windows.

class public Board

field private BBjString cells$

method public Board()
rem cells$ holds nine characters, cell 1 first: "X", "O" or a space for a free cell
#reset()
methodend

method public void reset()
s$ = ""
for i = 1 to 9
s$ = s$ + " "
next i
#cells$ = s$
methodend

method public BBjNumber place(BBjNumber cell, BBjString mark$)
rem put a mark into a free cell from 1 to 9. Returns 1 when it worked and 0 when the move is not allowed.
if (cell < 1) or (cell > 9) then
methodret 0
endif

s$ = #cells$
if s$(cell,1) <> " " then
methodret 0
endif

rem build the new board string one character at a time
result$ = ""
for i = 1 to 9
if i = cell then
result$ = result$ + mark$
else
result$ = result$ + s$(i,1)
endif
next i
#cells$ = result$
methodret 1
methodend

method public BBjString getCell(BBjNumber cell)
rem the mark in a cell, or an empty string for a free cell
s$ = #cells$
if s$(cell,1) = " " then
methodret ""
endif
methodret s$(cell,1)
methodend

method public BBjString winner()
rem "X" or "O" when that mark has three in a line, otherwise an empty string
s$ = #cells$
winLines$ = "123456789147258369159357"
for k = 0 to 7
a = num(winLines$(k*3+1,1))
b = num(winLines$(k*3+2,1))
c = num(winLines$(k*3+3,1))
if (s$(a,1) <> " ") and (s$(a,1) = s$(b,1)) and (s$(a,1) = s$(c,1)) then
methodret s$(a,1)
endif
next k
methodret ""
methodend

method public BBjNumber isFull()
rem 1 when every cell holds a mark, otherwise 0
s$ = #cells$
for i = 1 to 9
if s$(i,1) = " " then
methodret 0
endif
next i
methodret 1
methodend

classend
Player.bbj
rem Player.bbj
rem Part of the possible solution for the bonus exercise Make your Tic-Tac-Toe object-oriented, written for the Introduction to BBj Development book
rem Player keeps the name and the mark of one player, so the window can ask who is up without knowing about X and O.

class public Player

field public BBjString Name$
field public BBjString Mark$

method public Player(BBjString playerName$, BBjString playerMark$)
#Name$ = playerName$
#Mark$ = playerMark$
methodend

classend
GameWindow.bbj
rem GameWindow.bbj
rem Part of the possible solution for the bonus exercise Make your Tic-Tac-Toe object-oriented, written for the Introduction to BBj Development book
rem GameWindow is the view. It owns the controls and the callbacks, and asks the Board and the Players what to show.
rem It uses plain BBj controls and callbacks, without CSS, and also runs in the DWC.

use ::Board.bbj::Board
use ::Player.bbj::Player

class public GameWindow

field private BBjTopLevelWindow wnd!
field private BBjStaticText status!
field private BBjVector cellButtons!
field private Board board!
field private Player playerX!
field private Player playerO!
field private Player currentPlayer!
field private BBjNumber gameOver

method public GameWindow()
#board! = new Board()
#playerX! = new Player("Player X","X")
#playerO! = new Player("Player O","O")
#currentPlayer! = #playerX!
#gameOver = 0
methodend

method public void show()
api! = BBjAPI()
#wnd! = CAST(BBjTopLevelWindow,api!.openSysGui("X0").addWindow(100,100,320,400,"Tic-Tac-Toe"))

rem the nine cell buttons get the control IDs 101 to 109, laid out in three rows of three
#cellButtons! = CAST(BBjVector,api!.makeVector())
for i = 0 to 8
btn! = #wnd!.addButton(101+i,10+mod(i,3)*100,10+int(i/3)*100,95,95,"")
btn!.setCallback(BBjAPI.ON_BUTTON_PUSH,#this!,"onCellPush")
#cellButtons!.addItem(btn!)
next i

#status! = #wnd!.addStaticText(110,10,315,300,25,"")
newBtn! = #wnd!.addButton(111,10,350,300,30,"New game")
newBtn!.setCallback(BBjAPI.ON_BUTTON_PUSH,#this!,"onNewGame")

#wnd!.setCallback(BBjAPI.ON_CLOSE,#this!,"onClose")

#resetGame()
methodend

method public void onCellPush(BBjButtonPushEvent ev!)
rem a click on one of the nine cells. The pushed button tells us its control ID, cell 1 has ID 101.
ctrl! = ev!.getControl()
cell = ctrl!.getID() - 100

rem ignore every click after the game has ended, and a click on a taken cell (place returns 0)
if #gameOver = 0 then
if #board!.place(cell,#currentPlayer!.getMark()) = 1 then
btn! = #cellButtons!.getItem(cell-1)
btn!.setText(#currentPlayer!.getMark())

rem the win check comes first, so a ninth move that completes a line is a win and not a draw
if #board!.winner() <> "" then
#status!.setText(#currentPlayer!.getName()+" wins!")
#gameOver = 1
else
if #board!.isFull() = 1 then
#status!.setText("It's a draw.")
#gameOver = 1
else
if #currentPlayer!.getMark() = #playerX!.getMark() then
#currentPlayer! = #playerO!
else
#currentPlayer! = #playerX!
endif
#status!.setText(#currentPlayer!.getName()+" is up")
endif
endif
endif
endif
methodend

method public void onNewGame(BBjButtonPushEvent ev!)
#resetGame()
methodend

method public void onClose(BBjCloseEvent ev!)
release
methodend

method private void resetGame()
rem empty the board, clear the nine buttons and give the first move to Player X
#board!.reset()
#currentPlayer! = #playerX!
#gameOver = 0
for i = 0 to 8
btn! = #cellButtons!.getItem(i)
btn!.setText("")
next i
#status!.setText(#currentPlayer!.getName()+" is up")
methodend

classend
PlayTicTacToe.bbj
rem PlayTicTacToe.bbj
rem Part of the possible solution for the bonus exercise Make your Tic-Tac-Toe object-oriented, written for the Introduction to BBj Development book
rem The launcher only starts the game: it creates a GameWindow, shows it and waits for events. Run this file.

use ::GameWindow.bbj::GameWindow

declare auto GameWindow game!

game! = new GameWindow()
game!.show()

process_events