Simple LightsOut game in Pharo Smalltalk (following tutorial)

26 Jul 2019

lighton board game I built using smalltalk. Yellow board with two sets of four squares

Notes are based on Pharo by Example, a free book suggested by a Mooc called Live Object Programming in Pharo

At the moment I'm working through book examples to get familiar with the developer environment and interacting with the language itself. This is one of the exercises.

The lightsOut game is a board of 100 'tiles' that are all one color. When you click on one of the tiles, the four surrounding cells change to a different color (lights have been turned on). When you click on the same cell again, the lights toggle off and on. The aim of the game is to turn as many lights on as possible.

According to the chapter, this game contains two objects, the game board itself, and the individual cells. So we will be writing two classes, one for each of these.

Creating a new package

The first step was to open the 'System Browser' from the 'World Menu'. To create a new package, we right click on any of the existing packages in the package panel and select "Add package...". Then we type in the package name and click anter. I gave my package the same name as the example in the book did: "PBE-LightsOut".

Defining the cell class

To create a new class, click on the package we want to create a class for. The main editing pane will display a template for creating a new class easier. This is the code for the class we are defining for our LightsOut Cells.

LightsOut cell class creation
  
  SimpleSwitchMorph subclass: #LOCell
    instanceVariableNames: 'mouseAction'
    classVariableNames: ''
    package: 'PBE-LightsOut'
  
  

