PongJS/index.html

267 lines
9.0 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pong in WebGL</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
color: white;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
canvas {
display: block;
}
#score {
position: absolute;
top: 20px;
font-size: 24px;
}
</style>
</head>
<body>
<div id="score">Player: 0 | Opponent: 0</div>
<canvas id="gameCanvas"></canvas>
<script>
const canvas = document.getElementById('gameCanvas');
const gl = canvas.getContext('webgl');
const scoreDisplay = document.getElementById('score');
if (!gl) {
alert('WebGL not supported');
throw new Error('WebGL not supported');
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const vertexShaderSource = `
attribute vec2 a_position;
uniform vec2 u_resolution;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
}
`;
const fragmentShaderSource = `
precision mediump float;
void main() {
gl_FragColor = vec4(1, 1, 1, 1);
}
`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const resolutionUniformLocation = gl.getUniformLocation(program, 'u_resolution');
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const paddleWidth = 20;
const paddleHeight = 100;
const ballSize = 15;
let paddle1Y = canvas.height / 2 - paddleHeight / 2;
let paddle2Y = canvas.height / 2 - paddleHeight / 2;
let balls = [{ x: canvas.width / 2, y: canvas.height / 2, speedX: 5, speedY: 5 }];
let playerScore = 0;
let opponentScore = 0;
let speedIncrement = 0.2;
let powerups = [];
let bricks = [];
let opponentInverted = false;
function spawnPowerup() {
powerups.push({
x: Math.random() * (canvas.width - 50) + 25,
y: Math.random() * (canvas.height - 50) + 25,
type: Math.floor(Math.random() * 3) // 0: multi-ball, 1: invert opponent, 2: bricks
});
}
function spawnBricks() {
for (let i = 0; i < 5; i++) {
bricks.push({
x: canvas.width / 2 - 100 + i * 40,
y: canvas.height / 2 - 20,
width: 30,
height: 10
});
}
}
document.addEventListener('mousemove', (e) => {
paddle1Y = e.clientY - paddleHeight / 2;
paddle1Y = Math.max(0, Math.min(canvas.height - paddleHeight, paddle1Y));
});
function drawRect(x, y, width, height) {
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x, y,
x + width, y,
x, y + height,
x, y + height,
x + width, y,
x + width, y + height,
]), gl.STATIC_DRAW);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function update() {
// Clear canvas
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0);
gl.uniform2f(resolutionUniformLocation, canvas.width, canvas.height);
// Draw paddles
drawRect(10, paddle1Y, paddleWidth, paddleHeight);
drawRect(canvas.width - paddleWidth - 10, paddle2Y, paddleWidth, paddleHeight);
// Update and draw balls
balls.forEach((ball, index) => {
ball.x += ball.speedX;
ball.y += ball.speedY;
if (ball.y <= 0 || ball.y + ballSize >= canvas.height) {
ball.speedY *= -1;
}
if (ball.x <= paddleWidth + 10 && ball.y + ballSize >= paddle1Y && ball.y <= paddle1Y + paddleHeight) {
ball.speedX *= -1;
ball.speedX += speedIncrement;
ball.speedY += (Math.random() - 0.5) * 2;
}
if (ball.x + ballSize >= canvas.width - paddleWidth - 10 && ball.y + ballSize >= paddle2Y && ball.y <= paddle2Y + paddleHeight) {
ball.speedX *= -1;
ball.speedX -= speedIncrement;
ball.speedY += (Math.random() - 0.5) * 2;
}
if (ball.x <= 0) {
opponentScore++;
balls.splice(index, 1);
} else if (ball.x + ballSize >= canvas.width) {
playerScore++;
balls.splice(index, 1);
}
drawRect(ball.x, ball.y, ballSize, ballSize);
});
if (balls.length === 0) {
balls.push({ x: canvas.width / 2, y: canvas.height / 2, speedX: 5, speedY: 5 });
}
// Draw and check powerups
powerups.forEach((powerup, index) => {
drawRect(powerup.x, powerup.y, 20, 20);
balls.forEach((ball) => {
if (
ball.x < powerup.x + 20 &&
ball.x + ballSize > powerup.x &&
ball.y < powerup.y + 20 &&
ball.y + ballSize > powerup.y
) {
if (powerup.type === 0) {
balls.push({ x: ball.x, y: ball.y, speedX: -ball.speedX, speedY: -ball.speedY });
} else if (powerup.type === 1) {
opponentInverted = !opponentInverted;
} else if (powerup.type === 2) {
spawnBricks();
}
powerups.splice(index, 1);
}
});
});
// Draw and check bricks
bricks.forEach((brick, index) => {
drawRect(brick.x, brick.y, brick.width, brick.height);
balls.forEach((ball) => {
if (
ball.x < brick.x + brick.width &&
ball.x + ballSize > brick.x &&
ball.y < brick.y + brick.height &&
ball.y + ballSize > brick.y
) {
ball.speedY *= -1;
bricks.splice(index, 1);
}
});
});
// Opponent AI
const targetY = balls[0].y - paddleHeight / 2;
paddle2Y += (opponentInverted ? -1 : 1) * (targetY - paddle2Y) * 0.1;
paddle2Y = Math.max(0, Math.min(canvas.height - paddleHeight, paddle2Y));
// Update score
scoreDisplay.textContent = `Player: ${playerScore} | Opponent: ${opponentScore}`;
// Spawn powerups occasionally
if (Math.random() < 0.01) {
spawnPowerup();
}
requestAnimationFrame(update);
}
update();
</script>
</body>
</html>