Моя играбельная игра с настраиваемыми параметрами W, H и numPlayers находится здесь:
http://pconstrictor.github.io/cellsurround/
(Исходный код тоже есть. Все еще нужно реорганизовать его в синтаксис модуля ES6 и, надеюсь, переписать с использованием FP. Поэтому, если есть более простой дизайн модели, я бы хотел знать.)
Для модели данных я использовал один двумерный массив размером w на h. Каждая ячейка имеет список сторон (четыре в случае этой квадратной сетки) и становится «заполненной», когда все стороны «заполнены». Обратите внимание, что пользователь получает дополнительный ход. Кроме того, иногда одно действие приводит к заполнению двух ячеек одновременно.
// MODEL
exports.SquareGrid = function(width, height, players) {
// reset (also serves as init)
this.reset = function(w, h, players) {
this.players = players;
this.player = players.firstPlayer();
var m = [];
this.matrix = m; // will be a 2D array (well, array of arrays)
this.height = h;
this.width = w;
// fill matrix
var toLeft = null, above = null; // these will be used for cells
// sharing sides
for (var row = 0; row < h; row++) {
m[row] = [];
for (var col = 0; col < w; col++) {
toLeft = col ? m[row][col - 1] : null;
above = row ? m[row - 1][col] : null;
m[row][col] = exports.createSquareCell(above, toLeft);
}
}
}
...
}
Для фактического отображения пользовательского интерфейса я использовал один 2D-массив (размером 2w + 1 на 2h + 1) в качестве модели представления, в которой точки, ребра и ячейки просто представлены в виде либо заполнен, либо пуст. (Точки начинаются заполненными и всегда остаются таковыми.) Это хорошо переводится, скажем, в таблицу HTML, которую можно легко отобразить с помощью двух циклов и без дополнительной логики. Вот массив 7 на 9, который соответствует модели 3х4. Обратите внимание, что эти псевдостолбцы и строки в идеале должны отображаться в чередующихся размерах только для наглядности.
(w = 3, h = 4) so (ww = 7, hh = 9)
. ___ . ___ . ___ .
| | | |
| | p1 | p1 |
. . ___ . ___ .
| | | |
| | p1 | p2 |
. . ___ . ___ .
| | |
| | |
. . ___ . .
| |
| |
. ___ . ___ . ___ .
Вот фактическая модель вида.
// VIEW MODEL
exports.SquareGridView = function(gameModel, appId, resetFuncString) {
// prepare to render the latest of whatever is in the model
this.refresh = function() {
var h = this.gridModel.height;
var w = this.gridModel.width;
// Initialize the UI table, whose dimensions are bigger than the
// model's.
var viewPm = [];
var hh = viewCoord(h);
var ww = viewCoord(w);
for (var i = 0; i < hh; i++) {
viewPm[i] = [];
}
// But loop over the model when actually filling it in. (Shared
// cells cause double writes to viewPm, but oh well.)
for (var row = 0; row < h; row++) {
for (var col = 0; col < w; col++) {
var cell = this.gridModel.matrix[row][col];
var i = viewCoord(row), j = viewCoord(col);
viewPm[i][j] = cell.owner;
viewPm[i - 1][j] = cell.sides['top'];
viewPm[i + 1][j] = cell.sides['bottom'];
viewPm[i][j - 1] = cell.sides['left'];
viewPm[i][j + 1] = cell.sides['right'];
// Note: vertices can be either filled or left undefined here (and hard-coded as filled in the HTML).
}
}
...
А вот фактический шаг рендеринга как HTML:
var t = []; // the html text
// TODO: split the HTML bits out into a template file? Use React or Elm?
...
t.push('<table class="squaregrid">\n');
var tdClass, tdId; // 'vertex', '0.0';
for (var i = 0; i < hh; i++) {
t.push(" <tr> \n");
for (var j = 0; j < ww; j++) {
t.push(this.tdHtml(viewPm, i, j));
}
t.push(" </tr>\n");
}
t.push("</table>\n");
...
Функция tdHtml()
опущена - она генерирует TD с правильным ID и классами.