ボクの運営するYouTubeチャンネルのひとつ「たけちゃんねる プログラミング」。
JavaScript入門シリーズ『完全初心者がテトリスを作れるようになる動画』もすでに大詰め。
あとは実際にテトリスを作るばかりとなっています。
とは言うものの・・・。
ボクはJavaScriptでのテトリスは作ったことがないので、事前に一回作ってみよう!・・・という動画がコチラです。
この記事は、実際に作ってみたテトリスのコードを公開しています(それと「遊んでみよう!」のコーナー)。
テトリスのプレイはコチラから。
※操作方法は以下を参照。
是非、是非m(_ _)m
【PCでの操作方法】
左右移動「←」「→」
右回転 「↑」
左回転 「スペース」
高速落下「↓」※押下中落下
左右移動「←」「→」
右回転 「↑」
左回転 「スペース」
高速落下「↓」※押下中落下
結構がんばって作ったテトリスのコードがこれです。
※プログラミング講座で作成した「簡易版テトリス」も、このさらに下にあります。
※プログラミング講座で作成した「簡易版テトリス」も、このさらに下にあります。
<!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.0">
<title>JavaScriptで作るテトリス</title>
<style>
/* スタイルシート */
body{background-color:#008;}
#exit{
margin:0 10px 0 0;
background-color:#f77;
font-size:18px;
float:left; }
#score{
color:#fff;
font-size:20px;
font-weight:bold; }
#start{
font-size:40px;
border-radius:5px;
background-color:#f77;
color:#fdd;
position:absolute;
top:200px;
left:58px; }
</style>
</head>
<body>
<!-- ゲーム画面 -->
<button id="exit" onclick="over()">×</button>
<div id="score">SCORE:</div>
<canvas id="field" width="350px" height="500px"></canvas>
<button id="start" onclick="gameStart()">START</button>
<script>
//サウンドファイルをインスタンス化
let bgm = new Audio("bgm.wav");
let inSound = new Audio("in.wav");
let delSound = new Audio("delete.wav");
let gameOver = new Audio("gameover.wav");
//エレメントの取得など
let field = document.getElementById("field");
let can = field.getContext("2d");
let sc = document.getElementById("score");
let exit = document.getElementById("exit");
// 変数など
let arrayField = []; //ゲームフィールドの状態を格納
let tetroList = []; //操作するテトロミノとNext表示の3つを格納
let score; //スコアを格納
let tetroDown = false; //ダウンキー状態初期値
let gameSpeed = 500; //ゲーム速度初期値
let timer1, timer2, timer3;
//テトロミノデータ [x1,y1, x2,y2, x3,y3, x4,y4, color]
let arrayTetro = [[-1,-1, 0,-1, 0, 0, 1, 0, "red" ], // Z型
[ 0,-1, 1,-1,-1, 0, 0, 0, "green" ], // S型
[ 1,-1,-1, 0, 0, 0, 1, 0, "orange" ], // L型
[-1,-1,-1, 0, 0, 0, 1, 0, "blue" ], // J型
[-1,-1, 0,-1,-1, 0, 0, 0, "yellow" ], // O型
[ 0,-1,-1, 0, 0, 0, 1, 0, "magenta"], // T型
[-1, 0, 0, 0, 1, 0, 2, 0, "cyan" ]]; // I型
//テトロミノクラス
class Tetro {
constructor(){
this.x = 4;
this.y = 0;
this.tetro = arrayTetro[Math.floor(Math.random()*7)].concat(); //7種類の中からランダムに選択
}
}
//ファーストスクリプト
window.onload = firstScript;
function firstScript(){
can.fillStyle = "#000";
can.fillRect(0, 0, 250, 500);
arrayField = []; //ゲームフィールド初期化(全部"black")
for(let y=0; y<20; y++){
let sub = [];
for(let x=0; x<10; x++){sub.push("black");}
arrayField.push(sub);
}
tetroList = []; //テトロミノを4つインスタンス化
for(let i=0; i<4; i++){
tetroList.push(new Tetro());
}
document.getElementById("start").style.visibility = "visible"; //スタートボタン表示
}
//ゲームフィールドをクリアしてから描画する関数
function fieldDraw(){
can.fillStyle = "#000"; //フィールドクリア
can.fillRect(0, 0, 250, 500);
for(let y=0; y<20; y++){ //フィールド描画
for(let x=0; x<10; x++){
can.fillStyle = arrayField[y][x];
can.fillRect(x*25, y*25, 25, 25);
can.strokeStyle = "black";
can.strokeRect(x*25, y*25, 25, 25);
}
}
for(let i=0; i<4; i++){ //テトロミノ描画
can.fillStyle = tetroList[0].tetro[8];
can.fillRect((tetroList[0].tetro[i*2]+tetroList[0].x)*25, (tetroList[0].tetro[i*2+1]+tetroList[0].y)*25, 25, 25);
can.strokeStyle = "black";
can.strokeRect((tetroList[0].tetro[i*2]+tetroList[0].x)*25, (tetroList[0].tetro[i*2+1]+tetroList[0].y)*25, 25, 25);
}
}
//ネクストフィールドをクリアして再描画する関数
function nextDraw(){
can.fillStyle = "#008";
can.fillRect(250, 0, 100, 500);
for(let i=1; i<4; i++){
can.fillStyle = tetroList[i].tetro[8];
for(let j=0; j<4; j++){
can.fillRect((tetroList[i].tetro[j*2]+1)*15+260, (tetroList[i].tetro[j*2+1]+i*5-2)*15, 15, 15);
can.strokeStyle = "white";
can.strokeRect((tetroList[i].tetro[j*2]+1)*15+260, (tetroList[i].tetro[j*2+1]+i*5-2)*15, 15, 15);
}
}
}
//ゲームスタート関数
function gameStart(){
firstScript();
score = 0;
tetroDown = false;
document.getElementById("start").style.visibility = "hidden"; //スタートボタン非表示
bgm.play(); //BGM再生
bgm.addEventListener("ended", function(){bgm.play();});
document.addEventListener("keydown", keyDown); //イベントリスナー
document.addEventListener("keyup", keyUp);
document.addEventListener("touchstart", touchStart, {passive:false});
document.addEventListener("touchend", touchEnd, {passive:false});
exit.addEventListener("touchstart", over);
nextDraw(); //ネクストフィールド描画
timer1 = setInterval(fieldDraw, 10); //セットインターバル
timer2 = setInterval(highSpeedDown, 80);
timer3 = setInterval(nomalDown, gameSpeed);
}
//移動(回転)可能判定をする関数
function judgeMove(afterTetro, positionX, positionY){
for(let i=0; i<4; i++){
let afterX = afterTetro[i*2] + positionX;
let afterY = afterTetro[i*2+1] + positionY;
if(afterX<0 || afterY<0 || afterX>9 || afterY>19){return false;}
if(arrayField[afterY][afterX]!="black"){return false;}
}
return true;
}
//イベントハンドラー(キーボード)
function keyDown(e){ //キーが押されたとき
let afterTetro = tetroList[0].tetro.concat();
let positionX = tetroList[0].x;
let positionY = tetroList[0].y;
if(e.key=="ArrowRight" || e.key=="Right"){ //右キー(右移動)
positionX = tetroList[0].x+1;
}
if(e.key=="ArrowLeft" || e.key=="Left"){ //左キー(左移動)
positionX = tetroList[0].x-1;
}
if(e.key=="ArrowUp" || e.key=="Up"){ //上キー(時計回転)
afterTetro = [];
for(let i=0; i<4; i++){
afterTetro.push(tetroList[0].tetro[i*2+1]*-1);
afterTetro.push(tetroList[0].tetro[i*2]);
}
afterTetro.push(tetroList[0].tetro[8]);
}
if(e.key==" "){ //スペースキー(反時計回転)
afterTetro = [];
for(let i=0; i<4; i++){
afterTetro.push(tetroList[0].tetro[i*2+1]);
afterTetro.push(tetroList[0].tetro[i*2]*-1);
}
afterTetro.push(tetroList[0].tetro[8]);
}
if(e.key=="ArrowDown" || e.key=="Down"){ //下キー(押下中落下加速True)
tetroDown = true;
}
let isMove = judgeMove(afterTetro, positionX, positionY);
if(isMove){
tetroList[0].tetro = afterTetro;
tetroList[0].x = positionX;
tetroList[0].y = positionY;
}
}
function keyUp(e){ //キーが離されたとき
if(e.key=="ArrowDown" || e.key=="Down"){ //下キー(リリースで落下加速False)
tetroDown = false;
}
}
//イベントハンドラー(スマホタップ)
let startX = 0;
let startY = 0;
function touchStart(e){
e.preventDefault();
startX = e.touches[0].pageX;
startY = e.touches[0].pageY;
let afterTetro = tetroList[0].tetro.concat();
let positionX = tetroList[0].x;
let positionY = tetroList[0].y;
if(startY<500 && startY>200 && startX>85 && startX<165){
afterTetro = [];
for(let i=0; i<4; i++){
afterTetro.push(tetroList[0].tetro[i*2+1]*-1);
afterTetro.push(tetroList[0].tetro[i*2]);
}
afterTetro.push(tetroList[0].tetro[8]);
}
if(startY>200 && startX<80){
positionX = tetroList[0].x-1;
}
if(startY>200 && startX>170){
positionX = tetroList[0].x+1;
}
if(startY>500 && startX>85 && startX<165){
tetroDown = true;
}
let isMove = judgeMove(afterTetro, positionX, positionY);
if(isMove){
tetroList[0].tetro = afterTetro;
tetroList[0].x = positionX;
tetroList[0].y = positionY;
}
}
function touchEnd(e){
e.preventDefault();
tetroDown = false;
}
//高速落下を制御する関数
function highSpeedDown(){
if(tetroDown){
let afterTetro = tetroList[0].tetro.concat();
let positionX = tetroList[0].x;
let positionY = tetroList[0].y+1;
let isMove = judgeMove(afterTetro, positionX, positionY);
if(isMove){
tetroList[0].tetro = afterTetro;
tetroList[0].x = positionX;
tetroList[0].y = positionY;
score += 1;
sc.textContent = "SCORE: " + score;
}
else{fixedTetro();}
}
}
//自然落下を制御する関数
function nomalDown(){
let afterTetro = tetroList[0].tetro.concat()
let positionX = tetroList[0].x;
let positionY = tetroList[0].y+1;
let isMove = judgeMove(afterTetro, positionX, positionY);
if(isMove){
tetroList[0].tetro = afterTetro;
tetroList[0].x = positionX;
tetroList[0].y = positionY;
}
else{fixedTetro();}
}
//フィールドにテトロミノを固定する関数
function fixedTetro(){
let beforeX, beforeY, color;
for(let i=0; i<4; i++){
beforeX = tetroList[0].tetro[i*2] + tetroList[0].x;
beforeY = tetroList[0].tetro[i*2+1] + tetroList[0].y;
color = tetroList[0].tetro[8];
arrayField[beforeY][beforeX] = color;
}
checkLine();
changeTetro();
}
//ラインがそろっているか確認する関数
function checkLine(){
let point = [0, 40, 100, 300, 1200];
let lineCount = 0;
for(let i=0; i<20; i++){
if(arrayField[i].indexOf("black")==-1){lineCount++;}
}
score += point[lineCount];
sc.textContent = "SCORE: " + score;
for(let i=19; i>=0; i--){
if(arrayField[i].indexOf("black")==-1){
arrayField.splice(i, 1);
delSound.play();
}
}
for(let i=0; i<lineCount; i++){
let sub = [];
for(let j=0; j<10; j++){
sub.push("black");
}
arrayField.unshift(sub);
}
}
//固定したテトロミノをリストから削除&新テトロミノをインスタンス化する関数
function changeTetro(){
tetroList.shift();
tetroList.push(new Tetro());
inSound.play();
let isMove = judgeMove(tetroList[0].tetro, 4, 1);
if(isMove==false){over();}
nextDraw();
}
//ゲームオーバー処理の関数
function over(){
fieldDraw();
bgm.pause();
bgm.currentTime = 0;
gameOver.play();
clearInterval(timer1);
clearInterval(timer2);
clearInterval(timer3);
document.getElementById("start").style.visibility = "visible";
document.removeEventListener("keydown", keyDown);
document.removeEventListener("keyup", keyUp);
document.removeEventListener("touchstart", touchStart, {passive:false});
document.removeEventListener("touchend", touchEnd, {passive:false});
}
</script>
</body>
</html>
プログラミング講座で作った「簡易版」テトリスのコードはコチラ。
<!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.0">
</head>
<body style="background-color:blue">
<canvas id="canvas1" width="250px" height="500px"></canvas>
<script>
let x=4, y=0, moveDown=false;
let can = document.getElementById("canvas1").getContext("2d");
//テトロミノデータ
let arrayTetros = [[-1, 0, 0, 0, 1, 0, 0, 1, "magenta"], //T型
[ 0, 0, 1, 0,-1, 1, 0, 1, "green" ], //S型
[-1, 0, 0, 0, 1, 0,-1, 1, "orange" ], //L型
[-1, 0, 0, 0, 1, 0, 1, 1, "blue" ], //J型
[-1, 0, 0, 0, 1, 0, 2, 0, "cyan" ], //I型
[-1, 0, 0, 0,-1, 1, 0, 1, "yellow" ], //O型
[-1, 0, 0, 0, 0, 1, 1, 1, "red" ]]; //Z型
//フィールドデータ → all"black"
let arrayField = []
for(let y=0; y<20; y++){
let sub = [];
for(let x=0; x<10; x++){
sub.push("black");
}
arrayField.push(sub);
}
//最初のテトロミノをランダムに選択
let arrayTetro = arrayTetros[Math.floor(Math.random()*arrayTetros.length)];
//ブロックを描画する関数
function draw(){
if(moveDown==true){ //下キーが押されていれば下移動
let afterTetro=arrayTetro, afterX=x, afterY=y;
afterY+=1;
let result = judgeMove(afterTetro, afterX, afterY);
if(result){
arrayTetro = afterTetro, x = afterX, y = afterY;
}
}
for(let y=0; y<arrayField.length; y++){ //クリア
for(let x=0; x<arrayField[y].length; x++){
can.fillStyle = arrayField[y][x];
can.fillRect(x*25, y*25, 25,25);
can.strokeStyle = "black";
can.strokeRect(x*25, y*25, 25, 25);
}
}
for(let i=0; i<4; i++){ //テトロミノ描画
can.fillStyle = arrayTetro[8];
can.strokeStyle = "black";
can.fillRect((arrayTetro[i*2]+x)*25,(arrayTetro[i*2+1]+y)*25,25,25);
can.strokeRect((arrayTetro[i*2]+x)*25,(arrayTetro[i*2+1]+y)*25,25,25);
}
}
//テトロミノをキー入力とは関係なく落下させる関数
function dropTetro(){
let afterTetro=arrayTetro, afterX=x, afterY=y+1;
let result = judgeMove(afterTetro, afterX, afterY);
if(result){
arrayTetro = afterTetro, x = afterX, y = afterY;
}
else if(result==false){ //テトロミノをフィールドに固定する
for(let i=0; i<4; i++){
let fixX = arrayTetro[i*2]+x;
let fixY = arrayTetro[i*2+1]+y;
let color = arrayTetro[8];
arrayField[fixY][fixX] = color;
}
lineDelete(); //そろった列を削除する関数をコールバック
//新しいテトロミノ選択と初期化
arrayTetro = arrayTetros[Math.floor(Math.random()*arrayTetros.length)];
x=4, y=0, moveDown=false;
//ゲームオーバーの判定
afterTetro=arrayTetro, afterX=x, afterY=y+1;
result = judgeMove(afterTetro, afterX, afterY);
if(result==false){
clearInterval(timer1);
clearInterval(timer2);
window.alert("Game Over!");
}
}
}
//そろったラインを削除する関数
function lineDelete(){
for(let i=0; i<20; i++){
if(arrayField[i].indexOf("black")==-1){
arrayField.splice(i,1);
let sub = []
for(let j=0; j<10; j++){
sub.push("black");
}
arrayField.unshift(sub);
}
}
}
//セットインターバル(繰り返し処理)
let timer1 = setInterval(draw, 50);
let timer2 = setInterval(dropTetro, 1000);
//イベントリスナーを設定
document.addEventListener("keydown", keyDown);
document.addEventListener("keyup", keyUp);
//キーが押されたときの関数
function keyDown(e){
let afterTetro=arrayTetro.concat(), afterX=x, afterY=y;
//下移動
if(e.key=="ArrowDown"){moveDown=true;}
//左右移動
if(e.key=="ArrowRight"){afterX+=1;}
if(e.key=="ArrowLeft"){afterX-=1;}
//回転(時計回り)
if(e.key=="ArrowUp" && arrayTetro[8]!="yellow"){
for(let i=0; i<4; i++){
afterTetro[i*2] = arrayTetro[i*2+1]*-1;
afterTetro[i*2+1] = arrayTetro[i*2];
}
}
//回転(反時計回り)
if(e.key==" " && arrayTetro[8]!="yellow"){
for(let i=0; i<4; i++){
afterTetro[i*2] = arrayTetro[i*2+1];
afterTetro[i*2+1] = arrayTetro[i*2]*-1;
}
}
//移動可能判定をする関数をコールバック
let result = judgeMove(afterTetro, afterX, afterY);
if(result){
arrayTetro = afterTetro, x = afterX, y = afterY;
}
}
//キーが離されたときの関数
function keyUp(e){
if(e.key=="ArrowDown"){moveDown=false;}
}
//移動可能判定をする関数
function judgeMove(afterTetro, afterX, afterY){
for(let i=0; i<4; i++){
if(afterTetro[i*2]+afterX<0 || afterTetro[i*2]+afterX>9 ||
afterTetro[i*2+1]+afterY<0 || afterTetro[i*2+1]+afterY>19){
return false;
}
let moveX = afterTetro[i*2]+afterX, moveY = afterTetro[i*2+1]+afterY;
if(arrayField[moveY][moveX]!="black"){
return false;
}
}
return true;
}
</script>
</body>
</html>