Created
May 20, 2016 11:29
-
-
Save dolpen/856db559e066956bc2be54a947597725 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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