Skip to content

Instantly share code, notes, and snippets.

@dolpen
Created May 20, 2016 11:29
Show Gist options
  • Select an option

  • Save dolpen/856db559e066956bc2be54a947597725 to your computer and use it in GitHub Desktop.

Select an option

Save dolpen/856db559e066956bc2be54a947597725 to your computer and use it in GitHub Desktop.
// Windowsの4色窓合わせゲーム
// 探索範囲の行と列に、連続した4セルに4色が1個ずつあれば消える
(function (d, w) {
var config = {
game: {
depth: 4,
mask: 0x0f,
states: {MENU: 0, MAIN: 1, PAUSE: 2, OVER: 3},
keys: {
DOWN: 40,
LEFT: 37,
RIGHT: 39,
ROTATE_L: 90, // Z,
ROTATE_R: 88, // X
PAUSE: 67 //c
}
},
field: {
width: 8, height: 20,
states: {NEXT: 0, CONTROL: 1, LAND: 2, SEARCH: 3, WIPE: 4}
},
block: {
width: 11, height: 11, margin: 1,
colors: ['#ff0000', '#00ff00', '#0000ff', '#ffff00'],
states: {NEXT: 0, CONTROL: 1, LAND: 2, WIPE: 3}
},
shape: {
width: 3, height: 3,
states: {NEXT: 0, CONTROL: 1, LAND: 2}
}
};
// 盤面オブジェクト
var Block = function () {
// 色インデックス
var c = config.block;
this.color = Math.floor(Math.random() * c.colors.length);
this.state = c.states.NEXT;
};
Block.prototype = {
getIndexBit: function () {
return 0x01 << this.color;
},
getColor: function () {
return config.block.colors[this.color];
},
put: function () {
if (this.state == config.block.states.NEXT) {
this.state = config.block.states.CONTROL;
} else {
console.error("invalid block#put call");
}
return this;
},
land: function () {
if (this.state == config.block.states.CONTROL) {
this.state = config.block.states.LAND;
} else {
console.error("invalid block#view call");
}
return this;
},
wipe: function () {
if (this.state == config.block.states.WIPE) {
return this;
} else if (this.state == config.block.states.LAND) {
this.state = config.block.states.WIPE;
} else {
console.error("invalid block#wipe call");
}
return this;
},
isWiped: function () {
return this.state == config.block.states.WIPE;
},
clone: function () {
var ret = new Block();
ret.color = this.color;
ret.state = this.state;
return ret;
}
};
// next,controlled
var Shape = function () {
this.w = 3;
this.h = 3;
this.x = 2;
this.y = 0;
this.blocks = [[null, null, null], [null, null, null], [null, null, null]];
this.blocks[0][1] = new Block();
this.blocks[1][1] = new Block();
this.state = config.block.states.NEXT;
};
Shape.prototype = {
clone: function () {
if (this.state != config.block.states.CONTROL) {
console.error("invalid shape#clone call");
return null;
}
var ret = new Shape();
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
ret.blocks[i][j] =
this.blocks[i][j] == null
? null
: this.blocks[i][j].clone();
}
}
ret.x = this.x;
ret.y = this.y;
ret.state = this.state;
return ret;
},
rotateLeft: function () {
if (this.state != config.block.states.CONTROL) {
console.error("invalid shape#rotateLeft call");
return;
}
var nb = [[null, null, null], [null, null, null], [null, null, null]];
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
nb[-j + 2][i] = this.blocks[i][j];
}
}
this.blocks = nb;
},
rotateRight: function () {
if (this.state != config.block.states.CONTROL) {
console.error("invalid shape#rotateRight call");
return;
}
var nb = [[null, null, null], [null, null, null], [null, null, null]];
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
nb[i][j] = this.blocks[-j + 2][i];
}
}
this.blocks = nb;
},
put: function () {
if (this.state != config.block.states.NEXT) {
console.error("invalid shape#put call");
return;
}
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
if (this.blocks[i][j] != null)this.blocks[i][j].put();
}
}
this.state = config.block.states.CONTROL;
return this;
}
};
var Cell = function () {
this.block = null;
// ui
this._rect = null;
};
Cell.prototype = {
isPresent: function () {
return this.block !== null;
},
ifBlockPresent: function (f) {
if (this.isPresent())f(this.block);
return this;
},
setBlock: function (b) {
this.block = b;
},
pickBlock: function () {
var ret = this.block;
this.block = null;
return ret;
},
init: function (svg, x, y) {
var fw = (config.block.width + config.block.margin * 2);
var fh = (config.block.height + config.block.margin * 2);
var fx = fw * x;
var fy = fh * y;
var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
rect.setAttributeNS(null, 'x', fx);
rect.setAttributeNS(null, 'y', fy);
rect.setAttributeNS(null, 'width', fw);
rect.setAttributeNS(null, 'height', fh);
rect.setAttributeNS(null, 'stroke', '#000000');
rect.setAttributeNS(null, 'stroke-width', config.block.margin);
rect.setAttributeNS(null, 'fill', this.isPresent() ? this.block.getColor() : '#ffffff');
svg.appendChild(rect);
this._rect = rect;
},
render: function () {
this._rect.setAttributeNS(null, 'stroke', this.isPresent() && this.block.isWiped() ? '#ff0000' : '#000000');
this._rect.setAttributeNS(null, 'fill', this.isPresent() ? this.block.getColor() : '#ffffff');
}
};
var Field = function () {
var c = config.field;
this.w = c.width;
this.h = c.height;
this.next = new Shape(); // shape
this.shape = null; // shape
this.land = []; // block[][]
this.view = []; // cell[][]
this.state = config.field.states.NEXT;
};
Field.prototype = {
init: function (svg) {
this.view = (function (w, h) {
var r = [];
for (var i = 0; i < h; i++)r.push((function (w, y) {
var r = [];
for (var i = 0; i < w; i++)r.push((function (x, y) {
var c = new Cell();
c.init(svg, x, y);
return c;
})(i, y));
return r;
})(w, i));
return r;
})(this.w, this.h);
this.nextView = (function (w, h, fw, fh) {
var r = [];
for (var i = 0; i < h; i++)r.push((function (w, y, fw, fh) {
var r = [];
for (var i = 0; i < w; i++)r.push((function (x, y, fw, fh) {
var c = new Cell();
c.init(svg, x + fw + 1, y);
return c;
})(i, y, fw, fh));
return r;
})(w, i, fw, fh));
return r;
})(config.shape.width, config.shape.height, this.w, this.h);
this.land = (function (w, h) {
var r = [];
for (var i = 0; i < h; i++)r.push((function (w, y) {
var r = [];
for (var i = 0; i < w; i++)r.push(null);
return r;
})(w, i));
return r;
})(this.w, this.h);
},
reset: function () {
this.land = (function (w, h) {
var r = [];
for (var i = 0; i < h; i++)r.push((function (w, y) {
var r = [];
for (var i = 0; i < w; i++)r.push(null);
return r;
})(w, i));
return r;
})(this.w, this.h);
},
_canPutShape: function (shape) {
for (var i = 0; i < shape.h; i++) {
for (var j = 0; j < shape.w; j++) {
if (shape.y + i < 0
|| shape.y + i >= this.h
|| shape.x + j < 0
|| shape.x + j >= this.w) {
if (shape.blocks[i][j] != null) {
return false;
}
} else {
if (shape.blocks[i][j] != null && this.land[shape.y + i][shape.x + j] != null) {
return false;
}
}
}
}
return true;
},
_landShape: function () {
for (var i = 0; i < this.shape.h; i++) {
for (var j = 0; j < this.shape.w; j++) {
if (this.shape.y + i < 0
|| this.shape.y + i >= this.h
|| this.shape.x + j < 0
|| this.shape.x + j >= this.w) continue;
if (this.shape.blocks[i][j] != null) {
this.land[this.shape.y + i][this.shape.x + j] = this.shape.blocks[i][j];
this.land[this.shape.y + i][this.shape.x + j].land();
}
}
}
this.shape = null;
},
key: function (kc) {
if (this.state != config.field.states.CONTROL)return;
var cshape = this.shape.clone();
switch (kc) {
case config.game.keys.DOWN :
cshape.y++;
break;
case config.game.keys.LEFT :
cshape.x--;
break;
case config.game.keys.RIGHT :
cshape.x++;
break;
case config.game.keys.ROTATE_L :
cshape.rotateLeft();
break;
case config.game.keys.ROTATE_R :
cshape.rotateRight();
break;
default:
return;
}
if (!this._canPutShape(cshape)) {
return;
}
this.shape = cshape;
this.render();
},
commit: function () {
switch (this.state) {
case config.field.states.NEXT:
this.shape = this.next.put();
this.next = new Shape();
if (!this._canPutShape(this.shape)) {
return config.game.states.OVER;
}
this.state = config.field.states.CONTROL;
break;
case config.field.states.CONTROL:
var cshape = this.shape.clone();
cshape.y++;
if (this._canPutShape(cshape)) { // 接地
this.shape = cshape;
} else {
this._landShape();
this.shape = null;
this.state = config.field.states.LAND;
}
break;
case config.field.states.LAND:
// fall landing block
for (var j = 0; j < this.w; j++) {
var top = this.h; // 列の最上位ブロック
for (var i = this.h - 1; i >= 0; i--) {
if (this.land[i][j] == null)continue;
if (this.land[i][j].state != config.block.states.LAND) {
console.error("invalid state : " + this.land[i][j].state);
}
if (top - 1 > i && this.land[top - 1][j] == null) {
this.land[top - 1][j] = this.land[i][j];
this.land[i][j] = null;
}
top--;
}
}
this.state = config.field.states.SEARCH;
break;
case config.field.states.SEARCH:
var wiped = false;
for (var i = 0; i < this.h; i++) {
for (var j = 0; j <= this.w - config.game.depth; j++) {
// 下向き
var mask = 0;
for (var k = 0; k < config.game.depth; k++) {
if (this.land[i][j + k] == null)break;
mask |= this.land[i][j + k].getIndexBit();
}
if (mask == config.game.mask) {
wiped = true;
for (var k = 0; k < config.game.depth; k++) {
this.land[i][j + k].wipe();
}
}
}
}
for (var i = 0; i <= this.h - config.game.depth; i++) {
for (var j = 0; j < this.w; j++) {
// 右向き
var mask = 0;
for (var k = 0; k < config.game.depth; k++) {
if (this.land[i + k][j] == null)break;
mask |= this.land[i + k][j].getIndexBit();
}
if (mask == config.game.mask) {
wiped = true;
for (var k = 0; k < config.game.depth; k++) {
this.land[i + k][j].wipe();
}
}
}
}
this.state = wiped
? config.field.states.WIPE
: config.field.states.NEXT;
break;
case config.field.states.WIPE:
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
if (this.land[i][j] == null)continue;
if (this.land[i][j].isWiped()) {
this.land[i][j] = null;
}
}
}
this.state = config.field.states.LAND;
break;
}
return config.game.states.MAIN;
},
render: function () {
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
this.view[i][j].setBlock(this.land[i][j]);
}
}
if (this.shape != null) {
for (var i = 0; i < this.shape.h; i++) {
if (this.shape.y + i < 0 || this.shape.y + i >= this.h)continue;
for (var j = 0; j < this.shape.w; j++) {
if (this.shape.x + j < 0 || this.shape.x + j >= this.w)continue;
if (this.shape.blocks[i][j] == null)continue;
this.view[this.shape.y + i][this.shape.x + j].setBlock(this.shape.blocks[i][j]);
}
}
}
for (var i = 0; i < this.next.h; i++) {
for (var j = 0; j < this.next.w; j++) {
this.nextView[i][j].setBlock(this.next.blocks[i][j]);
}
}
for (var i = 0; i < this.h; i++) {
for (var j = 0; j < this.w; j++) {
this.view[i][j].render();
}
}
for (var i = 0; i < this.next.h; i++) {
for (var j = 0; j < this.next.w; j++) {
this.nextView[i][j].render();
}
}
}
};
var Game = function (svg) {
this.svg = svg;
this.state = config.game.states.MENU;
this.field = new Field();
};
Game.prototype = {
init: function () {
this.field.init(this.svg);
},
key: function (kc) {
switch (this.state) {
case config.game.states.MENU:
if (kc == config.game.keys.PAUSE) {
this.field.reset(kc);
this.state = config.game.states.MAIN;
}
break;
case config.game.states.MAIN:
if (kc == config.game.keys.PAUSE) {
this.state = config.game.states.PAUSE;
} else {
this.field.key(kc);
}
break;
case config.game.states.PAUSE:
if (kc == config.game.keys.PAUSE) {
this.state = config.game.states.MAIN;
}
break;
case config.game.states.OVER:
this.state = config.game.states.MENU;
break;
}
},
commit: function () {
switch (this.state) {
case config.game.states.MENU:
break;
case config.game.states.MAIN:
this.state = this.field.commit();
break;
case config.game.states.OVER:
break;
}
},
render: function () {
switch (this.state) {
case config.game.states.MENU:
break;
case config.game.states.MAIN:
this.field.render(this.svg);
break;
case config.game.states.OVER:
break;
}
}
};
var bootstrap = function () {
var game = new Game(
d.getElementById('app')
);
game.init();
var commit = function () {
game.commit();
game.render();
setTimeout(commit, 300);
};
game.commit();
game.render();
setTimeout(commit, 300);
d.addEventListener("keydown", function (ev) {
game.key(ev.keyCode);
});
w.game = game;
};
if (d.readyState !== "loading") {
bootstrap();
} else {
d.addEventListener("DOMContentLoaded", bootstrap);
}
})(document, window);
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
</head>
<body>
<h1>クソゲー</h1>
<svg id="app" xmlns="http://www.w3.org/2000/svg" width="600" height="600" version="1.1" viewBox="0 0 300 300">
</svg>
<div>
<h2>ルール</h2>
<p>全て異なる色のセルが、縦か横に4つ連続で並ぶと消えます</p>
<ul>
<li>c : ゲームスタート / ポーズ</li>
<li>z : 左回転</li>
<li>x : 右回転</li>
<li>方向キー : 移動</li>
</ul>
</div>
<script src="game.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment