목 차
1. 들어가며
2. 사전설명
1) AI를 통해 공 패턴 파악하는 방법
2) AI를 통해 패들 이동시키기
3) eval 함수 사용하기
3. 코드설명
4. 전체코드
5. 마치며
1. 들어가며
지난 글에서는 벽돌깨기 게임의 볼(ball.centery) 이동 위치에 따른 경로를 텍스트 파일에 저장 및 활용하는 방법을 알아보았다.
이번 글에서는 AI에게 게임의 규칙만 알려준 후(=코드만 설정), AI가 자동으로 벽돌을 깨는 방법에 대해 설명한다.
2016년 3월, 구글 딥마인드(Google Deep Mind)는 알파고(AlphaGo)라는 인공지능 프로그램으로 이세돌 9단과의 바둑 대결에서 압도적인 기량을 보여주었다. 알파고는 4단계(선택-확장-시뮬레이션-역전파)를 거쳐 최선의 수(手)를 찾은 후, 바둑판에 바둑알을 두었다. 이 때 주요하게 쓰인 것은 몬테카를로 트리 서치(MCTS), 강화학습 등이며, 이는 현재 AI 시장을 주도하고 있는 개념을 선도하고 있다.
필자가 관심있는 것은 MCTS인데, 이 개념은 이해를 했으나 코드로 구성하는게 만만하지는 않다. 또한, 강화학습은 개념이나 코드는 이해를 했으나, 벽돌깨기를 할 때 강화학습을 굳이 구현할 필요가 없어서, 이 글에서는 강화학습을 이야기 하지 않을 예정이다.
알파고가 바둑을 두기 전, 구글 딥마인드는 Atari 회사의 벽돌깨기 게임을 선보였다. 처음에는 공을 자꾸 놓치는 공이, 몇 시간이 지나자, 왼쪽/오른쪽 모서리를 먼저 공략후, 상단으로 보낸 공이 알아서 벽돌을 깨는 모습을 보여준다.
- 구글 딥마인드의 벽돌깨기 : https://www.youtube.com/watch?v=V1eYniJ0Rnk&t=4s
혹자는 "언제적 알파고를 말하는 것이냐"라고 말할 수 있겠지만, AI 공부를 위해서는 최신 트렌드를 파악하기에는 기초지식이 부족할 것이다. 차라리, 벽돌게임을 통해 AI를 학습시킨 후, 학습시킨 데이터를 통해 알아서 벽돌을 깨는 것을 공부하면 좋을 것 같다.
※ 참고 : 구글 딥마인드의 벽돌깨기를 그대로 구현하지는 않을 것이다. 강화학습은 좀더 공부가 필요할 것 같다. 강화학습을 코드에 구현하지 않았으므로, 왼쪽/오른쪽 벽끝에 공을 임의로 보낼 수는 없다.
너무 큰 기대는 부담된다. 이렇게 벽돌깨기 게임을 자동으로 실행하는 방법도 있구나... 정도로 생각해주시면 감사할 것 같다. 우리가 구현하고자 하는 것은 결국 해외선물 자동매매이다. ^^;
전체 코드는 직접플레이(157줄) → 학습시키기(232줄, +75줄) → AI 플레이(252줄, +20줄)로 각각 이루어진다. AI 게임을 해보면 알겠지만, AI를 통해 플레이 2번 정도 플레이하여 데이터를 학습시키면, 3번째 플레이부터는 174초 이내 56개의 벽돌을 알아서 다 깬다.
원본 코드의 출처는 아래와 같다.
※ ai-creator 님 코드 출처 : https://ai-creator.tistory.com/534
※ 토닥토닥 파이썬 출처 : https://wikidocs.net/65737
전체 파일은 맨 하단에 업로드해 두었다.
아래 < 그림1 >을 보면, 학습을 진행할수록(=게임수를 진행할수록), 공이 바닥(bottom)으로 빠지는 횟수는 줄어든다.
- (1회차 학습) 165초, missed 19회
- (2회차 학습) 151초, missed 7회
- (3회차 학습) 174초, missed 0회
회차가 늘어날수록 missed는 줄어든 반면, 시간은 증가하였다. y = x + b의 형태로 공이 이동하는데, 공과 패들이 충돌하는 지점이 항상 동일하기에, 3회 이상의 학습은 불필요하다. 게임은 항상 173~174초에 끝난다.
2. 사전설명
1) AI를 통해 공 패턴 파악하는 방법
AI 데이터 구축을 위해 필자는 아래 < 그림2 >와 같이 공의 y축 위치(ball.centery)에 따른 x축 위치(ball.centerx)를 파악하고자 하였다. < 그림2 >을 간략히 설명해보자.
- ① 볼의 y축 중심위치가 520~530를 지나갈 때, 공이 지나가는 x축(x1)을 찾아서 brick_ai.txt 파일에 저장한다.
- ② 볼의 y축 중심위치가 560~570를 지나갈 때, 공이 지나가는 x축(x2)을 찾아서 brick_ai.txt 파일에 저장한다.
- ③ 볼의 y축 중심위치가 590~600를 지나갈 때, 저장된 데이터(brick_ai.txt)에서 x3값을 찾는다.
- ④ 볼의 y축 중심위치가 765~775를 지나갈 때, 공이 지나가는 x축(x3)을 찾아서 brick_ai.txt 파일에 저장한다. (x3 자리에 패들을 위치시킨다)
- ⑤ 공의 이동 경로이다.
- ⑥ 공(ball)이 패들(paddle)과 부딪히는 곳이다. 우리가 알고 싶은 것은 ⑥의 x축 위치(ball.centerx)이다.
여기서 헷갈릴 만한 내용은 ③번과 ④번이다.
③번(볼의 y축이 590~600픽셀)은 ①,②,④을 통해 brick_ai.txt에 저장된 데이터 중 볼이 y축의 765~775에 위치할 때의 저장된 x3값을 말한다. 바꾸어 이야기하면, 공이 590~600을 통과할 때, 765~775 지점에서의 x3 위치를 미리 예상하는 것이다. (미리 예상하는 이유는 x3의 위치에 패들을 미리 이동시키기 위함이다)
④번(볼의 y축이 765~775픽셀)은 ③의 데이터를 만들기 위해 공이 765~775를 통과할 때의 x3 값 로데이터이다.
쉽게 이야기 하면, ①번(x1),②번(x2),④번(x3)의 데이터는 ③번(x3 예측점)을 만들기 위한 로데이터이다.
2) AI를 통해 패들 이동시키기
엉뚱하게도 AI가 패들을 이동시키는 내용에서 3일을 헤맸다. 이전 글까지는 사용자가 직접 방향키를 움직여서 패들을 움직였다. 근데, 자동으로 게임을 진행시키고자 할 때, pygame 라이브러리를 이용하여 패들을 어떻게 이동시킬 것인가?
아무리 검색을 해보아도, pygame 라이브러리의 방향키를 이용하여 자동으로 이동시키는 것을 찾기가 힘들었다. 즉, 어떤 특정한 값을 입력받은 AI가 방향키로 이동하는 내용을 좀처럼 찾을 수 없어 답답했다.
그런데, 정말 간단하게도... "AI가 왜 방향키로 이동하는 것을 고집하는가?"라는 고민에서 문제가 해결되었다. < 그림2 >처럼 생각하였을 때, ③번(x3 예측점)에 패들을 그리면 되는 것 아닌가? 쉽게 이야기하면, 방향키를 이용한 이동보다는, 예측점(x3)에 패들을 그려주면 되는 것 아닌가?
생각보다 싱겁게 문제가 해결되니, 약간은 허무하다는 생각이 들었다. 그래도 3일 동안 고민했던 내용이 해결되니, 기분이 좋았다. ^^;
바뀐 코드는 아래와 같다.
< 그림3-1 >의 왼쪽(①)은 사용자가 직접 컨트롤 하는 코드이며, 오른쪽(②)은 학습된 데이터를 AI가 게임을 하는 코드이다.
< 그림3-1 >의 왼쪽(①번)은 게임 종료(QUIT) 및 방향키를 누르고 뗐을 때, 패들의 이동(paddle_dx)을 설정한 내용이다.
< 그림3-1 >의 오른쪽(②번)은 게임 종료(QUIT) 및 예측 지점(alghorism_x_position)에 따라 패들을 이동시킨 경우이다. "3번 코드설명"에서 상술하도록 하겠다.
3) eval 함수 사용하기
예전에 파이썬 내장함수인 eval 함수에 대해 설명한 적이 있다.
(https://springcoming.tistory.com/55)
eval함수를 한마디로 정의하면 "매개변수로 받은 내용을 문자열로 받아서 실행하는 함수"이다.
내용이 추상적이다. 예시를 통해 eval함수 개념을 이해해보자.
print("1+2")
# 결과값 : 1+2
print(eval("1+2"))
# 결과값 : 3
위의 코드를 이해해보자.
- print("1+2")를 출력하면, 1+2가 출력된다. print 안에 문자형("1+2")를 넣었기 때문에, 결과값은 1+2가 출력된다.
- 이에 비해, print(eval("1+2"))를 출력하면 1+2의 결과값인 3인 출력된다.
위의 eval 함수가 이 글과 무슨 상관일까? 학습된 데이터 활용을 통해 공이 바닥에 닿기 전에 패들의 위치를 이동시킨다.
- ① 필자는 파이썬을 통해 "패턴 데이터"를 리스트 태형을 만든후,
- ② "패턴 데이터"를 텍스트에 저장한다. (=벽돌게임을 학습시킴)
- ③ 학습시킨 텍스트에 저장된 "패턴 데이터"를 파이썬으로 불러와서, 공이 바닥에 닿기전에 공을 팅길 위치에 패들을 위치시킨다.
이때 관심을 끄는 것은 ③번의 과정이다. 여기서 2~3일은 헤맨거 같다.
< 그림3-2 >의 빨간색 패턴을 확인해 보자. 태형은 리스트 형태이다. 하지만, 빨간색으로 된 패턴을 파이참의 for문으로 불러오면, 문자형이 된다. for문으로 요소를 하나씩 추출하려고 했으나, 문자가 하나씩 추출된다. 리스트 데이터( [578, 528, ....] )가 [,5,7,8, 5,2,8,....로 하나씩 추출된다.
이때 활용되는 것은 eval 함수이다. 텍스트에 저장된 리스트를 파이참으로 불러오면 문자형으로 변한다. 이걸 방지해 주는 것이 eval(텍스트에 불러온 리스트)로 명령어를 넣으면, "텍스트에서 불러운 리스트"는 문자형이 아닌, 리스트 태형으로 바뀌어서 for문을 통해 리스트 내의 요소를 하나씩 추출할 수 있다.
여기서 설명하는 코드의 105줄~106줄에서 eval이 활용된다. eval 함수의 사용형태를 알 수 있을 것이다.
3. 코드설명
총 252줄이다. 앞의 글에서 설명한 내용은 최소화하고, 주의깊게 봐야하는 코드 위주로 설명한다.
주의깊게 봐야하는 코드는 아래의 2파트이다.
- 70줄~131줄(알고리즘을 만들어서 저장하고, 그 알고리즘을 활용)
- 141줄~151줄(AI가 패들을 이동시키는 방법)
이외에는 대략적으로 설명한다.
1줄~33줄 : pygame 라이브러리를 활용하여 게임을 진행하기 위해 변수들을 선언한다.
35줄~60줄 : 벽돌/공/패들을 정의한다.
59줄~60줄 : 알고리즘을 만들기 위한 로데이터를 담을 리스트 2개를 선언한다.
62줄 : 중요한 내용이다. 공(ball)과 패들(paddle)이 겹치는 지점(부딪히는 지점)을 저장하게 위해 전역변수로 선언한다. 전역변수로 선언하는 이유는 96줄에서 선언한 전역변수를 106줄(공의 y중심축이 거의 바닥인 780일 때의 x축을 저장), 121줄(for문을 통해 동일한 패턴은 저장하지 않음), 141줄~150줄(알고리즘에 따라 패들을 이동시킴)에서 모두 활용한다.
70줄~86줄 : 알고리즘을 만드는 단계이다. 아래 < 그림2 >에서 ①은 공의 y중심축이 520~530을 지날 때의 x1좌표를 구하고, ②은 공의 y중심축이 560~570사이를 지날 때의 x2좌표를 구해서 리스트에 각각 담는다.
88줄~107줄 : 상당히 중요한 내용이다. 여기서는 "알고리즘"을 저장한 텍스트에서 알고리즘을 불러와서, 텍스트 내 저장된 모든 알고리즘을 x_position_file 변수에 담는다 (=98줄)
101줄~102줄 : 104줄에서 for문을 이용해 x_posiotion_file에 저장된 모든 알고리즘을 하나씩 추출할 것이다. 이때 x1(73줄~79줄에 의해 저장된 x1좌표), x2(81줄~86줄에 의해 저장된 x2 좌표)를 101~102줄의 변수에 담는다.
104줄~107줄 : 이 내용을 구현하는게 조금 어려웠다. 105줄이 짤렸는데, 아래 코드에서 해당 코드를 확인하자.
for i in x_position_file:
if (-10 <= eval(i)[0] - upper_point_1 <= 10) and (-10 <= eval(i)[2] - upper_point_2 <= 10):
위에서 설명(2번 사전설명)하였지만, eval 함수는 매개변수로 받은 내용을 문자열로 받아서 실행하는 함수이다.
for i in x_position_file:를 통해 i가 하나씩 추출이 되는데, 이 때 추출되는 데이터는 [ 578, 528, 572,568, 372, 1 ]이다.
문제는 텍스트에서 불러온 리스트 태형은 문자형을 유지한다는 것이다. 필자가 하고 싶은건, "텍스트에서의 리스트"는 파이참에서 불러왔을 때 리스트 태형을 해야 하나, "문자형"을 유지하다는 것이다. 파이참으로 불려온 데이터를 "리스트" 태형으로 만들고자 했을 때, 사용하는 함수가 eval이다. 즉, eval(i)는 알고리즘이 저장된 텍스트 파일의 리스트를 파이썬에서도 리스트 태형으로 활용하고자 할 때 사용한다.
eval(i)[0] : 74줄~79줄에 의해 생성된 x1 데이터(볼의 y중심축이 520~530일 때의 x1 데이터이다)
eval(i)[2] : 81줄~86줄에 의해 생성된 x2 데이터(볼의 y중심축이 560~570일 때의 x2 데이터이다)
-10 <= eval(i)[0] - upper_point_1 <= 10에서 eval(i)[0] - upper_point_1이 뜻하는 것은 무엇인가?
eval(i)[0] : 텍스트에 이미 저장된 과거 데이터(알고리즘)
upper_point_1 : 현재 공의 y중심이 520~530일 때의 x1데이터이다.
-10 <= eval(i)[0] - upper_point_1 <10 : "알고리즘이 저장된 텍스트"에서 가장 유사한 x1을 찾는다.
이와 동일하게 -10 <= eval(i)[2] - upper_point_2 <10 : "알고리즘이 저장된 텍스트"에서 가장 유사한 x2을 찾는다
109줄~112줄 : 알고리즘을 만든다. 공의 y중심점이 화면의 765~775를 지날 때의 x3를 구한다.
114줄~131줄 : 만든 알고리즘을 텍스트에 기록하는 것(=학습)이다. 74줄~86줄(x1, x2) 및 109줄~11줄(x3)에서 만든 데이터를 텍스트로 보낸다.
* 121줄 : 만든 알고리즘이 텍스트에 없을 때, 텍스트에 추가한다.
141줄 : 106줄에서 구한 x3 좌표가 0이 아니면 (=106줄을 통해 x3 좌표의 값을 구한 경우이다)
142줄 : alghorism_x_position은 "텍스트에 저장(학습)된 공의 x3 좌표(공의 y축이 765~775)이다.
"alghorism_x_position - paddle.centerx"는 공이 바닥으로 떨어지는 바로 직전의 x3지점에서 패들의 중간을 빼준다. 약간 어렵게 표현하였지만, 결국 패들(paddle)이 얼마나 이동해야 하는지를 표현한다. 나누기 10을 한 이유는 단위가 픽셀이다.
if alghorism_x_position !=0:
movement_distance = (alghorism_x_position - paddle.centerx) // 10
144줄~151줄 : 이 글의 핵심이다.
"alghorism_x_position - paddle.centerx"은 패들을 이동시키는 횟수라고 표현하였다. 즉, alghorism_x_position은 공이 바닥으로 들어가기 직전의 x좌표를 말하며, paddle.centerx는 패들의 현재 x좌표를 말한다.
144줄~145줄 : "alghorism_x_position - paddle.centerx"의 차이가 -5 ~ 5인 경우, 패들을 움직이지 않는다.
147줄~148줄 : "alghorism_x_position - paddle.centerx" > 5인 경우는, 예측포인트(alghorism_x_position)가 패들의 현재위치보다 오른쪽에 있다는 것이다. 이 때는 패들(paddle)이 오른쪽으로 이동(+10)해야 한다.(그래야 공을 팅겨낼 수 있다)
150줄~151줄 : "alghorism_x_position - paddle.centerx" < -5인 경우는, 예측포인트(alghorism_x_position)가 패들의 현재위치보다 왼쪽에 있다는 것이다. 이 때는 패들(paddle)이 왼쪽으로 이동(-10)해야 한다.
182줄 : 학습을 지속적으로 시키기 위해 최초 3번(missed = 3)인 시도를 100번으로 바꾸었다.
209줄~243줄 : 게임에 필요한 텍스트 등을 게임 화면에 나타낸다.
247줄 : 한번의 게임이 끝나면, 10초 대기후 게임을 재시작(247줄)한다.
4. 전체코드
전체 코드는 직접플레이(157줄) → 학습시키기(232줄, +75줄) → AI 플레이(252줄, +20줄)로 각각 이루어진다.
# 1. pygame 선언
import pygame
import time
import sys
import os
# 2. pygame 초기화
pygame.init()
# 3. pygame에 사용되는 전역변수 선언
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
WHITE = (255, 255, 255)
YELLOW = (255, 255, 0)
large_font = pygame.font.SysFont(None, 72)
small_font = pygame.font.SysFont(None, 36)
screen_width = 600
screen_height = 800
screen = pygame.display.set_mode((screen_width, screen_height))
clock = pygame.time.Clock()
# 4. pygame 무한루프
def runGame():
### 벽돌/공/패널 초기화 ###
score = 0
missed = 0
SUCCESS = 1
FAILURE = 2
game_over = 0
start_time = time.time() # 223줄
bricks = []
COLUMN_COUNT = 8
ROW_COUNT = 7
for column_index in range(COLUMN_COUNT):
for row_index in range(ROW_COUNT):
brick = pygame.Rect(column_index * (60 + 10) + 35, row_index * (16 + 5) + 35, 60, 16)
bricks.append(brick)
ball = pygame.Rect(screen_width // 2, 250, 16, 16) # pygame.Rect(x시작좌표, y시작좌표, x오른쪽 이동, y아래로 이동)
### 최초 공 이동
ball_dx = 10 # 기본 이동 속도
# ball_dx = 5 # 기본 이동 속도
ball_dy = 10 # 기본 이동 속도
# ball_dy = 5 # 기본 이동 속도 # 최초 시작할 때 공이 아래로 이동한다.
paddle = pygame.Rect(screen_width // 2 - 80 // 2, screen_height - 16, 80, 16)
paddle_dx = 0 # 패들은 좌우로만 이동
#### 초기화 끝 ####
brick_list_first = []
brick_list_second = []
global alghorism_x_position
alghorism_x_position = 0
while True:
clock.tick(30) # 초당 30프레임 설정 (초당 30번의 화면을 띄워준다) / FPS 설정 (Frame Per Second)
screen.fill(BLACK)
### 공이 스크린 기준 440~550 사이를 지나갈 때, 패널의 예측 위치를 설정
if 0 <= ball.centery < 500:
brick_list_first = []
brick_list_second = []
elif brick_list_first == [] and 520 <= ball.centery < 530: # 520~530일 때도 체크
brick_list_first.append(ball.centerx)
brick_list_first.append(ball.centery)
brick_list_second.append(ball.centerx)
brick_list_second.append(ball.centery)
elif brick_list_first != [] and 560 <= ball.centery < 570: # 560~570일 때도 체크
brick_list_first.append(ball.centerx)
brick_list_first.append(ball.centery)
brick_list_second.append(ball.centerx)
brick_list_second.append(ball.centery)
if len(brick_list_first) == 4 and 590 <= ball.centery < 600 : # 공이 590 ~ 600을 지날 때, 텍스트에 있는 780에 있는 데이터를 가져온다.
### 팬들 위치 찾기
print("91줄")
print(brick_list_first)
dir = './bricks_ai.txt'
if os.path.isfile(dir):
global x_posiotion_file
f = open('bricks_ai.txt', 'r')
x_position_file = f.readlines()
f.close()
upper_point_1 = brick_list_first[0] # 공의 궤적1
upper_point_2 = brick_list_first[2] # 공의 궤적2
for i in x_position_file:
if (-10 <= eval(i)[0] - upper_point_1 <= 10) and (-10 <= eval(i)[2] - upper_point_2 <= 10):
alghorism_x_position = eval(i)[4] # 공의 y위치가 780일 때의 x축 좌표를 찾으면 종료(break) # "~~~ referenced before assignment" 에러가 발생함에 따라 110줄에서 global을 선언
break
elif brick_list_second != [] and 765 <= ball.centery <= 775 : # 400일 때도 체크
brick_list_second.append(ball.centerx)
# brick_list_second.append(ball.centery)
brick_list_first = []
if len(brick_list_second) == 5:
### 알고리즘을 텍스트(text)에 보내기
bricks_ai_data = "%s %d, %d, %d,%d, %d, %d %s\n" % ("[",brick_list_second[0], brick_list_second[1], brick_list_second[2], brick_list_second[3], brick_list_second[4], 1,"]")
dir = './bricks_ai.txt' # 상대경로의 파일 유무 (https://wikidocs.net/14304)
if os.path.isfile(dir):
if bricks_ai_data not in x_position_file:
f = open('bricks_ai.txt', 'a') # 추가
f.write(bricks_ai_data)
f.close()
else:
f = open('bricks_ai.txt', 'w') # 쓰기
f.write(bricks_ai_data)
f.close()
brick_list_second = []
### 게임종료 하기
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
break
### 알고리즘에 따라 패널을 이동 ###
if alghorism_x_position !=0:
movement_distance = (alghorism_x_position - paddle.centerx) // 10
if -5 <= alghorism_x_position - paddle.centerx <= 5:
paddle_dx = 0
elif alghorism_x_position - paddle.centerx > 5: # 예측포인트 > 현재위치의 패널인 경우이며, 패널이 오른쪽으로 이동 필요(+10)
paddle_dx = 10 * movement_distance
elif alghorism_x_position - paddle.centerx < -5: # 예측포인트 < 현재위치의 패널인 경우이며, 패널이 왼쪽으로 이동 필요(-10)
paddle_dx = 10 * movement_distance
### 게임종료 하기
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
break
paddle.left += paddle_dx
ball.left += ball_dx
ball.top += ball_dy
if ball.left <= 0: # 공이 왼쪽 벽면을 닿은 경우
ball.left = 0
ball_dx = -ball_dx
elif ball.left >= screen_width - ball.width: # 공이 오른쪽 벽면에 닿은 경우
ball.left = screen_width - ball.width
ball_dx = -ball_dx
if ball.top < 0: # 공이 천장에 닿은 경우
ball.top = 0
ball_dy = -ball_dy
elif ball.top >= screen_height: # 공이 바닥에 닿은 경우 (=missed 인 경우)
missed += 1
ball.left = screen_width // 2 - ball.width // 2
ball.top = screen_height // 2 - ball.width // 2
ball_dy = ball_dy # 공이 아래로 간다. (missed가 났을 때, 공이 위쪽으로 움직인다)
# if missed >= 3:
if missed >= 100:
game_over = FAILURE
print("10초 대기")
time.sleep(10)
if paddle.left < 0:
paddle.left = 0
elif paddle.left > screen_width - paddle.width:
paddle.left = screen_width - paddle.width
# 공과 벽돌 충돌 처리
for brick in bricks: # 공과 벽돌의 충돌을 감지하는 라인
if ball.colliderect(brick):
bricks.remove(brick)
ball_dy = -ball_dy # 공이 방향을 바꾼다.
score += 1
break
if ball.colliderect(paddle):
ball_dy = -ball_dy
if ball.centerx <= paddle.left or ball.centerx > paddle.right:
ball_dx = ball_dx * -1
if len(bricks) == 0:
print('success')
game_over = SUCCESS
# 화면 그리기
for brick in bricks: # bricks 리스트들의 모든 벽돌들을 출력한다.
pygame.draw.rect(screen, GREEN, brick)
if game_over == 0: # 게임 종료가 아니면(0이면), 공을 화면에 출력한다.
pygame.draw.circle(screen, WHITE, (ball.centerx, ball.centery), ball.width // 2)
# 첫 번째 값은 화면, 두 번째 값은 색, 세 번째 값은 중심 좌표, 네 번째 값은 반지름, 다섯 번째 값은 선 굵기이다. # 선 굵기는 생략 가능하고 기본 값은 0이며 색을 채운다.
pygame.draw.rect(screen, BLUE, paddle) # 패들을 출력한다.
### 소요되는 시간을 출력한다.
end_time = time.time()
game_time = int(end_time - start_time) # 초 단위 # 33줄
### 화면에 포인트 및 미스(missed)를 출력한다.
score_image = small_font.render('Point {}'.format(score), True, YELLOW)
screen.blit(score_image, (10, 10))
game_time_image = small_font.render('time {}'.format(game_time), True, YELLOW) # 73줄
screen.blit(game_time_image, game_time_image.get_rect(right=screen_width // 2 + 80, top=10))
ball_speed_image = small_font.render('speed {}'.format(abs(ball_dx)), True, YELLOW) # 73줄
screen.blit(ball_speed_image, ball_speed_image.get_rect(right=screen_width // 2 - 40, top=10))
missed_image = small_font.render('Missed {}'.format(missed), True, YELLOW)
screen.blit(missed_image, missed_image.get_rect(right=screen_width - 10, top=10))
if game_over > 0:
if game_over == SUCCESS:
success_image = large_font.render('성공', True, RED)
screen.blit(success_image, success_image.get_rect(centerx=screen_width // 2, centery=screen_height // 2))
elif game_over == FAILURE:
failure_image = large_font.render('실패', True, RED)
screen.blit(failure_image, failure_image.get_rect(centerx=screen_width // 2, centery=screen_height // 2))
print("10초 대기 후 재시작")
time.sleep(10)
runGame()
pygame.display.update()
runGame()
pygame.quit()
5. 마치며
필자도 이 코드를 짜기 위해 10일 이상을 고민했다. 특히 141줄~151줄(AI를 통한 패들의 이동)의 10줄에서 막혀서, 며칠동안 코드 진도를 나갈 수 없어 굉장히 답답했다. 그래도 이렇게 자동으로 벽돌깨기 게임 코드를 짤 수 있어서 기분이 좋다.
이 글에서는 AI를 학습시키고, 그 학습된 데이터를 활용하여 벽돌을 깨는 것을 알아보았다. 처음에는 개념도 생소하고, 코드가 어색할 수 있지만, 그래도 조금만 더 고민하면 충분히 구현할 수 있을 것으로 생각된다.
다음 글에서는 나스타 차트의 지지, 저항을 구현하는 방법을 알아보자.
'2. 해외선물 > 2-2. 해외선물 알고리즘 연구' 카테고리의 다른 글
(해외선물 자동매매) 차트 지지/저항 구하기 (5) 나스타 차트 지지, 저항 업데이트 하기 (3) | 2024.10.14 |
---|---|
(해외선물 자동매매) 차트 지지/저항 구하기 (4) 나스타 차트 지지, 저항 구하기 (3) | 2024.10.07 |
(해외선물 자동매매) 차트 지지/저항 구하기 (2) 벽돌깨기 게임 학습하기 (0) | 2024.10.05 |
(해외선물 자동매매) 차트 지지/저항 구하기 (1) 벽돌깨기 게임 구현하기(BricksBreak) (1) | 2024.10.04 |
(해외선물 자동매매 알고리즘) (2) 역전파(backpropagation) 알고리즘 구현하기 (6) | 2024.02.08 |
(해외선물 자동매매 알고리즘) (1) 역전파(backpropagation) 구현을 위한 numpy 이해하기 (2) | 2024.02.07 |
(해외선물 자동매매 알고리즘) (2) 선형회귀분석 설명 (경사하강법) (2) | 2024.01.30 |
(해외선물 자동매매 알고리즘) (1) 선형회귀분석 설명 (개념설명) (2) | 2024.01.29 |