Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="main">
<div id="left-information">
<table>
<tr><td>[←]</td><td>左へ移動</td></tr>
<tr><td>[→]</td><td>右へ移動</td></tr>
<tr><td>[Z][Ctrl]</td><td>左回転</td></tr>
<tr><td>[X][↑]</td><td>右回転</td></tr>
<tr><td>[↓]</td><td>下へ移動</td></tr>
<tr><td>[Space]</td><td>下へ2つ移動</td></tr>
<tr><td>[ESC]</td><td>Pause</td></tr>
</table>
</div>
<canvas id="field"></canvas>
<div id="right-information">
<div>Score:<span id="score"></span></div>
<div>Level:<span id="level"></span></div>
<div>Lines:<span id="lines"></span></div>
<div>Status:<span id="status"></span></div>
<div><button id="startButton" onclick="OnButtonClick();">Start</button>
<div>
</div>
</div>
              
            
!

CSS

              
                * {
	margin: 0;
	padding: 0;
	border: 0;
}
html {
	background: #ddd;
}
#main {
	margin: 10px auto;
	width: 712px; /* 200 + 312 + 200 */
	display: flex;
	flex-wrap: wrap;
	background: #ccc;
	color: #888;
}
#left-information, #right-information {
	width: 200px;
	box-sizing: border-box;
	padding: 10px;
	display: block;
	background: #ccc;
}
#field {
	width: 312px; /* 24 * (11+2) */
	height: 528px; /* 24 * (21+1) */
	background: #eee;
}
button {
	padding: 10px;
	background: #ddd;
	color: #888;
}
              
            
!

