Skip to content

Commit 809c8d9

Browse files
Refactoring and support for recursive win-check.
1 parent 8a2a34d commit 809c8d9

File tree

9 files changed

+211
-212
lines changed

9 files changed

+211
-212
lines changed

docs/metaTTT.js

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/control.js

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import $ from 'jquery';
2-
3-
const ATTR_PLAYER_MARK = 'player-mark';
4-
51
export class Control {
6-
constructor(metaGrid) {
7-
let _currentPlayer = 0;
2+
constructor() {
3+
let _previousPlayer = 0;
84

9-
function nextPlayer() {
10-
_currentPlayer = 1 - _currentPlayer;
11-
}
5+
const currentPlayer = () => {
6+
return _previousPlayer ^= 1;
7+
};
128

13-
metaGrid.onCellClick(function (cell) {
14-
$(cell).attr(ATTR_PLAYER_MARK, _currentPlayer);
15-
nextPlayer();
16-
});
9+
this.handleClick = gridLeaf => {
10+
if (gridLeaf.getMark() == null)
11+
gridLeaf.setMark(currentPlayer());
12+
};
1713
}
1814
}

src/app/main.js

+22-18
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
import { MetaGrid } from './metaGrid';
2-
import $ from 'jquery';
3-
import { Control } from "./control";
2+
import { Control } from './control';
43

54
import '../styles/main.css';
65

7-
const NODE_CONTAINER = '#grid-container',
8-
NODE_SLIDER = '#meta-level',
9-
NODE_LEVEL = '#level-value',
10-
NODE_NEW_GAME = '#new-game';
6+
const ID_CONTAINER = 'grid-container',
7+
ID_META_SLIDER = 'meta-level',
8+
ID_META_DISPLAY = 'level-value',
9+
ID_NEW_GAME_BUTTON = 'new-game',
10+
GRID_SIZE = 3;
1111

12-
let metaSlider = $(NODE_SLIDER);
13-
let metaLevelNode = $(NODE_LEVEL);
14-
metaLevelNode.text(metaSlider.val() - 1);
15-
metaSlider.on('input', function() {
16-
metaLevelNode.text(metaSlider.val() - 1);
17-
});
12+
const metaSlider = document.getElementById(ID_META_SLIDER);
13+
const metaDisplay = document.getElementById(ID_META_DISPLAY);
1814

19-
$(NODE_NEW_GAME).click(function () {
20-
let metaGrid = new MetaGrid(metaSlider.val());
21-
new Control(metaGrid);
22-
$(NODE_CONTAINER).empty();
23-
$(NODE_CONTAINER).append(metaGrid.getContainer());
24-
});
15+
const setMetaDisplay = () => {
16+
metaDisplay.innerText = String(metaSlider.value - 1)
17+
};
18+
19+
setMetaDisplay();
20+
metaSlider.oninput = setMetaDisplay;
21+
22+
document.getElementById(ID_NEW_GAME_BUTTON).onclick = () => {
23+
const gridContainer = document.getElementById(ID_CONTAINER);
24+
gridContainer.innerHTML = '';
25+
const control = new Control();
26+
const metaGrid = new MetaGrid(GRID_SIZE, metaSlider.value, control.handleClick);
27+
gridContainer.appendChild(metaGrid.getElement());
28+
};

src/app/metaGrid.js

+103-96
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,112 @@
1-
import $ from 'jquery';
2-
3-
import '../styles/table.css';
4-
5-
const GRID_SIZE = 3,
6-
NODE_DIV = '<div>',
7-
CLASS_TABLE = 'table',
8-
CLASS_ROW = 'row',
9-
CLASS_CELL = 'cell',
10-
CLASS_DISABLED = 'disabled',
11-
CLASS_CLICKED = 'played',
12-
ATTR_LOCATION = 'location';
13-
14-
/**
15-
* Recursively generates a grid in the container.
16-
* @param container The container to add the grid to.
17-
* @param depth The number of recursions.
18-
*/
19-
function generateTable(container, depth) {
20-
if (depth < 1) {
21-
$(container).addClass(CLASS_CELL);
22-
} else {
23-
$(container).addClass(CLASS_TABLE);
24-
for (let y = 0; y < GRID_SIZE; y++) {
25-
let row = $(NODE_DIV);
26-
$(row).addClass(CLASS_ROW);
27-
for (let x = 0; x < GRID_SIZE; x++) {
28-
let cell = $(NODE_DIV);
29-
$(cell).attr(ATTR_LOCATION, y * GRID_SIZE + x);
30-
generateTable(cell, depth - 1);
31-
row.append(cell);
1+
import '../styles/grid.css';
2+
3+
export class MetaGrid {
4+
constructor(size, depth, onLeafClick, parent) {
5+
const ATTR_MARK = 'mark',
6+
ELEM_GRID = 'div';
7+
8+
const _cells = [];
9+
const _element = document.createElement(ELEM_GRID);
10+
let _mark;
11+
12+
const updateElement = () => {
13+
_element.setAttribute(ATTR_MARK, _mark);
14+
};
15+
16+
const makeChildren = () => {
17+
_cells.length = 0;
18+
_element.innerHTML = '';
19+
for (let row = 0; row < size; row++) {
20+
_cells[row] = [];
21+
for (let col = 0; col < size; col++) {
22+
const cell = new MetaGrid(size, depth - 1, onLeafClick, this);
23+
_cells[row].push(cell);
24+
_element.appendChild(cell.getElement());
25+
}
3226
}
33-
$(container).append(row);
34-
}
35-
}
36-
}
27+
};
3728

38-
function getClickStack(cell) {
39-
let locations = [$(cell).attr(ATTR_LOCATION)];
40-
$(cell).parents('.' + CLASS_TABLE).each(function () {
41-
let location = $(this).attr(ATTR_LOCATION);
42-
if (location != null)
43-
locations.push($(this).attr(ATTR_LOCATION));
44-
});
45-
return locations;
46-
}
29+
const checkRows = () => {
30+
for (let row = 0; row < size; row++) {
31+
const firstCell = _cells[row][0].getMark();
32+
if (firstCell == null)
33+
continue;
4734

48-
function enableTable(table) {
49-
table.find('.' + CLASS_DISABLED).removeClass(CLASS_DISABLED);
50-
}
35+
let col;
36+
for (col = 0; col < size; col++) {
37+
if (_cells[row][col].getMark() !== firstCell)
38+
break;
39+
}
5140

52-
function disableWithStack(currentTable, locations) {
53-
let root = currentTable;
54-
enableTable(root);
55-
56-
while (locations.length > 1) {
57-
let location = locations.shift();
58-
currentTable.children().closest('.' + CLASS_ROW).children().each(function () {
59-
if ($(this).attr(ATTR_LOCATION) === location)
60-
currentTable = $(this);
61-
else
62-
$(this).addClass(CLASS_DISABLED);
63-
});
64-
}
41+
if (col === size)
42+
return true;
43+
}
6544

66-
if (isTableFull(currentTable)) {
67-
let freeCell = root.find('.' + CLASS_CELL).not('.' + CLASS_CLICKED)[0];
68-
disableWithStack(root, getClickStack(freeCell));
69-
}
70-
}
45+
return false;
46+
};
7147

72-
function isTableFull(table) {
73-
let numPlayed = $(table).children().children('.' + CLASS_CLICKED).length;
74-
return numPlayed === GRID_SIZE * GRID_SIZE;
75-
}
48+
const checkColumns = () => {
49+
for (let col = 0; col < size; col++) {
50+
const firstCell = _cells[0][col].getMark();
51+
if (firstCell == null)
52+
continue;
7653

54+
let row;
55+
for (row = 0; row < size; row++) {
56+
if (_cells[row][col].getMark() !== firstCell)
57+
break;
58+
}
7759

78-
function isCellDisabled(cell) {
79-
let containingTable = $(cell).parents('.' + CLASS_TABLE);
80-
return containingTable.hasClass(CLASS_DISABLED) || $(cell).hasClass(CLASS_CLICKED);
81-
}
60+
if (row === size)
61+
return true;
62+
}
8263

83-
/**
84-
* Generates a grid with multiple grids inside itself.
85-
*/
86-
export class MetaGrid {
87-
constructor(metaLevel) {
88-
let _rootTable = $(NODE_DIV);
89-
generateTable(_rootTable, metaLevel);
90-
91-
this.getContainer = function() {
92-
return _rootTable;
93-
};
94-
95-
this.onCellClick = function(onClick) {
96-
_rootTable.find('.' + CLASS_CELL).click(function () {
97-
if (!isCellDisabled(this)) {
98-
onClick(this);
99-
disableWithStack(_rootTable, getClickStack(this));
100-
$(this).addClass(CLASS_CLICKED);
101-
}
102-
});
103-
};
104-
}
105-
};
64+
return false;
65+
};
66+
67+
const checkDiagonals = () => {
68+
const firstLeftCell = _cells[0][0].getMark();
69+
const firstRightCell = _cells[0][size-1].getMark();
70+
71+
let leftDiagonal = true, rightDiagonal = true;
72+
for (let index = 0; index < size; index++) {
73+
if (_cells[index][index].getMark() !== firstLeftCell) {
74+
leftDiagonal = false;
75+
}
76+
77+
let mirrorIndex = size - index - 1;
78+
if (_cells[index][mirrorIndex].getMark() !== firstRightCell) {
79+
rightDiagonal = false;
80+
}
81+
}
82+
return firstLeftCell != null && leftDiagonal ||
83+
firstRightCell != null && rightDiagonal;
84+
};
85+
86+
this.checkWin = (checkMark) => {
87+
if (_mark)
88+
return;
89+
90+
if (checkRows() || checkColumns() || checkDiagonals())
91+
this.setMark(checkMark);
92+
};
93+
94+
this.setMark = mark => {
95+
_mark = mark;
96+
if (parent)
97+
parent.checkWin(mark);
98+
updateElement();
99+
};
100+
101+
this.getMark = () => {
102+
return _mark;
103+
};
104+
105+
this.getElement = () => _element;
106+
107+
if (depth > 0)
108+
makeChildren();
109+
else
110+
_element.onclick = () => onLeafClick(this);
111+
}
112+
}

src/styles/grid.css

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#grid-container {
2+
display: table;
3+
position: absolute;
4+
top: 0;
5+
bottom: 0;
6+
left: 0;
7+
right: 0;
8+
margin: auto;
9+
width: 75vh;
10+
height: 75vh;
11+
}
12+
13+
#grid-container:empty {
14+
border-radius: 1vh;
15+
border: 1px solid black;
16+
}
17+
18+
#grid-container:empty:after {
19+
line-height: 75vh;
20+
text-align: center;
21+
content: 'Please select a meta level and press the button.';
22+
}
23+
24+
#grid-container div {
25+
display: grid;
26+
grid-template-columns: auto auto auto;
27+
width: 95%;
28+
height: 95%;
29+
margin: 2.5%;
30+
31+
border-radius: 1vh;
32+
border: 1px solid rgba(0, 0, 0, 0.5);
33+
background-color: rgba(255, 255, 255, 0.8);;
34+
}
35+
36+
#grid-container div div {
37+
border: 1px dashed rgba(0, 0, 0, 0.5);
38+
}
39+
40+
#grid-container div[mark='0'] {
41+
background-color: green;
42+
}
43+
44+
#grid-container div[mark='1'] {
45+
background-color: red;
46+
}

0 commit comments

Comments
 (0)