forked from golbin/TensorFlow-Tutorials
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame.py
191 lines (151 loc) · 8.11 KB
/
game.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# 장애물 회피 게임 즉, 자율주행차:-D 게임을 구현합니다.
import numpy as np
import random
import matplotlib.pyplot as plt
import matplotlib.patches as patches
class Game:
def __init__(self, screen_width, screen_height, show_game=True):
self.screen_width = screen_width
self.screen_height = screen_height
# 도로의 크기는 스크린의 반으로 정하며, 도로의 좌측 우측의 여백을 계산해둡니다.
self.road_width = int(screen_width / 2)
self.road_left = int(self.road_width / 2 + 1)
self.road_right = int(self.road_left + self.road_width - 1)
# 자동차와 장애물의 초기 위치와, 장애물 각각의 속도를 정합니다.
self.car = {"col": 0, "row": 2}
self.block = [
{"col": 0, "row": 0, "speed": 1},
{"col": 0, "row": 0, "speed": 2},
]
self.total_reward = 0.
self.current_reward = 0.
self.total_game = 0
self.show_game = show_game
if show_game:
self.fig, self.axis = self._prepare_display()
def _prepare_display(self):
"""게임을 화면에 보여주기 위해 matplotlib 으로 출력할 화면을 설정합니다."""
fig, axis = plt.subplots(figsize=(4, 6))
fig.set_size_inches(4, 6)
# 화면을 닫으면 프로그램을 종료합니다.
fig.canvas.mpl_connect('close_event', exit)
plt.axis((0, self.screen_width, 0, self.screen_height))
plt.tick_params(top='off', right='off',
left='off', labelleft='off',
bottom='off', labelbottom='off')
plt.draw()
# 게임을 진행하며 화면을 업데이트 할 수 있도록 interactive 모드로 설정합니다.
plt.ion()
plt.show()
return fig, axis
def _get_state(self):
"""게임의 상태를 가져옵니다.
게임의 상태는 screen_width x screen_height 크기로 각 위치에 대한 상태값을 가지고 있으며,
빈 공간인 경우에는 0, 사물이 있는 경우에는 1이 들어있는 1차원 배열입니다.
계산의 편의성을 위해 2차원 -> 1차원으로 변환하여 사용합니다.
"""
state = np.zeros((self.screen_width, self.screen_height))
state[self.car["col"], self.car["row"]] = 1
if self.block[0]["row"] < self.screen_height:
state[self.block[0]["col"], self.block[0]["row"]] = 1
if self.block[1]["row"] < self.screen_height:
state[self.block[1]["col"], self.block[1]["row"]] = 1
return state
def _draw_screen(self):
title = " Avg. Reward: %d Reward: %d Total Game: %d" % (
self.total_reward / self.total_game,
self.current_reward,
self.total_game)
self.axis.clear()
self.axis.set_title(title, fontsize=12)
road = patches.Rectangle((self.road_left - 1, 0),
self.road_width + 1, self.screen_height,
linewidth=0, facecolor="#333333")
# 자동차, 장애물들을 1x1 크기의 정사각형으로 그리도록하며, 좌표를 기준으로 중앙에 위치시킵니다.
# 자동차의 경우에는 장애물과 충돌시 확인이 가능하도록 0.5만큼 아래쪽으로 이동하여 그립니다.
car = patches.Rectangle((self.car["col"] - 0.5, self.car["row"] - 0.5),
1, 1,
linewidth=0, facecolor="#00FF00")
block1 = patches.Rectangle((self.block[0]["col"] - 0.5, self.block[0]["row"]),
1, 1,
linewidth=0, facecolor="#0000FF")
block2 = patches.Rectangle((self.block[1]["col"] - 0.5, self.block[1]["row"]),
1, 1,
linewidth=0, facecolor="#FF0000")
self.axis.add_patch(road)
self.axis.add_patch(car)
self.axis.add_patch(block1)
self.axis.add_patch(block2)
self.fig.canvas.draw()
# 게임의 다음 단계 진행을 위해 matplot 의 이벤트 루프를 잠시 멈춥니다.
plt.pause(0.0001)
def reset(self):
"""자동차, 장애물의 위치와 보상값들을 초기화합니다."""
self.current_reward = 0
self.total_game += 1
self.car["col"] = int(self.screen_width / 2)
self.block[0]["col"] = random.randrange(self.road_left, self.road_right + 1)
self.block[0]["row"] = 0
self.block[1]["col"] = random.randrange(self.road_left, self.road_right + 1)
self.block[1]["row"] = 0
self._update_block()
return self._get_state()
def _update_car(self, move):
"""액션에 따라 자동차를 이동시킵니다.
자동차 위치 제한을 도로가 아니라 화면의 좌우측 끝으로 하고,
도로를 넘어가면 패널티를 주도록 학습해서 도로를 넘지 않게 만들면 더욱 좋을 것 같습니다.
"""
# 자동차의 위치가 도로의 좌측을 넘지 않도록 합니다: max(0, move) > 0
self.car["col"] = max(self.road_left, self.car["col"] + move)
# 자동차의 위치가 도로의 우측을 넘지 않도록 합니다.: min(max, screen_width) < screen_width
self.car["col"] = min(self.car["col"], self.road_right)
def _update_block(self):
"""장애물을 이동시킵니다.
장애물이 화면 내에 있는 경우는 각각의 속도에 따라 위치 변경을,
화면을 벗어난 경우에는 다시 방해를 시작하도록 재설정을 합니다.
"""
reward = 0
if self.block[0]["row"] > 0:
self.block[0]["row"] -= self.block[0]["speed"]
else:
self.block[0]["col"] = random.randrange(self.road_left, self.road_right + 1)
self.block[0]["row"] = self.screen_height
reward += 1
if self.block[1]["row"] > 0:
self.block[1]["row"] -= self.block[1]["speed"]
else:
self.block[1]["col"] = random.randrange(self.road_left, self.road_right + 1)
self.block[1]["row"] = self.screen_height
reward += 1
return reward
def _is_gameover(self):
# 장애물과 자동차가 충돌했는지를 파악합니다.
# 사각형 박스의 충돌을 체크하는 것이 아니라 좌표를 체크하는 것이어서 화면에는 약간 다르게 보일 수 있습니다.
if ((self.car["col"] == self.block[0]["col"] and
self.car["row"] == self.block[0]["row"]) or
(self.car["col"] == self.block[1]["col"] and
self.car["row"] == self.block[1]["row"])):
self.total_reward += self.current_reward
return True
else:
return False
def step(self, action):
# action: 0: 좌, 1: 유지, 2: 우
# action - 1 을 하여, 좌표를 액션이 0 일 경우 -1 만큼, 2 일 경우 1 만큼 옮깁니다.
self._update_car(action - 1)
# 장애물을 이동시킵니다. 장애물이 자동차에 충돌하지 않고 화면을 모두 지나가면 보상을 얻습니다.
escape_reward = self._update_block()
# 움직임이 적을 경우에도 보상을 줘서 안정적으로 이동하는 것 처럼 보이게 만듭니다.
stable_reward = 1. / self.screen_height if action == 1 else 0
# 게임이 종료됐는지를 판단합니다. 자동차와 장애물이 충돌했는지를 파악합니다.
gameover = self._is_gameover()
if gameover:
# 장애물에 충돌한 경우 -2점을 보상으로 줍니다. 장애물이 두 개이기 때문입니다.
# 장애물을 회피했을 때 보상을 주지 않고, 충돌한 경우에만 -1점을 주어도 됩니다.
reward = -2
else:
reward = escape_reward + stable_reward
self.current_reward += reward
if self.show_game:
self._draw_screen()
return self._get_state(), reward, gameover