ai-snake-lab 0.4.7__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ai_snake_lab/ai/AIAgent.py +10 -16
- ai_snake_lab/ai/ReplayMemory.py +155 -49
- ai_snake_lab/ai/models/ModelRNN.py +9 -6
- ai_snake_lab/constants/DDef.py +1 -1
- ai_snake_lab/constants/DLabels.py +2 -0
- ai_snake_lab/constants/DLayout.py +5 -0
- ai_snake_lab/constants/DModelLRNN.py +3 -3
- ai_snake_lab/constants/DReplayMemory.py +13 -4
- ai_snake_lab/constants/DSim.py +1 -1
- ai_snake_lab/game/GameBoard.py +117 -0
- ai_snake_lab/game/SnakeGame.py +3 -3
- ai_snake_lab/ui/AISim.py +50 -17
- ai_snake_lab/{utils → ui}/AISim.tcss +16 -3
- {ai_snake_lab-0.4.7.dist-info → ai_snake_lab-0.5.0.dist-info}/METADATA +33 -4
- ai_snake_lab-0.5.0.dist-info/RECORD +31 -0
- ai_snake_lab-0.4.7.dist-info/RECORD +0 -31
- {ai_snake_lab-0.4.7.dist-info → ai_snake_lab-0.5.0.dist-info}/WHEEL +0 -0
- {ai_snake_lab-0.4.7.dist-info → ai_snake_lab-0.5.0.dist-info}/entry_points.txt +0 -0
- {ai_snake_lab-0.4.7.dist-info → ai_snake_lab-0.5.0.dist-info}/licenses/LICENSE +0 -0
ai_snake_lab/ai/AIAgent.py
CHANGED
@@ -60,28 +60,22 @@ class AIAgent:
|
|
60
60
|
def played_game(self, score):
|
61
61
|
self.epsilon_algo.played_game()
|
62
62
|
|
63
|
-
def remember(self, state, action, reward, next_state, done):
|
63
|
+
def remember(self, state, action, reward, next_state, done, score=None):
|
64
64
|
# Store the state, action, reward, next_state, and done in memory
|
65
|
-
self.memory.append((state, action, reward, next_state, done))
|
65
|
+
self.memory.append((state, action, reward, next_state, done, score))
|
66
66
|
|
67
67
|
def set_optimizer(self, optimizer):
|
68
68
|
self.trainer.set_optimizer(optimizer)
|
69
69
|
|
70
70
|
def train_long_memory(self):
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
return # no games to train on yet
|
79
|
-
|
80
|
-
for count, (state, action, reward, next_state, done) in enumerate(
|
81
|
-
game, start=1
|
82
|
-
):
|
83
|
-
# print(f"Move #{count}: {action}")
|
84
|
-
self.trainer.train_step(state, action, reward, next_state, [done])
|
71
|
+
# Ask ReplayMemory for data
|
72
|
+
training_data = self.memory.get_training_data(n_games=1)
|
73
|
+
if not training_data:
|
74
|
+
return # either no memory or user chose None
|
75
|
+
|
76
|
+
for state, action, reward, next_state, done, *_ in training_data:
|
77
|
+
self.trainer.train_step(state, action, reward, next_state, [done])
|
85
78
|
|
86
79
|
def train_short_memory(self, state, action, reward, next_state, done):
|
80
|
+
# Always train on the current frame
|
87
81
|
self.trainer.train_step(state, action, reward, next_state, [done])
|
ai_snake_lab/ai/ReplayMemory.py
CHANGED
@@ -11,11 +11,9 @@ This file contains the ReplayMemory class.
|
|
11
11
|
"""
|
12
12
|
|
13
13
|
import os
|
14
|
-
from collections import deque
|
15
14
|
import random
|
16
15
|
import sqlite3, pickle
|
17
16
|
import tempfile
|
18
|
-
import shutil
|
19
17
|
|
20
18
|
from ai_snake_lab.constants.DReplayMemory import MEM_TYPE
|
21
19
|
from ai_snake_lab.constants.DDef import DDef
|
@@ -26,21 +24,12 @@ class ReplayMemory:
|
|
26
24
|
def __init__(self, seed: int):
|
27
25
|
random.seed(seed)
|
28
26
|
self.batch_size = 250
|
29
|
-
# Valid options: shuffle, random_game
|
27
|
+
# Valid options: shuffle, random_game or none
|
30
28
|
self._mem_type = MEM_TYPE.RANDOM_GAME
|
31
29
|
self.min_games = 1
|
32
|
-
self.max_states = 15000
|
33
|
-
self.max_shuffle_games = 40
|
34
|
-
self.max_games = 500
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
self.memories = deque(maxlen=self.max_states)
|
39
|
-
|
40
|
-
elif self._mem_type == MEM_TYPE.RANDOM_GAME:
|
41
|
-
# All of the states for a game are stored, in order, in a deque.
|
42
|
-
# A complete game will be returned
|
43
|
-
self.cur_memory = []
|
31
|
+
# All of the states for a game are stored, in order.
|
32
|
+
self.cur_memory = []
|
44
33
|
|
45
34
|
# Get a temporary directory for the DB file
|
46
35
|
self._tmpfile = tempfile.NamedTemporaryFile(suffix=DDef.DOT_DB, delete=False)
|
@@ -70,22 +59,45 @@ class ReplayMemory:
|
|
70
59
|
except Exception:
|
71
60
|
pass # avoid errors on interpreter shutdown
|
72
61
|
|
73
|
-
def append(self, transition):
|
62
|
+
def append(self, transition, final_score=None):
|
74
63
|
"""Add a transition to the current game."""
|
75
|
-
|
76
|
-
raise NotImplementedError(
|
77
|
-
"Only RANDOM_GAME memory type is implemented for SQLite backend"
|
78
|
-
)
|
64
|
+
old_state, move, reward, new_state, done, final_score = transition
|
79
65
|
|
80
|
-
self.cur_memory.append(
|
81
|
-
_, _, _, _, done = transition
|
66
|
+
self.cur_memory.append((old_state, move, reward, new_state, done))
|
82
67
|
|
83
68
|
if done:
|
84
|
-
|
85
|
-
|
69
|
+
if final_score is None:
|
70
|
+
raise ValueError("final_score must be provided when the game ends")
|
71
|
+
|
72
|
+
total_frames = len(self.cur_memory)
|
73
|
+
|
74
|
+
# Record the game
|
86
75
|
self.cursor.execute(
|
87
|
-
"INSERT INTO games (
|
76
|
+
"INSERT INTO games (score, total_frames) VALUES (?, ?)",
|
77
|
+
(final_score, total_frames),
|
88
78
|
)
|
79
|
+
game_id = self.cursor.lastrowid
|
80
|
+
|
81
|
+
# Record the frames
|
82
|
+
for i, (state, action, reward, next_state, done) in enumerate(
|
83
|
+
self.cur_memory
|
84
|
+
):
|
85
|
+
self.cursor.execute(
|
86
|
+
"""
|
87
|
+
INSERT INTO frames (game_id, frame_index, state, action, reward, next_state, done)
|
88
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
89
|
+
""",
|
90
|
+
(
|
91
|
+
game_id,
|
92
|
+
i,
|
93
|
+
pickle.dumps(state),
|
94
|
+
pickle.dumps(action),
|
95
|
+
reward,
|
96
|
+
pickle.dumps(next_state),
|
97
|
+
done,
|
98
|
+
),
|
99
|
+
)
|
100
|
+
|
89
101
|
self.conn.commit()
|
90
102
|
self.cur_memory = []
|
91
103
|
|
@@ -98,44 +110,138 @@ class ReplayMemory:
|
|
98
110
|
os.remove(self.db_file)
|
99
111
|
self.db_file = None
|
100
112
|
|
113
|
+
def get_average_game_length(self):
|
114
|
+
self.cursor.execute("SELECT AVG(total_frames) FROM games")
|
115
|
+
avg = self.cursor.fetchone()[0]
|
116
|
+
return int(avg) if avg else 0
|
117
|
+
|
118
|
+
def get_random_frames(self, n=None):
|
119
|
+
if n is None:
|
120
|
+
n = self.get_average_game_length() or 32 # fallback if no data
|
121
|
+
|
122
|
+
self.cursor.execute(
|
123
|
+
"SELECT state, action, reward, next_state, done "
|
124
|
+
"FROM frames ORDER BY RANDOM() LIMIT ?",
|
125
|
+
(n,),
|
126
|
+
)
|
127
|
+
rows = self.cursor.fetchall()
|
128
|
+
|
129
|
+
frames = [
|
130
|
+
(
|
131
|
+
pickle.loads(state_blob),
|
132
|
+
pickle.loads(action),
|
133
|
+
float(reward),
|
134
|
+
pickle.loads(next_state_blob),
|
135
|
+
bool(done),
|
136
|
+
)
|
137
|
+
for state_blob, action, reward, next_state_blob, done in rows
|
138
|
+
]
|
139
|
+
return frames
|
140
|
+
|
101
141
|
def get_random_game(self):
|
102
|
-
"""Return a random full game from the database."""
|
103
142
|
self.cursor.execute("SELECT id FROM games")
|
104
143
|
all_ids = [row[0] for row in self.cursor.fetchall()]
|
105
|
-
if len(all_ids)
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
if
|
116
|
-
return
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
144
|
+
if not all_ids or len(all_ids) < self.min_games:
|
145
|
+
return False
|
146
|
+
|
147
|
+
rand_id = random.choice(all_ids)
|
148
|
+
self.cursor.execute(
|
149
|
+
"SELECT state, action, reward, next_state, done "
|
150
|
+
"FROM frames WHERE game_id = ? ORDER BY frame_index ASC",
|
151
|
+
(rand_id,),
|
152
|
+
)
|
153
|
+
rows = self.cursor.fetchall()
|
154
|
+
if not rows:
|
155
|
+
return False
|
156
|
+
|
157
|
+
game = [
|
158
|
+
(
|
159
|
+
pickle.loads(state_blob),
|
160
|
+
pickle.loads(action),
|
161
|
+
float(reward),
|
162
|
+
pickle.loads(next_state_blob),
|
163
|
+
bool(done),
|
164
|
+
)
|
165
|
+
for state_blob, action, reward, next_state_blob, done in rows
|
166
|
+
]
|
167
|
+
return game
|
125
168
|
|
126
169
|
def get_num_games(self):
|
127
170
|
"""Return number of games stored in the database."""
|
128
171
|
self.cursor.execute("SELECT COUNT(*) FROM games")
|
129
172
|
return self.cursor.fetchone()[0]
|
130
173
|
|
174
|
+
def get_training_data(self, n_games=None, n_frames=None):
|
175
|
+
"""
|
176
|
+
Returns a list of transitions for training based on the current memory type.
|
177
|
+
|
178
|
+
- n_games: used for RANDOM_GAME (how many full games to sample)
|
179
|
+
- n_frames: used for SHUFFLE (how many frames to sample)
|
180
|
+
- Returns empty list if memory type is NONE or if database/memory is empty
|
181
|
+
"""
|
182
|
+
mem_type = self.mem_type()
|
183
|
+
|
184
|
+
print(f"SELECTED memory type: {mem_type}")
|
185
|
+
if mem_type == MEM_TYPE.NONE:
|
186
|
+
return []
|
187
|
+
|
188
|
+
elif mem_type == MEM_TYPE.RANDOM_GAME:
|
189
|
+
n_games = n_games or 1
|
190
|
+
training_data = []
|
191
|
+
for _ in range(n_games):
|
192
|
+
game = self.get_random_game()
|
193
|
+
if game:
|
194
|
+
training_data.extend(game)
|
195
|
+
return training_data
|
196
|
+
|
197
|
+
elif mem_type == MEM_TYPE.SHUFFLE:
|
198
|
+
n_frames = n_frames or self.get_average_game_length()
|
199
|
+
frames = self.get_random_frames(n=n_frames)
|
200
|
+
return frames
|
201
|
+
|
202
|
+
else:
|
203
|
+
raise ValueError(f"Unknown memory type: {mem_type}")
|
204
|
+
|
131
205
|
def init_db(self):
|
132
206
|
self.cursor.execute(
|
133
207
|
"""
|
134
|
-
|
135
|
-
|
136
|
-
|
208
|
+
CREATE TABLE IF NOT EXISTS games (
|
209
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
210
|
+
score INTEGER NOT NULL,
|
211
|
+
total_frames INTEGER NOT NULL
|
212
|
+
);
|
213
|
+
"""
|
137
214
|
)
|
138
|
-
|
215
|
+
self.conn.commit()
|
216
|
+
|
217
|
+
self.cursor.execute(
|
218
|
+
"""
|
219
|
+
CREATE TABLE IF NOT EXISTS frames (
|
220
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
221
|
+
game_id INTEGER NOT NULL,
|
222
|
+
frame_index INTEGER NOT NULL,
|
223
|
+
state BLOB NOT NULL,
|
224
|
+
action BLOB NOT NULL,
|
225
|
+
reward INTEGER NOT NULL,
|
226
|
+
next_state BLOB NOT NULL,
|
227
|
+
done INTEGER NOT NULL, -- 0 or 1
|
228
|
+
FOREIGN KEY (game_id) REFERENCES games(id)
|
229
|
+
);
|
230
|
+
"""
|
231
|
+
)
|
232
|
+
self.conn.commit()
|
233
|
+
|
234
|
+
self.cursor.execute(
|
235
|
+
"""
|
236
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_game_frame ON frames (game_id, frame_index);
|
237
|
+
"""
|
238
|
+
)
|
239
|
+
self.conn.commit()
|
240
|
+
|
241
|
+
self.cursor.execute(
|
242
|
+
"""
|
243
|
+
CREATE INDEX IF NOT EXISTS idx_frames_game_id ON frames (game_id);
|
244
|
+
"""
|
139
245
|
)
|
140
246
|
self.conn.commit()
|
141
247
|
|
@@ -12,16 +12,19 @@ import torch
|
|
12
12
|
import torch.nn as nn
|
13
13
|
import torch.nn.functional as F
|
14
14
|
|
15
|
+
from ai_snake_lab.constants.DSim import DSim
|
16
|
+
from ai_snake_lab.constants.DModelLRNN import DModelRNN
|
17
|
+
|
15
18
|
|
16
19
|
class ModelRNN(nn.Module):
|
17
20
|
def __init__(self, seed: int):
|
18
21
|
super(ModelRNN, self).__init__()
|
19
22
|
torch.manual_seed(seed)
|
20
|
-
input_size =
|
21
|
-
hidden_size =
|
22
|
-
output_size =
|
23
|
-
rnn_layers =
|
24
|
-
rnn_dropout =
|
23
|
+
input_size = DSim.STATE_SIZE
|
24
|
+
hidden_size = DModelRNN.HIDDEN_SIZE
|
25
|
+
output_size = DSim.OUTPUT_SIZE
|
26
|
+
rnn_layers = DModelRNN.RNN_LAYERS
|
27
|
+
rnn_dropout = DModelRNN.RNN_DROPOUT
|
25
28
|
self.m_in = nn.Sequential(
|
26
29
|
nn.Linear(input_size, hidden_size),
|
27
30
|
nn.ReLU(),
|
@@ -37,7 +40,7 @@ class ModelRNN(nn.Module):
|
|
37
40
|
|
38
41
|
def forward(self, x):
|
39
42
|
x = self.m_in(x)
|
40
|
-
inputs = x.view(1, -1,
|
43
|
+
inputs = x.view(1, -1, DModelRNN.HIDDEN_SIZE)
|
41
44
|
x, h_n = self.m_rnn(inputs)
|
42
45
|
x = self.m_out(x)
|
43
46
|
return x[len(x) - 1]
|
ai_snake_lab/constants/DDef.py
CHANGED
@@ -30,12 +30,14 @@ class DLabel(ConstGroup):
|
|
30
30
|
GAME_SCORE: str = "Game Score"
|
31
31
|
GAME_NUM: str = "Game Number"
|
32
32
|
HIGHSCORE: str = "Highscore"
|
33
|
+
HIGHSCORES: str = "Highscores"
|
33
34
|
MEM_TYPE: str = "Memory Type"
|
34
35
|
MIN_EPSILON: str = "Minimum Epsilon"
|
35
36
|
MODEL_LINEAR: str = "Linear"
|
36
37
|
MODEL_RNN: str = "RNN"
|
37
38
|
MODEL_TYPE: str = "Model Type"
|
38
39
|
MOVE_DELAY: str = "Move Delay"
|
40
|
+
N_SLASH_A: str = "N/A"
|
39
41
|
PAUSE: str = "Pause"
|
40
42
|
QUIT: str = "Quit"
|
41
43
|
RESTART: str = "Restart"
|
@@ -37,12 +37,17 @@ class DLayout(ConstGroup):
|
|
37
37
|
GAME_BOX: str = "game_box"
|
38
38
|
GAME_SCORE: str = "game_score"
|
39
39
|
GAME_SCORE_PLOT: str = "game_score_plot"
|
40
|
+
HIGHSCORES: str = "highscores"
|
41
|
+
HIGHSCORES_BOX: str = "highscores_box"
|
42
|
+
HIGHSCORES_HEADER: str = "highscores_header"
|
40
43
|
EPSILON_DECAY: str = "epsilon_decay"
|
41
44
|
EPSILON_INITIAL: str = "initial_epsilon"
|
42
45
|
EPSILON_MIN: str = "epsilon_min"
|
43
46
|
INPUT_10: str = "input_10"
|
44
47
|
LABEL: str = "label"
|
45
48
|
LABEL_SETTINGS: str = "label_settings"
|
49
|
+
LABEL_SETTINGS_12: str = "label_settings_12"
|
50
|
+
MEM_TYPE: str = "memory_type"
|
46
51
|
MOVE_DELAY: str = "move_delay"
|
47
52
|
NUM_GAMES: str = "num_games"
|
48
53
|
RUNTIME_BOX: str = "runtime_box"
|
@@ -14,12 +14,21 @@ from ai_snake_lab.utils.ConstGroup import ConstGroup
|
|
14
14
|
class MEM_TYPE(ConstGroup):
|
15
15
|
"""Replay Memory Type"""
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
NONE: str = "none"
|
18
|
+
NONE_LABEL: str = "None"
|
19
19
|
RANDOM_GAME: str = "random_game"
|
20
|
-
RANDOM_GAME_LABEL: str = "Random
|
20
|
+
RANDOM_GAME_LABEL: str = "Random Game"
|
21
|
+
SHUFFLE: str = "shuffle"
|
22
|
+
SHUFFLE_LABEL: str = "Random Frames"
|
21
23
|
|
22
24
|
MEM_TYPE_TABLE: dict = {
|
23
|
-
|
25
|
+
NONE: NONE_LABEL,
|
24
26
|
RANDOM_GAME: RANDOM_GAME_LABEL,
|
27
|
+
SHUFFLE: SHUFFLE_LABEL,
|
25
28
|
}
|
29
|
+
|
30
|
+
MEMORY_TYPES: list = [
|
31
|
+
(NONE_LABEL, NONE),
|
32
|
+
(RANDOM_GAME_LABEL, RANDOM_GAME),
|
33
|
+
(SHUFFLE_LABEL, SHUFFLE),
|
34
|
+
]
|
ai_snake_lab/constants/DSim.py
CHANGED
ai_snake_lab/game/GameBoard.py
CHANGED
@@ -82,6 +82,123 @@ class GameBoard(ScrollView):
|
|
82
82
|
return out_list
|
83
83
|
|
84
84
|
def get_state(self):
|
85
|
+
head = self.snake_head
|
86
|
+
direction = self.direction
|
87
|
+
|
88
|
+
# Adjacent points
|
89
|
+
point_l = Offset(head.x - 1, head.y)
|
90
|
+
point_r = Offset(head.x + 1, head.y)
|
91
|
+
point_u = Offset(head.x, head.y - 1)
|
92
|
+
point_d = Offset(head.x, head.y + 1)
|
93
|
+
|
94
|
+
# Direction flags
|
95
|
+
dir_l = direction == Direction.LEFT
|
96
|
+
dir_r = direction == Direction.RIGHT
|
97
|
+
dir_u = direction == Direction.UP
|
98
|
+
dir_d = direction == Direction.DOWN
|
99
|
+
|
100
|
+
# Length encoded in 7-bit binary
|
101
|
+
slb = self.get_binary(7, len(self.snake_body))
|
102
|
+
|
103
|
+
# Normalized distances to walls (0=touching, 1=center)
|
104
|
+
width = height = self.board_size()
|
105
|
+
dist_left = head.x / width
|
106
|
+
dist_right = (width - head.x - 1) / width
|
107
|
+
dist_up = head.y / height
|
108
|
+
dist_down = (height - head.y - 1) / height
|
109
|
+
|
110
|
+
# Relative food direction (normalized)
|
111
|
+
dx = self.food.x - head.x
|
112
|
+
dy = self.food.y - head.y
|
113
|
+
food_dx = dx / max(1, width)
|
114
|
+
food_dy = dy / max(1, height)
|
115
|
+
|
116
|
+
# Free space straight ahead
|
117
|
+
free_ahead = 0
|
118
|
+
probe = Offset(head.x, head.y)
|
119
|
+
while (
|
120
|
+
0 <= probe.x < width
|
121
|
+
and 0 <= probe.y < height
|
122
|
+
and not self.is_snake_collision(probe)
|
123
|
+
):
|
124
|
+
free_ahead += 1
|
125
|
+
if dir_r:
|
126
|
+
probe = Offset(probe.x + 1, probe.y)
|
127
|
+
elif dir_l:
|
128
|
+
probe = Offset(probe.x - 1, probe.y)
|
129
|
+
elif dir_u:
|
130
|
+
probe = Offset(probe.x, probe.y - 1)
|
131
|
+
elif dir_d:
|
132
|
+
probe = Offset(probe.x, probe.y + 1)
|
133
|
+
free_ahead = free_ahead / max(width, height) # normalize
|
134
|
+
|
135
|
+
# Local free cell count (0–4)
|
136
|
+
adjacent_points = [point_l, point_r, point_u, point_d]
|
137
|
+
local_free = (
|
138
|
+
sum(
|
139
|
+
1
|
140
|
+
for p in adjacent_points
|
141
|
+
if not self.is_wall_collision(p) and not self.is_snake_collision(p)
|
142
|
+
)
|
143
|
+
/ 4.0
|
144
|
+
)
|
145
|
+
|
146
|
+
# Optional context (if tracked elsewhere)
|
147
|
+
recent_growth = getattr(self, "recent_growth", 0.0)
|
148
|
+
time_since_food = getattr(self, "steps_since_food", 0.0) / 100.0 # normalize
|
149
|
+
|
150
|
+
# --- EXISTING FEATURES ---
|
151
|
+
state = [
|
152
|
+
# 1-3. Snake collision directions
|
153
|
+
(dir_r and self.is_snake_collision(point_r))
|
154
|
+
or (dir_l and self.is_snake_collision(point_l))
|
155
|
+
or (dir_u and self.is_snake_collision(point_u))
|
156
|
+
or (dir_d and self.is_snake_collision(point_d)),
|
157
|
+
(dir_u and self.is_snake_collision(point_r))
|
158
|
+
or (dir_d and self.is_snake_collision(point_l))
|
159
|
+
or (dir_l and self.is_snake_collision(point_u))
|
160
|
+
or (dir_r and self.is_snake_collision(point_d)),
|
161
|
+
(dir_d and self.is_snake_collision(point_r))
|
162
|
+
or (dir_u and self.is_snake_collision(point_l))
|
163
|
+
or (dir_r and self.is_snake_collision(point_u))
|
164
|
+
or (dir_l and self.is_snake_collision(point_d)),
|
165
|
+
# 4-6. Wall collision directions
|
166
|
+
(dir_r and self.is_wall_collision(point_r))
|
167
|
+
or (dir_l and self.is_wall_collision(point_l))
|
168
|
+
or (dir_u and self.is_wall_collision(point_u))
|
169
|
+
or (dir_d and self.is_wall_collision(point_d)),
|
170
|
+
(dir_u and self.is_wall_collision(point_r))
|
171
|
+
or (dir_d and self.is_wall_collision(point_l))
|
172
|
+
or (dir_l and self.is_wall_collision(point_u))
|
173
|
+
or (dir_r and self.is_wall_collision(point_d)),
|
174
|
+
(dir_d and self.is_wall_collision(point_r))
|
175
|
+
or (dir_u and self.is_wall_collision(point_l))
|
176
|
+
or (dir_r and self.is_wall_collision(point_u))
|
177
|
+
or (dir_l and self.is_wall_collision(point_d)),
|
178
|
+
# 7-10. Direction flags
|
179
|
+
dir_l,
|
180
|
+
dir_r,
|
181
|
+
dir_u,
|
182
|
+
dir_d,
|
183
|
+
# 11-14. Food relative direction
|
184
|
+
food_dx,
|
185
|
+
food_dy,
|
186
|
+
# 15-21. Snake length bits
|
187
|
+
*slb,
|
188
|
+
# 22-26. Distances
|
189
|
+
dist_left,
|
190
|
+
dist_right,
|
191
|
+
dist_up,
|
192
|
+
dist_down,
|
193
|
+
free_ahead,
|
194
|
+
local_free,
|
195
|
+
recent_growth,
|
196
|
+
time_since_food,
|
197
|
+
]
|
198
|
+
|
199
|
+
return [float(x) for x in state]
|
200
|
+
|
201
|
+
def get_state2(self):
|
85
202
|
|
86
203
|
head = self.snake_head
|
87
204
|
direction = self.direction
|
ai_snake_lab/game/SnakeGame.py
CHANGED
@@ -155,9 +155,9 @@ class SnakeGame:
|
|
155
155
|
|
156
156
|
## 6. Set a negative reward if the snake head is adjacent to the snake body.
|
157
157
|
# This is to discourage snake collisions.
|
158
|
-
for segment in self.snake[1:]:
|
159
|
-
|
160
|
-
|
158
|
+
# for segment in self.snake[1:]:
|
159
|
+
# if abs(self.head.x - segment.x) < 2 and abs(self.head.y - segment.y) < 2:
|
160
|
+
# reward -= -2
|
161
161
|
|
162
162
|
self.game_reward += reward
|
163
163
|
self.game_board.update_snake(snake=self.snake, direction=self.direction)
|
ai_snake_lab/ui/AISim.py
CHANGED
@@ -14,9 +14,8 @@ import sys, os
|
|
14
14
|
from datetime import datetime, timedelta
|
15
15
|
|
16
16
|
from textual.app import App, ComposeResult
|
17
|
-
from textual.widgets import Label, Input, Button, Static
|
17
|
+
from textual.widgets import Label, Input, Button, Static, Log, Select
|
18
18
|
from textual.containers import Vertical, Horizontal
|
19
|
-
from textual.reactive import var
|
20
19
|
from textual.theme import Theme
|
21
20
|
|
22
21
|
from ai_snake_lab.constants.DDef import DDef
|
@@ -32,10 +31,13 @@ from ai_snake_lab.constants.DDb4EPlot import Plot
|
|
32
31
|
|
33
32
|
from ai_snake_lab.ai.AIAgent import AIAgent
|
34
33
|
from ai_snake_lab.ai.EpsilonAlgo import EpsilonAlgo
|
34
|
+
|
35
35
|
from ai_snake_lab.game.GameBoard import GameBoard
|
36
36
|
from ai_snake_lab.game.SnakeGame import SnakeGame
|
37
|
+
|
37
38
|
from ai_snake_lab.ui.Db4EPlot import Db4EPlot
|
38
39
|
|
40
|
+
|
39
41
|
RANDOM_SEED = 1970
|
40
42
|
|
41
43
|
snake_lab_theme = Theme(
|
@@ -63,21 +65,19 @@ class AISim(App):
|
|
63
65
|
"""A Textual app that has an AI Agent playing the Snake Game."""
|
64
66
|
|
65
67
|
TITLE = DDef.APP_TITLE
|
66
|
-
CSS_PATH =
|
68
|
+
CSS_PATH = DFile.CSS_FILE
|
67
69
|
|
68
70
|
## Runtime values
|
69
71
|
# Current epsilon value (degrades in real-time)
|
70
|
-
cur_epsilon_widget = Label(
|
71
|
-
# Current memory type
|
72
|
-
cur_mem_type_widget = Label("N/A", id=DLayout.CUR_MEM_TYPE)
|
72
|
+
cur_epsilon_widget = Label(DLabel.N_SLASH_A, id=DLayout.CUR_EPSILON)
|
73
73
|
# Current model type
|
74
|
-
cur_model_type_widget = Label(
|
74
|
+
cur_model_type_widget = Label(DLabel.N_SLASH_A, id=DLayout.CUR_MODEL_TYPE)
|
75
75
|
# Time delay between moves
|
76
76
|
cur_move_delay = DDef.MOVE_DELAY
|
77
77
|
# Number of stored games in the ReplayMemory
|
78
|
-
cur_num_games_widget = Label(
|
78
|
+
cur_num_games_widget = Label(DLabel.N_SLASH_A, id=DLayout.NUM_GAMES)
|
79
79
|
# Elapsed time
|
80
|
-
cur_runtime_widget = Label(
|
80
|
+
cur_runtime_widget = Label(DLabel.N_SLASH_A, id=DLayout.RUNTIME)
|
81
81
|
|
82
82
|
# Intial Settings for Epsilon
|
83
83
|
initial_epsilon_input = Input(
|
@@ -185,6 +185,13 @@ class AISim(App):
|
|
185
185
|
),
|
186
186
|
self.move_delay_input,
|
187
187
|
),
|
188
|
+
Horizontal(
|
189
|
+
Label(
|
190
|
+
f"{DLabel.MEM_TYPE}",
|
191
|
+
classes=DLayout.LABEL_SETTINGS_12,
|
192
|
+
),
|
193
|
+
Select(MEM_TYPE.MEMORY_TYPES, compact=True, id=DLayout.MEM_TYPE),
|
194
|
+
),
|
188
195
|
id=DLayout.SETTINGS_BOX,
|
189
196
|
)
|
190
197
|
|
@@ -202,7 +209,7 @@ class AISim(App):
|
|
202
209
|
),
|
203
210
|
Horizontal(
|
204
211
|
Label(f"{DLabel.MEM_TYPE}", classes=DLayout.LABEL),
|
205
|
-
|
212
|
+
Label(DLabel.N_SLASH_A, id=DLayout.CUR_MEM_TYPE),
|
206
213
|
),
|
207
214
|
Horizontal(
|
208
215
|
Label(f"{DLabel.STORED_GAMES}", classes=DLayout.LABEL),
|
@@ -236,7 +243,11 @@ class AISim(App):
|
|
236
243
|
)
|
237
244
|
|
238
245
|
# Empty fillers
|
239
|
-
yield
|
246
|
+
yield Vertical(
|
247
|
+
Static(id=DLayout.HIGHSCORES_HEADER),
|
248
|
+
Log(highlight=False, auto_scroll=True, id=DLayout.HIGHSCORES),
|
249
|
+
id=DLayout.HIGHSCORES_BOX,
|
250
|
+
)
|
240
251
|
yield Static(id=DLayout.FILLER_2)
|
241
252
|
yield Static(id=DLayout.FILLER_3)
|
242
253
|
|
@@ -252,10 +263,14 @@ class AISim(App):
|
|
252
263
|
settings_box.border_title = DLabel.SETTINGS
|
253
264
|
runtime_box = self.query_one(f"#{DLayout.RUNTIME_BOX}", Vertical)
|
254
265
|
runtime_box.border_title = DLabel.RUNTIME_VALUES
|
255
|
-
self.
|
256
|
-
|
257
|
-
)
|
258
|
-
|
266
|
+
highscore_box = self.query_one(f"#{DLayout.HIGHSCORES_BOX}", Vertical)
|
267
|
+
highscore_box.border_title = DLabel.HIGHSCORES
|
268
|
+
cur_mem_type_widget = self.query_one(f"#{DLayout.CUR_MEM_TYPE}", Label)
|
269
|
+
cur_mem_type_widget.update(DLabel.N_SLASH_A)
|
270
|
+
highscores_header = self.query_one(f"#{DLayout.HIGHSCORES_HEADER}", Static)
|
271
|
+
highscores_header.update(f" [b #3e99af]{DLabel.GAME:6s}{DLabel.SCORE:6s}[/]")
|
272
|
+
memory_type_widget = self.query_one(f"#{DLayout.MEM_TYPE}")
|
273
|
+
memory_type_widget.value = MEM_TYPE.RANDOM_GAME
|
259
274
|
# Initial state is that the app is stopped
|
260
275
|
self.add_class(DField.STOPPED)
|
261
276
|
# Register the theme
|
@@ -305,6 +320,8 @@ class AISim(App):
|
|
305
320
|
game_box = self.query_one(f"#{DLayout.GAME_BOX}", Vertical)
|
306
321
|
game_box.border_title = ""
|
307
322
|
game_box.border_subtitle = ""
|
323
|
+
highscores = self.query_one(f"#{DLayout.HIGHSCORES}", Log)
|
324
|
+
highscores.clear()
|
308
325
|
|
309
326
|
# Recreate events and get a new thread
|
310
327
|
self.stop_event = threading.Event()
|
@@ -324,13 +341,21 @@ class AISim(App):
|
|
324
341
|
self.remove_class(DField.PAUSED)
|
325
342
|
self.cur_move_delay = float(self.move_delay_input.value)
|
326
343
|
self.cur_model_type_widget.update(self.agent.model_type())
|
344
|
+
memory_type_widget = self.query_one(f"#{DLayout.MEM_TYPE}")
|
345
|
+
self.agent.memory.mem_type(memory_type_widget.value)
|
346
|
+
cur_mem_type_widget = self.query_one(f"#{DLayout.CUR_MEM_TYPE}", Label)
|
347
|
+
cur_mem_type_widget.update(
|
348
|
+
MEM_TYPE.MEM_TYPE_TABLE[memory_type_widget.value]
|
349
|
+
)
|
327
350
|
|
328
|
-
#
|
351
|
+
# Defaults button was pressed
|
329
352
|
elif button_id == DLayout.BUTTON_DEFAULTS:
|
330
353
|
self.initial_epsilon_input.value = str(DEpsilon.EPSILON_INITIAL)
|
331
354
|
self.epsilon_decay_input.value = str(DEpsilon.EPSILON_DECAY)
|
332
355
|
self.epsilon_min_input.value = str(DEpsilon.EPSILON_MIN)
|
333
356
|
self.move_delay_input.value = str(DDef.MOVE_DELAY)
|
357
|
+
memory_type_widget = self.query_one(f"#{DLayout.MEM_TYPE}")
|
358
|
+
memory_type_widget.value = MEM_TYPE.RANDOM_GAME
|
334
359
|
|
335
360
|
# Quit button was pressed
|
336
361
|
elif button_id == DLayout.BUTTON_QUIT:
|
@@ -339,6 +364,8 @@ class AISim(App):
|
|
339
364
|
# Update button was pressed
|
340
365
|
elif button_id == DLayout.BUTTON_UPDATE:
|
341
366
|
self.cur_move_delay = float(self.move_delay_input.value)
|
367
|
+
memory_type_widget = self.query_one(f"#{DLayout.MEM_TYPE}")
|
368
|
+
self.agent.memory.mem_type(memory_type_widget.value)
|
342
369
|
|
343
370
|
def start_sim(self):
|
344
371
|
self.snake_game.reset()
|
@@ -351,6 +378,8 @@ class AISim(App):
|
|
351
378
|
game_box = self.query_one(f"#{DLayout.GAME_BOX}", Vertical)
|
352
379
|
game_box.border_title = f"{DLabel.GAME} #{self.epoch}"
|
353
380
|
start_time = datetime.now()
|
381
|
+
self.cur_num_games_widget.update(str(self.agent.memory.get_num_games()))
|
382
|
+
highscores = self.query_one(f"#{DLayout.HIGHSCORES}", Log)
|
354
383
|
|
355
384
|
while not self.stop_event.is_set():
|
356
385
|
if self.pause_event.is_set():
|
@@ -363,6 +392,8 @@ class AISim(App):
|
|
363
392
|
reward, game_over, score = snake_game.play_step(move)
|
364
393
|
if score > highscore:
|
365
394
|
highscore = score
|
395
|
+
# Update the UI
|
396
|
+
highscores.write_line(f"{self.epoch:6d} {score:6d}")
|
366
397
|
game_box.border_subtitle = (
|
367
398
|
f"{DLabel.HIGHSCORE}: {highscore}, {DLabel.SCORE}: {score}"
|
368
399
|
)
|
@@ -378,7 +409,9 @@ class AISim(App):
|
|
378
409
|
game_box = self.query_one(f"#{DLayout.GAME_BOX}", Vertical)
|
379
410
|
game_box.border_title = f"{DLabel.GAME} #{self.epoch}"
|
380
411
|
# Remember the last move
|
381
|
-
agent.remember(
|
412
|
+
agent.remember(
|
413
|
+
old_state, move, reward, new_state, game_over, score=score
|
414
|
+
)
|
382
415
|
# Train long memory
|
383
416
|
agent.train_long_memory()
|
384
417
|
# Reset the game
|
@@ -2,7 +2,7 @@ Screen {
|
|
2
2
|
layout: grid;
|
3
3
|
grid-size: 3 4;
|
4
4
|
grid-rows: 3 7 6 11 10;
|
5
|
-
grid-columns: 32 46
|
5
|
+
grid-columns: 32 46 32;
|
6
6
|
}
|
7
7
|
|
8
8
|
#title {
|
@@ -34,6 +34,7 @@ Screen {
|
|
34
34
|
}
|
35
35
|
|
36
36
|
#runtime_box {
|
37
|
+
height: 100%;
|
37
38
|
border-title-color: #5fc442;
|
38
39
|
border-title-style: bold;
|
39
40
|
border: round #0c323e;
|
@@ -41,7 +42,14 @@ Screen {
|
|
41
42
|
background: black;
|
42
43
|
}
|
43
44
|
|
44
|
-
#
|
45
|
+
#highscores_box {
|
46
|
+
row-span: 2;
|
47
|
+
border-title-color: #5fc442;
|
48
|
+
border-title-style: bold;
|
49
|
+
border: round #0c323e;
|
50
|
+
padding: 0 1;
|
51
|
+
background: black;
|
52
|
+
|
45
53
|
}
|
46
54
|
|
47
55
|
#filler_2 {
|
@@ -54,7 +62,7 @@ Screen {
|
|
54
62
|
dock: bottom;
|
55
63
|
border: round #0c323e;
|
56
64
|
height: 15;
|
57
|
-
width:
|
65
|
+
width: 110;
|
58
66
|
background: black
|
59
67
|
}
|
60
68
|
|
@@ -121,6 +129,11 @@ Button {
|
|
121
129
|
width: 18;
|
122
130
|
}
|
123
131
|
|
132
|
+
.label_settings_12 {
|
133
|
+
color: #5fc442;
|
134
|
+
width: 12;
|
135
|
+
}
|
136
|
+
|
124
137
|
.paused #button_pause {
|
125
138
|
display: none;
|
126
139
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ai-snake-lab
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.5.0
|
4
4
|
Summary: Interactive reinforcement learning sandbox for experimenting with AI agents in a classic Snake Game environment.
|
5
5
|
License: GPL-3.0
|
6
6
|
License-File: LICENSE
|
@@ -35,6 +35,10 @@ Project-URL: Documentation, https://snakelab.osoyalce.com/
|
|
35
35
|
Project-URL: Source, https://github.com/NadimGhaznavi/ai_snake_lab
|
36
36
|
Description-Content-Type: text/markdown
|
37
37
|
|
38
|
+
# AI Snake Lab
|
39
|
+
|
40
|
+
---
|
41
|
+
|
38
42
|
# Introduction
|
39
43
|
|
40
44
|
**AI Snake Lab** is an interactive reinforcement learning sandbox for experimenting with AI agents in a classic Snake Game environment — featuring a live Textual TUI interface, flexible replay memory database, and modular model definitions.
|
@@ -95,10 +99,35 @@ ai-snake-lab
|
|
95
99
|
|
96
100
|
---
|
97
101
|
|
98
|
-
#
|
102
|
+
# Technical Docs
|
103
|
+
|
104
|
+
- [Database Schema Documentation](/pages/db_schema.html)
|
105
|
+
- [Project Layout](/pages/project_layout.html)
|
106
|
+
|
107
|
+
---
|
108
|
+
|
109
|
+
# Acknowledgements
|
99
110
|
|
100
|
-
|
111
|
+
The original code for this project was based on a YouTube tutorial, [Python + PyTorch + Pygame Reinforcement Learning – Train an AI to Play Snake](https://www.youtube.com/watch?v=L8ypSXwyBds&t=1042s&ab_channel=freeCodeCamp.org) by Patrick Loeber. You can access his original code [here](https://github.com/patrickloeber/snake-ai-pytorch) on GitHub. Thank you Patrick!!! You are amazing!!!! This project is a port of the pygame and matplotlib solution.
|
101
112
|
|
102
|
-
Thanks also go out to Will McGugan and the [Textual](https://textual.textualize.io/) team. Textual is an amazing framework. Talk about *
|
113
|
+
Thanks also go out to Will McGugan and the [Textual](https://textual.textualize.io/) team. Textual is an amazing framework. Talk about *Rapid Application Development*. Porting this from a Pygame and MatPlotLib solution to Textual took less than a day.
|
103
114
|
|
104
115
|
---
|
116
|
+
|
117
|
+
# Inspiration
|
118
|
+
|
119
|
+
Creating an artificial intelligence agent, letting it loose and watching how it performs is an amazing process. It's not unlike having children, except on a much, much, much smaller scale, at least today! Watching the AI driven Snake Game is mesmerizing. I'm constantly thinking of ways I could improve it. I credit Patrick Loeber for giving me a fun project to explore the AI space.
|
120
|
+
|
121
|
+
Much of my career has been as a Linux Systems administrator. My comfort zone is on the command line. I've never worked as a programmer and certainly not as a front end developer. [Textual](https://textual.textualize.io/), as a framework for building rich *Terminal User Interfaces* is exactly my speed and when I saw [Dolphie](https://github.com/charles-001/dolphie), I was blown away. Built-in, real-time plots of MySQL metrics: Amazing!
|
122
|
+
|
123
|
+
Richard S. Sutton is also an inspiration to me. His thoughts on *Reinforcement Learning* are a slow motion revolution. His criticisms of the existing AI landscape with it's focus on engineering a specific AI to do a specific task and then considering the job done is spot on. His vision for an AI agent that does continuous, non-linear learning remains the next frontier on the path to *General Artificial Intelligence*.
|
124
|
+
|
125
|
+
---
|
126
|
+
|
127
|
+
# Links
|
128
|
+
|
129
|
+
- Patrick Loeber's [YouTube Tutorial](https://www.youtube.com/watch?v=L8ypSXwyBds&t=1042s&ab_channel=freeCodeCamp.org)
|
130
|
+
- Will McGugan's [Textual](https://textual.textualize.io/) *Rapid Application Development* framework
|
131
|
+
- [Dolphie](https://github.com/charles-001/dolphie): *A single pane of glass for real-time analytics into MySQL/MariaDB & ProxySQL*
|
132
|
+
- Richard Sutton's [Homepage](http://www.incompleteideas.net/)
|
133
|
+
- Richard Sutton [quotes](/pages/richard-sutton.html) and other materials.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
ai_snake_lab/ai/AIAgent.py,sha256=7vODKjg2OHYSx5-ztQPZHI8tGc0BbF_P8fI1C4shhsA,2888
|
2
|
+
ai_snake_lab/ai/AITrainer.py,sha256=ssX6B03yZLEKhNCJv9D83iFAEEhU3_XbW4sA0z5QMRM,3304
|
3
|
+
ai_snake_lab/ai/EpsilonAlgo.py,sha256=Q2U_Ow28ZRQn1hlLybWEhB2Gn101x99mrQWupAooRjk,2142
|
4
|
+
ai_snake_lab/ai/ReplayMemory.py,sha256=chWGz5YotkHaYn0DO3xEHx2zUtF905ILzrT02Vqu6KE,7827
|
5
|
+
ai_snake_lab/ai/models/ModelL.py,sha256=hK7MoPyIF1_K43R_9xW_MYuaweQGo9qSMUIVrHDQZnQ,1249
|
6
|
+
ai_snake_lab/ai/models/ModelRNN.py,sha256=VDw8sgmO-AJzmp-pJKRK9DxpeJwXcriO3ojnB7-_pZc,1307
|
7
|
+
ai_snake_lab/constants/DDb4EPlot.py,sha256=2g8SdDVZWrTVSTw-rpO84OrNfl7ToBYtf6J7sYB7q8w,442
|
8
|
+
ai_snake_lab/constants/DDef.py,sha256=C_pPJ1Z_gOoaPgYe_U01vjjtTyBf_GcewnKmIapcb80,397
|
9
|
+
ai_snake_lab/constants/DDir.py,sha256=6XiS6OTsqL-3AhYyZAZApziMFpw8kNd2QY4v0GLrb-Q,376
|
10
|
+
ai_snake_lab/constants/DEpsilon.py,sha256=HuhMGxy3WIebcFQ2XZvlOpl03JwPYhHZdO40Bzph9yM,420
|
11
|
+
ai_snake_lab/constants/DFields.py,sha256=0MV21hLy-Ojnn4Qa3C9_dQLPjC-3VYBjes_YU5V1Bio,521
|
12
|
+
ai_snake_lab/constants/DFile.py,sha256=VAVH5qf2sFY62J6jcIJhfDnbGuEGpndqnwZr-ysFb7E,341
|
13
|
+
ai_snake_lab/constants/DLabels.py,sha256=yulQhCMzh66Y4pmEe09okMy9oJWphonKrWb34_5RQUI,1563
|
14
|
+
ai_snake_lab/constants/DLayout.py,sha256=qJ-tUK8i5a6lQoKr1i5FoD-p4ZcEZTngi-h6Y0vovcQ,1826
|
15
|
+
ai_snake_lab/constants/DModelL.py,sha256=VzAK5Uqkb0ovMPKTo_DJ2lQmIJkpRQyF-z3g3y4-j20,506
|
16
|
+
ai_snake_lab/constants/DModelLRNN.py,sha256=0Po9TKHPkTwqyJdhPvENyv409cGqFWJwZXWBQlU_Yj4,443
|
17
|
+
ai_snake_lab/constants/DReplayMemory.py,sha256=7F4R2rd1qNc3JgAjBbczqMvL5AJRbyk48cE2GsnVHO4,806
|
18
|
+
ai_snake_lab/constants/DSim.py,sha256=ef96XziFt9Qc-fApUhiWDYw3Gctm8q_rZ3XQmUJ82LY,510
|
19
|
+
ai_snake_lab/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
+
ai_snake_lab/game/GameBoard.py,sha256=ywK8FDMDnZk6cxPZYN4sMingk4vJoWy0l8C_glQCrVs,12802
|
21
|
+
ai_snake_lab/game/GameElements.py,sha256=9vcNH2YWAzYyBZosvj1DzO6ZmUCUMUd8MaIhORWQ8go,470
|
22
|
+
ai_snake_lab/game/SnakeGame.py,sha256=2cAKGx2h59S1B8pe_avdSCnj1iCYEicm34iSPsvjg0M,6510
|
23
|
+
ai_snake_lab/ui/AISim.py,sha256=CI00u93yXIKuGqn3eHQ2qW7d1-jR5K4Jq1XaXEodr8Y,16904
|
24
|
+
ai_snake_lab/ui/AISim.tcss,sha256=3XS-dmh7lTDmDMsJR_H53248OgkX0t488GC6yMfr0Ho,2350
|
25
|
+
ai_snake_lab/ui/Db4EPlot.py,sha256=pcEb0ydXNX2wL0EFBSrqhIoTEhryk4GD0Ua8FFEaZHY,5352
|
26
|
+
ai_snake_lab/utils/ConstGroup.py,sha256=ZYyQxFd9PudBUZmc_NsNvWCp___utOe1MptqD3eyVH8,1174
|
27
|
+
ai_snake_lab-0.5.0.dist-info/METADATA,sha256=BQyhm0ZxMzuisF14YDAbcwQ_u-rBeAp8xIsQsl4XsrA,5733
|
28
|
+
ai_snake_lab-0.5.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
29
|
+
ai_snake_lab-0.5.0.dist-info/entry_points.txt,sha256=ThFx-0yPF5pdwOXUbEo95FEXLUUbOcjfhgMv67PHuIw,59
|
30
|
+
ai_snake_lab-0.5.0.dist-info/licenses/LICENSE,sha256=f-FHFu0xzHH8O_mvKTw2jUZKhTpw6obpmVOI9rnpKeU,35151
|
31
|
+
ai_snake_lab-0.5.0.dist-info/RECORD,,
|
@@ -1,31 +0,0 @@
|
|
1
|
-
ai_snake_lab/ai/AIAgent.py,sha256=p_O7zC15s_tBcI7oqu60X2-3AbrzAWlGXWkrLVC4UsQ,3005
|
2
|
-
ai_snake_lab/ai/AITrainer.py,sha256=ssX6B03yZLEKhNCJv9D83iFAEEhU3_XbW4sA0z5QMRM,3304
|
3
|
-
ai_snake_lab/ai/EpsilonAlgo.py,sha256=Q2U_Ow28ZRQn1hlLybWEhB2Gn101x99mrQWupAooRjk,2142
|
4
|
-
ai_snake_lab/ai/ReplayMemory.py,sha256=yX_dTEkzXxJyK6OjWHDUDoWHTB2sah1Vgg-X9MpK4e4,4529
|
5
|
-
ai_snake_lab/ai/models/ModelL.py,sha256=hK7MoPyIF1_K43R_9xW_MYuaweQGo9qSMUIVrHDQZnQ,1249
|
6
|
-
ai_snake_lab/ai/models/ModelRNN.py,sha256=Ky63BUqJEmwe1PbM4gtjvqd7SmSy_2XA1n-yu8DNexI,1104
|
7
|
-
ai_snake_lab/constants/DDb4EPlot.py,sha256=2g8SdDVZWrTVSTw-rpO84OrNfl7ToBYtf6J7sYB7q8w,442
|
8
|
-
ai_snake_lab/constants/DDef.py,sha256=InS6P0tUCvwZE_xiQtcxrrmLrts0HpO6CpU75UNXVnQ,402
|
9
|
-
ai_snake_lab/constants/DDir.py,sha256=6XiS6OTsqL-3AhYyZAZApziMFpw8kNd2QY4v0GLrb-Q,376
|
10
|
-
ai_snake_lab/constants/DEpsilon.py,sha256=HuhMGxy3WIebcFQ2XZvlOpl03JwPYhHZdO40Bzph9yM,420
|
11
|
-
ai_snake_lab/constants/DFields.py,sha256=0MV21hLy-Ojnn4Qa3C9_dQLPjC-3VYBjes_YU5V1Bio,521
|
12
|
-
ai_snake_lab/constants/DFile.py,sha256=VAVH5qf2sFY62J6jcIJhfDnbGuEGpndqnwZr-ysFb7E,341
|
13
|
-
ai_snake_lab/constants/DLabels.py,sha256=Nv7vCs8pxpF6Px5FD3JFkI7KEPWy6G8wLqzv31_nXxw,1501
|
14
|
-
ai_snake_lab/constants/DLayout.py,sha256=XhRMpOqgzOkL0kcFz4pfS-HuVsYZZLBC6GIAq-E1_Ik,1616
|
15
|
-
ai_snake_lab/constants/DModelL.py,sha256=VzAK5Uqkb0ovMPKTo_DJ2lQmIJkpRQyF-z3g3y4-j20,506
|
16
|
-
ai_snake_lab/constants/DModelLRNN.py,sha256=nyPYa1-8JB7KERWEJM5jBFH3PmISNcr9JPi-KJ_Cq2g,445
|
17
|
-
ai_snake_lab/constants/DReplayMemory.py,sha256=m0SSxFL6v2mVXVxf0lJTJzKfXguxrnsnrJph0v-2lX8,589
|
18
|
-
ai_snake_lab/constants/DSim.py,sha256=ULi_e9g2eBPPZv_swQQoagKLT8dL3gJTwlQihkQk8RY,510
|
19
|
-
ai_snake_lab/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
20
|
-
ai_snake_lab/game/GameBoard.py,sha256=iNgain-8YQg2Ky0GkmGX-ViVmL8P7zPiZYU6Qwrp9Cc,8465
|
21
|
-
ai_snake_lab/game/GameElements.py,sha256=9vcNH2YWAzYyBZosvj1DzO6ZmUCUMUd8MaIhORWQ8go,470
|
22
|
-
ai_snake_lab/game/SnakeGame.py,sha256=ADJSVD-bXBruSHf0rzQFh2P9hPWUAnCkQ95B-RFk1vI,6506
|
23
|
-
ai_snake_lab/ui/AISim.py,sha256=_XUJtiQ_CMWB7beiXq3o9wgmIL-3QGwjHxy8KbSmd_c,15173
|
24
|
-
ai_snake_lab/ui/Db4EPlot.py,sha256=pcEb0ydXNX2wL0EFBSrqhIoTEhryk4GD0Ua8FFEaZHY,5352
|
25
|
-
ai_snake_lab/utils/AISim.tcss,sha256=XnhqjuxtOiNQHDyXFrbAYSlCw-6ch4e1o52nbytoeF0,2118
|
26
|
-
ai_snake_lab/utils/ConstGroup.py,sha256=ZYyQxFd9PudBUZmc_NsNvWCp___utOe1MptqD3eyVH8,1174
|
27
|
-
ai_snake_lab-0.4.7.dist-info/METADATA,sha256=dpFZQWlAb9c2I0Ru4t6JKB4HJ1bsxxlX39v0Cna23LY,3689
|
28
|
-
ai_snake_lab-0.4.7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
29
|
-
ai_snake_lab-0.4.7.dist-info/entry_points.txt,sha256=ThFx-0yPF5pdwOXUbEo95FEXLUUbOcjfhgMv67PHuIw,59
|
30
|
-
ai_snake_lab-0.4.7.dist-info/licenses/LICENSE,sha256=f-FHFu0xzHH8O_mvKTw2jUZKhTpw6obpmVOI9rnpKeU,35151
|
31
|
-
ai_snake_lab-0.4.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|