In this example, we are sending a message to the class SimpleSwitchMorph, asking it to create a subclass called LOCell, the message is in the form of a symbol (#LOCell).

Then we defined an instance variable called 'mouseAction', which will be used to defined the behaviour that happened if the cell is clicked on with a mouse.

We have not set any class variable names right now, the tutorial doesn't mention why it has been included in the code at all. A quick google shows that a class variable when added is applied to all instances of the class. I'm assuming we are going to use it to set the default color of all the cells but don't know that for sure yet. Whilst the instanceVariables can be set for each individual object that is created from that class (each instance).

The last line of the class definition above specifies which package the object belongs to, in this case it was the package we created for our game (PBE-LightsOut).

To actually send our subclass message, we save our code with Command-S, which results in our new class being compiled. The name of our compiled class then shows up in the class pane of the System Browser, which is right next to the Package pane.

At this point you will also get a feedback from the Quality Assistent at the bottom of the screen. In this case, there were four messages:

I like to really try and understand the meaning behind error messages, so usually google them one by one to find out what they mean. In this case they are self explanatory except for the 'No class comment' message, but it's always a good idea to look them up even if you think you know what they mean.

We can find out the rationale behind the error messages by clicking on the question mark next to the error message (which I have just found out is actually called 'critic text'.

Another thing I was interested is what the little icons represent next to the critic text. I looked up Pharo Quality Assistant and found a repo which explains what they represent. The '(i)' icons represent information, '/!\' icons represent warnings and '(-)', '(!)' icons represent errors. There are a few more icons listed but those are the common icons.

Comments are important

According to the authors, good quality comments are a priority in Pharo as well as high code readability. This is to encourage people to be intentional about everything that they write.

Method comments

Method comments shoud explain what the method is doing, its context or the reasons behind it's implementation.

Class comments

A strong class comment is based on CRC cards (I wrote about these here). CRC cards stand for Class, Responsibility and Collaborators. Pharo has a build in class comment template that has the basic structure of CRC cards. However, Kent Beck and Ward Cunningham (the creators of the CRC method) advise you NOT to use digital versions of these, but to write them down on paper such as index cards so that you can get a personal connection to your object. Out of sight is out of mind.

Though it could be a good idea to have both. To open the class comment, you can click on the '? Comment' menu option which was just above my main editor containing the class code. It was a little bunched up and hard to see.

When you write your class comment using the CRC format, you are basically stating the responsibility of your class briefly, and how it collaborates with other classes to achieve the responsibilities. It can also be a good idea to include an API (messages that the object understands - a lightbulb moment for me), give an example, and some details about the internal representation or implementation rational.

Adding methods to our class

To add a new message to our class, we start by selecting a protocol from the protocol pane. In this case, the example told us to select the 'no messages' protocol. A quick google says that the 'no messages' protocol makes all of your subsequently added methods 'as yet unclassified', which removes any quality assistant messages like 'no protocols' without having to include protocols. Answer found here

However, after right-clicking the protocals pane, selecting 'new protocol' and searching for 'no messages' with and without spaces, and with a capital NO like some of the other protocol names listed, I couldn't find the 'no messages' protocol. I also didn't see the no protocol message in my quality assistant panel so am assuming it was okay. Instead, I clicked on the '--all--' option in the protocol pane and replaced the method template that showed up in the editor with the following:

cell method
  
  initialize
    super initialize.
    self label: ''.
    self borderWidth: 2.
    bounds := 0 @ 0 corner: 16 @ 16.
    offColor := Color paleYellow.
    onColor := Color paleBlue darker.
    self useSquareCorners.
    self turnOff
  
  

Without reading ahead to see what this code does. Here are my thoughts. The initialize initialises the containing class (#LOCell) so that it can be used. The super initialize expression initializes the parent class (PBE-LightsOut). I know this after having worked with web components in JavaScript, which have the equivalent methods called Constructor() and super().

I'm assuming the self label is assigning an empty label to the cell. I'm not sure why it's there if it's empty. My assumption is that it will be used to store a value like 'on' and 'off' for toggling purposes.

The border width could be like setting the border to 2 pixels wide in CSS. The bounds is the most confusing expression to me. The whole expression is drawing the cell as 16 pixels by 16 pixels (I think). The offColor sets the cell to yellow when the cell is clicked 'off', whilst the onColor sets the cell color to blue when the cell is clicked 'on'. The cell is instructed to use square corners (in HTML corners are square by default, you set the border radius in CSS to set the roundness of the corners). I assume the turnOff expression will be used to toggle the cell 'off', though if that's the case, where is the equivalent 'turnOn' option?

Okay, now I'll read and take note of what the example explains these to mean.

Inspect your newly created object in the playground

You can inspect your object by opening the playground and typing the expression LOCell new and running the Command-I command.

This opens an inspector that shows you the variables contained in your object as well as the values contained within those variables. Clicking on one of the variables will split the screen into two panels, the right panel showing the details of the instance variables you clicked on.

In the bottom pane of the inspector, you can run expressions like you would in the playground. Typing in self openInWorld and running it with the Do It command (Command-D) will add your cell object to the top left of the World window. When I clicked on the cell it changed from yellow to blue, and when I clicked on it again it changed back to yellow. I was surprised by this because I didn't expect it to actually work based on the code that we had written.

On the World window, you can meta click on the cell (Control-shift-click) to bring up the Morphic halo, a bunch of handles that let you move the cell around and resize it etc. The bounds in the inspector will change to match the new positioning. You can also delete it with the 'x' handle.

Defining the board class

Add a new class called LOGame and write a class comment for it. Then initialize the game with the following code:

Initialize board class
  
  initialize
    | sampleCell width height n |
    super initialize.
    n := self cellsPerSide.
    sampleCell := sampleCell width.
    height := sampleCell height.
    self bounds: (5 @ 5 extent: (width * n) @ height * n) + (2 * self
    borderWidth)).
    cells := Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j]
  
  

When you save this code, you will get an error message popup box that says "Unknown variable: cells please correct, or cancel:" with a list of options for correcting the problem. We chose the 'Declare new instance variable' option because we want our cells to be an instance variable.

After selecting this, the following Critic texts were provided by the Quality Assistant:

The next step is to open the playground and type in LOGame and run it with the Do It (Command-D) command. A debugger method will pop up complaining that it doesn't know what some of the terms are. Instead of closing the debugger, click on the Create button and select "LOGame" which will contain the method and click "ok". When prompted for a method protocol, select "accessing". This will cause the debugger to create the method on the fly and invoke it for you immediately. The resulting method will look like the following:

cellsPerSide method stub
  
  cellsPerSide
    self shouldBeImplemented.
  
  

Replace the code stub with the following method

Cells per side method
  
  cellsPerSide
    "The number of cells along each side of the game"
    ^ 10
  
  

All this code does is specify how many cells go along the side of the game board.

Okay, so after trying to run this code, there was a bug. Turns out I made a mistake earlier in the process. So I went back and rewrote all of the code. I forgot to capture the steps again because I was so focused on getting it right. This is the finished result:

code editor with lights on game
showing