teste2
teste2
DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width,user-scalable=no">
<meta charset="UTF-8">
<meta name="referrer" content="no-referrer-when-downgrade">
<title>Stackerful 3D</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Comfortaa">
<style>
body {
margin: 0;
overflow: hidden;
height: 100vh;
font-family: 'Comfortaa', cursive;
background: linear-gradient(135deg, #D0CBC7 0%, #9B8E8A 100%);
display: flex;
justify-content: center;
align-items: center;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
#score {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
font-size: 10vh;
color: #333344;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
display: none;
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
#game {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.game-over,
.game-ready {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.5s ease;
z-index: 10;
background-color: rgba(255, 255, 255, 0.9);
padding: 40px;
border-radius: 20px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.game-over.active,
.game-ready.active {
opacity: 1;
}
#start-button,
#restart-button {
border: none;
padding: 15px 30px;
background-color: #4CAF50;
color: white;
font-size: 24px;
cursor: pointer;
margin-top: 20px;
border-radius: 50px;
transition: all 0.3s ease;
font-family: 'Comfortaa', cursive;
}
#start-button:hover,
#restart-button:hover {
background-color: #45a049;
transform: scale(1.05);
}
#instructions {
position: absolute;
width: 100%;
top: 16vh;
left: 0;
text-align: center;
transition: opacity 0.5s ease;
display: none;
color: #333344;
font-size: 2.5vh;
text-shadow: 1px 1px 2px rgba(255, 255, 255, 0.5);
}
#highscore {
position: absolute;
top: 20px;
right: 20px;
font-size: 24px;
color: #333344;
}
</style>
</head>
<body>
<div id="container">
<div id="game"></div>
<div id="score">0</div>
<div id="instructions">Click or press spacebar to place the block</div>
<div id="highscore">High Score: 0</div>
<div class="game-over">
<h2>Game Over</h2>
<p>You did great, you're the best!</p>
<p>Your score: <span id="final-score"></span></p>
<p>Click the button to start again</p>
<div id="restart-button">Start Again</div>
</div>
<div class="game-ready active">
<h2>Welcome to Stackerful 3D</h2>
<p>Stack blocks as high as you can!</p>
<div id="start-button">Start</div>
</div>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function() {
let GG_ALL_GAME_CONFIG = {
blockColors: [0x000000, 0x00BFFF, 0x32CD32, 0xFFD700, 0xFF8C00, 0xFF4500],
blockWidth: 15,
blockHeight: 2,
blockDepth: 15,
blockSpeed: 0.1,
cameraDistance: 50,
gameOverThreshold: 0.01,
cameraRotationSpeed: 0,
fallingSpeed: 0.2, // Reduced for smoother falling animation
fallingRotationSpeed: 0.05, // Reduced for smoother rotation
fallingAnimationDuration: 2000 // Duration of falling animation in
milliseconds
};
(function() {
class Stage {
constructor() {
this.container = document.getElementById('game');
this.renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.container.appendChild(this.renderer.domElement);
this.scene = new THREE.Scene();
let aspect = window.innerWidth / window.innerHeight;
let d = GG_ALL_GAME_CONFIG.cameraDistance;
this.camera = new THREE.PerspectiveCamera(45, aspect, 1, 1000);
this.camera.position.set(d, d, d);
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
this.light = new THREE.DirectionalLight(0xffffff, 0.8);
this.light.position.set(d, d, d);
this.scene.add(this.light);
this.softLight = new THREE.AmbientLight(0xffffff, 0.5);
this.scene.add(this.softLight);
window.addEventListener('resize', () => this.onResize());
this.onResize();
}
moveCamera(y) {
this.camera.position.y = y + GG_ALL_GAME_CONFIG.cameraDistance;
this.camera.lookAt(new THREE.Vector3(0, y, 0));
}
onResize() {
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
}
render() {
this.renderer.render(this.scene, this.camera);
}
add(elem) {
this.scene.add(elem);
}
remove(elem) {
this.scene.remove(elem);
}
}
class Block {
constructor(yPosition, index, previousBlockWidth) {
this.width = previousBlockWidth || GG_ALL_GAME_CONFIG.blockWidth;
this.height = GG_ALL_GAME_CONFIG.blockHeight;
this.depth = GG_ALL_GAME_CONFIG.blockDepth;
this.position = {
x: this.getRandomStartPosition(),
y: yPosition,
z: 0
};
this.speed = GG_ALL_GAME_CONFIG.blockSpeed;
this.direction = this.position.x > 0 ? -1 : 1;
this.state = 'active';
this.geometry = new THREE.BoxGeometry(this.width, this.height,
this.depth);
this.material = this.createMaterial(index);
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.mesh.position.set(this.position.x, this.position.y,
this.position.z);
this.mesh.scale.set(1, 1, 1);
this.fallingPart = null;
}
getRandomStartPosition() {
const side = Math.random() > 0.5 ? 1 : -1;
return side * (GG_ALL_GAME_CONFIG.blockWidth + Math.random() * 7); //
Increased range for larger blocks
}
createMaterial(index) {
const colors = GG_ALL_GAME_CONFIG.blockColors;
return new THREE.MeshPhongMaterial({
color: colors[index % colors.length],
flatShading: true,
shininess: 100
});
}
tick() {
if (this.state === 'active') {
this.position.x += this.direction * this.speed;
if (Math.abs(this.position.x) > GG_ALL_GAME_CONFIG.blockWidth + 7)
{ // Increased range for larger blocks
this.direction *= -1;
}
this.mesh.position.x = this.position.x;
}
}
place(previousBlock) {
this.state = 'placed';
this.mesh.position.y = this.position.y;
if (previousBlock) {
let overlap = this.calculateOverlap(previousBlock);
if (overlap < this.width) {
this.cutBlock(overlap, previousBlock);
}
}
}
calculateOverlap(previousBlock) {
let previousBlockRight = previousBlock.mesh.position.x +
previousBlock.width / 2;
let previousBlockLeft = previousBlock.mesh.position.x -
previousBlock.width / 2;
let currentBlockRight = this.mesh.position.x + this.width / 2;
let currentBlockLeft = this.mesh.position.x - this.width / 2;
if (currentBlockLeft > previousBlockRight || currentBlockRight <
previousBlockLeft) {
return 0;
}
return Math.min(previousBlockRight, currentBlockRight) -
Math.max(previousBlockLeft, currentBlockLeft);
}
cutBlock(overlap, previousBlock) {
let newWidth = overlap;
let cutWidth = this.width - newWidth;
// Adjust the size and position of the main block
this.width = newWidth;
this.mesh.scale.x = newWidth / GG_ALL_GAME_CONFIG.blockWidth;
this.mesh.position.x = previousBlock.mesh.position.x;
// Create the falling part
let fallingGeometry = new THREE.BoxGeometry(cutWidth, this.height,
this.depth);
let fallingMaterial = this.material.clone();
this.fallingPart = new THREE.Mesh(fallingGeometry, fallingMaterial);
// Set the position of the falling part
let fallingPartX = this.mesh.position.x + (this.direction * (newWidth +
cutWidth) / 2);
this.fallingPart.position.set(fallingPartX, this.mesh.position.y,
this.mesh.position.z);
// Add the falling part to the scene
game.stage.add(this.fallingPart);
// Start the falling animation
this.animateFallingPart();
}
animateFallingPart() {
if (this.fallingPart) {
const startTime = Date.now();
const startY = this.fallingPart.position.y;
const startRotation = this.fallingPart.rotation.z;
const animate = () => {
const elapsedTime = Date.now() - startTime;
const progress = Math.min(elapsedTime /
GG_ALL_GAME_CONFIG.fallingAnimationDuration, 1);
// Use easing function for smoother animation
const easeProgress = this.easeOutQuad(progress);
this.fallingPart.position.y = startY - easeProgress * 60; // Fall
60 units
this.fallingPart.rotation.z = startRotation + easeProgress *
Math.PI * 2; // Rotate 360 degrees
if (progress < 1) {
requestAnimationFrame(animate);
} else {
game.stage.remove(this.fallingPart);
this.fallingPart = null;
}
};
animate();
}
}
easeOutQuad(t) {
return t * (2 - t);
}
}
class Game {
constructor() {
this.state = 'loading';
this.blocks = [];
this.score = 0;
this.highScore = 0;
this.stage = new Stage();
this.scoreContainer = document.getElementById('score');
this.startButton = document.getElementById('start-button');
this.restartButton = document.getElementById('restart-button');
this.gameOverScreen = document.querySelector('.game-over');
this.startButton.addEventListener('click', () => this.startGame());
this.restartButton.addEventListener('click', () => this.startGame());
document.addEventListener('keydown', e => {
if (e.key === ' ' && this.state === 'playing') this.placeBlock();
});
document.addEventListener('click', (e) => {
if (this.state === 'playing' && e.target !== this.restartButton)
this.placeBlock();
});
this.loadHighScore();
this.tick();
this.updateState('ready');
this.canPlaceBlock = false;
}
addInitialBlock() {
let initialBlock = new Block(0, 0);
initialBlock.position.x = 0;
initialBlock.mesh.position.x = 0;
initialBlock.state = 'placed';
initialBlock.width = GG_ALL_GAME_CONFIG.blockWidth;
this.stage.add(initialBlock.mesh);
this.blocks.push(initialBlock);
}
updateState(newState) {
this.state = newState;
document.querySelector('.game-ready').classList.toggle('active',
newState === 'ready');
this.gameOverScreen.classList.toggle('active', newState === 'ended');
this.scoreContainer.style.display = newState === 'playing' ? 'block' :
'none';
document.getElementById('instructions').style.display = newState ===
'playing' ? 'block' : 'none';
}
startGame() {
this.blocks.forEach(block => this.stage.remove(block.mesh));
this.blocks = [];
this.score = 0;
this.scoreContainer.innerHTML = this.score;
this.updateState('playing');
this.addInitialBlock();
this.addBlock();
this.stage.camera.position.set(GG_ALL_GAME_CONFIG.cameraDistance,
GG_ALL_GAME_CONFIG.cameraDistance, GG_ALL_GAME_CONFIG.cameraDistance);
this.stage.camera.lookAt(new THREE.Vector3(0, 0, 0));
// Add a small delay before allowing block placement
setTimeout(() => {
this.canPlaceBlock = true;
}, 500);
}
endGame() {
this.updateState('ended');
document.getElementById('final-score').textContent = this.score;
if (this.score > this.highScore) {
this.highScore = this.score;
this.saveHighScore();
}
document.getElementById('highscore').textContent = `High Score: $
{this.highScore}`;
this.canPlaceBlock = false;
}
placeBlock() {
if (this.state !== 'playing' || !this.canPlaceBlock) return;
let currentBlock = this.blocks[this.blocks.length - 1];
if (currentBlock.state === 'active') {
let prevBlock = this.blocks.length > 1 ?
this.blocks[this.blocks.length - 2] : null;
currentBlock.place(prevBlock);
if (currentBlock.width <= GG_ALL_GAME_CONFIG.blockWidth *
GG_ALL_GAME_CONFIG.gameOverThreshold) {
this.endGame();
return;
}
this.score++;
this.scoreContainer.innerHTML = this.score;
this.addBlock();
}
}
addBlock() {
let newIndex = this.blocks.length %
GG_ALL_GAME_CONFIG.blockColors.length;
let previousBlock = this.blocks[this.blocks.length - 1];
let newBlock = new Block(
previousBlock.position.y + GG_ALL_GAME_CONFIG.blockHeight,
newIndex,
previousBlock.width
);
this.stage.add(newBlock.mesh);
this.blocks.push(newBlock);
newBlock.state = 'active';
}
tick() {
if (this.state === 'playing') {
this.blocks.forEach(block => {
block.tick();
if (block.fallingPart) {
block.animateFallingPart();
}
});
if (this.blocks.length > 0) {
const topBlockY = this.blocks[this.blocks.length - 1].position.y;
this.stage.moveCamera(topBlockY / 2);
}
}
this.stage.render();
requestAnimationFrame(() => this.tick());
}
loadHighScore() {
const savedHighScore = localStorage.getItem('stackerful3DHighScore');
if (savedHighScore) {
this.highScore = parseInt(savedHighScore);
document.getElementById('highscore').textContent = `High Score: $
{this.highScore}`;
}
}
saveHighScore() {
localStorage.setItem('stackerful3DHighScore',
this.highScore.toString());
}
}
const game = new Game();
})();
})();
</script>
</body>
</html>