4
6

銀河の中心にブラックホールを置き、その周りを多数のカラフルなパーティクルが回るゲーム。

Last updated at Posted at 2024-08-13

image.png

スクリーンショット 2024-08-14 065051.png

画面左上にブラックホールを置き、その周りを多数のカラフルなパーティクルが回るゲームです。

スペースキーを押すとゲームが始まります。

import http.server
import socketserver
import tempfile
import webbrowser

html_content = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Galaxy Simulation</title>
    <style>
        body { margin: 0; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        // Canvasと2Dコンテキストを取得
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');
        // パーティクルの配列と設定
        let particles = [];
        const numParticles = 100; // パーティクルの数
        const centerX = canvas.width / 2; // 中心X座標
        const centerY = canvas.height / 2; // 中心Y座標
        const blackHoleRadius = 20; // ブラックホールの半径
        let isSimulating = false; // シミュレーションの状態

        // Canvasのサイズをウィンドウに合わせる
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        // パーティクルを定義するクラス
        function Particle(x, y, vx, vy, radius, color) {
            this.x = x; // X座標
            this.y = y; // Y座標
            this.vx = vx; // X方向の速度
            this.vy = vy; // Y方向の速度
            this.radius = radius; // 半径
            this.color = color; // 色
        }

        // パーティクルを生成する関数
        function createParticles() {
            particles = []; // 配列を初期化
            for (let i = 0; i < numParticles; i++) {
                const angle = Math.random() * 2 * Math.PI; // 角度をランダムに生成
                const speed = 0.5 + Math.random() * 1.5; // 速度をランダムに設定
                const radius = 5 + Math.random() * 10; // 半径をランダムに設定
                const color = `hsl(${Math.random() * 360}, 100%, 50%)`; // 色をランダムに設定
                const x = centerX + (blackHoleRadius + 50) * Math.cos(angle); // 初期X座標
                const y = centerY + (blackHoleRadius + 50) * Math.sin(angle); // 初期Y座標
                const vx = -speed * Math.sin(angle); // 初期X方向の速度
                const vy = speed * Math.cos(angle); // 初期Y方向の速度
                particles.push(new Particle(x, y, vx, vy, radius, color)); // パーティクルを追加
            }
        }

        // パーティクルの位置と速度を更新する関数
        function updateParticles() {
            if (!isSimulating) return; // シミュレーションがオフの場合は処理しない

            particles.forEach(p => {
                const dx = p.x - centerX; // 中心からのX方向の距離
                const dy = p.y - centerY; // 中心からのY方向の距離
                const distance = Math.sqrt(dx * dx + dy * dy); // 距離
                const force = 500 / (distance * distance); // 重力の強さ
                const ax = -force * (dx / distance); // X方向の加速度
                const ay = -force * (dy / distance); // Y方向の加速度
                p.vx += ax; // X方向の速度を更新
                p.vy += ay; // Y方向の速度を更新
                p.x += p.vx; // X座標を更新
                p.y += p.vy; // Y座標を更新

                // エッジでのバウンス処理
                if (p.x < 0 || p.x > canvas.width) p.vx *= -1;
                if (p.y < 0 || p.y > canvas.height) p.vy *= -1;
            });
        }

        // パーティクルとブラックホールを描画する関数
        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height); // Canvasをクリア
            ctx.beginPath();
            ctx.arc(centerX, centerY, blackHoleRadius, 0, 2 * Math.PI); // ブラックホールを描画
            ctx.fillStyle = 'black';
            ctx.fill();
            particles.forEach(p => {
                ctx.beginPath();
                ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI); // パーティクルを描画
                ctx.fillStyle = p.color;
                ctx.fill();
            });
        }

        // シミュレーションのループ
        function loop() {
            updateParticles(); // パーティクルを更新
            draw(); // 描画
            requestAnimationFrame(loop); // 次のフレームをリクエスト
        }

        // スペースキーでシミュレーションを開始/停止するイベントリスナー
        window.addEventListener('keydown', (e) => {
            if (e.code === 'Space') {
                isSimulating = !isSimulating; // シミュレーションの状態を切り替え
                if (isSimulating) {
                    createParticles(); // パーティクルを作成
                    loop(); // ループを開始
                }
            }
        });

        // ウィンドウリサイズ時にCanvasのサイズを調整
        window.addEventListener('resize', () => {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        });
    </script>
</body>
</html>

"""

with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as temp_html:
    temp_html.write(html_content.encode("utf-8"))
    temp_html.flush()
    webbrowser.open(f"file://{temp_html.name}")

PORT = 8000
Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print(f"Serving at port {PORT}")
    httpd.serve_forever()

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6