これは改良版です。
これが初期の「テニスゲーム」です。このプログラムは以下のようになっています。
tennis_pygame.py — pygame-ce版(サウンドは安全に初期化、失敗しても続行)
import pygame, sys, random, math, io, wave, struct
W, H, FPS = 1000, 600, 60
WHITE=(240,240,240); CYAN=(120,220,235); YELLOW=(250,220,80); BG=(15,18,25)
WIN_POINT = 11
簡易サウンド(合成、追加ファイル不要)
SR = 44100
def _wav_bytes(int16_bytes):
bio = io.BytesIO()
import wave
with wave.open(bio, “wb”) as wf:
wf.setnchannels(1); wf.setsampwidth(2); wf.setframerate(SR)
wf.writeframes(int16_bytes)
bio.seek(0); return bio
def tone(freq=600, ms=120, vol=0.5, wave_type=”sine”):
n = int(SRms/1000); buf = bytearray() for i in range(n): t = i / SR if wave_type == “sine”: v = math.sin(2math.pifreqt)
elif wave_type == “square”:
v = 1.0 if math.sin(2math.pifreqt) >= 0 else -1.0 else: v = math.sin(2math.pifreqt)
v = 1.0 – i/max(1,n) buf += struct.pack(“32767*vol))
return pygame.mixer.Sound(_wav_bytes(buf))
class Tennis:
def init(self):
pygame.init()
# サウンド初期化(失敗しても動く)
self.sound_ok = False
try:
pygame.mixer.init(frequency=SR, size=-16, channels=1, buffer=1024)
self.sound_ok = True
except Exception as e:
print(“Sound init error:”, e)
self.sound_enabled = True
self.screen = pygame.display.set_mode((W,H))
pygame.display.set_caption("Tennis (Pygame)")
self.clock = pygame.time.Clock()
self.font = pygame.font.SysFont(None, 36)
self.big = pygame.font.SysFont(None, 84)
self.mid = pygame.font.SysFont(None, 54)
if self.sound_ok:
self.snd_hit = tone(700, 60, 0.5, "square")
self.snd_wall = tone(500, 50, 0.4, "sine")
self.snd_score = tone(330,160, 0.6, "sine")
self.snd_serve = tone(900, 80, 0.4, "square")
else:
self.snd_hit = self.snd_wall = self.snd_score = self.snd_serve = None
self.state = "TITLE" # TITLE / PLAY / PAUSE / GAMEOVER
self.mode = "P1" # P1 / P2
self.pw, self.ph = 14, 110
self.left = pygame.Rect(40, H//2 - self.ph//2, self.pw, self.ph)
self.right = pygame.Rect(W-40-self.pw, H//2 - self.ph//2, self.pw, self.ph)
self.ball = pygame.Rect(W//2-8, H//2-8, 16, 16)
self.left_speed = self.right_speed = 7
self.ball_vx = self.ball_vy = 0
self.scoreL = self.scoreR = 0
self.server = random.choice(["L","R"])
self.title_anim_ms = 0
def serve(self, who):
self.ball.center = (W//2, H//2)
angle = random.uniform(-0.35, 0.35)
speed = 7.0
self.ball_vx = speed * (1 if who=="R" else -1)
self.ball_vy = speed * math.sin(angle)
if self.sound_ok and self.sound_enabled and self.snd_serve:
self.snd_serve.play()
def reset_match(self):
self.scoreL = self.scoreR = 0
self.server = random.choice(["L","R"])
self.ball_vx = self.ball_vy = 0
self.ball.center = (W//2, H//2)
self.left.centery = self.right.centery = H//2
def run(self):
self.reset_match()
while True:
dt = self.clock.tick(FPS)
self.handle_events()
self.update(dt)
self.draw()
def handle_events(self):
for e in pygame.event.get():
if e.type == pygame.QUIT:
pygame.quit(); sys.exit()
if e.type == pygame.KEYDOWN:
if self.state == "TITLE":
if e.key == pygame.K_RETURN:
self.state = "PLAY"
if self.ball_vx == 0 == self.ball_vy:
self.serve(self.server)
elif e.key == pygame.K_1: self.mode = "P1"
elif e.key == pygame.K_2: self.mode = "P2"
elif e.key == pygame.K_m: self.sound_enabled = not self.sound_enabled
elif e.key == pygame.K_ESCAPE: pygame.quit(); sys.exit()
elif self.state == "PLAY":
if e.key == pygame.K_ESCAPE: self.state = "PAUSE"
elif e.key == pygame.K_m: self.sound_enabled = not self.sound_enabled
elif e.key == pygame.K_SPACE and self.ball_vx == 0 == self.ball_vy:
self.serve(self.server)
elif self.state == "PAUSE":
if e.key == pygame.K_ESCAPE: self.state = "PLAY"
elif e.key == pygame.K_m: self.sound_enabled = not self.sound_enabled
elif self.state == "GAMEOVER":
if e.key == pygame.K_RETURN: self.reset_match(); self.state = "TITLE"
elif e.key == pygame.K_m: self.sound_enabled = not self.sound_enabled
if self.state == "PLAY":
k = pygame.key.get_pressed()
if k[pygame.K_w] and self.left.top > 20: self.left.y -= self.left_speed
if k[pygame.K_s] and self.left.bottom < H-20: self.left.y += self.left_speed
if self.mode == "P2":
if k[pygame.K_UP] and self.right.top > 20: self.right.y -= self.right_speed
if k[pygame.K_DOWN] and self.right.bottom < H-20: self.right.y += self.right_speed
if k[pygame.K_SPACE] and (self.ball_vx == 0 == self.ball_vy):
self.serve(self.server)
def update(self, dt):
if self.state != "PLAY":
self.title_anim_ms += dt
return
if self.mode == "P1":
target = self.ball.centery + random.randint(-14, 14)
if self.right.centery < target and self.right.bottom < H-20: self.right.y += self.right_speed
elif self.right.centery > target and self.right.top > 20: self.right.y -= self.right_speed
self.ball.x += self.ball_vx; self.ball.y += self.ball_vy
if self.ball.top <= 20:
self.ball.top = 20; self.ball_vy *= -1
if self.sound_ok and self.sound_enabled and self.snd_wall: self.snd_wall.play()
if self.ball.bottom >= H-20:
self.ball.bottom = H-20; self.ball_vy *= -1
if self.sound_ok and self.sound_enabled and self.snd_wall: self.snd_wall.play()
def bounce(pad, from_left=True):
if self.ball.colliderect(pad):
rel = (self.ball.centery - pad.centery) / (pad.height/2)
rel = max(-1.0, min(1.0, rel))
speed = abs(self.ball_vx) + 0.8
self.ball_vy = 6.0 * rel
self.ball_vx = (abs(speed) if from_left else -abs(speed))
if self.sound_ok and self.sound_enabled and self.snd_hit: self.snd_hit.play()
if self.ball_vx < 0 and self.ball.colliderect(self.left):
bounce(self.left, True); self.ball.left = self.left.right
if self.ball_vx > 0 and self.ball.colliderect(self.right):
bounce(self.right, False); self.ball.right = self.right.left
if self.ball.right < 0:
self.scoreR += 1; self.server = "L"; self.ball_vx = self.ball_vy = 0; self.ball.center = (W//2, H//2)
if self.sound_ok and self.sound_enabled and self.snd_score: self.snd_score.play()
elif self.ball.left > W:
self.scoreL += 1; self.server = "R"; self.ball_vx = self.ball_vy = 0; self.ball.center = (W//2, H//2)
if self.sound_ok and self.sound_enabled and self.snd_score: self.snd_score.play()
if self.scoreL >= WIN_POINT or self.scoreR >= WIN_POINT:
self.state = "GAMEOVER"
def draw_table(self):
self.screen.fill(BG)
pygame.draw.rect(self.screen,(60,90,110),(16,16,W-32,H-32),4,8)
for y in range(16,H-16,24):
pygame.draw.line(self.screen,(90,120,140),(W//2,y),(W//2,y+12),3)
def draw_title(self):
self.draw_table()
t = self.big.render("TENNIS", True, WHITE)
self.screen.blit(t,(W//2 - t.get_width()//2, 120))
sub = self.mid.render("Press ENTER to Start", True, CYAN)
self.screen.blit(sub,(W//2 - sub.get_width()//2, 210))
mode = self.font.render(f"Mode: {'1P (W/S vs CPU)' if self.mode=='P1' else '2P (W/S & Up/Down)'}", True, WHITE)
self.screen.blit(mode,(W//2 - mode.get_width()//2, 270))
tip = self.font.render("1: 1P / 2: 2P / M: Mute / Esc: Quit", True, (210,210,210))
self.screen.blit(tip,(W//2 - tip.get_width()//2, 310))
phase = (self.title_anim_ms/1000.0) % 2.0
bx = int(100 + (W-200) * (phase if phase<=1 else 2-phase))
by = int(H*0.75 + math.sin(self.title_anim_ms/260.0)*30)
pygame.draw.circle(self.screen, YELLOW, (bx,by), 8)
def draw_hud(self):
sL = self.big.render(str(self.scoreL), True, WHITE)
sR = self.big.render(str(self.scoreR), True, WHITE)
self.screen.blit(sL, (W//2 - 120 - sL.get_width(), 20))
self.screen.blit(sR, (W//2 + 120, 20))
srv = self.font.render(f"Serve: {'Left' if self.server=='L' else 'Right'}", True, WHITE)
self.screen.blit(srv, (W//2 - srv.get_width()//2, 24 + sL.get_height()))
mute = self.font.render("M: Mute OFF" if self.sound_enabled else "M: Mute ON", True, WHITE)
self.screen.blit(mute, (W - mute.get_width() - 16, 16))
def draw_play(self):
self.draw_table()
pygame.draw.rect(self.screen, CYAN, self.left, border_radius=6)
pygame.draw.rect(self.screen, CYAN, self.right, border_radius=6)
pygame.draw.ellipse(self.screen, YELLOW, self.ball)
self.draw_hud()
def draw_pause(self):
self.draw_play()
overlay = pygame.Surface((W,H), pygame.SRCALPHA); overlay.fill((0,0,0,140))
self.screen.blit(overlay,(0,0))
p = self.mid.render("PAUSED", True, WHITE)
s = self.font.render("Press ESC to resume / M: Mute", True, WHITE)
self.screen.blit(p,(W//2 - p.get_width()//2, H//2 - 36))
self.screen.blit(s,(W//2 - s.get_width()//2, H//2 + 10))
def draw_gameover(self):
self.draw_table()
g = self.big.render("GAME SET", True, WHITE)
self.screen.blit(g,(W//2 - g.get_width()//2, 160))
sc = self.mid.render(f"{self.scoreL} - {self.scoreR}", True, CYAN)
self.screen.blit(sc,(W//2 - sc.get_width()//2, 230))
tip = self.font.render("Press ENTER for Title", True, WHITE)
self.screen.blit(tip,(W//2 - tip.get_width()//2, 300))
def draw(self):
if self.state == "TITLE": self.draw_title()
elif self.state == "PLAY": self.draw_play()
elif self.state == "PAUSE": self.draw_pause()
elif self.state == "GAMEOVER": self.draw_gameover()
pygame.display.flip()
if name == “main“:
Tennis().run()

