SOURCE

console 命令行工具 X clear

                    
>
console
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

// 游戏配置
const CONFIG = {
    BALL_COUNT: 10,
    RADIUS: 20,
    PLAYER_COLOR: '#3498db',
    OTHER_COLOR: '#e74c3c',
    BASE_SPEED: 6,
    MAX_HITS: 5  // 最大允许碰撞次数
};

/**
 * 游戏状态管理类
 */
class GameState {
    constructor() {
        this.reset();
    }

    reset() {
        this.isPlaying = false;
        this.mouseControl = false;
        this.countdown = 0;
        this.balls = [];
        this.collisions = 0;
    }

    startGame() {
        this.isPlaying = false;
        this.mouseControl = true;
        this.countdown = 3;
        this.collisions = 0;
        this.balls = [];
    }

    gameOver() {
        this.isPlaying = false;
        this.mouseControl = false;
    }
}

const gameState = new GameState();

class Ball {
    constructor(x, y, isPlayer = false) {
        this.x = x;
        this.y = y;
        this.vx = 0;
        this.vy = 0;
        this.radius = CONFIG.RADIUS;
        this.color = isPlayer ? CONFIG.PLAYER_COLOR : CONFIG.OTHER_COLOR;
        this.isPlayer = isPlayer;
        this.lastCollision = 0; // 防重复计数

        if (!isPlayer) {
            const angle = Math.random() * Math.PI * 2;
            this.vx = Math.cos(angle) * CONFIG.BASE_SPEED;
            this.vy = Math.sin(angle) * CONFIG.BASE_SPEED;
        }
    }

    update() {
        if (this.isPlayer) return;

        const boundaryX = canvas.width - this.radius;
        const boundaryY = canvas.height - this.radius;
        
        if (this.x < this.radius || this.x > boundaryX) {
            this.vx *= -1;
            this.x = Math.max(this.radius, Math.min(this.x, boundaryX));
        }
        if (this.y < this.radius || this.y > boundaryY) {
            this.vy *= -1;
            this.y = Math.max(this.radius, Math.min(this.y, boundaryY));
        }
        
        this.x += this.vx;
        this.y += this.vy;
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
    }
}

function initGame() {
    gameState.balls = [];
    
    const player = new Ball(canvas.width/2, canvas.height/2, true);
    gameState.balls.push(player);

    for (let i = 1; i < CONFIG.BALL_COUNT; i++) {
        let isValid, newBall;
        let attempts = 0;
        
        do {
            newBall = new Ball(
                CONFIG.RADIUS + Math.random()*(canvas.width - CONFIG.RADIUS*2),
                CONFIG.RADIUS + Math.random()*(canvas.height - CONFIG.RADIUS*2)
            );
            isValid = gameState.balls.every(b => {
                const dx = b.x - newBall.x;
                const dy = b.y - newBall.y;
                return Math.sqrt(dx**2 + dy**2) >= CONFIG.RADIUS*2;
            });
        } while (!isValid && ++attempts < 100);

        if (isValid) gameState.balls.push(newBall);
    }
}

function processCollisions() {
    const now = Date.now();
    
    for (let i = 0; i < gameState.balls.length; i++) {
        for (let j = i+1; j < gameState.balls.length; j++) {
            const a = gameState.balls[i];
            const b = gameState.balls[j];
            const dx = b.x - a.x;
            const dy = b.y - a.y;
            const distance = Math.sqrt(dx**2 + dy**2);
            const minDist = a.radius + b.radius;

            if (distance < minDist) {
                // 碰撞计数(新增逻辑)
                if (a.isPlayer !== b.isPlayer && now - a.lastCollision > 100) {
                    gameState.collisions++;
                    a.lastCollision = now;
                    b.lastCollision = now;
                }

                // 物理计算
                const nx = dx / distance;
                const ny = dy / distance;
                const dvx = b.vx - a.vx;
                const dvy = b.vy - a.vy;
                const impulse = (2 * (dvx*nx + dvy*ny)) / 2;

                a.vx += impulse * nx;
                a.vy += impulse * ny;
                b.vx -= impulse * nx;
                b.vy -= impulse * ny;

                const overlap = (minDist - distance)/2;
                a.x -= overlap * nx;
                a.y -= overlap * ny;
                b.x += overlap * nx;
                b.y += overlap * ny;
            }
        }
    }

    // 碰撞次数检查(新增)
    if (gameState.collisions >= CONFIG.MAX_HITS) {
        resetGame();
    }
}

