はじめに
遅ればせながら 『Amazon Q CLi Developer で ゲームを作ろう』 のキャンペーンに応募すべく、ゲームを作成しました。
Amazon Q Developer CLI はリリース当初からお世話になっている推しAIツールので、このTシャツは絶対に欲しい。
動作環境
- Windows 11
- WSL2
- ubuntu
- Amazon Q Developer CLI
セッティング
丁度、Claude4へのモデル変更が可能になったアップデートが入っていたので、せっかくなので最新のClaudeのモデルの性能も確認すべく、モデルの変更を実施。
/model
でモデル変更の選択肢が現れ、変更が可能に。
もちろん claude4-sonnetを選択。
毎度ですがターミナルに表示されるAAにはロマンがありますね...
q chat --model claude-4-sonnet
で起動時のモデルをclaude4に変更できるので、マストで変更しておきます。
今回作ったもの
インベーダーゲーム
ベタですがレトロゲームをサクッと作ってみたかったのでインベーダーゲームにしました。
実装
Claude4のパワーもあり、3,4回のやり取りで完成しました。
一発目の出力でほぼほぼインベーダーゲームとしての機能はできあがりました。
- 自機・敵機の存在
- 自弾・敵弾の当たり判定
- 衝突判定からの消滅
- 左右への移動・全身
しかしながら自機も敵機も完全に豆腐で色気が無さすぎるので、もう少しグラフィカルに、かつゲーム性を持たせるためスコア表示機能の追加を依頼。
追加で以下の機能も実装しました。
- 残機の追加
- ステージの概念(3面クリアで終了)
- クリアタイムの記録
- GAME OVER 機能
- 敵弾が命中した際の点滅エフェクト
- 自弾が命中した際の爆発エフェクト
開始時は敵機の数が多いのでなかなかの弾幕が降り注ぎます。
舐めてると意外と簡単にやられてしまいますね。
3面倒しきるとクリアです!
まとめ
普段はAWSリソースのパラメータ取得やエラー調査ばかり依頼しているAmazon Q Developer CLIですが、Claude4にアップグレードしたこともあり、これくらいのゲームなら余裕で作ってくれますね。
リリース当初から頻繁なアップデートによりどんどん使いやすくなっていくAmazon Q Developer CLI 君に今後も期待しています!!
作成してくれたコード
良かったら遊んでみてください!
kuraken_space_invaders.py
import pygame
import random
import sys
# Initialize Pygame
pygame.init()
# Screen settings
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Space Invaders - Advanced")
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)
GRAY = (128, 128, 128)
# Font
font = pygame.font.Font(None, 36)
small_font = pygame.font.Font(None, 24)
# Player class
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface([50, 30], pygame.SRCALPHA)
self.original_image = pygame.Surface([50, 30], pygame.SRCALPHA)
self.draw_fighter()
self.rect = self.image.get_rect()
self.rect.x = SCREEN_WIDTH // 2
self.rect.y = SCREEN_HEIGHT - 50
self.speed = 5
self.lives = 3
self.invincible = False
self.invincible_timer = 0
self.blink_timer = 0
def draw_fighter(self):
"""Draw a fighter jet shape"""
# Clear both surfaces
self.image.fill((0, 0, 0, 0))
self.original_image.fill((0, 0, 0, 0))
# Main body
pygame.draw.polygon(self.image, GREEN, [
(25, 0), # nose
(15, 20), # left wing back
(20, 25), # left body
(30, 25), # right body
(35, 20), # right wing back
(25, 0) # back to nose
])
# Wings
pygame.draw.polygon(self.image, GRAY, [
(10, 15), (15, 20), (20, 25), (15, 25)
])
pygame.draw.polygon(self.image, GRAY, [
(40, 15), (35, 20), (30, 25), (35, 25)
])
# Cockpit
pygame.draw.circle(self.image, BLUE, (25, 10), 3)
# Copy to original image
self.original_image.blit(self.image, (0, 0))
def take_damage(self):
"""Handle taking damage"""
if not self.invincible:
self.lives -= 1
self.invincible = True
self.invincible_timer = 120 # 2 seconds at 60 FPS
return True
return False
def update(self):
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and self.rect.left > 0:
self.rect.x -= self.speed
if keys[pygame.K_RIGHT] and self.rect.right < SCREEN_WIDTH:
self.rect.x += self.speed
# Handle invincibility and blinking
if self.invincible:
self.invincible_timer -= 1
self.blink_timer += 1
# Blink every 8 frames
if self.blink_timer % 8 < 4:
# Make transparent (blink off)
self.image.fill((0, 0, 0, 0))
else:
# Show normal image (blink on)
self.image.fill((0, 0, 0, 0))
self.image.blit(self.original_image, (0, 0))
# End invincibility
if self.invincible_timer <= 0:
self.invincible = False
self.blink_timer = 0
# Restore normal image
self.image.fill((0, 0, 0, 0))
self.image.blit(self.original_image, (0, 0))
# Enemy class
class Enemy(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([30, 30], pygame.SRCALPHA)
self.draw_ufo()
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.speed_x = 2
self.speed_y = 20
self.direction = 1
self.shoot_timer = random.randint(60, 300)
def draw_ufo(self):
"""Draw a UFO shape"""
# Main body (ellipse)
pygame.draw.ellipse(self.image, GRAY, (5, 10, 20, 10))
# Top dome
pygame.draw.ellipse(self.image, RED, (8, 5, 14, 12))
# Bottom lights
pygame.draw.circle(self.image, YELLOW, (10, 15), 2)
pygame.draw.circle(self.image, YELLOW, (15, 15), 2)
pygame.draw.circle(self.image, YELLOW, (20, 15), 2)
def update(self):
# Move horizontally
self.rect.x += self.speed_x * self.direction
# Check if this enemy hits the screen edge
if self.rect.right >= SCREEN_WIDTH or self.rect.left <= 0:
# Move all enemies down and reverse their direction
for enemy in enemies:
enemy.rect.y += enemy.speed_y
enemy.direction *= -1
# Move back from the edge to prevent getting stuck
if self.rect.right >= SCREEN_WIDTH:
self.rect.right = SCREEN_WIDTH
else:
self.rect.left = 0
# Shoot randomly
self.shoot_timer -= 1
if self.shoot_timer <= 0:
self.shoot_timer = random.randint(300, 600)
enemy_bullet = EnemyBullet(self.rect.centerx, self.rect.bottom)
enemy_bullets.add(enemy_bullet)
all_sprites.add(enemy_bullet)
# Player bullet class
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([4, 10])
self.image.fill(WHITE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.speed = 7
def update(self):
self.rect.y -= self.speed
if self.rect.bottom < 0:
self.kill()
# Enemy bullet class
class EnemyBullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface([4, 10])
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.speed = 3
def update(self):
self.rect.y += self.speed
if self.rect.top > SCREEN_HEIGHT:
self.kill()
# Explosion effect class
class Explosion(pygame.sprite.Sprite):
def __init__(self, x, y, score=0):
super().__init__()
self.images = []
# Create explosion animation frames
for size in [20, 25, 30, 25, 20, 15, 10]:
img = pygame.Surface([size, size], pygame.SRCALPHA)
pygame.draw.circle(img, ORANGE, (size//2, size//2), size//2)
self.images.append(img)
self.index = 0
self.image = self.images[self.index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.counter = 0
# Score display
self.score = score
self.score_counter = 30 # frames to show score
self.score_y_offset = 0
def update(self):
self.counter += 1
if self.counter >= 4: # Update animation every 4 frames
self.counter = 0
self.index += 1
if self.index >= len(self.images):
self.kill()
else:
self.image = self.images[self.index]
# Update floating score position
if self.score > 0:
self.score_y_offset -= 1
self.score_counter -= 1
if self.score_counter <= 0:
self.score = 0
# Game state
class GameState:
def __init__(self):
self.score = 0
self.level = 1
self.game_over = False
self.victory = False
self.level_clear = False
self.start_time = pygame.time.get_ticks()
self.level_start_time = pygame.time.get_ticks()
self.clear_time = 0
def draw_text(text, x, y, color=WHITE):
"""Draw text on screen"""
text_surface = font.render(text, True, color)
screen.blit(text_surface, (x, y))
def game_over_screen():
"""Display game over screen"""
screen.fill(BLACK)
draw_text("GAME OVER", SCREEN_WIDTH//2 - 100, SCREEN_HEIGHT//2 - 50, RED)
draw_text(f"Final Score: {game_state.score}", SCREEN_WIDTH//2 - 120, SCREEN_HEIGHT//2)
draw_text("Press R to restart or Q to quit", SCREEN_WIDTH//2 - 180, SCREEN_HEIGHT//2 + 50)
pygame.display.flip()
def victory_screen():
"""Display victory screen"""
screen.fill(BLACK)
draw_text("VICTORY!", SCREEN_WIDTH//2 - 80, SCREEN_HEIGHT//2 - 50, GREEN)
draw_text(f"Final Score: {game_state.score}", SCREEN_WIDTH//2 - 120, SCREEN_HEIGHT//2)
draw_text("Press R to restart or Q to quit", SCREEN_WIDTH//2 - 180, SCREEN_HEIGHT//2 + 50)
pygame.display.flip()
def level_clear_screen():
"""Display level clear screen"""
screen.fill(BLACK)
draw_text("LEVEL CLEAR!", SCREEN_WIDTH//2 - 100, SCREEN_HEIGHT//2 - 50, GREEN)
clear_time = (game_state.clear_time - game_state.level_start_time) / 1000 # Convert to seconds
draw_text(f"Clear Time: {clear_time:.2f} seconds", SCREEN_WIDTH//2 - 150, SCREEN_HEIGHT//2)
draw_text(f"Score: {game_state.score}", SCREEN_WIDTH//2 - 80, SCREEN_HEIGHT//2 + 50)
draw_text("Press SPACE to continue", SCREEN_WIDTH//2 - 150, SCREEN_HEIGHT//2 + 100)
pygame.display.flip()
def create_enemies():
"""Create a new wave of enemies"""
for row in range(5):
for column in range(8):
enemy = Enemy(80 + column * 80, 50 + row * 50)
enemies.add(enemy)
all_sprites.add(enemy)
# Reset level start time when creating new enemies
game_state.level_start_time = pygame.time.get_ticks()
# Create sprite groups
all_sprites = pygame.sprite.Group()
enemies = pygame.sprite.Group()
bullets = pygame.sprite.Group()
enemy_bullets = pygame.sprite.Group()
explosions = pygame.sprite.Group()
# Create player
player = Player()
all_sprites.add(player)
# Create game state
game_state = GameState()
# Create initial enemies
create_enemies()
# Game loop
running = True
clock = pygame.time.Clock()
while running:
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if game_state.game_over or game_state.victory:
if event.key == pygame.K_r:
# Restart game
game_state = GameState()
player.lives = 3
player.rect.x = SCREEN_WIDTH // 2
player.rect.y = SCREEN_HEIGHT - 50
# Clear all sprites except player
for sprite in all_sprites:
if sprite != player:
sprite.kill()
enemies.empty()
bullets.empty()
enemy_bullets.empty()
explosions.empty()
create_enemies()
elif event.key == pygame.K_q:
running = False
elif game_state.level_clear:
if event.key == pygame.K_SPACE:
# Continue to next level
game_state.level_clear = False
game_state.level += 1
create_enemies()
# Increase difficulty
for enemy in enemies:
enemy.speed_x += 0.5
else:
if event.key == pygame.K_SPACE:
bullet = Bullet(player.rect.centerx - 2, player.rect.top)
bullets.add(bullet)
all_sprites.add(bullet)
if not game_state.game_over and not game_state.victory and not game_state.level_clear:
# Update
all_sprites.update()
explosions.update()
# Collision detection - player bullets hit enemies
hits = pygame.sprite.groupcollide(enemies, bullets, True, True)
for enemy in hits:
# Create explosion effect
explosion = Explosion(enemy.rect.centerx, enemy.rect.centery, 10)
explosions.add(explosion)
all_sprites.add(explosion)
game_state.score += 10
# Collision detection - enemy bullets hit player
player_hits = pygame.sprite.spritecollide(player, enemy_bullets, True)
if player_hits and player.take_damage():
if player.lives <= 0:
game_state.game_over = True
# Check if enemies reach player
if pygame.sprite.spritecollide(player, enemies, False):
game_state.game_over = True
# Check if all enemies are destroyed
if len(enemies) == 0:
game_state.clear_time = pygame.time.get_ticks()
game_state.level_clear = True
# Check victory condition (for demo purposes, victory at level 3)
if game_state.level > 3:
game_state.victory = True
# Draw
if game_state.game_over:
game_over_screen()
elif game_state.victory:
victory_screen()
elif game_state.level_clear:
level_clear_screen()
else:
screen.fill(BLACK)
all_sprites.draw(screen)
explosions.draw(screen)
# Draw floating scores from explosions
for explosion in explosions:
if explosion.score > 0:
score_text = small_font.render(f"+{explosion.score}", True, YELLOW)
screen.blit(score_text, (explosion.rect.centerx - 10,
explosion.rect.centery - 20 + explosion.score_y_offset))
# Draw UI
draw_text(f"Score: {game_state.score}", 10, 10)
draw_text(f"Lives: {player.lives}", 10, 50)
draw_text(f"Level: {game_state.level}", 10, 90)
# Draw elapsed time
elapsed_time = (pygame.time.get_ticks() - game_state.level_start_time) / 1000
draw_text(f"Time: {elapsed_time:.2f}", SCREEN_WIDTH - 150, 10)
pygame.display.flip()
# Control game speed
clock.tick(60)
# Quit game
pygame.quit()
sys.exit()
```
</details>