🍃소스코드
<!DOCTYPE html>
<html>
<head>
<title>Basic Tetris HTML Game</title>
<meta charset="UTF-8">
<style>
html, body {
height: 100%;
margin: 0;
}
body {
background: #0c0c0c;
display: flex;
align-items: center;
justify-content: center;
}
canvas {
border: 1px solid white;
}
</style>
</head>
<body>
<canvas width="320" height="640" id="game"></canvas>
<script>
// https://tetris.fandom.com/wiki/Tetris_Guideline
// get a random integer between the range of [min,max]
// @see https://stackoverflow.com/a/1527820/2124254
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// generate a new tetromino sequence
// @see https://tetris.fandom.com/wiki/Random_Generator
function generateSequence() {
const sequence = ['I', 'J', 'L', 'O', 'S', 'T', 'Z'];
while (sequence.length) {
const rand = getRandomInt(0, sequence.length - 1);
const name = sequence.splice(rand, 1)[0];
tetrominoSequence.push(name);
}
}
// get the next tetromino in the sequence
function getNextTetromino() {
if (tetrominoSequence.length === 0) {
generateSequence();
}
const name = tetrominoSequence.pop();
const matrix = tetrominos[name];
// I and O start centered, all others start in left-middle
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
// I starts on row 21 (-1), all others start on row 22 (-2)
const row = name === 'I' ? -1 : -2;
return {
name: name, // name of the piece (L, O, etc.)
matrix: matrix, // the current rotation matrix
row: row, // current row (starts offscreen)
col: col // current col
};
}
// rotate an NxN matrix 90deg
// @see https://codereview.stackexchange.com/a/186834
function rotate(matrix) {
const N = matrix.length - 1;
const result = matrix.map((row, i) =>
row.map((val, j) => matrix[N - j][i])
);
return result;
}
// check to see if the new matrix/row/col is valid
function isValidMove(matrix, cellRow, cellCol) {
for (let row = 0; row < matrix.length; row++) {
for (let col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] && (
// outside the game bounds
cellCol + col < 0 ||
cellCol + col >= playfield[0].length ||
cellRow + row >= playfield.length ||
// collides with another piece
playfield[cellRow + row][cellCol + col])
) {
return false;
}
}
}
return true;
}
// place the tetromino on the playfield
function placeTetromino() {
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// game over if piece has any part offscreen
if (tetromino.row + row < 0) {
return showGameOver();
}
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
}
}
}
// check for line clears starting from the bottom and working our way up
for (let row = playfield.length - 1; row >= 0; ) {
if (playfield[row].every(cell => !!cell)) {
// drop every row above this one
for (let r = row; r >= 0; r--) {
for (let c = 0; c < playfield[r].length; c++) {
playfield[r][c] = playfield[r-1][c];
}
}
}
else {
row--;
}
}
tetromino = getNextTetromino();
}
// show the game over screen
function showGameOver() {
cancelAnimationFrame(rAF);
gameOver = true;
context.fillStyle = 'black';
context.globalAlpha = 0.75;
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
context.globalAlpha = 1;
context.fillStyle = 'white';
context.font = '36px monospace';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
}
const canvas = document.getElementById('game');
const context = canvas.getContext('2d');
const grid = 32;
const tetrominoSequence = [];
// keep track of what is in every cell of the game using a 2d array
// tetris playfield is 10x20, with a few rows offscreen
const playfield = [];
// populate the empty state
for (let row = -2; row < 20; row++) {
playfield[row] = [];
for (let col = 0; col < 10; col++) {
playfield[row][col] = 0;
}
}
// how to draw each tetromino
// @see https://tetris.fandom.com/wiki/SRS
const tetrominos = {
'I': [
[0,0,0,0],
[1,1,1,1],
[0,0,0,0],
[0,0,0,0]
],
'J': [
[1,0,0],
[1,1,1],
[0,0,0],
],
'L': [
[0,0,1],
[1,1,1],
[0,0,0],
],
'O': [
[1,1],
[1,1],
],
'S': [
[0,1,1],
[1,1,0],
[0,0,0],
],
'Z': [
[1,1,0],
[0,1,1],
[0,0,0],
],
'T': [
[0,1,0],
[1,1,1],
[0,0,0],
]
};
// color of each tetromino
const colors = {
'I': 'cyan',
'O': 'yellow',
'T': 'purple',
'S': 'green',
'Z': 'red',
'J': 'blue',
'L': 'orange'
};
let count = 0;
let tetromino = getNextTetromino();
let rAF = null; // keep track of the animation frame so we can cancel it
let gameOver = false;
// game loop
function loop() {
rAF = requestAnimationFrame(loop);
context.clearRect(0,0,canvas.width,canvas.height);
// draw the playfield
for (let row = 0; row < 20; row++) {
for (let col = 0; col < 10; col++) {
if (playfield[row][col]) {
const name = playfield[row][col];
context.fillStyle = colors[name];
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect(col * grid, row * grid, grid-1, grid-1);
}
}
}
// draw the active tetromino
if (tetromino) {
// tetromino falls every 35 frames
if (++count > 35) {
tetromino.row++;
count = 0;
// place piece if it runs into anything
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
tetromino.row--;
placeTetromino();
}
}
context.fillStyle = colors[tetromino.name];
for (let row = 0; row < tetromino.matrix.length; row++) {
for (let col = 0; col < tetromino.matrix[row].length; col++) {
if (tetromino.matrix[row][col]) {
// drawing 1 px smaller than the grid creates a grid effect
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid-1, grid-1);
}
}
}
}
}
// listen to keyboard events to move the active tetromino
document.addEventListener('keydown', function(e) {
if (gameOver) return;
// left and right arrow keys (move)
if (e.which === 37 || e.which === 39) {
const col = e.which === 37
? tetromino.col - 1
: tetromino.col + 1;
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
tetromino.col = col;
}
}
// up arrow key (rotate)
if (e.which === 38) {
const matrix = rotate(tetromino.matrix);
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
tetromino.matrix = matrix;
}
}
// down arrow key (drop)
if(e.which === 40) {
const row = tetromino.row + 1;
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
tetromino.row = row - 1;
placeTetromino();
return;
}
tetromino.row = row;
}
});
// start the game
rAF = requestAnimationFrame(loop);
</script>
</body>
</html>
🍀테트리스의 역사
테트리스(Tetris)는 소련의 컴퓨터 과학자 알렉세이 파지노프(Alexey Pajitnov)에 의해 1984년에 개발된 퍼즐 비디오 게임입니다. 모스크바의 도로시너 연구소에서 근무하던 파지노프는, 복잡한 도형들을 조합하여 완성된 행을 제거하는 게임을 개발하고자 했으며, 이는 나중에 '테트리스'라는 이름으로 널리 알려지게 되었습니다.
그리고 파지노프는 테트리스의 기본 아이디어를 펜토미노(Pentomino)라는 게임에서 영감을 받아 개발하였습니다. 펜토미노는 고대 로마에서 유래된 퍼즐로서, 5개의 단위 정사각형이 변끼리 붙어 이루어진 도형으로 모양을 만드는 놀이입니다. 조각은 12가지의 알파벳 모양인데, 각 조각들이 5개(그리스어: πέντε / pente / 다섯을 의미)의 정사각형으로 만들어졌기 때문에 펜토미노라 합니다.
[출처] 위키백과
🍀테트리스(Tetris)게임 어원
테트리스의 이름은 그리스어로 '네'를 의미하는 '테트라(Tetra)'와 테니스(Tennis)를 결합한 것입니다. 게임에서 사용되는 블록들이 4개의 정사각형으로 이루어진 도형임을 나타냅니다. 이러한 도형들은 "테트로미노(Tetromino)"라고 불리며, "테트라"와 "도미노(Domino)"의 합성어입니다. 테트로미노는 4개의 정사각형이 다양한 방식으로 결합된 7가지 기본 도형으로 구성됩니다.
테트로미노
🍀군사용 목적으로 사용되었던 테트리스(Tetris)
냉전 시대와 테트리스의 확산: 테트리스는 냉전 시기에 개발되었으며, 당시 소련과 서방 세계 간의 기술 및 문화 교류가 제한적이었습니다. 그러나 테트리스는 아이러니하게도 소련의 소프트웨어가 서방 세계로 확산된 대표적인 사례가 되었습니다. 이 게임은 특히 서방의 군인들과 과학자들 사이에서 인기를 끌었으며, 이를 통해 소련의 문화와 기술을 접할 수 있는 기회를 제공했습니다.
군사 훈련에서의 활용: 테트리스의 단순한 규칙과 복잡한 문제 해결 능력은 군사 훈련에서 활용될 수 있는 요소들을 가지고 있었습니다. 게임은 빠른 판단력과 손-눈의 협응 능력을 요구하며, 이는 군인들이 전장에서 필요로 하는 능력들과 유사합니다. 따라서 일부 군대에서는 테트리스를 교육 도구로 사용하여 병사들의 인지 능력을 향상시키려는 시도를 했습니다.
정신 건강과 스트레스 완화: 연구에 따르면 테트리스는 정신 건강에 긍정적인 영향을 미칠 수 있습니다. 특히, 테트리스는 트라우마를 겪은 군인들이 스트레스를 완화하고 불안을 줄이는 데 도움이 될 수 있다는 연구 결과가 있습니다. 게임이 제공하는 몰입감과 도전 과제 해결의 성취감은 스트레스 해소와 정신적 안정감을 주는 데 기여할 수 있습니다.
🍀결론
테트리스는 단순한 비디오 게임을 넘어 문화적, 심리적, 그리고 군사적 측면에서도 중요한 의미를 지니고 있습니다. 또한, 내가 가장 사랑하는 게임 2가지 중 1가지이며 아마 죽을 때 까지 하지 않을까 싶습니다. 다양한 버전의 테트리스를 즐겨보시기 바랍니다.