Skip to content

Commit a1184c6

Browse files
committed
Very, very important feature
1 parent cdf8926 commit a1184c6

File tree

4 files changed

+353
-0
lines changed

4 files changed

+353
-0
lines changed

extras/games/tetris/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Tetris Game</title>
7+
<link rel="stylesheet" href="styles.css" />
8+
</head>
9+
<body>
10+
<canvas id="gameCanvas"></canvas>
11+
<script src="tetris.js"></script>
12+
</body>
13+
</html>

extras/games/tetris/styles.css

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
body,
2+
html {
3+
height: 100%;
4+
margin: 0;
5+
display: flex;
6+
justify-content: center;
7+
align-items: center;
8+
transition: background-color 0.3s;
9+
}
10+
11+
canvas {
12+
border: 0px;
13+
display: block;
14+
background-color: white;
15+
padding: 0.5rem;
16+
border-radius: 0.5rem;
17+
}
18+
19+
body.colorful {
20+
animation: gradientFlash 5s infinite;
21+
background: linear-gradient(to right, #ffc700, #ff0000);
22+
}
23+
24+
@keyframes gradientFlash {
25+
0%,
26+
100% {
27+
background-position: 0%;
28+
}
29+
25% {
30+
background-position: 100%;
31+
}
32+
50% {
33+
background-position: 100%;
34+
}
35+
75% {
36+
background-position: 0%;
37+
}
38+
}

extras/games/tetris/tetris.js

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
const canvas = document.getElementById('gameCanvas');
2+
const context = canvas.getContext('2d');
3+
const grid = 32;
4+
const tetrominoSequence = [];
5+
6+
// Keep track of what is in every cell of the game using a 2d array
7+
const playfield = [];
8+
9+
// Create the empty state for the playfield
10+
for (let row = -2; row < 20; row++) {
11+
playfield[row] = [];
12+
13+
for (let col = 0; col < 10; col++) {
14+
playfield[row][col] = 0;
15+
}
16+
}
17+
18+
// how to draw each tetromino
19+
// @see https://tetris.fandom.com/wiki/Tetris_Guideline
20+
const tetrominos = {
21+
'I': [
22+
[0,0,0,0],
23+
[1,1,1,1],
24+
[0,0,0,0],
25+
[0,0,0,0]
26+
],
27+
'J': [
28+
[1,0,0],
29+
[1,1,1],
30+
[0,0,0],
31+
],
32+
'L': [
33+
[0,0,1],
34+
[1,1,1],
35+
[0,0,0],
36+
],
37+
'O': [
38+
[1,1],
39+
[1,1],
40+
],
41+
'S': [
42+
[0,1,1],
43+
[1,1,0],
44+
[0,0,0],
45+
],
46+
'Z': [
47+
[1,1,0],
48+
[0,1,1],
49+
[0,0,0],
50+
],
51+
'T': [
52+
[0,1,0],
53+
[1,1,1],
54+
[0,0,0],
55+
]
56+
};
57+
58+
// color of each tetromino
59+
const colors = {
60+
'I': '#36b5ff',
61+
'O': '#ffe436',
62+
'T': '#d036ff',
63+
'S': '#36ff6b',
64+
'Z': '#ff3636',
65+
'J': '#3646ff',
66+
'L': '#ff9a36'
67+
};
68+
69+
// Keep track of the position of the current tetromino
70+
let tetromino = getNextTetromino();
71+
let rAF = null; // keep track of the animation frame so we can cancel it
72+
let gameOver = false;
73+
74+
// Get the next tetromino in the sequence
75+
function getNextTetromino() {
76+
if (tetrominoSequence.length === 0) {
77+
const tetrominos = ['I', 'J', 'L', 'O', 'S', 'Z', 'T'];
78+
79+
while (tetrominos.length) {
80+
const rand = getRandomInt(0, tetrominos.length - 1);
81+
const name = tetrominos.splice(rand, 1)[0];
82+
tetrominoSequence.push(name);
83+
}
84+
}
85+
86+
const name = tetrominoSequence.pop();
87+
const matrix = tetrominos[name];
88+
89+
const col = playfield[0].length / 2 - Math.ceil(matrix[0].length / 2);
90+
91+
const row = name === 'I' ? -1 : -2;
92+
93+
document.body.style.backgroundColor = colors[name]
94+
95+
return {
96+
name: name, // name of the piece (L, O, etc.)
97+
matrix: matrix, // the current rotation matrix
98+
row: row, // current row (starts offscreen)
99+
col: col // current col
100+
};
101+
}
102+
103+
// Generate a random number between min and max (inclusive)
104+
function getRandomInt(min, max) {
105+
min = Math.ceil(min);
106+
max = Math.floor(max);
107+
return Math.floor(Math.random() * (max - min + 1)) + min;
108+
}
109+
110+
// Rotate the matrix 90 degrees clockwise
111+
function rotate(matrix) {
112+
const N = matrix.length - 1;
113+
const result = matrix.map((row, i) =>
114+
row.map((val, j) => matrix[N - j][i])
115+
);
116+
117+
return result;
118+
}
119+
120+
// Check to see if the new matrix/row/col is valid
121+
function isValidMove(matrix, cellRow, cellCol) {
122+
for (let row = 0; row < matrix.length; row++) {
123+
for (let col = 0; col < matrix[row].length; col++) {
124+
if (matrix[row][col] && (
125+
cellCol + col < 0 ||
126+
cellCol + col >= playfield[0].length ||
127+
cellRow + row >= playfield.length ||
128+
playfield[cellRow + row][cellCol + col])
129+
) {
130+
return false;
131+
}
132+
}
133+
}
134+
135+
return true;
136+
}
137+
138+
// Place the tetromino on the playfield
139+
function placeTetromino() {
140+
for (let row = 0; row < tetromino.matrix.length; row++) {
141+
for (let col = 0; col < tetromino.matrix[row].length; col++) {
142+
if (tetromino.matrix[row][col]) {
143+
144+
// game over if piece has any part offscreen
145+
if (tetromino.row + row < 0) {
146+
return showGameOver();
147+
}
148+
149+
playfield[tetromino.row + row][tetromino.col + col] = tetromino.name;
150+
}
151+
}
152+
}
153+
154+
for (let row = playfield.length - 1; row >= 0; ) {
155+
if (playfield[row].every(cell => !!cell)) {
156+
157+
for (let r = row; r >= 0; r--) {
158+
for (let c = 0; c < playfield[r].length; c++) {
159+
playfield[r][c] = playfield[r-1][c];
160+
}
161+
}
162+
}
163+
else {
164+
row--;
165+
}
166+
}
167+
168+
tetromino = getNextTetromino();
169+
}
170+
171+
// Show the game over screen
172+
function showGameOver() {
173+
cancelAnimationFrame(rAF);
174+
gameOver = true;
175+
176+
context.fillStyle = 'black';
177+
context.globalAlpha = 0.75;
178+
context.fillRect(0, canvas.height / 2 - 30, canvas.width, 60);
179+
180+
context.globalAlpha = 1;
181+
context.fillStyle = 'white';
182+
context.font = '36px monospace';
183+
context.textAlign = 'center';
184+
context.textBaseline = 'middle';
185+
context.fillText('GAME OVER!', canvas.width / 2, canvas.height / 2);
186+
}
187+
188+
// Draw the tetromino
189+
function drawTetromino() {
190+
context.fillStyle = colors[tetromino.name];
191+
192+
for (let row = 0; row < tetromino.matrix.length; row++) {
193+
for (let col = 0; col < tetromino.matrix[row].length; col++) {
194+
if (tetromino.matrix[row][col]) {
195+
196+
context.fillRect((tetromino.col + col) * grid, (tetromino.row + row) * grid, grid, grid);
197+
}
198+
}
199+
}
200+
}
201+
202+
// Draw the playfield
203+
function drawPlayfield() {
204+
for (let row = 0; row < playfield.length; row++) {
205+
for (let col = 0; col < playfield[row].length; col++) {
206+
if (playfield[row][col]) {
207+
const name = playfield[row][col];
208+
context.fillStyle = colors[name];
209+
210+
context.fillRect(col * grid, row * grid, grid, grid);
211+
}
212+
}
213+
}
214+
}
215+
216+
// Listen to keyboard events to move the active tetromino
217+
document.addEventListener('keydown', function(e) {
218+
if (gameOver) return;
219+
220+
// Left arrow key (move left)
221+
if (e.which === 37 || e.keyCode === 37) {
222+
const col = tetromino.col - 1;
223+
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
224+
tetromino.col = col;
225+
}
226+
}
227+
228+
// Right arrow key (move right)
229+
if (e.which === 39 || e.keyCode === 39) {
230+
const col = tetromino.col + 1;
231+
if (isValidMove(tetromino.matrix, tetromino.row, col)) {
232+
tetromino.col = col;
233+
}
234+
}
235+
236+
// Up arrow key (rotate)
237+
if (e.which === 38 || e.keyCode === 38) {
238+
const matrix = rotate(tetromino.matrix);
239+
if (isValidMove(matrix, tetromino.row, tetromino.col)) {
240+
tetromino.matrix = matrix;
241+
}
242+
}
243+
244+
// Down arrow key (soft drop)
245+
if (e.which === 40 || e.keyCode === 40) {
246+
const row = tetromino.row + 1;
247+
if (!isValidMove(tetromino.matrix, row, tetromino.col)) {
248+
tetromino.row = row - 1;
249+
placeTetromino();
250+
return;
251+
}
252+
253+
tetromino.row = row;
254+
}
255+
256+
if (e.which === 32 || e.keyCode === 32) {
257+
while (isValidMove(tetromino.matrix, tetromino.row + 1, tetromino.col)) {
258+
tetromino.row++;
259+
}
260+
placeTetromino();
261+
}
262+
});
263+
264+
// Game loop
265+
function loop() {
266+
rAF = requestAnimationFrame(loop);
267+
context.clearRect(0,0,canvas.width,canvas.height);
268+
269+
drawPlayfield();
270+
drawTetromino();
271+
272+
if (++count > 35) {
273+
tetromino.row++;
274+
count = 0;
275+
276+
if (!isValidMove(tetromino.matrix, tetromino.row, tetromino.col)) {
277+
tetromino.row--;
278+
placeTetromino();
279+
}
280+
}
281+
}
282+
283+
// Fit the canvas to the screen
284+
canvas.width = 10 * grid; // 10 columns
285+
canvas.height = 20 * grid; // 20 rows
286+
287+
// Keep track of time
288+
let count = 0;
289+
290+
// Start the game loop
291+
rAF = requestAnimationFrame(loop);

extras/popup/popup.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,17 @@ function searchAndSort(query) {
15231523
}
15241524

15251525
let searchbar = document.querySelector(".searchbar");
1526+
1527+
searchbar.addEventListener("keypress", function (e) {
1528+
if (e.which === 13) {
1529+
if (searchbar.value === "node tetris.js") {
1530+
searchbar.value = "";
1531+
searchbar.dispatchEvent(new Event("change", { bubbles: true }));
1532+
chrome.tabs.create({ url: "/extras/games/tetris/index.html" });
1533+
}
1534+
}
1535+
});
1536+
15261537
searchbar.addEventListener("input", function () {
15271538
if (document.querySelector(".welcome")) {
15281539
if (searchbar.value) {

0 commit comments

Comments
 (0)