画面左上にブラックホールを置き、その周りを多数のカラフルなパーティクルが回るゲームです。
スペースキーを押すとゲームが始まります。
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()