However, wait! Our game can only work if the same color appears in only two cells: a cell and its twin cell. Moreover, a cell can be hidden or not: can the color be seen or not? To take care of this, the Cell
class gets two new attributes:
Cell twin; bool hidden = true;
The _colorBox
method in the Board
class can now show the color of the cell when hidden
is false
(line (2)
); when hidden = true
(the default state), a neutral gray color will be used for the cell (line (1)
):
static const String COLOR_CODE = '#f0f0f0';
We also gave the gap
variable a better name, boxSize
:
void _colorBox(Cell cell) { var x = cell.column * boxSize; var y = cell.row * boxSize; context.beginPath(); if (cell.hidden) { context.fillStyle = COLOR_CODE; (1) } else { context.fillStyle = colorMap[cell.color]; (2) } // same code as in Spiral 3 }
The lines (1)
and (2)
can also be stated more succinctly with the ?
ternary operator. Remember that the drawing changes because the _colorBox
method is called via drawing at 60 frames per second and the board can react to a mouse click. In this spiral, we will show a cell when it is clicked together with its twin cell and then they will stay visible. Attaching an event handler for this is easy. We add the following line to the Board
constructor:
querySelector('#canvas').onMouseDown.listen(onMouseDown);
The onMouseDown
event handler has to know on which cell the click occurred. The mouse event e
contains the coordinates of the click in its e.offset.x
and e.offset.y
properties (lines (3)
and (4)
). We will obtain the cell's row and column by using a truncating division ~/
operator dividing the x
(which gives the column) and y
(which gives the row) values by boxSize
:
void onMouseDown(MouseEvent e) { int row = e.offset.y ~/ boxSize; (3) int column = e.offset.x ~/ boxSize; (4) Cell cell = memory.getCell(row, column); (5) cell.hidden = false; (6) cell.twin.hidden = false; (7) }
Memory has a collection of cells. To get the cell with a specified row
and column
value, we will add a getCell
method to memory
and call it in line (5)
. When we have the cell, we will set its hidden
property and that of its twin cell to false
(lines (6)
to (7)
). The getCell
method must return the cell at the given row and column. It loops through all the cells in line (8)
and checks each cell, whether it is positioned at that row
and column
(line (9)
). If yes, it will return that cell:
Cell getCell(int row, int column) { for (Cell cell in cells) { (8) if (cell.intersects(row, column)) { (9) return cell; } } }
For this purpose, we will add an intersects
method to the Cell
class. This checks whether its row and column match the given row and column for the current cell (see line (10)
):
bool intersects(int row, int column) { if (this.row == row && this.column == column) { (10) return true; } return false; }
Now, we have already added a lot of functionality, but the drawing of the board will need some more thinking:
To end this, we will have to make the constructor of Memory
a lot more intelligent:
Memory(this.length) { if (length.isOdd) { (1) throw new Exception( 'Memory length must be an even integer: $length.'), } cells = new Cells(); var cell, twinCell; for (var x = 0; x < length; x++) { for (var y = 0; y < length; y++) { cell = getCell(y, x); (2) if (cell == null) { (3) cell = new Cell(this, y, x); cell.color = _getFreeRandomColor(); (4) cells.add(cell); twinCell = _getFreeRandomCell(); (5) cell.twin = twinCell; (6) twinCell.twin = cell; twinCell.color = cell.color; cells.add(twinCell); } } } }
The number of pairs given by ((length * length) / 2) must be even. This is only true if the length
parameter of Memory
itself is even, so we checked it in line (1)
. Again, we coded a nested loop and got the cell at that row and column. However, as the cell at that position has not yet been made (line (3)
), we continued to construct it and assign its color and twin. In line (4)
, we called _getFreeRandomColor
to get a color that is not yet used:
String _getFreeRandomColor() { var color; do { color = randomColor(); } while (usedColors.any((c) => c == color)); (7) usedColors.add(color); (8) return color; }
The do…while
loop continues as long as the color is already in a list of usedColors
(this is elegantly checked with the any
functional from Chapter 3, Structuring Code with Classes and Libraries). On exiting from the loop, we found an unused color, which is added to usedColors
in line (8)
and also returned. We then had to set everything for the twin cell. We searched for a free one with the _getFreeRandomCell
method in line (5)
. Here, the do…while
loop continues until a (row, column)
position is found where cell == null
is, meaning that we haven't yet created a cell there (line (9)
). We will promptly do this in line (10)
:
Cell _getFreeRandomCell() { var row, column; Cell cell; do { row = randomInt(length); column = randomInt(length); cell = getCell(row, column); } while (cell != null); (9) return new Cell(this, row, column); (10) }
From line (6)
onwards, the properties of the twin cell are set and added to the list. This is all we need to produce the following result:
18.219.4.174