Skip to content

Instantly share code, notes, and snippets.

@kishida
Created March 18, 2025 00:40
Show Gist options
  • Save kishida/3974571e7020252cf9543ae84d2c2fba to your computer and use it in GitHub Desktop.
Save kishida/3974571e7020252cf9543ae84d2c2fba to your computer and use it in GitHub Desktop.
Claude 3.7 sonnetに見た目にこだわったブロック崩しをJavaのSwingで作ってもらった
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;
}
}
}
@kishida
Copy link
Author

kishida commented Mar 18, 2025

temp.mp4

@kishida
Copy link
Author

kishida commented Mar 18, 2025

プロンプト「JavaのSwingでブロック崩しを作って。3回まで失敗していい。3回失敗したらゲームオーバー表示して、キー入力で新規ゲームを。ちょっと見た目にこだわってみて。」

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment