テニスゲーム

ゲーム

これは改良版です。

これが初期の「テニスゲーム」です。このプログラムは以下のようになっています。


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()

タイトルとURLをコピーしました