Skip to main content
Engineering LibreTexts

3.6: Defining the Class LOGame

  • Page ID
    39582
    \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Now let’s create the other class that we need for the game, which we will name LOGame.

    Class creation

    Make the class definition template visible in the browser main window. Do this by clicking on the package name (or right-clicking on the Class pane and selecting Add Class). Edit the code so that it reads as follows, and Accept it.

    BorderedMorph subclass: #LOGame
        instanceVariableNames: ''
        classVariableNames: ''
        package: 'PBE-LightsOut'
    

    Here we subclass BorderedMorph. Morph is the superclass of all of the graphical shapes in Pharo, and (unsurprisingly) a BorderedMorph is a Morph with a border. We could also insert the names of the instance variables between the quotes on the second line, but for now, let’s just leave that list empty.

    Initializing our game

    Now let’s define an initialize method for LOGame. Type the following into the browser as a method for LOGame and try to Accept it.

    initialize
        | sampleCell width height n |
        super initialize.
        n := self cellsPerSide.
        sampleCell := LOCell new.
        width := 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 ]
    

    Pharo will complain that it doesn’t know the meaning of cells (see Figure \(\PageIndex{1}\)). It will offer you a number of ways to fix this.

    Declaring cells as a new instance variable.
    Figure \(\PageIndex{1}\): Declaring cells as a new instance variable.

    Choose Declare new instance variable, because we want cells to be an instance variable.

    Taking advantage of the debugger

    At this stage if you open a Playground, type LOGame new, and Do it, Pharo will complain that it doesn’t know the meaning of some of the terms (see Figure \(\PageIndex{2}\)). It will tell you that it doesn’t know of a message cellsPerSide, and will open a debugger. But cellsPerSide is not a mistake; it is just a method that we haven’t yet defined. We will do so, shortly.

    Pharo detecting an unknown selector.
    Figure \(\PageIndex{2}\): Pharo detecting an unknown selector.

    Now let us do it: type LOGame new and Do it. Do not close the debugger. Click on the button Create of the debugger, when prompted, select LOGame, the class which will contain the method, click on ok, then when prompted for a method protocol enter accessing. The debugger will create the method cellsPerSide on the fly and invoke it immediately. As there is no magic, the created method will simply raise an exception and the debugger will stop again (as shown in Figure \(\PageIndex{3}\)) giving you the opportunity to define the behavior of the method.

    The system created a new method with a body to be defined.
    Figure \(\PageIndex{3}\): The system created a new method with a body to be defined.

    Here you can write your method. This method could hardly be simpler: it answers the constant 10. One advantage of representing constants as methods is that if the program evolves so that the constant then depends on some other features, the method can be changed to calculate this value.

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

    Define the method cellsPerSide in the debugger. Do not forget to compile the method definition by using Accept. You should obtain a situation as shown by Figure \(\PageIndex{4}\). If you press the button Proceed the program will continue its execution - here it will stop since we did not define the method newCellAt:. We could use the same process but for now we stop to explain a bit what we did so far. Close the debugger, and look at the class definition once again (which you can do by clicking on LOGame on the second pane of the System Browser), you will see that the browser has modified it to include the instance variable cells.

    Defining cellsPerSide in the debugger.
    Figure \(\PageIndex{4}\): Defining cellsPerSide in the debugger.

    Studying the initialize method

    Let us now study the method initialize.

    initialize
        | sampleCell width height n |
        super initialize.
        n := self cellsPerSide.
        sampleCell := LOCell new.
        width := sampleCell width.
        height := sampleCell height.
        selfbounds:(5@5extent:(width*n)@(height*n)+(2*self
            borderWidth)).
        cells := Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ]
    
    • At line 2, the expression | sampleCell width height n | declares 4 temporary variables. They are called temporary variables because their scope and lifetime are limited to this method. Temporary variables with explanatory names are helpful in making code more readable. Lines 4-7 set the value of these variables.
    • How big should our game board be? Big enough to hold some integral number of cells, and big enough to draw a border around them. How many cells is the right number? 5? 10? 100? We don’t know yet, and if we did, we would probably change our minds later. So we delegate the responsibility for knowing that number to another method, which we name cellsPerSide, and which we will write in a minute or two. Don’t be put off by this: it is actually good practice to code by referring to other methods that we haven’t yet defined. Why? Well, it wasn’t until we started writing the initialize method that we realized that we needed it. And at that point, we can give it a meaningful name, and move on, without interrupting our flow.
    • The fourth line uses this method, n := self cellsPerSide. sends the message cellsPerSide to self, i.e., to this very object. The response, which will be the number of cells per side of the game board, is assigned to n.
    • The next three lines create a new LOCell object, and assign its width and height to the appropriate temporary variables.
    • Line 8 sets the bounds of the new object. Without worrying too much about the details just yet, believe us that the expression in parentheses creates a square with its origin (i.e., its top-left corner) at the point (5,5) and its bottom-right corner far enough away to allow space for the right number of cells.
    • The last line sets the LOGame object’s instance variable cells to a newly created Matrix with the right number of rows and columns. We do this by sending the message new: tabulate: to the Matrix class (classes are objects too, so we can send them messages). We know that new: tabulate: takes two arguments because it has two colons (:) in its name. The arguments go right after the colons. If you are used to languages that put all of the arguments together inside parentheses, this may seem weird at first. Don’t panic, it’s only syntax! It turns out to be a very good syntax because the name of the method can be used to explain the roles of the arguments. For example, it is pretty clear that Matrix rows: 5 columns: 2 has 5 rows and 2 columns, and not 2 rows and 5 columns.
    • Matrix new: n tabulate: [ :i :j | self newCellAt: i at: j ] creates a new n X n matrix and initializes its elements. The initial value of each element will depend on its coordinates. The (i,j)th element will be initialized to the result of evaluating self newCellAt: i at: j.

    This page titled 3.6: Defining the Class LOGame is shared under a CC BY-SA 3.0 license and was authored, remixed, and/or curated by via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.