テトリス

#tetris.html

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>テトリス</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/tetris.css">
<script type="text/javascript" src="js/tetris.js"></script>
</head>
<body>
<h1>テトリス</h1>
<div class="wrapper-container">
<span class="tetris-container">
<canvas id="stage" width="250px" height="500px" style="background-color:black;">
</canvas>
<span class="tetris-panel-container">
<p>Next:</p>
<canvas id="next" width="150px" height="150px" style="background-color:black;">
</canvas>
<p>LINES:<span id="lines">0</span></p>
<p><span id="message"></span></p>
<div class="tetris-panel-container-padding"></div>
<table class="tetris-button-panel">
<tr>
<td></td>
<td id="tetris-rotate-button" class="tetris-button">↻</td>
<td></td>
</tr>
<tr>
<td id="tetris-move-left-button"class="tetris-button">←</td>
<td id="tetris-fall-button"class="tetris-button">↓</td>
<td id="tetris-move-right-button"class="tetris-button">→</td>
</tr>
</table>
</span>
</span>
</div>
<script>
var tetris = new Tetris();
tetris.startGame();
</script>
</body>
</html>
```

#css/tetris.css

```css
html {
touch-action: manipulation;
}

.wrapper-container {
display: inline-block;
}


.tetris-container {
display: flex;

flex-direction: row;
margin: 10px;
background-color: #333333;
}

.tetris-panel-container {
display: flex;
padding-left: 10px;
padding-right: 10px;
flex-direction: column;
color: white;
background-color: #333333;
}

.tetris-panel-container-padding {
flex-grow: 1;
}

.tetris-panel-container p {
margin: 0;
padding: 0;
}

.tetris-button-panel {
border-style: none;
width: 100%;
}

.tetris-button {
padding-top: 10px;
padding-bottom: 10px;
text-align: center;
background: #444444;
 }
