├── README.md
├── index.html
├── canvashelper.js
└── squares24.js
/README.md:
--------------------------------------------------------------------------------
1 | #GardnerSquaresJs
2 |
3 | Game described by Martin Gardner as "24 Color Squares"
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | McMahon's 24 Color Squares in JavaScript
6 |
7 |
8 |
9 |
10 |
11 |
McMahon's 24 Color Squares
12 |
13 |
Drag squares to swap them, click to rotate.
14 | Goal is to make all the border "triangles" of
15 | the same color, while pairs of inner "triangles" should match when they touch the same edge.
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/canvashelper.js:
--------------------------------------------------------------------------------
1 | function CanvasHelper(canvas, context) {
2 | this.canvas = canvas;
3 | this.ctx = context;
4 | this.rndval = 0;
5 | }
6 |
7 | CanvasHelper.prototype.lineRel = function(x1, y1, dx, dy) {
8 | this.line(x1, y1, x1 + dx, y1 + dy);
9 | }
10 |
11 | CanvasHelper.prototype.line = function(x1, y1, x2, y2) {
12 | var ctx = this.ctx;
13 | ctx.beginPath();
14 | ctx.moveTo(x1, y1);
15 | ctx.lineTo(x2, y2);
16 | ctx.closePath();
17 | ctx.stroke();
18 | }
19 |
20 | CanvasHelper.prototype.circle = function(x, y, r) {
21 | var ctx = this.ctx;
22 | ctx.beginPath();
23 | ctx.arc(x, y, r, 0, 2 * Math.PI, false);
24 | ctx.closePath();
25 | ctx.fill();
26 | ctx.stroke();
27 | }
28 |
29 | CanvasHelper.prototype.posFromEvent = function(e) {
30 | var e = e || window.event;
31 | var cnv = this.canvas;
32 | var offsetX = e.pageX - cnv.clientLeft - cnv.offsetLeft;
33 | var offsetY = e.pageY - cnv.clientTop - cnv.offsetTop;
34 | return {x: offsetX, y: offsetY};
35 | }
36 |
37 | CanvasHelper.prototype.poly = function(pts) {
38 | var ctx = this.ctx;
39 | ctx.beginPath();
40 | ctx.moveTo(pts[0][0], pts[0][1]);
41 | for (var i = 1; i < pts.length; i++) {
42 | p = arguments[i];
43 | ctx.lineTo(pts[i][0], pts[i][1]);
44 | }
45 | ctx.closePath();
46 | }
47 |
48 | CanvasHelper.prototype.fillPoly = function() {
49 | this.poly(arguments);
50 | this.ctx.fill();
51 | }
52 |
53 | CanvasHelper.prototype.drawPoly = function() {
54 | this.poly(arguments);
55 | this.ctx.stroke();
56 | }
57 |
58 | CanvasHelper.prototype.rand = function(seed) {
59 | if (typeof(seed) != 'undefined') {
60 | this.rndval = seed;
61 | }
62 | this.rndval = parseFloat('0.' + Math.sin(this.rndval + 0.31415926).toString().substr(6));
63 | return this.rndval;
64 | }
65 |
--------------------------------------------------------------------------------
/squares24.js:
--------------------------------------------------------------------------------
1 | function Squares24(opts) {
2 | this.w = 6;
3 | this.h = 4;
4 | this.colors = ['#FF0000', '#E0E000', '#3020FF', '#000000'];
5 | this.size = 80;
6 | this.moving = null;
7 | this.rotating = null;
8 | this.infoCallback = null;
9 | if (typeof(opts) == 'object') {
10 | for (var key in opts) {
11 | this[key] = opts[key];
12 | }
13 | }
14 | this.init();
15 | }
16 |
17 | Squares24.prototype.init = function() {
18 | var canvas = document.getElementById('demo');
19 | this.setupGeometry(canvas);
20 | this.ctx = canvas.getContext('2d');
21 | this.ch = new CanvasHelper(canvas, this.ctx);
22 | this.initialSetup();
23 | this.draw();
24 | var self = this;
25 | canvas.onmousedown = function(e) {self.mouseDown(e)};
26 | canvas.onmouseup = function(e) {self.mouseUp(e)};
27 | canvas.onmousemove = function(e) {self.mouseMove(e)};
28 | setInterval(function() {self.animator()}, 50);
29 | }
30 |
31 | Squares24.prototype.initialSetup = function() {
32 | this.genSquares();
33 | this.shuffle();
34 | }
35 |
36 | Squares24.prototype.genSquares = function() {
37 | var res = [];
38 | for (var i = 0; i < 81; i++) {
39 | var s = '';
40 | var p = i;
41 | for (var j = 0; j < 4; j++) {
42 | s += p % 3;
43 | p = Math.floor(p / 3);
44 | }
45 | var best = s;
46 | for (var k = 0; k < 3; k++) {
47 | s = s.substring(1) + s.charAt(0);
48 | if (s < best) {
49 | best = s;
50 | }
51 | }
52 | res[best] = best;
53 | }
54 | this.squares = [];
55 | for (var sq in res) {
56 | this.squares.push(sq);
57 | }
58 | }
59 |
60 | Squares24.prototype.shuffle = function() {
61 | for (var i = 0; i < this.squares.length; i++) {
62 | var j = Math.floor(Math.random() * this.squares.length);
63 | var r = Math.floor(Math.random() * 4);
64 | var t = this.squares[i];
65 | if (r != 0) {
66 | this.rotate(i);
67 | }
68 | this.squares[i] = this.squares[j];
69 | this.squares[j] = t;
70 | }
71 | this.recalculate();
72 | }
73 |
74 | Squares24.prototype.shuffleAndDraw = function() {
75 | this.shuffle();
76 | this.draw();
77 | }
78 |
79 | Squares24.prototype.setupGeometry = function(canvas) {
80 | canvas.width = this.size * this.w;
81 | canvas.height = this.size * this.h;
82 | }
83 |
84 | Squares24.prototype.draw = function() {
85 | var ctx = this.ctx;
86 | ctx.fillStyle = this.colors[3];
87 | ctx.fillRect(0, 0, this.w * this.size, this.h * this.size);
88 | var moving = this.moving;
89 | var rotating = this.rotating;
90 | var skip = moving !== null ? moving.index : (rotating !== null ? rotating.index : -1);
91 | for (var i in this.squares) {
92 | if (i != skip) {
93 | this.drawSquare(i, 0, 0, 0);
94 | }
95 | }
96 | if (moving !== null) {
97 | this.drawSquare(moving.index, moving.dx, moving.dy, 0);
98 | } else if (rotating !== null) {
99 | this.drawSquare(rotating.index, 0, 0, rotating.angle);
100 | }
101 | }
102 |
103 | Squares24.prototype.drawSquare = function(index, dx, dy, da) {
104 | var coords = this.coordsByIndex(index);
105 | var cx = coords[0] + dx;
106 | var cy = coords[1] + dy;
107 | var sq = this.squares[index];
108 | var stricken = (dx != 0 || dy != 0 || da != 0);
109 | for (var i = 0; i < sq.length; i++) {
110 | this.drawTriangle(coords[0] + dx, coords[1] + dy, i + da, parseInt(sq.charAt(i)), stricken);
111 | }
112 | }
113 |
114 | Squares24.prototype.drawTriangle = function (cx, cy, a, color, stricken) {
115 | var hs = Math.floor(this.size * 0.67);
116 | this.ctx.fillStyle = this.colors[color];
117 | a /= 2;
118 | var a1 = Math.PI * (a - 0.25);
119 | var a2 = Math.PI * (a + 0.25);
120 | var x1 = cx + hs * Math.cos(a1);
121 | var y1 = cy + hs * Math.sin(a1);
122 | var x2 = cx + hs * Math.cos(a2);
123 | var y2 = cy + hs * Math.sin(a2);
124 | this.ch.fillPoly([cx, cy], [x1, y1], [x2, y2]);
125 | if (stricken) {
126 | this.ctx.lineWidth = 2;
127 | this.ctx.strokeStyle = this.colors[3];
128 | this.ch.line(x1, y1, x2, y2);
129 | }
130 | }
131 |
132 | Squares24.prototype.coordsByIndex = function(index) {
133 | return [Math.floor(index % this.w) * this.size + Math.floor(this.size / 2),
134 | Math.floor(index / this.w) * this.size + Math.floor(this.size / 2)];
135 | }
136 |
137 | Squares24.prototype.rotate = function(index) {
138 | var s = this.squares[index];
139 | this.squares[index] = s.substring(1) + s.charAt(0);
140 | }
141 |
142 | Squares24.prototype.recalculate = function() {
143 | var score = this.recalculateBorder();
144 | score += this.recalculateHorz();
145 | score += this.recalculateVert();
146 | if (typeof(this.infoCallback) == 'function') {
147 | this.infoCallback(score);
148 | }
149 | return score;
150 | }
151 |
152 | Squares24.prototype.recalculateVert = function() {
153 | var sum = 0;
154 | var sq = this.squares;
155 | for (var x = 1; x < this.w; x++) {
156 | for (var y = 0; y < this.h; y++) {
157 | var pos = y * this.w + x;
158 | if (sq[pos - 1].charAt(0) == sq[pos].charAt(2)) {
159 | sum++;
160 | }
161 | }
162 | }
163 | return sum;
164 | }
165 |
166 | Squares24.prototype.recalculateHorz = function() {
167 | var sum = 0;
168 | var sq = this.squares;
169 | for (var y = 1; y < this.h; y++) {
170 | for (var x = 0; x < this.w; x++) {
171 | var pos = y * this.w + x;
172 | if (sq[pos - this.w].charAt(1) == sq[pos].charAt(3)) {
173 | sum++;
174 | }
175 | }
176 | }
177 | return sum;
178 | }
179 |
180 | Squares24.prototype.recalculateBorder = function() {
181 | var cnt = [0, 0, 0];
182 | var sq = this.squares;
183 | for (var x = 0; x < this.w; x++) {
184 | cnt[parseInt(sq[x].charAt(3))]++;
185 | cnt[parseInt(sq[x + (this.h - 1) * this.w].charAt(1))]++;
186 | }
187 | for (var y = 0; y < this.h; y++) {
188 | cnt[parseInt(sq[y * this.w].charAt(2))]++;
189 | cnt[parseInt(sq[(y + 1) * this.w - 1].charAt(0))]++;
190 | }
191 | var best = 0;
192 | for (var i = 0; i < 3; i++) {
193 | if (cnt[i] > cnt[best]) {
194 | best = i;
195 | }
196 | }
197 | return cnt[best];
198 | }
199 |
200 | Squares24.prototype.mouseDown = function(event) {
201 | if (this.rotating !== null) {
202 | return;
203 | }
204 | event.preventDefault();
205 | var pos = this.ch.posFromEvent(event);
206 | pos.ts = new Date().getTime();
207 | pos.index = this.nearestSquare(pos);
208 | pos.dx = 0;
209 | pos.dy = 0;
210 | this.moving = pos;
211 | }
212 |
213 | Squares24.prototype.mouseUp = function(event) {
214 | event.preventDefault();
215 | var pos = this.ch.posFromEvent(event);
216 | var sq = this.nearestSquare(pos);
217 | var t = new Date().getTime();
218 | if (sq == this.moving.index) {
219 | if (t - this.moving.ts < 300) {
220 | this.rotating = {index: sq, angle: 0};
221 | }
222 | } else {
223 | t = this.squares[sq];
224 | this.squares[sq] = this.squares[this.moving.index];
225 | this.squares[this.moving.index] = t;
226 | this.recalculate();
227 | }
228 | this.moving = null;
229 | this.draw();
230 | }
231 |
232 | Squares24.prototype.mouseMove = function(event) {
233 | if (this.moving === null) {
234 | return;
235 | }
236 | var pos = this.ch.posFromEvent(event);
237 | var moving = this.moving;
238 | moving.dx = pos.x - moving.x;
239 | moving.dy = pos.y - moving.y;
240 | this.draw();
241 | }
242 |
243 | Squares24.prototype.animator = function() {
244 | if (this.rotating === null) {
245 | return;
246 | }
247 | this.rotating.angle -= 0.33333333;
248 | if (this.rotating.angle <= -0.9999) {
249 | this.rotate(this.rotating.index);
250 | this.recalculate();
251 | this.rotating = null;
252 | }
253 | this.draw();
254 | }
255 |
256 | Squares24.prototype.nearestSquare = function(pos) {
257 | var best = -1;
258 | var bestVal = Infinity;
259 | for (var i in this.squares) {
260 | var coords = this.coordsByIndex(i);
261 | var dist = Math.pow(pos.x - coords[0], 2) + Math.pow(pos.y - coords[1], 2);
262 | if (dist < bestVal) {
263 | best = i;
264 | bestVal = dist;
265 | }
266 | }
267 | return best;
268 | }
269 |
--------------------------------------------------------------------------------