But wait, our game can only work if the same color only appears in two cells: a cell and its twin cell. Moreover, a cell can be hidden or not, that is, the color can be seen or not? To take care of this, the Cell
class gets two new attributes:
Cell twin; bool hidden = true;
The method
_colorBox
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 that 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 method _colorBox
is called via draw 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 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
(lines (3)
and (4)
beneath). We 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 add a getCell
method to memory
and call it in line (5)
. When we have the cell, we 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 returns 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 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 needs some more thinking:
To this end, 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 check that in line (1)
. Again, we code a nested loop and we get the cell at that row and column. But when the cell at that position has not yet been made (line (3)
), we continue to construct it and assign its color and twin. In line (4)
, we call _getFreeRandomColor
to get a color which 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 functional any
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. Then, we have to set everything for the twin cell. We search 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
, meaning we haven't yet created a cell there (line (9)
). We 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)
onward, the properties of the twin cell are set and it is added to the list. That's all we need to produce the following result:
3.139.239.41