Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@bellbind
Created May 21, 2011 15:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bellbind/984605 to your computer and use it in GitHub Desktop.
Save bellbind/984605 to your computer and use it in GitHub Desktop.
[html5][js]ECMAScript5 SameGame example
"use strict";
this.SameGame2D = (function (global) {
var requestAnimationFrame = (global.requestAnimationFrame ||
global.webkitRequestAnimationFrame ||
global.mozRequestAnimationFrame ||
global.oRequestAnimationFrame ||
global.msRequestAnimationFrame ||
function (callback, elem) {
global.setTimeout(callback, 30);
});
var Animation = function (props, msec) {
return Object.create(Animation.prototype, {
span: {value: msec},
previous: {value: props, writable: true},
goal: {value: props, writable: true},
start: {value: new Date(), writable: true},
end: {value: new Date(), writable: true},
});
};
Animation.prototype = {
update: function (props) {
var now = new Date();
var value = this.get(now);
Object.keys(props).forEach(function (key) {
this.goal
}, this);
},
get: function (now) {
if (now > this.end) return this.goal;
},
};
var SimpleView = function SimpleView(canvas) {
return Object.seal(Object.create(SimpleView.prototype, {
canvas: {value: canvas},
c2d: {value: canvas.getContext("2d")},
leastSelected: {value: 1, writable: true},
scale: {value: 80, writable: true},
colors: {value: ["red", "green", "blue"], writable: true},
board: {value: null, writable: true},
selected: {value: null, writable: true},
log: {value: [], writable: true},
listeners: {value: [], writable: true},
}).init());
};
SimpleView.prototype = Object.freeze({
score: function () {
return this.log.reduce(function (sum, selected) {
return sum + selected.count() * selected.count();
}, 0) * 100;
},
newGame: function () {
this.board = this.newBoard();
this.selected = this.board.noSelect();
this.log = [];
this.updated();
},
undo: function () {
if (this.board.prev) {
this.board = this.board.prev.prev;
this.selected = this.board.noSelect();
this.log.pop();
this.updated();
}
return this;
},
addListener: function (listener) {
this.listeners.push(listener);
return this;
},
removeListener: function (listener) {
this.listeners = this.listeners.filter(function (registered) {
return listener !== registered;
});
return this;
},
updated: function () {
this.listeners.forEach(function (listener) {
try {listener(this);} catch (ex) {}
}, this);
this.draw();
},
init: function () {
this.canvas.addEventListener(
"mousemove", this.onHover.bind(this), false);
this.canvas.addEventListener(
"mouseout", this.onOut.bind(this), false);
this.canvas.addEventListener(
"mouseup", this.onPress.bind(this), false);
this.newGame();
return this;
},
draw: function () {
var c2d = this.c2d;
c2d.clearRect(0, 0, this.canvas.width, this.canvas.height);
c2d.save();
c2d.translate(0, this.canvas.height);
c2d.scale(this.scale, -this.scale);
this.board.forEach(this.drawGem.bind(this));
c2d.restore();
if (this.board.isGameClear()) {
this.drawMessage("Conguraturation!");
} else if (this.board.isGameOver()) {
this.drawMessage("Game Over");
}
},
drawGem: function (cell) {
var c2d = this.c2d;
c2d.save();
c2d.translate(cell.x, cell.y);
c2d.beginPath();
c2d.arc(0.5, 0.5, 0.5, 0, Math.PI * 2, false);
if (this.selected.has(cell.x, cell.y)) {
c2d.lineWidth = 1.0 / this.scale;
c2d.strokeStyle = this.colors[cell.gem.color];
c2d.stroke();
} else {
c2d.fillStyle = this.colors[cell.gem.color];
c2d.fill();
}
c2d.restore();
},
drawMessage: function (text) {
var c2d = this.c2d;
c2d.textAlign = "center";
c2d.textBaseline = "middle";
c2d.font = "50px 'sans-serif'";
c2d.fillText(text, this.canvas.width / 2, this.canvas.height / 2);
},
newBoard: function () {
var width = 0|(this.canvas.width / this.scale);
var height = 0|(this.canvas.height / this.scale);
return SameGame.newRandomBoard({
colors: this.colors.length, selectedMin: this.leastSelected,
width: width, height: height,
});
},
selectedBy: function (ev) {
var rect = ev.target.getBoundingClientRect();
var offX = ev.clientX - rect.left;
var offY = ev.clientY - rect.top;
var x = 0|(offX / this.scale);
var y = 0|((this.canvas.height - offY) / this.scale);
return this.board.select(x, y);
},
onPress: function (ev) {
this.selected = this.selectedBy(ev);
var isInvalid = this.selected.count() === 0;
if (isInvalid) return;
this.board = this.board.remove(this.selected);
this.log.push(this.selected);
this.selected = this.board.noSelect();
this.updated();
this.board = this.board.shrink();
this.selected = this.selectedBy(ev);
this.updated();
},
onHover: function (ev) {
this.selected = this.selectedBy(ev);
this.updated();
},
onOut: function (ev) {
this.selected = this.board.noSelect();
this.updated();
},
});
return {
newView: SimpleView,
};
})(this);
"use strict";
this.SameGame2D = (function () {
var SimpleView = function SimpleView(canvas) {
return Object.seal(Object.create(SimpleView.prototype, {
canvas: {value: canvas},
c2d: {value: canvas.getContext("2d")},
leastSelected: {value: 1, writable: true},
scale: {value: 80, writable: true},
colors: {value: ["red", "green", "blue"], writable: true},
board: {value: null, writable: true},
selected: {value: null, writable: true},
log: {value: [], writable: true},
listeners: {value: [], writable: true},
}).init());
};
SimpleView.prototype = Object.freeze({
score: function () {
return this.log.reduce(function (sum, selected) {
return sum + selected.count() * selected.count();
}, 0) * 100;
},
newGame: function () {
this.board = this.newBoard();
this.selected = this.board.noSelect();
this.log = [];
this.updated();
},
undo: function () {
if (this.board.prev) {
this.board = this.board.prev.prev;
this.selected = this.board.noSelect();
this.log.pop();
this.updated();
}
return this;
},
addListener: function (listener) {
this.listeners.push(listener);
return this;
},
removeListener: function (listener) {
this.listeners = this.listeners.filter(function (registered) {
return listener !== registered;
});
return this;
},
updated: function () {
this.listeners.forEach(function (listener) {
try {listener(this);} catch (ex) {}
}, this);
this.draw();
},
init: function () {
this.canvas.addEventListener(
"mousemove", this.onHover.bind(this), false);
this.canvas.addEventListener(
"mouseout", this.onOut.bind(this), false);
this.canvas.addEventListener(
"mouseup", this.onPress.bind(this), false);
this.newGame();
return this;
},
draw: function () {
var c2d = this.c2d;
c2d.clearRect(0, 0, this.canvas.width, this.canvas.height);
c2d.save();
c2d.translate(0, this.canvas.height);
c2d.scale(this.scale, -this.scale);
this.board.forEach(this.drawGem.bind(this));
c2d.restore();
if (this.board.isGameClear()) {
this.drawMessage("Conguraturation!");
} else if (this.board.isGameOver()) {
this.drawMessage("Game Over");
}
},
drawGem: function (cell) {
var c2d = this.c2d;
c2d.save();
c2d.translate(cell.x, cell.y);
c2d.beginPath();
c2d.arc(0.5, 0.5, 0.5, 0, Math.PI * 2, false);
if (this.selected.has(cell.x, cell.y)) {
c2d.lineWidth = 1.0 / this.scale;
c2d.strokeStyle = this.colors[cell.gem.color];
c2d.stroke();
} else {
c2d.fillStyle = this.colors[cell.gem.color];
c2d.fill();
}
c2d.restore();
},
drawMessage: function (text) {
var c2d = this.c2d;
c2d.textAlign = "center";
c2d.textBaseline = "middle";
c2d.font = "50px 'sans-serif'";
c2d.fillText(text, this.canvas.width / 2, this.canvas.height / 2);
},
newBoard: function () {
var width = 0|(this.canvas.width / this.scale);
var height = 0|(this.canvas.height / this.scale);
return SameGame.newRandomBoard({
colors: this.colors.length, selectedMin: this.leastSelected,
width: width, height: height,
});
},
selectedBy: function (ev) {
var rect = ev.target.getBoundingClientRect();
var offX = ev.clientX - rect.left;
var offY = ev.clientY - rect.top;
var x = 0|(offX / this.scale);
var y = 0|((this.canvas.height - offY) / this.scale);
return this.board.select(x, y);
},
onPress: function (ev) {
this.selected = this.selectedBy(ev);
var isInvalid = this.selected.count() === 0;
if (isInvalid) return;
this.board = this.board.remove(this.selected);
this.log.push(this.selected);
this.selected = this.board.noSelect();
this.updated();
this.board = this.board.shrink();
this.selected = this.selectedBy(ev);
this.updated();
},
onHover: function (ev) {
this.selected = this.selectedBy(ev);
this.updated();
},
onOut: function (ev) {
this.selected = this.board.noSelect();
this.updated();
},
});
return {
newView: SimpleView,
};
})();
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1" />
<title>es5samegame by canvas 2d</title>
<meta name="viewport" content="width=640" />
<script src="https://gist.github.com/raw/1000718/es5compat-gs.js"
type="text/javascript"></script>
<script src="samegame-rules.js"></script>
<script src="samegame-canvas2d-ui.js"></script>
<script>//<!--
"use strict";
window.addEventListener("load", function (ev) {
var view = SameGame2D.newView(document.getElementById("samegame"));
var colors = document.getElementById("colors");
var scale = document.getElementById("scale");
var score = document.getElementById("score");
var leastSelected = document.getElementById("leastSelected");
var newGame = function () {
try {view.colors = JSON.parse(colors.value);} catch (ex) {}
view.scale = 0|scale.value;
view.leastSelected = 0|leastSelected.value;
view.newGame();
};
view.addListener(function () {
score.textContent = view.score();
});
document.getElementById("undo").addEventListener(
"click", view.undo.bind(view), false);
document.getElementById("newgame").addEventListener(
"click", newGame, false);
newGame();
}, false);
//--></script>
</head>
<body>
<div>
<canvas id="samegame" width="600" height="300"
style="-webkit-user-select: none;
-webkit-touch-callout: none;"></canvas>
</div>
<div>
Score: <span id="score"></span>
<button id="undo">undo</button>
<button id="newgame">new game</button>
<button onclick='
var setting = document.getElementById("setting");
setting.style.display = setting.style.display === "none" ? "block" : "none";
'>setting</button>
</div>
<div id="setting" style="display: none;">
<label>colors:
<input id="colors" type="text" value='["red","green","blue"]'/>
</label>
<label>scale:
<input id="scale" type="range" min="30" max="150" step="30" value="60" />
</label>
<label>min gems:
<input id="leastSelected" type="number" min="1" step="1" value="2"/>
</label>
</div>
</body>
</html>
"use strict";
this.SameGameCss = (function () {
var SimpleView = function SimpleView(root, message) {
return Object.seal(Object.create(SimpleView.prototype, {
root: {value: root},
message: {value: message},
leastSelected: {value: 1, writable: true},
scale: {value: 80, writable: true},
colors: {value: ["red", "green", "blue"], writable: true},
board: {value: null, writable: true},
selected: {value: null, writable: true},
log: {value: [], writable: true},
listeners: {value: [], writable: true},
}).init());
};
SimpleView.prototype = Object.freeze({
score: function () {
return this.log.reduce(function (sum, selected) {
return sum + selected.count() * selected.count();
}, 0) * 100;
},
newGame: function () {
this.board = this.newBoard();
this.selected = this.board.noSelect();
this.log = [];
this.newPanel();
this.updated();
},
undo: function () {
if (this.board.prev) {
this.board = this.board.prev.prev;
this.selected = this.board.noSelect();
this.log.pop();
this.updatePanel();
this.updated();
}
return this;
},
addListener: function (listener) {
this.listeners.push(listener);
return this;
},
removeListener: function (listener) {
this.listeners = this.listeners.filter(function (registered) {
return listener !== registered;
});
return this;
},
updated: function () {
this.listeners.forEach(function (listener) {
try {listener(this);} catch (ex) {}
}, this);
},
init: function () {
this.newGame();
return this;
},
newPanel: function () {
while (this.root.firstChild) {
this.root.removeChild(this.root.firstChild);
}
this.board.forEach(function (cell) {
var div = document.createElement("div");
div.style.display = "block";
div.style.position = "absolute";
div.style.left = (cell.x * this.scale) + "px";
div.style.top = (
this.root.clientHeight - (cell.y + 1) * this.scale) + "px";
div.style.width = div.style.height = this.scale + "px";
div.style.backgroundColor = this.colors[cell.gem.color];
div.style.borderRadius = (this.scale / 2) + "px";
div.style.transition = div.style.MozTransition =
div.style.WebkitTransition = div.style.OTransition =
div.style.MsTransition =
"all 0.3s ease-in-out";
div.gem = cell.gem;
div.addEventListener(
"mouseover", this.onHover.bind(this), false);
div.addEventListener(
"mouseout", this.onOut.bind(this), false);
div.addEventListener(
"click", this.onPress.bind(this), false);
this.root.appendChild(div);
}, this);
this.message.style.display = "none";
},
updatePanel: function () {
var divs = Array.prototype.slice.call(this.root.childNodes);
divs.forEach(function (div) {
var pos = this.board.getPos(div.gem);
if (pos === null) {
div.style.display = "none";
return;
}
div.style.display = "block";
div.style.left = (pos.x * this.scale) + "px";
div.style.top =
(this.root.clientHeight - (pos.y + 1) * this.scale) + "px";
var gemColor = this.colors[div.gem.color];
if (this.selected.has(pos.x, pos.y)) {
div.style.backgroundColor = "transparent";
div.style.border = "1px solid " + gemColor;
} else {
div.style.backgroundColor = gemColor;
div.style.border = "none";
}
div.style.borderRadius = (this.scale / 2) + "px";
}, this);
if (this.board.isGameClear()) {
this.drawMessage("Conguraturation!");
} else if (this.board.isGameOver()) {
this.drawMessage("Game Over");
} else {
this.message.style.display = "none";
}
},
drawMessage: function (text) {
this.message.textContent = text;
this.message.style.display = "block";
},
newBoard: function () {
var width = 0|(this.root.clientWidth / this.scale);
var height = 0|(this.root.clientHeight / this.scale);
return SameGame.newRandomBoard({
colors: this.colors.length, selectedMin: this.leastSelected,
width: width, height: height,
});
},
selectedBy: function (ev) {
var gem = ev.target.gem;
var pos = this.board.getPos(gem);
return this.board.select(pos.x, pos.y);
},
onPress: function (ev) {
this.selected = this.selectedBy(ev);
var isInvalid = this.selected.count() === 0;
if (isInvalid) return;
this.board = this.board.remove(this.selected);
this.log.push(this.selected);
this.selected = this.board.noSelect();
this.updatePanel();
this.board = this.board.shrink();
this.updatePanel();
this.updated();
},
onHover: function (ev) {
this.selected = this.selectedBy(ev);
this.updatePanel();
},
onOut: function (ev) {
this.selected = this.board.noSelect();
this.updatePanel();
},
});
return {
newView: SimpleView
};
})();
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1" />
<title>es5samegame by css3</title>
<meta name="viewport" content="width=640" />
<script src="https://gist.github.com/raw/1000718/es5compat-gs.js"
type="text/javascript"></script>
<script src="samegame-rules.js"></script>
<script src="samegame-css3-ui.js"></script>
<script>//<!--
"use strict";
window.addEventListener("load", function (ev) {
var view = SameGameCss.newView(
document.getElementById("samegame"),
document.getElementById("message"));
var colors = document.getElementById("colors");
var scale = document.getElementById("scale");
var score = document.getElementById("score");
var leastSelected = document.getElementById("leastSelected");
var newGame = function () {
try {view.colors = JSON.parse(colors.value);} catch (ex) {}
view.scale = 0|scale.value;
view.leastSelected = 0|leastSelected.value;
view.newGame();
};
view.addListener(function () {
score.textContent = view.score();
});
document.getElementById("undo").addEventListener(
"click", view.undo.bind(view), false);
document.getElementById("newgame").addEventListener(
"click", newGame, false);
newGame();
}, false);
//--></script>
</head>
<body>
<div style="position: relative;">
<div id="samegame" style="width: 600px; height: 300px;"></div>
<div style="position: absolute; left: 0px; top: 0px; width:600px;"
><p id="message"
style="line-height: 400px;
text-align: center; vertical-align: middle;
font: 50px 'sans-serif';"
></p></div>
</div>
<div>
Score: <span id="score"></span>
<button id="undo">undo</button>
<button id="newgame">new game</button>
<button onclick='
var setting = document.getElementById("setting");
setting.style.display = setting.style.display === "none" ? "block" : "none";
'>setting</button>
</div>
<div id="setting" style="display: none;">
<label>colors:
<input id="colors" type="text" value='["red","green","blue"]'/>
</label>
<label>scale:
<input id="scale" type="range" min="30" max="150" step="30" value="60" />
</label>
<label>min gems:
<input id="leastSelected" type="number" min="1" step="1" value="2"/>
</label>
</div>
</body>
</html>
"use strict";
this.SameGame = (function () {
var range = function (n) {
var a = [];
for (var i = 0; i < n; i += 1) {
a.push(n);
}
return Object.freeze(a);
};
var Selected = function Selected(board, x, y) {
return Object.freeze(Object.create(Selected.prototype, {
points: {value: []},
board: {value: board},
x: {value: x},
y: {value: y},
}).init());
};
Selected.prototype = Object.freeze({
count: function () {
return this.points.length;
},
forEach: function (callback) {
var thisp = arguments[1];
this.points.forEach(function (point) {
var xy = point.split(",");
var gem = this.board.getGem(xy[0], xy[1]);
callback.call(thisp, {gem: gem, x: xy[0], y: xy[1]}, this);
}, this);
},
has: function (x, y) {
var point = x + "," + y;
return this.points.indexOf(point) >= 0;
},
init: function () {
var gem = this.board.getGem(this.x, this.y);
if (gem !== null) this.progress(gem.color, this.x, this.y);
Object.freeze(this.points);
return this;
},
progress: function (color, x, y) {
var gem = this.board.getGem(x, y);
if (gem === null || gem.color !== color) return;
if (this.has(x, y)) return;
this.points.push(x + "," + y);
this.progress(color, x - 1, y);
this.progress(color, x + 1, y);
this.progress(color, x, y - 1);
this.progress(color, x, y + 1);
},
});
var Board = function Board(args) {
//args: {table: [], colors: 1, selectedMin: 1,
// width: 0, height: 0, prev: undefined,}
return Object.freeze(Object.create(Board.prototype, {
table: {value: args.table || Object.freeze([])},
colors: {value: args.colors || 1},
selectedMin: {value: args.selectedMin || 1},
width: {value: args.width || 0},
height: {value: args.height || 0},
prev: {value: args.prev},
}));
};
Board.prototype = Object.freeze({
getGem: function (x, y) {
if (typeof x !== "number" || typeof y !== "number") return null;
if (x < 0 || this.table.length <= x) return null;
if (y < 0 || this.table[x].length <= y) return null;
return this.table[x][y];
},
getPos: function (gem) {
var pos = null;
this.forEach(function (cell) {
if (cell.gem === gem) pos = {x: cell.x, y: cell.y};
});
return pos;
},
forEach: function (callback) {
var thisp = arguments[1];
this.table.forEach(function (col, x) {
col.forEach(function (gem, y) {
if (gem !== null) {
callback.call(thisp, {gem: gem, x: x, y: y}, this);
}
}, this);
}, this);
},
isGameClear: function () {
return this.table.every(function (col) {
return col.every(function (gem) {
return gem === null;
});
});
},
isGameOver: function () {
return !this.table.some(function (col, x) {
return col.some(function (gem, y) {
return this.directSelect(x, y).count() >= this.selectedMin;
}, this);
}, this);
},
select: function (x, y) {
var selected = this.directSelect(x, y);
if (selected.count() >= this.selectedMin) {
return selected;
} else {
return this.noSelect();
}
},
noSelect: function () {return Selected(this);},
directSelect: function (x, y) {return Selected(this, x, y);},
remove: function (selected) {
if (selected.board !== this) return this;
var table = Object.freeze(this.table.map(function(col, x) {
return Object.freeze(col.map(function (gem, y) {
return selected.has(x, y) ? null : gem;
}));
}));
return Board({
table: table, colors: this.colors,
selectedMin: this.selectedMin,
width: this.width, height: this.height,
prev: this});
},
shrink: function () {
var colsShrinked = this.table.map(function (col) {
return Object.freeze(
col.filter(function (gem) {return gem !== null;}));
});
var table = Object.freeze(colsShrinked.filter(function (col) {
return col.length !== 0;
}));
return Board({
table: table, colors: this.colors,
selectedMin: this.selectedMin,
width: this.width, height: this.height,
prev: this});
},
});
var newRandomBoard = function (args) {
//args: {colors, width, height, selectedMin}
var table = Object.freeze(range(args.width).map(function (x) {
return Object.freeze(range(args.height).map(function (y) {
var color = ~~(Math.random() * args.colors);
return Object.defineProperties({}, {
color: {value: color, enumerable: true}});
}));
}));
return Board({
table: table, colors: args.colors, selectedMin: args.selectedMin,
width: args.width, height: args.height,
});
};
return {
newRandomBoard: newRandomBoard,
};
})();
@bellbind
Copy link
Author

bellbind commented Feb 3, 2012

@bellbind
Copy link
Author

bellbind commented Feb 3, 2012

Three.js/WebGL version: gist:1715047

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment