<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tetris 3D Pro</title>
<style>
body {
margin: 0;
padding: 0;
background: #000;
color: #fff;
font-family: Arial, sans-serif;
overflow: hidden;
}
#gameContainer {
position: relative;
width: 100vw;
height: 100vh;
}
.controls {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
padding: 20px;
border-radius: 10px;
min-width: 250px;
border: 2px solid #0ff;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.5);
}
.controls h3 {
margin-top: 0;
color: #0ff;
text-shadow: 0 0 10px #0ff;
}
button {
background: #0ff;
color: #000;
border: none;
padding: 10px 15px;
cursor: pointer;
border-radius: 5px;
font-weight: bold;
margin: 5px 0;
width: 100%;
transition: all 0.3s;
}
button:hover {
background: #fff;
transform: scale(1.05);
}
.score-display {
font-size: 20px;
margin: 10px 0;
color: #0ff;
}
.controls-info {
background: rgba(51, 51, 51, 0.8);
padding: 10px;
border-radius: 5px;
margin-top: 20px;
font-size: 14px;
}
.controls-info kbd {
background: #555;
padding: 2px 5px;
border-radius: 3px;
margin: 0 2px;
}
#loadingScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1000;
}
.loader {
border: 5px solid #333;
border-top: 5px solid #0ff;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
</head>
<body>
<div id="gameContainer">
<div id="loadingScreen">
<div class="loader"></div>
<p>Chargement du jeu...</p>
</div>
<div class="controls">
<h3>Tetris 3D Pro</h3>
<div class="score-display">
Score: <span id="score">0</span><br>
Lignes: <span id="lines">0</span><br>
Niveau: <span id="level">1</span>
</div>
<button onclick="togglePause()">Pause</button>
<button onclick="resetGame()">Nouveau Jeu</button>
<div class="controls-info">
<strong>Contrôles:</strong><br>
<kbd>←</kbd> <kbd>→</kbd> Déplacer<br>
<kbd>↑</kbd> Rotation<br>
<kbd>↓</kbd> Descente rapide<br>
<kbd>Espace</kbd> Chute instantanée<br>
<kbd>P</kbd> Pause
</div>
</div>
</div>
<script>
// Configuration
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 1;
// Variables
let scene, camera, renderer;
let gameGroup;
let board = [];
let currentPiece = null;
let score = 0;
let lines = 0;
let level = 1;
let dropCounter = 0;
let lastTime = 0;
let paused = false;
// Textures
let textures = [];
// Pièces Tetris
const pieces = {
I: {
shape: [[1,1,1,1]],
rotationStates: [
[[1,1,1,1]],
[[1],[1],[1],[1]],
[[1,1,1,1]],
[[1],[1],[1],[1]]
],
color: 0x00ffff
},
O: {
shape: [[1,1],[1,1]],
rotationStates: [
[[1,1],[1,1]],
[[1,1],[1,1]],
[[1,1],[1,1]],
[[1,1],[1,1]]
],
color: 0xffff00
},
T: {
shape: [[0,1,0],[1,1,1]],
rotationStates: [
[[0,1,0],[1,1,1]],
[[1,0],[1,1],[1,0]],
[[1,1,1],[0,1,0]],
[[0,1],[1,1],[0,1]]
],
color: 0xff00ff
},
S: {
shape: [[0,1,1],[1,1,0]],
rotationStates: [
[[0,1,1],[1,1,0]],
[[1,0],[1,1],[0,1]],
[[0,1,1],[1,1,0]],
[[1,0],[1,1],[0,1]]
],
color: 0x00ff00
},
Z: {
shape: [[1,1,0],[0,1,1]],
rotationStates: [
[[1,1,0],[0,1,1]],
[[0,1],[1,1],[1,0]],
[[1,1,0],[0,1,1]],
[[0,1],[1,1],[1,0]]
],
color: 0xff0000
},
J: {
shape: [[1,0,0],[1,1,1]],
rotationStates: [
[[1,0,0],[1,1,1]],
[[1,1],[1,0],[1,0]],
[[1,1,1],[0,0,1]],
[[0,1],[0,1],[1,1]]
],
color: 0x0000ff
},
L: {
shape: [[0,0,1],[1,1,1]],
rotationStates: [
[[0,0,1],[1,1,1]],
[[1,0],[1,0],[1,1]],
[[1,1,1],[1,0,0]],
[[1,1],[0,1],[0,1]]
],
color: 0xffa500
}
};
class TetrisPiece {
constructor() {
const types = 'IOTSZJL';
this.type = types[Math.floor(Math.random() * types.length)];
this.rotationState = 0;
this.matrix = pieces[this.type].shape.map(row => [...row]);
this.x = Math.floor((COLS - this.matrix[0].length) / 2);
this.y = 0;
this.mesh = new THREE.Group();
this.blocks = [];
this.pieceColor = pieces[this.type].color;
this.pieceFaceIndices = this.generatePieceFaceIndices();
this.createMesh();
}
generatePieceFaceIndices() {
const maxBlocks = 9;
const indices = [];
for (let i = 0; i < maxBlocks; i++) {
indices.push(Array(6).fill().map(() =>
Math.floor(Math.random() * textures.length)
));
}
return indices;
}
createMesh() {
this.mesh.clear();
this.blocks = [];
let blockIndex = 0;
for (let y = 0; y < this.matrix.length; y++) {
this.blocks[y] = [];
for (let x = 0; x < this.matrix[y].length; x++) {
if (this.matrix[y][x]) {
const block = this.createCubeWithSpecificFaces(this.pieceFaceIndices[blockIndex]);
block.position.set(x * BLOCK_SIZE, -y * BLOCK_SIZE, 0);
this.mesh.add(block);
this.blocks[y][x] = block;
blockIndex++;
}
}
}
this.updatePosition();
}
createCubeWithSpecificFaces(faceIndices) {
const geometry = new THREE.BoxGeometry(BLOCK_SIZE * 0.95, BLOCK_SIZE * 0.95, BLOCK_SIZE * 0.95);
const materials = [];
for (let i = 0; i < 6; i++) {
materials.push(new THREE.MeshPhongMaterial({
map: textures[faceIndices[i]],
color: this.pieceColor,
emissive: this.pieceColor,
emissiveIntensity: 0.2
}));
}
const cube = new THREE.Mesh(geometry, materials);
const edges = new THREE.EdgesGeometry(geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000 }));
cube.add(line);
return cube;
}
updatePosition() {
this.mesh.position.set(
(this.x - COLS/2 + 0.5) * BLOCK_SIZE,
(ROWS/2 - this.y - 0.5) * BLOCK_SIZE,
0
);
}
rotate() {
const nextRotationState = (this.rotationState + 1) % 4;
const nextMatrix = pieces[this.type].rotationStates[nextRotationState];
const oldMatrix = this.matrix;
const oldRotationState = this.rotationState;
this.matrix = nextMatrix.map(row => [...row]);
this.rotationState = nextRotationState;
if (this.isValidPosition()) {
this.createMesh();
return true;
}
this.matrix = oldMatrix;
this.rotationState = oldRotationState;
return false;
}
isValidPosition(dx = 0, dy = 0) {
for (let y = 0; y < this.matrix.length; y++) {
for (let x = 0; x < this.matrix[y].length; x++) {
if (this.matrix[y][x]) {
const newX = this.x + x + dx;
const newY = this.y + y + dy;
if (newX < 0 || newX >= COLS || newY >= ROWS) {
return false;
}
if (newY >= 0 && board[newY] && board[newY][newX]) {
return false;
}
}
}
}
return true;
}
move(dx, dy) {
if (this.isValidPosition(dx, dy)) {
this.x += dx;
this.y += dy;
this.updatePosition();
return true;
}
return false;
}
}
function init() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 25);
camera.lookAt(0, 0, 0);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.getElementById('gameContainer').appendChild(renderer.domElement);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(10, 20, 10);
scene.add(directionalLight);
gameGroup = new THREE.Group();
scene.add(gameGroup);
createGameBoard();
board = Array(ROWS).fill().map(() => Array(COLS).fill(null));
loadTextures();
window.addEventListener('resize', onWindowResize);
document.addEventListener('keydown', onKeyDown);
document.getElementById('loadingScreen').style.display = 'none';
}
function loadTextures() {
// Créer 5 textures colorées
for (let i = 0; i < 5; i++) {
const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
const ctx = canvas.getContext('2d');
const gradient = ctx.createLinearGradient(0, 0, 64, 64);
gradient.addColorStop(0, `hsl(${i * 72}, 70%, 40%)`);
gradient.addColorStop(1, `hsl(${i * 72}, 100%, 60%)`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
ctx.fillStyle = '#fff';
ctx.font = 'bold 32px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(i + 1, 32, 32);
textures[i] = new THREE.CanvasTexture(canvas);
}
startGame();
}
function createGameBoard() {
const borderMaterial = new THREE.MeshPhongMaterial({ color: 0x00ffff });
const borderThickness = 0.2;
const leftBorder = new THREE.Mesh(
new THREE.BoxGeometry(borderThickness, ROWS * BLOCK_SIZE, 2),
borderMaterial
);
leftBorder.position.x = -COLS * BLOCK_SIZE / 2 - borderThickness / 2;
gameGroup.add(leftBorder);
const rightBorder = new THREE.Mesh(
new THREE.BoxGeometry(borderThickness, ROWS * BLOCK_SIZE, 2),
borderMaterial
);
rightBorder.position.x = COLS * BLOCK_SIZE / 2 + borderThickness / 2;
gameGroup.add(rightBorder);
const bottomBorder = new THREE.Mesh(
new THREE.BoxGeometry(COLS * BLOCK_SIZE + borderThickness * 2, borderThickness, 2),
borderMaterial
);
bottomBorder.position.y = -ROWS * BLOCK_SIZE / 2 - borderThickness / 2;
gameGroup.add(bottomBorder);
}
function startGame() {
currentPiece = new TetrisPiece();
gameGroup.add(currentPiece.mesh);
animate();
}
function animate(time = 0) {
requestAnimationFrame(animate);
if (!paused) {
const deltaTime = time - lastTime;
lastTime = time;
dropCounter += deltaTime;
if (dropCounter > (1000 / level)) {
dropPiece();
dropCounter = 0;
}
}
renderer.render(scene, camera);
}
function dropPiece() {
if (!currentPiece) return;
if (!currentPiece.move(0, 1)) {
lockPiece();
checkLines();
spawnNewPiece();
}
}
function lockPiece() {
let blockIndex = 0;
currentPiece.matrix.forEach((row, y) => {
row.forEach((value, x) => {
if (value) {
const boardY = currentPiece.y + y;
const boardX = currentPiece.x + x;
if (boardY >= 0 && boardY < ROWS && boardX >= 0 && boardX < COLS) {
const faceIndices = currentPiece.pieceFaceIndices[blockIndex];
const block = currentPiece.createCubeWithSpecificFaces(faceIndices);
block.position.set(
(boardX - COLS/2 + 0.5) * BLOCK_SIZE,
(ROWS/2 - boardY - 0.5) * BLOCK_SIZE,
0
);
board[boardY][boardX] = block;
gameGroup.add(block);
}
blockIndex++;
}
});
});
gameGroup.remove(currentPiece.mesh);
}
function checkLines() {
let linesCleared = 0;
for (let y = ROWS - 1; y >= 0; y--) {
let isComplete = true;
for (let x = 0; x < COLS; x++) {
if (!board[y][x]) {
isComplete = false;
break;
}
}
if (isComplete) {
for (let x = 0; x < COLS; x++) {
if (board[y][x]) {
gameGroup.remove(board[y][x]);
}
}
for (let moveY = y - 1; moveY >= 0; moveY--) {
for (let x = 0; x < COLS; x++) {
if (board[moveY][x]) {
board[moveY][x].position.y -= BLOCK_SIZE;
board[moveY + 1][x] = board[moveY][x];
} else {
board[moveY + 1][x] = null;
}
}
}
for (let x = 0; x < COLS; x++) {
board[0][x] = null;
}
linesCleared++;
y++;
}
}
if (linesCleared > 0) {
lines += linesCleared;
score += linesCleared * 100 * level;
level = Math.floor(lines / 10) + 1;
updateScore();
}
}
function spawnNewPiece() {
currentPiece = new TetrisPiece();
if (!currentPiece.isValidPosition()) {
gameGroup.remove(currentPiece.mesh);
currentPiece = null;
gameOver();
return;
}
gameGroup.add(currentPiece.mesh);
}
function gameOver() {
alert(`Game Over!\nScore: ${score}\nLignes: ${lines}`);
resetGame();
}
function resetGame() {
for (let y = 0; y < ROWS; y++) {
for (let x = 0; x < COLS; x++) {
if (board[y][x]) {
gameGroup.remove(board[y][x]);
board[y][x] = null;
}
}
}
board = Array(ROWS).fill().map(() => Array(COLS).fill(null));
score = 0;
lines = 0;
level = 1;
dropCounter = 0;
updateScore();
if (currentPiece && currentPiece.mesh) {
gameGroup.remove(currentPiece.mesh);
}
currentPiece = null;
spawnNewPiece();
paused = false;
}
function updateScore() {
document.getElementById('score').textContent = score;
document.getElementById('lines').textContent = lines;
document.getElementById('level').textContent = level;
}
function onKeyDown(event) {
if (!currentPiece) return;
if (paused && event.key !== 'p' && event.key !== 'P') return;
switch(event.key) {
case 'ArrowLeft':
currentPiece.move(-1, 0);
break;
case 'ArrowRight':
currentPiece.move(1, 0);
break;
case 'ArrowDown':
if (currentPiece.move(0, 1)) {
score++;
updateScore();
dropCounter = 0;
}
break;
case 'ArrowUp':
currentPiece.rotate();
break;
case ' ':
while (currentPiece.move(0, 1)) {
score += 2;
}
updateScore();
dropCounter = 0;
lockPiece();
checkLines();
spawnNewPiece();
break;
case 'p':
case 'P':
togglePause();
break;
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function togglePause() {
paused = !paused;
}
window.addEventListener('load', () => {
init();
});
</script>
</body>
</html>