본문 바로가기

카테고리 없음

AI Snake Game - Reinforcement Learning with PyTorch[2] by Patrick Loeber

728x90
반응형

아나콘다 환경에서 가상환경 생성 후 작업

conda create -n pygame_env python=3.7 콘다환경에서 파이썬3.7버전으로 'pygame_env'이름의 환경 생성

conda activate pygame_env 생성한 'pygame_env' 환경 사용

pip install pygame 파이게임 모듈 설치

pip3 install torch torchvision PyTorch 홈페이지를 참고하여 필요한 환경의 pytorch 모듈설치 (https://pytorch.org/get-started/locally/)

pip3 install matplotlib ipython 시각화 자료를 만들기 위한 모듈 matplotlib 설치

 

pygame을 사용하여 이용할 게임 snake_game의 예제 Script

import pygame
import random
from enum import Enum
from collections import namedtuple

pygame.init() # initialize the module
font = pygame.font.Font('arial.ttf', 25) # 동일 폴더에 'airal.ttf'와 같은 폰트 파일을 저장한 후 사용

#font = pygame.font.SysFont('arial', 25) # 동일 폴더에 폰트 파일이 저장되어 있지 않을때 사용, 파일 실행시 저장된 폰트파일 사용보다 프로그램 실행시 속도가 느리다는 단점

class Direction(Enum): 
    # Enum을 사용하여 방향을 키워드로 설정, Enum은 일반적으로 서로 관련이 있는 여러 개의 상수의 집합을 정의할 때 사용
    # enum 클래스를 사용하면 인스턴스의 종류를 제한할 수 있기 때문에 프로그램 에러를 예방할 수 있다.
    RIGHT = 1
    LEFT = 2
    UP = 3
    DOWN = 4

Point = namedtuple('Point', 'x, y')
# namedtuple은 객체의 인스턴스를 생성하듯이 튜플을 생성하여 각원소에 이름으로 접근이 가능한 튜플을 의미힌다. 각 인스턴스는 ''안에서 ,로 구분한다.

# rgb colors, 배경 및 snake 객체 블록에 설정해줄 색상을 사용하기 쉽게 변수로 설정
WHITE = (255, 255, 255)
RED = (200, 0, 0)
BLUE1 = (0, 0, 255)
BLUE2 = (0, 100, 255)
BLACK = (0, 0, 0)

BLOCK_SIZE = 20 # snake 및 food의 크기를 설정
SPEED = 20 # 프래임 반복 횟수, 숫자가 높을 수록 게임 진행 속도가 빨라짐

class SnakeGame:

    def __init__(self, w=640, h=480):
        self.w = w
        self.h = h
        # init display
        self.display = pygame.display.set_mode((self.w, self.h)) # 게임이 진행될 창 크기를 조정함, 가로, 세로 순서
        pygame.display.set_caption('Snake') # 게임 창 상단에 노출될 창 글씨
        self.clock = pygame.time.Clock() # FPS 초당 프레임 변수 설정

        # init game state, 초기 게임실행시 snake의 방향을 설정
        self.direction = Direction.RIGHT

        self.head = Point(self.w/2, self.h/2) # 게임 실행 초기 snake의 위치를 설정, 창크기를 반으로 나눔으로서 중앙에서 생성되도록 설정
        # 초기 3개의 블록을 가지는 snake 생성, snake는 리스트의 형태를 가진 오브젝트
        self.snake = [self.head, 
                    Point(self.head.x-BLOCK_SIZE, self.head.y), # 두번째 블록은 head의 좌표값에서 블록사이즈만큼만 빼주어 생성
                    Point(self.head.x-(2*BLOCK_SIZE), self.head.y)] # 세번째 블록은 head의 좌표값에서 2개의 블록사이즈만 빼주어 생성

        self.score = 0
        self.food = None
        self._place_food() # 초기 먹이 오브젝트를 설정할 함수

    def _place_food(self): # 먹이 오브젝트를 설정할 함수
        # random.randint(A, B) 함수는 A부터 B까지의 랜덤 정수를 리턴한다.
        x = random.randint(0, (self.w-BLOCK_SIZE)//BLOCK_SIZE) * BLOCK_SIZE #0 부터 가로화면 크기에서 블록사이즈를 뺀만큼에서 블록사이즈로 나눈 몫(나머지는 버림) 랜덤 정수에 블록사이즈를 곱해준 숫자
        y = random.randint(0, (self.h-BLOCK_SIZE)//BLOCK_SIZE) * BLOCK_SIZE
        self.food = Point(x, y)
        if self.food in self.snake: # food 객체가 snake객체 리스트안에 속하게 된다면 _place_food()함수를 실행하여 새로운 food 객체 생성
            self._place_food()

    def play_step(self): # 게임이 실행되는 동안 무한루프로 실행될 함수
        # 1. collect user input
        for event in pygame.event.get(): # 마우스, 키보드 입력값을 받는다. event.type에 입력되는 변수에 따라 다음 환경을 결정
            if event.type == pygame.QUIT: 
                pygame.quit()
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    self.direction = Direction.LEFT
                elif event.key == pygame.K_RIGHT:
                    self.direction = Direction.RIGHT
                elif event.key == pygame.K_DOWN:
                    self.direction = Direction.DOWN
                elif event.key == pygame.K_UP:
                    self.direction = Direction.UP
                    
                
        # 2. move
        self._move(self.direction) # update the head, 방향에 따른 snake 객체 리스트를 업데이트 해줄 함수
        self.snake.insert(0, self.head) # insert는 리스트에 요소를 삽입하는 함수로 insert(a, b)는 리스트 a번째 위치에 b를 삽입한다.
        # 이동 후에 haed의 위치를 바꾸어줌 (head뒤의 블록들은 snake리스트에 이미 설정되어있음)
        
        # 3. check if game over
        game_over = False # 기본 값을 False로 설정하여 게임이 끝나지 않도록 설정
        if self._is_collision():
            game_over = True # 다른 오브젝트에 head가 부딪히게 된다면 game_over를 True로 설정해준 후 점수와 함께 return
            return game_over, self.score
        # 4. place new food or just move
        if self.head == self.food:
            self.score += 1
            self._place_food()
        else:
            self.snake.pop() # pop함수를 통해 snake 이동 후 블록리스트의 마지막 값을 제거 함으로써 이동하는 것처럼 보이게 함
        # 5. update ui and clock
        self._update_ui() 
        self.clock.tick(SPEED) # SPEED에 설정된 숫자만큼 초당 프레임 업데이트
        # 6. return game over and score

        return game_over, self.score
    
    def _is_collision(self):
        # hits boundary
        if self.head.x > self.w - BLOCK_SIZE or self.head.x < 0 or self.head.y > self.h - BLOCK_SIZE or self.head.y < 0:
            return True
        # hits itself
        if self.head in self.snake[1:]: # 헤드값을 포함하지 않는 리스트에 헤드값이 중복되면 
            return True
        
        return False
    
    def _update_ui(self):
        self.display.fill(BLACK)
        
        for pt in self.snake: # draw.rect(화면, 색, (시작할 x좌표, 시작할 y좌표, 가로, 세로))
            pygame.draw.rect(self.display, BLUE1, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE)) # snake 외부
            pygame.draw.rect(self.display, BLUE2, pygame.Rect(pt.x+4, pt.y+4, 12, 12)) # snake 내부

        pygame.draw.rect(self.display, RED, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE)) # Food 그려넣기
        
        text = font.render("Score: " + str(self.score), True, WHITE) # 왼쪽 상단에 점수 업데이트
        self.display.blit(text, [0, 0]) # 0, 0 좌표 위치에 text 그려넣어주기
        pygame.display.flip() # update the full screen, pygame.display.update()함수를 사용할 수도 있다. 두 차이점은 flip은 전체 surface 업데이트, update는 특정 부분만을 업데이트
        
    def _move(self, direction): 
        x = self.head.x
        y = self.head.y
        if direction == Direction.RIGHT:
            x += BLOCK_SIZE
        elif direction == Direction.LEFT:
            x -= BLOCK_SIZE
        elif direction == Direction.DOWN:
            y += BLOCK_SIZE
        elif direction == Direction.UP:
            y -= BLOCK_SIZE
            
        self.head = Point(x, y)


if __name__ == '__main__':
    game = SnakeGame()

    # game loop
    while True:
        game_over, score = game.play_step()

        if game_over == True:
            break

    print('Final Score : ', score)
        # break if game over


    pygame.quit()

 

반응형