JS

              
                class Field {
	constructor(unit, rows, columns) { // 初回のみの初期化
		this.unit = unit; // cellの縦横サイズ
		this.rows = rows; // 行数
		this.columns = columns; // 列数
		this.field = document.getElementById("field");
		this.field.width = (columns + 2)* unit; // 両端は壁
		this.field.height = (rows + 1)*unit; // 底は壁
		this.context = field.getContext("2d");

		// Color
		this.MovableBlockColor = "#eee";
		// css: field #eee 
		this.EmptyCellColor = "#ddd";
		// css: html,button #ddd
		this.BackgroundColor = "#ccc";
		// css: information #ccc
		this.LockCellColor = "#bbb";
		this.WallColor = "#aaa";
		this.EffectHighColor = "#ddd";
		this.EffectLowColor = "#bbb";
		// css: color(文字色) #888

		this.DrawBackground();
		this.DrawWall();
		this.Clear();
	}
	Clear() { // 初期化とリトライ時の初期化
		this.lockCells = [];
		this.DrawEmptyField();
		this.GameOver = false;
		this.ClearLinesCount = 0;
		this.clearLines = [];
	}
	DrawCell(x, y) { // 1マス描画
		this.context.fillRect(x * this.unit, y * this.unit, this.unit - 1, this.unit - 1);
	}
	DrawBackground() { // 背景描画
		this.context.fillStyle = this.BackgroundColor;
		this.context.fillRect(0, 0, field.width, field.height);
	}
	DrawEmptyField() { // 空描画
		this.context.fillStyle = this.EmptyCellColor;
		for (let row = 0; row < this.rows; ++row) {
			for (let column = 1; column <= this.columns; ++column) {
				this.DrawCell(column, row);
			}
		}
	}
	DrawWall() { // 壁描画
		this.context.fillStyle = this.WallColor;
		for (let column = 0; column < this.columns+2; ++column)
		{
			this.DrawCell(column, this.rows);
		}
		for (let row = 0; row < this.rows; ++row)
		{
			this.DrawCell(0, row);
			this.DrawCell(this.columns + 2 - 1, row);
		}
	}
	DrawBlock(x, y, block) { // ブロック描画
		block.cells.forEach((cell) => {
			this.DrawCell(x + cell[0], y + cell[1]);
		});
	}
	DrawMovableBlock(x, y, block) { // 移動ブロック描画
		this.context.fillStyle = this.MovableBlockColor;
		this.DrawBlock(x, y, block);
	}
	DrawLockCells() { // 固定セル描画
		this.context.fillStyle = this.LockCellColor ;
		this.lockCells.forEach((cell) => {
			this.DrawCell(cell[0], cell[1]);
		});
	}
	ClearBlock(x, y, block) { // 移動前のブロック描画クリア
		this.context.fillStyle = this.EmptyCellColor;
		this.DrawBlock(x, y, block);
	}
	CheckBlock(x, y, block) { // 移動可能か?不可の場合trueを返す。
		return block.cells.some((cell) => {
			if (y + cell[1] >= this.rows) { // 壁
				return true;
			}
			if (x + cell[0] < 1 || x + cell[0] > this.columns) { // 壁
				return true;
			}
			let lock = this.lockCells.some((lockCell) => {
				if (x + cell[0] == lockCell[0] && y + cell[1] == lockCell[1]) { // 固定セル
					return true;
				}
			});
			if (lock) {
				return true;
			}
		});
	}
	LockBlock(x, y, block) { // 移動終了のためブロックを固定
		this.context.fillStyle = this.LockCellColor;
		this.DrawBlock(x, y, block);
		block.cells.forEach((cell) => {
			if (y+cell[1] < 0) {
				this.GameOver = true;
			}
			this.lockCells.push([x+cell[0], y+cell[1]]);
		});
		return false;
	}
	CheckLine() { // 行がそろっているか?
		let line = {};
		for (let row = 0; row < this.rows; ++row) {
			line[row] = 0;
		}
		this.lockCells.forEach((cell) => {
			line[cell[1]] = line[cell[1]] + 1;
		});
		let clearLines = 0;
		let lines = [];
		for (let row = 0; row < this.rows; ++row) {
			if (line[row] == this.columns) {
				++clearLines;
				lines.push(row);
			}
		}
		this.ClearLinesCount = clearLines;
		this.clearLines = lines;
	}
	ClearLineEffect(flag) { // 行がそろっているときの描画
		this.context.fillStyle = flag ? this.EffectHighColor : this.EffectLowColor;
		this.clearLines.forEach((row) => {
			for (let column = 1; column <= this.columns; ++column) {
				this.DrawCell(column, row);
			}
		});
	}
	DeleteLines() { // 行の削除と移動
		this.clearLines.forEach((row) => {
			let nextCells = [];
			this.lockCells.forEach((cell) => {
				if (cell[1] == row) {
					// delete
				} else if (cell[1] < row) {
					nextCells.push([cell[0],cell[1]+1]);
				} else {
					nextCells.push(cell);
				}

			});
			this.lockCells = nextCells;
		});
		this.DrawEmptyField();
		this.DrawLockCells();
		this.ClearLinesCount = 0;
		this.clearLines = [];
	}
}

