プレイリストを食べよう

# こんにちは！
Akira_0809です
Spotifyにある**プレイリストを食べてみよう**というミニゲームをプレイしたことがありますか？
最近ハマったので作ってみようと思います！

![RPReplay_Final1705595042-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3241738/27afb780-7dc2-44ba-6473-be3a95937432.gif)

プレイリストが表示されてる間は音楽が流れる感じです

# 準備
- Python
- pygame
- Spotify API

ちょっとした画像もいります。

```python
pip install pygame
pip install spotipy
```
:::note warn
spoti**py**なので注意
:::
これでちょっと時間溶かした、、

Spotify API の取得については公式ドキュメントや他の記事を見てください。

# 曲のダウンロード
まずは曲のアルバム画像と音楽をダウンロードする必要があります。
ゲーム内の処理として入れると面倒なので別のファイルで処理を書き実行します。

```python
import spotipy
from spotipy.oauth2 import SpotifyClientCredentials, SpotifyOAuth

import urllib.request

from PIL import Image

my_id = "ID"
my_secret = "SECRET"

sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=my_id,
                                               client_secret=my_secret,
                                               redirect_uri="http://localhost:3000",
                                               scope="user-read-recently-played"))

playlist_items = sp.playlist_items("PLAYLIST")

for item in playlist_items['items']:
    name = item['track']['name']
    music = item['track']['preview_url']
    image = item['track']['album']['images'][0]['url']
    urllib.request.urlretrieve(image, f'C:/Users/akira/program/test/snake_game/resources/spotify/images/{name}_image.jpg')
    img = Image.open(f'C:/Users/akira/program/test/snake_game/resources/spotify/images/{name}_image.jpg')
    img_resize = img.resize((40, 40)) # ヘビゲーム用にリサイズ
    img_resize.save(f'C:/Users/akira/program/test/snake_game/resources/spotify/images/{name}_image.jpg')
    urllib.request.urlretrieve(music, f'C:/Users/akira/program/test/snake_game/resources/spotify/musics/{name}_music.mp3')
```

Spotify API のドキュメントとにらめっこしながら作りました。
~~料金は1000リクエストまで無料だった気がする~~　(当てにしないで自分で調べてね)