// 重置游戏状态并显示结束信息
function resetGame() {
    gameState.isPlaying = false;
    gameState.mouseControl = false;
    gameState.balls = [];
    
    // 显示游戏结束提示
    ui.elements.countdown.textContent = `超过${CONFIG.MAX_HITS}次碰撞!`;
    ui.elements.countdown.style.opacity = '1';
    
    // 隐藏开始按钮直到提示消失
    ui.elements.startBtn.style.opacity = '0';
    ui.elements.startBtn.style.pointerEvents = 'none';
    
    // 2秒后隐藏提示并显示开始按钮
    setTimeout(() => {
        ui.elements.countdown.style.opacity = '0';
        ui.elements.startBtn.style.opacity = '1';
        ui.elements.startBtn.style.pointerEvents = 'auto';
        canvas.style.cursor = 'default';
    }, 2000);
}

/**
 * UI管理器类,负责创建和管理所有UI元素
 */
class UIManager {
    constructor() {
        this.elements = {
            startBtn: this._createStartButton(),
            countdown: this._createCountdownElement(),
            scoreBoard: this._createScoreBoard()
        };
    }

    _createStartButton() {
        const btn = document.createElement('div');
        btn.textContent = '开始游戏';
        btn.style.cssText = `
            position: fixed;
            top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            font: bold 48px Arial;
            color: white;
            text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
            cursor: pointer;
            transition: opacity 0.5s;
            user-select: none;
        `;
        document.body.appendChild(btn);
        return btn;
    }

    _createCountdownElement() {
        const el = document.createElement('div');
        el.style.cssText = `
            position: fixed;
            top: 50%; left: 50%;
            transform: translate(-50%, -50%);
            font: bold 100px Arial;
            color: white;
            text-shadow: 4px 4px 8px rgba(0,0,0,0.7);
            opacity: 0;
            pointer-events: none;
            user-select: none;
        `;
        document.body.appendChild(el);
        return el;
    }

    _createScoreBoard() {
        const board = document.createElement('div');
        board.style.cssText = `
            position: fixed;
            top: 20px;
            left: 20px;
            font: bold 24px Arial;
            color: ${CONFIG.PLAYER_COLOR};
            text-shadow: 1px 1px 2px rgba(0,0,0,0.3);
            user-select: none;
        `;
        document.body.appendChild(board);
        return board;
    }

    updateScore(current, max) {
        this.elements.scoreBoard.textContent = `碰撞次数: ${current}/${max}`;
    }
}

const ui = new UIManager();

// 鼠标控制
canvas.addEventListener('mousemove', e => {
    if (!gameState.mouseControl) return;
    
    const rect = canvas.getBoundingClientRect();
    const player = gameState.balls.find(b => b.isPlayer);
    
    player.x = Math.max(CONFIG.RADIUS, 
        Math.min(e.clientX - rect.left, canvas.width - CONFIG.RADIUS));
    player.y = Math.max(CONFIG.RADIUS,
        Math.min(e.clientY - rect.top, canvas.height - CONFIG.RADIUS));
});

function startGame() {
    // 重置碰撞计数
    gameState.collisions = 0;
    ui.updateScore(0, CONFIG.MAX_HITS);
    
    ui.elements.startBtn.style.opacity = '0';
    ui.elements.startBtn.style.pointerEvents = 'none';
    canvas.style.cursor = 'none';
    
    gameState.isPlaying = false;
    gameState.mouseControl = true;
    gameState.countdown = 3;
    
    initGame();

    // 倒计时逻辑
    ui.elements.countdown.style.opacity = '1';
    const countdownStep = () => {
        ui.elements.countdown.textContent = gameState.countdown;
        
        if (gameState.countdown > 0) {
            gameState.countdown--;
            setTimeout(countdownStep, 1000);
        } else {
            ui.elements.countdown.textContent = 'GO!';
            gameState.isPlaying = true;
            
            setTimeout(() => {
                ui.elements.countdown.style.opacity = '0';
            }, 100);
        }
    }
    countdownStep();

    animate();
}

function animate() {
    ctx.fillStyle = '#ffffff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 更新计分板
    ui.updateScore(gameState.collisions, CONFIG.MAX_HITS);

    gameState.balls.forEach(ball => {
        if (gameState.isPlaying) ball.update();
        ball.draw();
    });
    
    if (gameState.isPlaying) {
        processCollisions();
    }
    
    requestAnimationFrame(animate);
}

window.addEventListener('resize', () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    initGame();
});

// 初始化
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ui.elements.startBtn.addEventListener('click', startGame);
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
    <meta name="robots" content="noindex, nofollow"/>
    <meta name="googlebot" content="noindex, nofollow"/>
    <title>Avoid The Red Ball</title>
    <link rel="stylesheet" type="text/css" href="main.css"/>
</head>
<body>
        <canvas>
            <h1>提示:你的浏览器不支持canvas标签!</h1>
        </canvas>
<script type="application/javascript" src="main.js"></script>
</body>
</html>
html, body {
  margin: 0;
  padding: 0;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

canvas {
  display: block;
  width: 100%;
  height: 100%;
}