class Block { // ブロック
	constructor() {
		this.cells = []; // x:cell[0] y:cell[1]
	}
	Initialize()
	{
		let x = 0;
		let y = 0;
		let cells = [[x,y]];
		let tryCount = 6;
		for (let i = 0; i < tryCount && cells.length != 4; ++i) // 1ブロックは2~4セル以下で構成
		{
			let cell = this.CreateCell(x,y);
			x = cell[0];
			y = cell[1];
			let exist = false;
			for (let j = 0; j < cells.length; ++j)
			{
				if (cells[j][0] == cell[0] && cells[j][1] == cell[1]) {
					exist = true;
					break;
				}
			}
			if (exist == false) {
				cells.push(cell);
			}
		}
		this.cells = cells;
	}
	CreateCell(x, y) { // ブロックのセルを生成
		let direction = Math.floor(Math.random() * 4);
		switch (direction) {
		case 0: return [x,y-1];
		case 1: return [x+1,y];
		case 2: return [x,y+1];
		case 3: return [x-1,y];
		}
	}
}
class BlockFactory { // ブロック生成
	CreateBlock()
	{
		let block = new Block();
		block.Initialize();
		return block;
	}
	CreateEmptyBlock()
	{
		return new Block();
	}
}
class Game {
	constructor() {
		this.field = new Field(24, 21, 11); // 1セル24ポイント、21列、11行
		this.blockFactory = new BlockFactory();
		window.onkeydown = (e) => this.onKeyDown(e);
		this.scoreElement = document.getElementById("score");
		this.levelElement = document.getElementById("level");
		this.statusElement = document.getElementById("status");
		this.linesElement = document.getElementById("lines");
		this.keyElement = document.getElementById("key");
		this.xElement = document.getElementById("x");
		this.yElement = document.getElementById("y");
		this.startButtonElement = document.getElementById("startButton");
	}
	Start() {
		this.CreateBlock();
		this.framePerSecond = 30;
		this.timer = setInterval(() => {this.Update()}, 1000/this.framePerSecond);
		this.frame = 1;
		this.speed = 10; // 少ない方が速い
		this.score = 0;
		this.level = 1;
		this.scoreElement.innerText = 0;
		this.levelElement.innerText = this.level;
		this.statusElement.innerText = "Game";
		this.linesElement.innerText = 0;
		this.levelUpedLinesCount = 0;
		if (this.key) { // 開発デバッグ用htmlでコメントアウトしている場合のためのチェック
			this.keyElement.innerText = "";
		}
		this.ClearLineEffectCountDown = 0;
		this.clearLineCount = 0;
		this.startButtonElement.style.display = "none"; // ゲーム中は[Start]ボタンを非表示
	}
	Restart() { // リスタート
		this.field.Clear(); // 前回の状態をクリア
		this.Start();
	}
	CreateBlock() {
		this.movableBlock = this.blockFactory.CreateBlock();
		let maxCell = this.movableBlock.cells.reduce((a,b) => a[1] > b[1] ? a : b);
		this.movableX = 6;
		this.movableY = -maxCell[1];
		this.field.DrawMovableBlock(this.movableX,this.movableY,this.movableBlock);
	}
	Stop() { // 定期的な描画をやめる
		clearInterval(this.timer);
		this.timer = 0;
	}
	Pause() { // 一時停止
		if (this.field.GameOver) {
			return;
		}
		if (this.timer != 0) {
			this.statusElement.innerText = "Pause";
			this.Stop();
		} else {
			this.statusElement.innerText = "Game";
			this.timer = setInterval(() => {this.Update()}, 1000/this.framePerSecond);
		}
	}
	GameOver() { // ゲームオーバー、[Start]ボタン表示
		this.statusElement.innerText = "Game Over";
		this.Stop();
		this.startButtonElement.style.display = "block";
	}
	Update() { // 定期的な描画
		if (this.ClearLineEffectCountDown  != 0) // そろった行があった場合の描画
		{
			this.ClearLineEffectCountDown  = this.ClearLineEffectCountDown  - 1;
			if (this.ClearLineEffectCountDown % 10 == 0) {
				this.field.ClearLineEffect(true);
			} else if (this.ClearLineEffectCountDown % 10 == 5) {
				this.field.ClearLineEffect(false);
			}
			if (this.ClearLineEffectCountDown == 0) {
				this.field.DeleteLines();
			}
			return;
		}
		if (this.x && this.y) { // 開発用
			xElement.innerText = this.movableX;
			yElement.innerText = this.movableY;
		}
		if (this.frame % this.speed == 0) {
			this.field.ClearBlock(this.movableX, this.movableY, this.movableBlock);
			if (this.field.CheckBlock(this.movableX, this.movableY+1, this.movableBlock)) {
				this.field.LockBlock(this.movableX, this.movableY, this.movableBlock);
				if (this.field.GameOver) {
					this.GameOver();
				} else {
					this.field.CheckLine();
					if (this.field.ClearLinesCount > 0) {
						this.ClearLineEffectCountDown = 30;
						let scoreBase =  0;
						switch (this.field.ClearLinesCount) {
						case 1: scoreBase = 100; break;
						case 2: scoreBase = 300; break;
						case 3: scoreBase = 500; break;
						case 4: scoreBase = 800; break;
						}
						this.score += scoreBase * this.level;
						this.scoreElement.innerText = this.score;
						this.clearLineCount += this.field.ClearLinesCount;
						this.linesElement.innerText = this.clearLineCount;
						if (this.clearLineCount >= this.levelUpedLinesCount + 10 && this.speed > 3) {
							++this.level;
							this.levelElement.innerText = this.level;
							this.levelUpedLinesCount = this.clearLineCount;
							--this.speed;
						}
					}
					this.CreateBlock();
				}
			} else {
				this.movableY = this.movableY+ 1;
				this.field.DrawMovableBlock(this.movableX, this.movableY, this.movableBlock);
			}
		}
		this.frame = this.frame + 1;
	}
	Move(x, y) { // キーによる左右下移動
		if (this.field.CheckBlock(x, y, this.movableBlock))
		{
			return;
		}
		this.field.ClearBlock(this.movableX, this.movableY, this.movableBlock)
		this.movableX = x;
		this.movableY = y;
		this.field.DrawMovableBlock(this.movableX, this.movableY, this.movableBlock);
	}
	Rotate(right) { // 回転
		let success = false;
		let block = new Block();
		let length = this.movableBlock.cells.length;
		for (let i = 0; i < this.movableBlock.cells.length; ++i) {
			let x = this.movableBlock.cells[i][0];
			let y = this.movableBlock.cells[i][1];
			let cells = [];
			for (let j = 0; j < this.movableBlock.cells.length; ++j) {
				if (right) {
					cells.push([ (this.movableBlock.cells[j][1]-y)+x, -(this.movableBlock.cells[j][0]-x)+y]);
				} else {
					cells.push([-(this.movableBlock.cells[j][1]-y)+x,  (this.movableBlock.cells[j][0]-x)+y]);
				}
			}
			block.cells = cells;
			if (!this.field.CheckBlock(this.movableX, this.movableY, block)) {
				this.field.ClearBlock(this.movableX, this.movableY, this.movableBlock);
				this.movableBlock.cells = block.cells;
				this.field.DrawMovableBlock(this.movableX, this.movableY, this.movableBlock);
				return;
			}
		}
	}
	onKeyDown(e) {
		let keyCode = e.keyCode;
		switch (e.keyCode) {
		case 37: keyCode = "[←]"; this.Move(this.movableX-1,this.movableY); break;
		case 39: keyCode = "[→]"; this.Move(this.movableX+1,this.movableY); break;
		case 40: keyCode = "[↓]"; this.Move(this.movableX,this.movableY+1); break;
		case 32: keyCode = "[Space]"; // ↓へ二つ
			this.Move(this.movableX,this.movableY+1); 
			this.Move(this.movableX,this.movableY+1); 
			break;
		case 27: keyCode = "[ESC]"; this.Pause(); break;
		case 90: keyCode = "[Z]"; // 左回り(時計の逆回り)
		case 17: keyCode = "[Ctrl]";
			this.Rotate(false);
			break;
		case 88: keyCode = "[X]"; // 右回り(時計回り)
		case 38: keyCode = "[↑]";
			this.Rotate(true);
			break;
		}
		if (this.key) {
			this.keyElement.innerText = keyCode;
		}
	}
}
var game = new Game();
var first = true;
function OnButtonClick() { // [Startzzzzzzzxxxxzzzxxxxxxx]ボタン時
	if (first) {
		game.Start();
		first = false;
	} else {
		game.Restart();
	}
}
              
            
!
999px

Console