# ヘビゲーム
[freeCodeCamp.org](https://youtu.be/8dfePlONtls?si=zqP7qcPmc8mgTb7H)の動画を見ながら作りました。
いつも助かってます。

```python 
import pygame
import pygame.mixer
from pygame.locals import *

import spotipy
from spotipy.oauth2 import SpotifyOAuth

import random

from PIL import Image

SIZE = 40
name = []

class Spotify:
    def __init__(self):
        self.my_id = "ID"
        self.my_secret = "SECRET"
        self.sp = spotipy.Spotify(auth_manager=SpotifyOAuth(client_id=self.my_id,
                                                            client_secret=self.my_secret,
                                                            redirect_uri="http://localhost:3000",
                                                            scope="user-read-recently-played"))
        self.playlist_items = self.sp.playlist_items("PLAYLIST")
        self.blocks = []

        for item in self.playlist_items['items']:
            music_name = item['track']['name']
            self.blocks.append(music_name)

        random.shuffle(self.blocks)

        self.index = -1

    def get_random_block(self):
        self.index += 1
        if self.index >= len(self.blocks):
            self.index = 0
            random.shuffle(self.blocks)
        name.append(self.blocks[self.index])
        return self.blocks[self.index]


class Apple:
    def __init__(self, parent_screen):
        music_name = Spotify().get_random_block()
        self.apple = pygame.image.load(f'resources/spotify/images/{music_name}_image.jpg').convert()
        pygame.mixer.music.load(f'resources/spotify/musics/{music_name}_music.mp3')
        pygame.mixer.music.set_volume(0.1)
        pygame.mixer.music.play()
        self.parent_screen = parent_screen
        self.x = random.randint(0, 24)*SIZE
        self.y = random.randint(0, 19)*SIZE

    def average_color(image_path):
        img = Image.open(image_path)

        total_red = 0
        total_green = 0
        total_blue = 0

        for x in range(SIZE):
            for y in range(SIZE):
                red, green, blue = img.getpixel((x, y))
                total_red += red
                total_green += green
                total_blue += blue

        num_pixels = SIZE * SIZE
        avg_red = int(total_red / num_pixels)
        avg_green = int(total_green / num_pixels)
        avg_blue = int(total_blue / num_pixels)

        return (avg_red, avg_green, avg_blue)

    def draw(self):
        self.parent_screen.blit(self.apple, (self.x, self.y))
        pygame.display.flip()

    def move(self):
        pygame.mixer.music.stop()
        self.x = random.randint(0, 24)*SIZE
        self.y = random.randint(0, 19)*SIZE
        music_name = Spotify().get_random_block()
        self.apple = pygame.image.load(f'resources/spotify/images/{music_name}_image.jpg').convert()
        pygame.mixer.music.load(f'resources/spotify/musics/{music_name}_music.mp3')
        pygame.mixer.music.set_volume(0.1)
        pygame.mixer.music.play()


class Snake:
    def __init__(self, parent_screen, length):
        self.length = length
        self.parent_screen = parent_screen
        self.block = pygame.image.load('resources/block.jpg').convert()
        self.x = [SIZE]*length
        self.y = [SIZE]*length
        self.direction = 'right'

    def increase_length(self):
        self.length += 1
        self.x.append(-1)
        self.y.append(-1)

    def draw(self):
        self.parent_screen.fill((Apple.average_color(f'resources/spotify/images/{name[-1]}_image.jpg')))
        self.parent_screen.blit(self.block, (self.x[0], self.y[0]))
        for i in range(1, self.length):
            torso = pygame.image.load(f'resources/spotify/images/{name[i-1]}_image.jpg').convert()
            self.parent_screen.blit(torso, (self.x[i], self.y[i]))
        pygame.display.flip()

    def walk(self):

        for i in range(self.length-1, 0, -1):
            self.x[i] = self.x[i-1]
            self.y[i] = self.y[i-1]

        if self.direction == 'up':
            self.y[0] -= SIZE
            self.draw()
        elif self.direction == 'down':
            self.y[0] += SIZE
            self.draw()
        elif self.direction == 'left':
            self.x[0] -= SIZE
            self.draw()
        elif self.direction == 'right':
            self.x[0] += SIZE
            self.draw()


class Game:
    def __init__(self):
        pygame.init()
        self.surface = pygame.display.set_mode((1000, 800))
        self.apple = Apple(self.surface)
        self.apple.draw()
        self.snake = Snake(self.surface, 1)
        self.snake.draw()
        self.clock = pygame.time.Clock()

    def is_collision(self, x1, y1, x2, y2):
        if x1 >= x2 and x1 < x2+SIZE:
            if y1 >= y2 and y1 < y2+SIZE:
                return True

        return False

    def wall_collision(self, x1, y1):
        if x1 < 0 or x1 > 1000:
            return True
        if y1 < 0 or y1 > 800:
            return True

        return False

    def play(self):
        self.snake.walk()
        self.apple.draw()
        self.display_score()
        pygame.display.flip()

        if self.is_collision(self.snake.x[0], self.snake.y[0], self.apple.x, self.apple.y):
            self.apple.move()
            self.snake.increase_length()

        for i in range(3, self.snake.length):
            if self.is_collision(self.snake.x[0], self.snake.y[0], self.snake.x[i], self.snake.y[i]):
                raise "Game over"

        if self.wall_collision(self.snake.x[0], self.snake.y[0]):
            raise "Game over"



    def show_game_over(self):
        self.surface.fill((Apple.average_color(f'resources/spotify/images/{name[-1]}_image.jpg')))
        font = pygame.font.SysFont('arial', 30)
        line1 = font.render(f"Game is over! Your score is {self.snake.length}", True, (200, 200, 200))
        self.surface.blit(line1, (200, 300))
        line2 = font.render("To play again press Enter. To exit press Escape!", True, (200, 200, 200))
        self.surface.blit(line2, (200, 350))
        pygame.display.flip()

    def display_score(self):
        font = pygame.font.SysFont('arial', 30)
        score = font.render(f"Score: {self.snake.length}", True, (200, 200, 200))
        self.surface.blit(score, (850, 10))

    def run(self):
        running = True
        pause = False

        while running:
            for event in pygame.event.get():
                if event.type == KEYDOWN:
                    if event.key == K_ESCAPE:
                        running = False

                    if event.key == K_UP:
                        if self.snake.direction != 'down':
                            self.snake.direction = 'up'

                    if event.key == K_DOWN:
                        if self.snake.direction != 'up':
                            self.snake.direction = 'down'

                    if event.key == K_LEFT:
                        if self.snake.direction != 'right':
                            self.snake.direction = 'left'

                    if event.key == K_RIGHT:
                        if self.snake.direction != 'left':
                            self.snake.direction = 'right'

                    if event.key == K_RETURN:
                        pause = False
                        self.snake = Snake(self.surface, 1)
                        self.apple = Apple(self.surface)

                elif event.type == QUIT:
                    running = False

            try:
                if not pause:
                    self.play()
            except Exception as e:
                self.show_game_over()
                pause = True

            self.clock.tick(5)


if __name__ == '__main__':
    game = Game()
    game.run()
```
Spotifyでプレイリストを取得してランダムにアクセス

Appleで表示してる間に音楽を再生

Snakeで画像の平均RGBを背景に設定

ざっくりこんな感じです


# 完成
Spotify API 周りがちょっと難しかった。
クオリティが課題な感じですがpygameではこんなもんでしょう、

![pygame window 2024-01-19 01-56-58.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3241738/a38ccb79-53e6-7599-2a9e-dbd09d376332.gif)
音楽流れてます

# 最後に
ゲームを作ったことがなかったので楽しかった！
Unityとかもやってみます！