Created
March 18, 2025 00:40
-
-
Save kishida/3974571e7020252cf9543ae84d2c2fba to your computer and use it in GitHub Desktop.
Claude 3.7 sonnetに見た目にこだわったブロック崩しをJavaのSwingで作ってもらった
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.swing.*; | |
import java.awt.*; | |
import java.awt.event.*; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
import java.awt.geom.RoundRectangle2D; | |
public class BlockBreaker extends JFrame { | |
private GamePanel gamePanel; | |
public BlockBreaker() { | |
setTitle("ブロック崩し"); | |
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); | |
setResizable(false); | |
gamePanel = new GamePanel(); | |
add(gamePanel); | |
pack(); | |
setLocationRelativeTo(null); | |
setVisible(true); | |
} | |
public static void main(String[] args) { | |
SwingUtilities.invokeLater(() -> new BlockBreaker()); | |
} | |
class GamePanel extends JPanel implements ActionListener, KeyListener { | |
private static final int WIDTH = 800; | |
private static final int HEIGHT = 600; | |
private static final int PADDLE_WIDTH = 100; | |
private static final int PADDLE_HEIGHT = 20; | |
private static final int BALL_SIZE = 20; | |
private static final int BLOCK_WIDTH = 60; | |
private static final int BLOCK_HEIGHT = 30; | |
private static final int BLOCK_ROWS = 5; | |
private static final int BLOCK_COLS = 10; | |
private static final int MAX_LIVES = 3; | |
private Timer timer; | |
private int paddleX; | |
private int ballX, ballY; | |
private int ballSpeedX, ballSpeedY; | |
private List<Block> blocks; | |
private boolean inGame; | |
private boolean gameOver; | |
private int lives; | |
private int score; | |
private Color[] blockColors; | |
// エフェクト用の変数 | |
private List<Particle> particles; | |
private Random random; | |
public GamePanel() { | |
setPreferredSize(new Dimension(WIDTH, HEIGHT)); | |
setBackground(new Color(20, 20, 50)); | |
setFocusable(true); | |
addKeyListener(this); | |
random = new Random(); | |
particles = new ArrayList<>(); | |
// 鮮やかなブロックの色 | |
blockColors = new Color[] { | |
new Color(255, 87, 87), // 赤 | |
new Color(255, 189, 87), // オレンジ | |
new Color(255, 255, 87), // 黄色 | |
new Color(87, 255, 87), // 緑 | |
new Color(87, 87, 255), // 青 | |
new Color(189, 87, 255) // 紫 | |
}; | |
initGame(); | |
} | |
private void initGame() { | |
paddleX = WIDTH / 2 - PADDLE_WIDTH / 2; | |
ballX = WIDTH / 2 - BALL_SIZE / 2; | |
ballY = HEIGHT - PADDLE_HEIGHT - BALL_SIZE - 30; | |
ballSpeedX = 3; | |
ballSpeedY = -3; | |
blocks = new ArrayList<>(); | |
inGame = true; | |
gameOver = false; | |
lives = MAX_LIVES; | |
score = 0; | |
particles.clear(); | |
// ブロックの初期化 | |
for (int i = 0; i < BLOCK_ROWS; i++) { | |
for (int j = 0; j < BLOCK_COLS; j++) { | |
int x = j * (BLOCK_WIDTH + 10) + 50; | |
int y = i * (BLOCK_HEIGHT + 10) + 50; | |
int hp = BLOCK_ROWS - i; | |
Color color = blockColors[i % blockColors.length]; | |
blocks.add(new Block(x, y, BLOCK_WIDTH, BLOCK_HEIGHT, hp, color)); | |
} | |
} | |
// タイマー開始 | |
if (timer != null) { | |
timer.stop(); | |
} | |
timer = new Timer(10, this); | |
timer.start(); | |
} | |
@Override | |
protected void paintComponent(Graphics g) { | |
super.paintComponent(g); | |
Graphics2D g2d = (Graphics2D) g; | |
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); | |
// 背景のグラデーション | |
GradientPaint gradient = new GradientPaint( | |
0, 0, new Color(20, 20, 60), | |
0, HEIGHT, new Color(10, 10, 30) | |
); | |
g2d.setPaint(gradient); | |
g2d.fillRect(0, 0, WIDTH, HEIGHT); | |
if (gameOver) { | |
// ゲームオーバー画面 | |
g2d.setColor(Color.RED); | |
g2d.setFont(new Font("SansSerif", Font.BOLD, 50)); | |
String message = "GAME OVER"; | |
FontMetrics fm = g2d.getFontMetrics(); | |
g2d.drawString(message, (WIDTH - fm.stringWidth(message)) / 2, HEIGHT / 2 - 30); | |
g2d.setColor(Color.WHITE); | |
g2d.setFont(new Font("SansSerif", Font.BOLD, 30)); | |
message = "スコア: " + score; | |
fm = g2d.getFontMetrics(); | |
g2d.drawString(message, (WIDTH - fm.stringWidth(message)) / 2, HEIGHT / 2 + 20); | |
g2d.setFont(new Font("SansSerif", Font.BOLD, 20)); | |
message = "Enterキーで再スタート"; | |
fm = g2d.getFontMetrics(); | |
g2d.drawString(message, (WIDTH - fm.stringWidth(message)) / 2, HEIGHT / 2 + 60); | |
return; | |
} | |
// パーティクルの描画 | |
for (Particle p : particles) { | |
p.draw(g2d); | |
} | |
// ブロックの描画 | |
for (Block block : blocks) { | |
block.draw(g2d); | |
} | |
// パドルの描画(グラデーション) | |
GradientPaint paddleGradient = new GradientPaint( | |
paddleX, HEIGHT - PADDLE_HEIGHT - 10, new Color(100, 180, 255), | |
paddleX, HEIGHT - 10, new Color(50, 100, 200) | |
); | |
g2d.setPaint(paddleGradient); | |
RoundRectangle2D paddle = new RoundRectangle2D.Double( | |
paddleX, HEIGHT - PADDLE_HEIGHT - 10, | |
PADDLE_WIDTH, PADDLE_HEIGHT, 10, 10 | |
); | |
g2d.fill(paddle); | |
// パドルの光沢効果 | |
g2d.setColor(new Color(255, 255, 255, 80)); | |
g2d.fillRect(paddleX + 5, HEIGHT - PADDLE_HEIGHT - 8, PADDLE_WIDTH - 10, 5); | |
// ボールの描画 | |
RadialGradientPaint ballGradient = new RadialGradientPaint( | |
new Point(ballX + BALL_SIZE/2, ballY + BALL_SIZE/2), | |
BALL_SIZE/2, | |
new float[]{0.0f, 1.0f}, | |
new Color[]{Color.WHITE, new Color(100, 180, 255)} | |
); | |
g2d.setPaint(ballGradient); | |
g2d.fillOval(ballX, ballY, BALL_SIZE, BALL_SIZE); | |
// 残りライフと得点の表示 | |
g2d.setColor(Color.WHITE); | |
g2d.setFont(new Font("SansSerif", Font.BOLD, 20)); | |
g2d.drawString("残り: " + lives, 20, 30); | |
g2d.drawString("スコア: " + score, WIDTH - 150, 30); | |
} | |
private boolean leftPressed = false; | |
private boolean rightPressed = false; | |
@Override | |
public void actionPerformed(ActionEvent e) { | |
if (!inGame || gameOver) return; | |
// キーの状態に基づいてパドルを移動 | |
if (leftPressed) { | |
paddleX -= 8; // 移動速度を調整 | |
if (paddleX < 0) { | |
paddleX = 0; | |
} | |
} | |
if (rightPressed) { | |
paddleX += 8; // 移動速度を調整 | |
if (paddleX > WIDTH - PADDLE_WIDTH) { | |
paddleX = WIDTH - PADDLE_WIDTH; | |
} | |
} | |
// パーティクルの更新 | |
for (int i = particles.size() - 1; i >= 0; i--) { | |
Particle p = particles.get(i); | |
p.update(); | |
if (p.isExpired()) { | |
particles.remove(i); | |
} | |
} | |
// ボールの移動 | |
ballX += ballSpeedX; | |
ballY += ballSpeedY; | |
// 壁との衝突 | |
if (ballX <= 0) { | |
ballX = 0; | |
ballSpeedX = -ballSpeedX; | |
createParticles(ballX, ballY, Color.WHITE); | |
} | |
if (ballX >= WIDTH - BALL_SIZE) { | |
ballX = WIDTH - BALL_SIZE; | |
ballSpeedX = -ballSpeedX; | |
createParticles(ballX + BALL_SIZE, ballY, Color.WHITE); | |
} | |
if (ballY <= 0) { | |
ballY = 0; | |
ballSpeedY = -ballSpeedY; | |
createParticles(ballX, ballY, Color.WHITE); | |
} | |
// パドルとの衝突 | |
if (ballY >= HEIGHT - PADDLE_HEIGHT - BALL_SIZE - 10 && | |
ballY <= HEIGHT - BALL_SIZE - 5 && | |
ballX + BALL_SIZE >= paddleX && | |
ballX <= paddleX + PADDLE_WIDTH) { | |
ballY = HEIGHT - PADDLE_HEIGHT - BALL_SIZE - 10; | |
ballSpeedY = -Math.abs(ballSpeedY); | |
// パドルの位置に応じて反射角度を変える | |
float paddleCenter = paddleX + PADDLE_WIDTH / 2.0f; | |
float ballCenter = ballX + BALL_SIZE / 2.0f; | |
float ratio = (ballCenter - paddleCenter) / (PADDLE_WIDTH / 2.0f); | |
ballSpeedX = (int)(ratio * 5); | |
createParticles(ballX, ballY + BALL_SIZE, new Color(100, 180, 255)); | |
} | |
// ブロックとの衝突 | |
for (int i = blocks.size() - 1; i >= 0; i--) { | |
Block block = blocks.get(i); | |
if (ballX + BALL_SIZE >= block.x && | |
ballX <= block.x + block.width && | |
ballY + BALL_SIZE >= block.y && | |
ballY <= block.y + block.height) { | |
// ブロックのヒットポイントを減らす | |
block.hp--; | |
score += 10; | |
// エフェクトの生成 | |
createParticles( | |
ballX + BALL_SIZE/2, | |
ballY + BALL_SIZE/2, | |
block.color | |
); | |
// ブロックが破壊されたらリストから削除 | |
if (block.hp <= 0) { | |
blocks.remove(i); | |
score += 50; | |
} | |
// 衝突した方向によって反射方向を変える | |
// 横からの衝突 | |
if ((ballX + BALL_SIZE - ballSpeedX <= block.x || ballX - ballSpeedX >= block.x + block.width)) { | |
ballSpeedX = -ballSpeedX; | |
} | |
// 縦からの衝突 | |
else { | |
ballSpeedY = -ballSpeedY; | |
} | |
break; | |
} | |
} | |
// ボールが下に落ちた場合 | |
if (ballY > HEIGHT) { | |
lives--; | |
if (lives <= 0) { | |
gameOver = true; | |
} else { | |
// リセット | |
ballX = WIDTH / 2 - BALL_SIZE / 2; | |
ballY = HEIGHT - PADDLE_HEIGHT - BALL_SIZE - 30; | |
ballSpeedX = 3; | |
ballSpeedY = -3; | |
} | |
} | |
// すべてのブロックを壊したら | |
if (blocks.isEmpty()) { | |
inGame = false; | |
gameOver = true; | |
} | |
repaint(); | |
} | |
private void createParticles(int x, int y, Color color) { | |
for (int i = 0; i < 20; i++) { | |
particles.add(new Particle(x, y, color)); | |
} | |
} | |
@Override | |
public void keyTyped(KeyEvent e) {} | |
@Override | |
public void keyPressed(KeyEvent e) { | |
int key = e.getKeyCode(); | |
if (gameOver && key == KeyEvent.VK_ENTER) { | |
initGame(); | |
return; | |
} | |
if (!inGame) return; | |
if (key == KeyEvent.VK_LEFT) { | |
leftPressed = true; | |
} | |
if (key == KeyEvent.VK_RIGHT) { | |
rightPressed = true; | |
} | |
} | |
@Override | |
public void keyReleased(KeyEvent e) { | |
int key = e.getKeyCode(); | |
if (key == KeyEvent.VK_LEFT) { | |
leftPressed = false; | |
} | |
if (key == KeyEvent.VK_RIGHT) { | |
rightPressed = false; | |
} | |
} | |
} | |
class Block { | |
int x, y, width, height, hp; | |
Color color; | |
public Block(int x, int y, int width, int height, int hp, Color color) { | |
this.x = x; | |
this.y = y; | |
this.width = width; | |
this.height = height; | |
this.hp = hp; | |
this.color = color; | |
} | |
public void draw(Graphics2D g2d) { | |
// HPに応じて色を薄くする | |
Color baseColor = new Color( | |
color.getRed(), | |
color.getGreen(), | |
color.getBlue(), | |
128 + 127 * hp / 5 | |
); | |
// ブロックの本体 | |
GradientPaint gradient = new GradientPaint( | |
x, y, baseColor, | |
x, y + height, baseColor.darker() | |
); | |
g2d.setPaint(gradient); | |
RoundRectangle2D block = new RoundRectangle2D.Double( | |
x, y, width, height, 8, 8 | |
); | |
g2d.fill(block); | |
// ブロックの枠線 | |
g2d.setColor(baseColor.brighter()); | |
g2d.draw(block); | |
// 光沢効果 | |
g2d.setColor(new Color(255, 255, 255, 80)); | |
g2d.fillRect(x + 3, y + 3, width - 6, 5); | |
} | |
} | |
class Particle { | |
private float x, y; | |
private float speedX, speedY; | |
private float size; | |
private int life; | |
private int maxLife; | |
private Color color; | |
private Random random = new Random(); | |
public Particle(int x, int y, Color baseColor) { | |
this.x = x; | |
this.y = y; | |
this.speedX = (random.nextFloat() - 0.5f) * 6; | |
this.speedY = (random.nextFloat() - 0.5f) * 6; | |
this.size = random.nextFloat() * 5 + 2; | |
this.maxLife = 20 + random.nextInt(30); | |
this.life = maxLife; | |
this.color = baseColor; | |
} | |
public void update() { | |
x += speedX; | |
y += speedY; | |
speedY += 0.1f; // 重力 | |
life--; | |
} | |
public void draw(Graphics2D g2d) { | |
float alpha = (float)life / maxLife; | |
g2d.setColor(new Color( | |
color.getRed()/255f, | |
color.getGreen()/255f, | |
color.getBlue()/255f, | |
alpha | |
)); | |
g2d.fillOval((int)x, (int)y, (int)size, (int)size); | |
} | |
public boolean isExpired() { | |
return life <= 0; | |
} | |
} | |
} |
プロンプト「JavaのSwingでブロック崩しを作って。3回まで失敗していい。3回失敗したらゲームオーバー表示して、キー入力で新規ゲームを。ちょっと見た目にこだわってみて。」
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
temp.mp4