Tetris. played by AI.
Tetris AI
<!DOCTYPE html>
<html>
<head>
<title>Tetris AI</title>
<style>
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
margin: auto;
background: #111;
}
</style>
</head>
<body>
<canvas id="canvas" width="300" height="600"></canvas>
<script type="text/javascript">
// Define variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var width = canvas.width / 10;
var height = canvas.height / 20;
var board = [];
var score = 0;
var interval;
var currentPiece;
var nextPiece;
var aiMoves = [];
var moveCounter = 0;
// Define tetromino shapes
var shapes = [
{ color: "#00FFFF", blocks: [[0, 1], [1, 1], [2, 1], [3, 1]] }, // I
{ color: "#FFA500", blocks: [[0, 0], [0, 1], [1, 1], [2, 1]] }, // L
{ color: "#FFFF00", blocks: [[0, 1], [1, 1], [2, 1], [2, 0]] }, // J
{ color: "#FF0000", blocks: [[0, 1], [1, 1], [1, 0], [2, 0]] }, // Z
{ color: "#800080", blocks: [[0, 0], [1, 0], [1, 1], [2, 1]] }, // S
{ color: "#00FF00", blocks: [[0, 1], [1, 1], [1, 0], [2, 0]] }, // T
{ color: "#FF00FF", blocks: [[0, 0], [1, 0], [2, 0], [1, 1]] } // O
];
// Create game board
function init() {
for (var i = 0; i < 20; i++) {
board[i] = [];
for (var j = 0; j < 10; j++) {
board[i][j] = "";
}
}
}
// Randomly select next piece
function getNextPiece() {
var shape = Math.floor(Math.random() * shapes.length);
var blocks = shapes[shape].blocks;
var color = shapes[shape].color;
var piece = { blocks: blocks, color: color };
return piece;
}
// Draw tetromino piece
function drawPiece(piece) {
ctx.fillStyle = piece.color;
for (var i = 0; i < piece.blocks.length; i++) {
var x = piece.blocks[i][0] + currentPiece.x;
var y = piece.blocks[i][1] + currentPiece.y;
ctx.fillRect(x * width, y * height, width, height);
ctx.strokeRect(x * width, y * height, width, height);
}
}
// Erase tetromino piece
function erasePiece(piece) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < board.length; i++) {
for (var j = 0; j < board[i].length; j++) {
if (board[i][j] !== "") {
ctx.fillStyle = board[i][j];
ctx.fillRect(j * width, i * height, width, height);
ctx.strokeRect(j * width, i * height, width, height);
}
}
}
}
// Move piece down
function moveDown() {
erasePiece(currentPiece);
if (canMove(0, 1, currentPiece.blocks)) {
currentPiece.y++;
} else {
lockPiece();
currentPiece = nextPiece;
nextPiece = getNextPiece();
}
drawPiece(currentPiece);
}
// Lock piece in place
function lockPiece() {
for (var i = 0; i < currentPiece.blocks.length; i++) {
var x = currentPiece.blocks[i][0] + currentPiece.x;
var y = currentPiece.blocks[i][1] + currentPiece.y;
board[y][x] = currentPiece.color;
}
checkLine();
}
// Check if piece can move in given direction
function canMove(dx, dy, blocks) {
for (var i = 0; i < blocks.length; i++) {
var x = blocks[i][0] + currentPiece.x + dx;
var y = blocks[i][1] + currentPiece.y + dy;
if (x < 0 || x >= 10 || y >= 20 || board[y][x] !== "") {
return false;
}
}
return true;
}
// Check if any lines are complete
function checkLine() {
var count = 0;
for (var i = 0; i < board.length; i++) {
if (board[i].every((val) => val !== "")) {
board.splice(i, 1);
board.unshift(new Array(10).fill(""));
count++;
}
}
switch (count) {
case 1:
score += 100;
break;
case 2:
score += 300;
break;
case 3:
score += 500;
break;
case 4:
score += 800;
break;
default:
break;
}
if (score >= 1000 && !interval) {
interval = setInterval(moveAI, 500);
}
}
// Make a move for the AI
function makeAIMove() {
var move = aiMoves[moveCounter];
switch (move) {
case "left":
erasePiece(currentPiece);
if (canMove(-1, 0, currentPiece.blocks)) {
currentPiece.x--;
}
drawPiece(currentPiece);
break;
case "right":
erasePiece(currentPiece);
if (canMove(1, 0, currentPiece.blocks)) {
currentPiece.x++;
}
drawPiece(currentPiece);
break;
case "down":
moveDown();
break;
case "rotate":
erasePiece(currentPiece);
rotatePiece();
drawPiece(currentPiece);
break;
default:
break;
}
moveCounter++;
if (moveCounter === aiMoves.length) {
clearInterval(interval);
interval = null;
moveCounter = 0;
aiMoves = [];
}
}
// Move piece left
function moveLeft() {
erasePiece(currentPiece);
if (canMove(-1, 0, currentPiece.blocks)) {
currentPiece.x--;
}
drawPiece(currentPiece);
}
// Move piece right
function moveRight() {
erasePiece(currentPiece);
if (canMove(1, 0, currentPiece.blocks)) {
currentPiece.x++;
}
drawPiece(currentPiece);
}
// Rotate piece
function rotatePiece() {
var rotatedBlocks = [];
for (var i = 0; i < currentPiece.blocks.length; i++) {
var x = currentPiece.blocks[i][0];
var y = currentPiece.blocks[i][1];
rotatedBlocks.push([-y, x]);
}
if (canMove(0, 0, rotatedBlocks)) {
currentPiece.blocks = rotatedBlocks;
}
}
// Move piece down until it can't go further
function dropPiece() {
while (canMove(0, 1, currentPiece.blocks)) {
moveDown();
}
}
// Handle keyboard input
document.addEventListener("keydown", function (e) {
switch (e.keyCode) {
case 37: // left arrow
moveLeft();
break;
case 38: // up arrow
erasePiece(currentPiece);
rotatePiece();
drawPiece(currentPiece);
break;
case 39: // right arrow
moveRight();
break;
case 40: // down arrow
moveDown();
break;
case 32: // spacebar
dropPiece();
break;
default:
break;
}
});
// AI plays the game
function moveAI() {
var bestMove = getBestMove();
aiMoves = aiMoves.concat(bestMove.moves);
makeAIMove();
if (bestMove.score > score) {
score = bestMove.score;
document.getElementById(
"score"
).innerText = `Score: ${score} (AI)`;
}
}
// Get the best move for the AI
function getBestMove() {
var moves = [
{ name: "down", score: 0 },
{ name: "left", score: 0 },
{ name: "right", score: 0 },
{ name: "rotate", score: 0 },
];
for (var i = 0; i < moves.length; i++) {
var move = moves[i];
erasePiece(currentPiece);
switch (move.name) {
case "left":
if (canMove(-1, 0, currentPiece.blocks)) {
currentPiece.x--;
move.score = getHighestScore(0);
}
break;
case "right":
if (canMove(1, 0, currentPiece.blocks)) {
currentPiece.x++;
move.score = getHighestScore(0);
}
break;
case "rotate":
rotatePiece();
move.score = getHighestScore(0);
break;
case "down":
move.score = getHighestScore(1);
break;
default:
break;
}
moves[i] = move;
erasePiece(currentPiece);
}
var bestMove = moves.reduce((a, b) => (a.score > b.score ? a : b));
return bestMove;
}
// Get the highest score for a given move
function getHighestScore(depth) {
var highestScore = score;
var dropped = false;
while (canMove(0, 1, currentPiece.blocks)) {
moveDown();
dropped = true;
}
if (depth > 0 || dropped) {
lockPiece();
currentPiece = nextPiece;
nextPiece = getNextPiece();
if (depth > 0) {
checkLine();
highestScore = score;
} else {
highestScore = getBestMove().score;
}
}
return highestScore;
}
// Start game
init();
currentPiece = getNextPiece();
nextPiece = getNextPiece();
drawPiece(currentPiece);
document.getElementById(
"score"
).innerText = `Score: ${score} (Human)`;
// Show joke
console.log(
"Why did the Tetris piece refuse to fit in? It had commitment issues."
);
</script>
</body>
</html>