This tutorial has been included as a chapter in Fun With Pharo!
Tic-tac-toe (or Noughts and crosses, Xs and Os) is a paper-and-pencil game for two players, X and O, who take turns marking the spaces in a 3×3 grid. The player who succeeds in placing three respective marks in a horizontal, vertical, or diagonal row wins the game.
Because of the simplicity of Tic-tac-toe, it is often used as a pedagogical tool for teaching the concepts of good sportsmanship and the branch of artificial intelligence that deals with the searching of game trees. It is straightforward to write a computer program to play Tic-tac-toe perfectly, to enumerate the 765 essentially different positions (the state space complexity), or the 26,830 possible games up to rotations and reflections (the game tree complexity) on this space.
So , here we make a Pharo version of this well-known game by using Morph. This post provides a step-by-step approach on how to go about building this simple application.
A game package will be built having 3 subclasses :
Initially , we have created TicTacToe a subclass of the Object class. The subclasses we will make will be combined in the package game as mentioned in the category: parameter.
A category name is not required in order for the class to work, but you will not be able to access the class to make changes or to look at existing code unless you provide a category name. (The category name used can be a new category name or the name of an existing category.)
The poolDictionaries: parameter is seldom used and will not be discussed here, and the category: parameter specifies the category under which this class will be grouped in the system browser.
As we know, a class encapsulates data values and methods, and every object contains a set of the data values and can receive any of the methods as a message. The data values in each object are specified by providing a set of names of variables whose values will be an object’s internal data values. Each object has its own set of these values, and the set of data values for an object represents the object’s state (or value). The variables that contain the data values of an object are called the instance variables for the object, and the instanceVariableNames: parameter is a list of names, separated by blanks, for the instance variables. In the above code snippet , we have declared container and model as two instanceVariables.
The classVariableNames: parameter lists the identifiers that are the names of variables shared by the class and all of its objects. That is, there is only one set of these, and they are used by the class and all of its objects. Class variables (so called because they belong to the class, of which there is only one, rather than to the objects that are instances of the class) are rarely needed.
An example of a class variable that could be useful is in a case where we wanted a unique “serial number” to be assigned to each instance of the class as it is created. The variable containing the next available (or last used) serial number would appropriately be a class variable, and each time a new instance (object) is created the serial number would be recorded as an instance variable value in the object and the serial number in the class variable would be incremented. Thus, each object can be serially numbered as it is created (without using one of those nasty global variables!).
After executing the code above, class TicTacToe will exist. However, it will have no methods other than those that are inherited from class Object. To make it useful, we must add the methods that are needed for our implementation.
Adding methods to classes :
The subClasses interact by passing messages through objects only.
The notation TicTacToe>>#initialize means that we have a method named initialize in the subclass TicTacToe.
In the initialize: method above , we have a container which is the instance of the class Morph (Morphic is the name given to Pharo’s graphical interface. ). We define the various attributes of the container such as layoutPolicy: and color:. model is another instance of the class TicTacToeModel which we will be creating further in this example.
self refers to the receiver of the message. It is usually used within a method to send additional messages to the receiver. self is frequently used when it is desired to pass the sender object (self), as a message argument, to a receiver who requires knowledege of the sender or who will in some way manipulate the sender.
In short, self refers to the object itself that defines the method.
The method addRows (the name is self explanatory) is used to add rows in the Tic Tac Toe grid. It declares temporary (local) variables rowMorph , aCell and rowCol which can’t be used beyond this method.
1 to:3 do:[ :row |
rowMorph := Morph new layoutPolicy: RowLayout new.
1 to: 3 do: [ :col |
aCell := TicTacToeCell new.
aCell setModel: (model) row: row col: col.
rowMorph addMorph: aCell.
The above code snippet works as a nested loop that runs thrice for each three rows to create a 3X3 grid as per requirement.
This method adds controls to the game. The local variables are : rowMorph , newGameButton and exitGameButton.
rowMorph defines an instance of the class Morph which would be the placeholder for the two control buttons located at the top. The two control buttons are defined as New using the variable new GameButton which on click would restart the game , and Exit using the exitGameButton which on click would close the game. The buttons are created using a method createCtrlLabelled which we define next.
rowMorph addMorph: newGameButton adds the button to the Morph instance created earlier.
TicTacToe>>#createCtrlLabelled: aString onClickExecutes: aBlock method makes a simple button using Morph adds label and control to it.
The open method defines as to how the game/TicTacToe class would open. Here we have defined it to open in a dialog box.
It closes the game and calls for Garbage Collection (Garbage Collection (GC) is a form of automatic memory management. It finds data objects in a program that cannot be accessed in the future and reclaims the resources used by those objects.)
Here a subclass TicTacToeCell is defind in the SimpleButtonMorph class with parentModel , rowNum and colNum as the instance variables. This class defines the button for each cell of the grid.
This initialize method initialises the button size as 80X80 and gives it the color: yellow. An ‘onClick’ control is given to the button which then calls the onClickExecutionBlock method present in the same class.
The setModel: row: col: takes three arguments ticTacToeModel , aRow and aCol. The parentModel is assigned ticTacToeModel , roNum becomes the value of aRow and similiarly colNum has the value aCol.
This method defines what should happen when each cell in the grid is clicked. At every click , the label of the cell is changed to X or O depending upon whose turn it is , the row numbers and coloumn numbers are updated in the parentModel and win condition is checked by calling the checkWinCondition method of the class TicTacToeModel defined next.
A subclass TicTacToeModel is defined in the Matrix class with filledCellCount , currentFill and winner as the instance variables.
This initialize methods defines that initially no cell in the grid is filled and there is no winner as of now.
The updateRowAt: Col: method takes two arguments r and c used to update the currentFill and filledCellCount variables.
The method checkWinCondition is self explanatory. It is used to check if we have a winner or not at every move.
Now , we have made the game. To open the game , simply execute the following in the playground/workspace.
The messages : ‘Yes’ , ‘Player x is the winner’ will be displayed in the Transcript.
PS – This was just the basic implementation. I plan to improvise it further with graphics and other functionality/features.
Do like the post if it was helpful.
For any queries/suggestions please comment below.