```
#js/tetris.js

```js
class Tetris {
constructor() {
this.stageWidth = 10;
this.stageHeight = 20;
this.stageCanvas = document.getElementById("stage");
this.nextCanvas = document.getElementById("next");
let cellWidth = this.stageCanvas.width / this.stageWidth;
let cellHeight = this.stageCanvas.height / this.stageHeight;
this.cellSize = cellWidth < cellHeight ? cellWidth : cellHeight;
this.stageLeftPadding = (this.stageCanvas.width - this.cellSize * this.stageWidth) / 2;
this.stageTopPadding = (this.stageCanvas.height - this.cellSize * this.stageHeight) / 2;
this.blocks = this.createBlocks();
this.deletedLines = 0;

window.onkeydown = (e) => {
if (e.keyCode === 37) {
this.moveLeft();
} else if (e.keyCode === 38) {
this.rotate();
} else if (e.keyCode === 39) {
this.moveRight();
} else if (e.keyCode === 40) {
this.fall();
}
}
document.getElementById("tetris-move-left-button").onmousedown = (e) => {
this.moveLeft();
}
document.getElementById("tetris-rotate-button").onmousedown = (e) => {
this.rotate();

}
document.getElementById("tetris-move-right-button").onmousedown = (e) => {
this.moveRight();
}
document.getElementById("tetris-fall-button").onmousedown = (e) => {
this.fall();
}
}

createBlocks() {
let blocks = [
{
shape: [[[-1, 0], [0, 0], [1, 0], [2, 0]],
[[0, -1], [0, 0], [0, 1], [0, 2]],
[[-1, 0], [0, 0], [1, 0], [2, 0]],
[[0, -1], [0, 0], [0, 1], [0, 2]]],
color: "rgb(0, 255, 255)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(0, 128, 128)"
},
{
shape: [[[0, 0], [1, 0], [0, 1], [1, 1]],
[[0, 0], [1, 0], [0, 1], [1, 1]],
[[0, 0], [1, 0], [0, 1], [1, 1]],
[[0, 0], [1, 0], [0, 1], [1, 1]]],
color: "rgb(255, 255, 0)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(128, 128, 0)"
},
{
shape: [[[0, 0], [1, 0], [-1, 1], [0, 1]],
[[-1, -1], [-1, 0], [0, 0], [0, 1]],
[[0, 0], [1, 0], [-1, 1], [0, 1]],
[[-1, -1], [-1, 0], [0, 0], [0, 1]]],
color: "rgb(0, 255, 0)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(0, 128, 0)"
},
{
shape: [[[-1, 0], [0, 0], [0, 1], [1, 1]],
[[0, -1], [-1, 0], [0, 0], [-1, 1]],
[[-1, 0], [0, 0], [0, 1], [1, 1]],
[[0, -1], [-1, 0], [0, 0], [-1, 1]]],
color: "rgb(255, 0, 0)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(128, 0, 0)"
},
{
shape: [[[-1, -1], [-1, 0], [0, 0], [1, 0]],
[[0, -1], [1, -1], [0, 0], [0, 1]],
[[-1, 0], [0, 0], [1, 0], [1, 1]],
[[0, -1], [0, 0], [-1, 1], [0, 1]]],
color: "rgb(0, 0, 255)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(0, 0, 128)"
},
{
shape: [[[1, -1], [-1, 0], [0, 0], [1, 0]],
[[0, -1], [0, 0], [0, 1], [1, 1]],
[[-1, 0], [0, 0], [1, 0], [-1, 1]],
[[-1, -1], [0, -1], [0, 0], [0, 1]]],
color: "rgb(255, 165, 0)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(128, 82, 0)"
},
{
shape: [[[0, -1], [-1, 0], [0, 0], [1, 0]],
[[0, -1], [0, 0], [1, 0], [0, 1]],
[[-1, 0], [0, 0], [1, 0], [0, 1]],
[[0, -1], [-1, 0], [0, 0], [0, 1]]],
color: "rgb(255, 0, 255)",
highlight: "rgb(255, 255, 255)",
shadow: "rgb(128, 0, 128)"
}
];
return blocks;
}
drawBlock(x, y, type, angle, canvas) {
let context = canvas.getContext("2d");
let block = this.blocks[type];
for (let i = 0; i < block.shape[angle].length; i++) {
this.drawCell(context,
x + (block.shape[angle][i][0] * this.cellSize),
y + (block.shape[angle][i][1] * this.cellSize),
this.cellSize,
type);
}
}

drawCell(context, cellX, cellY, cellSize, type) {
let block = this.blocks[type];
let adjustedX = cellX + 0.5;
let adjustedY = cellY + 0.5;
let adjustedSize = cellSize - 1;
context.fillStyle = block.color;
context.fillRect(adjustedX, adjustedY, adjustedSize, adjustedSize);
context.strokeStyle = block.highlight;
context.beginPath();
context.moveTo(adjustedX, adjustedY + adjustedSize);
context.lineTo(adjustedX, adjustedY);
context.lineTo(adjustedX + adjustedSize, adjustedY);
context.stroke();
context.strokeStyle = block.shadow;
context.beginPath();
context.moveTo(adjustedX, adjustedY + adjustedSize);
context.lineTo(adjustedX + adjustedSize, adjustedY + adjustedSize);
context.lineTo(adjustedX + adjustedSize, adjustedY);
context.stroke();
}

startGame() {
let virtualStage = new Array(this.stageWidth);
for (let i = 0; i < this.stageWidth; i++) {
virtualStage[i] = new Array(this.stageHeight).fill(null);
}
this.virtualStage = virtualStage;
this.currentBlock = null;
this.nextBlock = this.getRandomBlock();
this.mainLoop();
}

mainLoop() {
if (this.currentBlock == null) {
if (!this.createNewBlock()) {
return;
}
} else {
this.fallBlock();
}
this.drawStage();
if (this.currentBlock != null) {
this.drawBlock(this.stageLeftPadding + this.blockX * this.cellSize,
this.stageTopPadding + this.blockY * this.cellSize,
this.currentBlock, this.blockAngle, this.stageCanvas);
}
setTimeout(this.mainLoop.bind(this), 500);
}

createNewBlock() {
this.currentBlock = this.nextBlock;
this.nextBlock = this.getRandomBlock();
this.blockX = Math.floor(this.stageWidth / 2 - 2);
this.blockY = 0;
this.blockAngle = 0;
this.drawNextBlock();
if (!this.checkBlockMove(this.blockX, this.blockY, this.currentBlock, this.blockAngle)) {
let messageElem = document.getElementById("message");
messageElem.innerText = "GAME OVER";
return false;
}
return true;
}

drawNextBlock() {
this.clear(this.nextCanvas);
this.drawBlock(this.cellSize * 2, this.cellSize, this.nextBlock,
0, this.nextCanvas);
}

getRandomBlock() {
return  Math.floor(Math.random() * 7);
}

fallBlock() {
if (this.checkBlockMove(this.blockX, this.blockY + 1, this.currentBlock, this.blockAngle)) {
this.blockY++;
} else {
this.fixBlock(this.blockX, this.blockY, this.currentBlock, this.blockAngle);
this.currentBlock = null;
}
}

checkBlockMove(x, y, type, angle) {
for (let i = 0; i < this.blocks[type].shape[angle].length; i++) {
let cellX = x + this.blocks[type].shape[angle][i][0];
let cellY = y + this.blocks[type].shape[angle][i][1];
if (cellX < 0 || cellX > this.stageWidth - 1) {
return false;
}
if (cellY > this.stageHeight - 1) {
return false;
}
if (this.virtualStage[cellX][cellY] != null) {
return false;
}
}
return true;
}

fixBlock(x, y, type, angle) {
for (let i = 0; i < this.blocks[type].shape[angle].length; i++) {
let cellX = x + this.blocks[type].shape[angle][i][0];
let cellY = y + this.blocks[type].shape[angle][i][1];
if (cellY >= 0) {
this.virtualStage[cellX][cellY] = type;
}
}
for (let y = this.stageHeight - 1; y >= 0; ) {
let filled = true;
for (let x = 0; x < this.stageWidth; x++) {
if (this.virtualStage[x][y] == null) {
filled = false;
break;
}
}
if (filled) {
for (let y2 = y; y2 > 0; y2--) {
for (let x = 0; x < this.stageWidth; x++) {
this.virtualStage[x][y2] = this.virtualStage[x][y2 - 1];
}
}
for (let x = 0; x < this.stageWidth; x++) {
this.virtualStage[x][0] = null;
}
let linesElem = document.getElementById("lines");
this.deletedLines++;
linesElem.innerText = "" + this.deletedLines;
} else {
y--;
}
}
}

drawStage() {
this.clear(this.stageCanvas);

let context = this.stageCanvas.getContext("2d");
for (let x = 0; x < this.virtualStage.length; x++) {
for (let y = 0; y < this.virtualStage[x].length; y++) {
if (this.virtualStage[x][y] != null) {
this.drawCell(context,
this.stageLeftPadding + (x * this.cellSize),
this.stageTopPadding + (y * this.cellSize),
this.cellSize,
this.virtualStage[x][y]);
}
}
}
}

moveLeft() {
if (this.checkBlockMove(this.blockX - 1, this.blockY, this.currentBlock, this.blockAngle)) {
this.blockX--;
this.refreshStage();
}
}

moveRight() {
if (this.checkBlockMove(this.blockX + 1, this.blockY, this.currentBlock, this.blockAngle)) {
this.blockX++;
this.refreshStage();
}
}

rotate() {
let newAngle;
if (this.blockAngle < 3) {
newAngle = this.blockAngle + 1;
} else {
newAngle = 0;
}
if (this.checkBlockMove(this.blockX, this.blockY, this.currentBlock, newAngle)) {
this.blockAngle = newAngle;
this.refreshStage();
}
}

fall() {
while (this.checkBlockMove(this.blockX, this.blockY + 1, this.currentBlock, this.blockAngle)) {
this.blockY++;
this.refreshStage();
}
}

refreshStage() {
this.clear(this.stageCanvas);
this.drawStage();
this.drawBlock(this.stageLeftPadding + this.blockX * this.cellSize,
this.stageTopPadding + this.blockY * this.cellSize,
this.currentBlock, this.blockAngle, this.stageCanvas);
}

clear(canvas) {
let context = canvas.getContext("2d");
context.fillStyle = "rgb(0, 0, 0)";
context.fillRect(0, 0, canvas.width, canvas.height);
}
}

```