-
Notifications
You must be signed in to change notification settings - Fork 1
/
WarIOMontage.lua
339 lines (288 loc) · 9.82 KB
/
WarIOMontage.lua
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
-- Full Credit to MarI/O and LuigI/O for inspiration in this project.
-- This project is more of an attempt for me to learn more about AI
-- than anything else. So, I took a look at the two aforementioned
-- projects quite a bit to see how things were structured, and then
-- tried to implement it on my own.
local BoxRadius = 3
local PlayerX
local PlayerY
local OldPlayerFitness
local Blocks
local CurrentSpecie = 1
local StationaryFrames = 0
function getPlayerLocation()
PlayerX = memory.readbyte(0x071C) + 256 * memory.readbyte(0x071A)
PlayerY = memory.readbyte(0x03B8)
end
function getBlocks(blocks)
for i = 0, 16 do
blocks[i] = {}
for j = 0, 13 do -- 16 * 13 = 208
local block = {}
local TempX = PlayerX + (i) * 16
local BlockX = math.floor((TempX % 256) / 16)
local BlockY = j * 16
local BlockAddress = 0
if (math.floor(TempX / 256) % 2 == 1) then BlockAddress = 208 end
BlockAddress = 0x0500 + BlockAddress + BlockY + BlockX
if (memory.readbyte(BlockAddress) ~= 0 and BlockY < 13 * 16 and BlockY >= 0) then block.value = 10
else block.value = 0 end
blocks[i][j] = block
end
end
return blocks
end
function getEnemies(blocks)
for i = 0, 5 do
if (memory.readbyte(0x00F + i) == 1) then
local enemy = {}
enemy.x = (memory.readbyte(0x0087 + i) + 256 * memory.readbyte(0x006E + i) - PlayerX)
if (enemy.x >= 0 and enemy.x <= 256) then
enemy.x = math.floor(enemy.x % 256 / 16)
enemy.y = math.floor((memory.readbyte(0x00CF + i) - 8) / 16)
enemy.value = -10
blocks[enemy.x + 1][enemy.y - 1] = enemy
end
end
end
blocks.enemies = enemies
return blocks
end
function getRandomGenome(number)
local genome = {}
for i = 1, number do
local specie = {}
-- Creating Layer 1
local NodesOne = {}
for j = 0, 16 do
NodesOne[j] = {}
for k = 0, 13 do
NodesOne[j][k] = {}
NodesOne[j][k].WeightA = math.random() - 0.5
NodesOne[j][k].WeightB = math.random() * 0.1
NodesOne[j][k].out = math.floor(math.random(6))
NodesOne[j][k].value = 0
end
end
specie.LayerOne = NodesOne
-- Creating Layer 2
local NodesTwo = {}
for i = 0, 6 do
NodesTwo[i] = {}
NodesTwo[i].value = 0
NodesTwo[i].NumberToAverage = 0
end
specie.LayerTwo = NodesTwo
genome[i] = specie
end
return genome
end
function testSpecie(specie, blocks)
-- First Layer
local NodesOne = specie.LayerOne
for i = 0, 16 do
for j = 0, 13 do
NodesOne[i][j].value = sigmoid(NodesOne[i][j].WeightA * blocks[i][j].value + NodesOne[i][j].WeightB)
end
end
-- Second Layer
local NodesTwo = specie.LayerTwo
for i = 0, 16 do
for j = 0, 13 do
NodesTwo[NodesOne[i][j].out].value = NodesTwo[NodesOne[i][j].out].value + NodesOne[i][j].value
NodesTwo[NodesOne[i][j].out].NumberToAverage = NodesTwo[NodesOne[i][j].out].NumberToAverage + 1
end
end
for i = 0, 6 do
NodesTwo[i].value = NodesTwo[i].value / NodesTwo[i].NumberToAverage
NodesTwo[i].NumberToAverage = 0
end
-- Sending Input
local buttons = {}
buttons["P1 A"] = NodesTwo[1].value > 0.5
buttons["P1 B"] = NodesTwo[2].value > 0.5
buttons["P1 Up"] = NodesTwo[3].value > 0.5
buttons["P1 Down"] = NodesTwo[4].value > 0.5
buttons["P1 Left"] = NodesTwo[5].value > 0.5
buttons["P1 Right"] = NodesTwo[6].value > 0.5
buttons["P1 Start"] = false
buttons["P1 Select"] = false
joypad.set(buttons)
-- Current Fitness
local fitness = memory.readbyte(0x0086) + 256 * memory.readbyte(0x006D)
specie.fitness = fitness
if (OldPlayerFitness == fitness) then
StationaryFrames = StationaryFrames + 1
if (StationaryFrames >= 180) then
memory.writebyte(0x00E, 0x0B) -- My way of killing Mario.
end
else
StationaryFrames = 0
end
OldPlayerFitness = fitness
gui.text(0, 40, "Fitness: " .. fitness)
end
function sigmoid(x)
return 1 / (1 + math.exp(-1 * x))
end
function display(blocks)
for i = 0, 16 do
for j = 0, 13 do
if (blocks[i][j].value == 10) then
gui.drawBox(i * 2 * BoxRadius - BoxRadius, j * 2 * BoxRadius - BoxRadius + 32, i * 2 * BoxRadius + BoxRadius, j * 2 * BoxRadius + BoxRadius + 32, "white")
end
if (blocks[i][j].value == -10) then
gui.drawBox(i * 2 * BoxRadius - BoxRadius, j * 2 * BoxRadius - BoxRadius + 32, i * 2 * BoxRadius + BoxRadius, j * 2 * BoxRadius + BoxRadius + 32, "red")
end
end
end
end
function displayJoypad(joypad)
if (joypad["P1 A"] == true) then gui.drawBox(240, 40, 250, 55, "white", "white") end
gui.drawText(240, 40, "A")
if (joypad["P1 B"] == true) then gui.drawBox(240, 55, 250, 70, "white", "white") end
gui.drawText(240, 55, "B")
if (joypad["P1 Up"] == true) then gui.drawBox(240, 70, 250, 85, "white", "white") end
gui.drawText(240, 70, "U")
if (joypad["P1 Down"] == true) then gui.drawBox(240, 85, 250, 100, "white", "white") end
gui.drawText(240, 85, "D")
if (joypad["P1 Left"] == true) then gui.drawBox(240, 100, 250, 115, "white", "white") end
gui.drawText(240, 100, "L")
if (joypad["P1 Right"] == true) then gui.drawBox(240, 115, 250, 129, "white", "white") end
gui.drawText(240, 115, "R")
end
function compare(speciea, specieb)
return speciea.fitness > specieb.fitness
end
function newgenome(oldgenome)
local PercentToBreed = 0.2
local newgenome = {}
local i = 1
table.sort(oldgenome, compare)
for i = 1, PercentToBreed * #oldgenome do
newgenome[i] = oldgenome[i]
end
newgenome = breed(newgenome, #oldgenome)
for i = 1, 20 do
print(newgenome[i].fitness)
end
return newgenome
end
function breed(newgenome, size)
local OldGenomeSize = #newgenome
for i = #newgenome + 1, size do
local ParentA = math.floor(math.random() * OldGenomeSize) + 1
local ParentB = math.floor(math.random() * OldGenomeSize) + 1
newgenome[i] = {}
newgenome[i].LayerOne = {}
newgenome[i].LayerTwo = {}
-- Layer One
for j = 0, 16 do
newgenome[i].LayerOne[j] = {}
for k = 0, 13 do
newgenome[i].LayerOne[j][k] = {}
if (math.random() > 0.5) then
newgenome[i].LayerOne[j][k].WeightA = newgenome[ParentA].LayerOne[j][k].WeightA
newgenome[i].LayerOne[j][k].WeightB = newgenome[ParentA].LayerOne[j][k].WeightB
else
newgenome[i].LayerOne[j][k].WeightA = newgenome[ParentB].LayerOne[j][k].WeightA
newgenome[i].LayerOne[j][k].WeightB = newgenome[ParentB].LayerOne[j][k].WeightB
end
if (math.random() > 0.5) then
newgenome[i].LayerOne[j][k].out = newgenome[ParentA].LayerOne[j][k].out
else
newgenome[i].LayerOne[j][k].out = newgenome[ParentB].LayerOne[j][k].out
end
newgenome[i].value = 0
if (math.random() < 0.01) then
newgenome[i].LayerOne[j][k].WeightA = newgenome[i].LayerOne[j][k].WeightA + ((math.random() * 0.3) - 0.15)
newgenome[i].LayerOne[j][k].WeightB = newgenome[i].LayerOne[j][k].WeightB + ((math.random() * 0.03) - 0.015)
end
end
end
-- Layer Two
for j = 0, 6 do
newgenome[i].LayerTwo[j] = {}
newgenome[i].LayerTwo[j].value = 0
newgenome[i].LayerTwo[j].NumberToAverage = 0
end
end
return newgenome
end
function loadGenome(number)
print("Loading File: " .. number .. ".gen")
local filename = number .. ".gen"
local file = io.open(filename, "r")
local genome = {}
for i = 1, 100 do
local specie = {}
-- Creating Layer 1
local NodesOne = {}
for j = 0, 16 do
NodesOne[j] = {}
for k = 0, 13 do
NodesOne[j][k] = {}
NodesOne[j][k].WeightA = file:read("*number")
NodesOne[j][k].WeightB = file:read("*number")
NodesOne[j][k].out = file:read("*number")
NodesOne[j][k].value = 0
end
end
specie.LayerOne = NodesOne
-- Creating Layer 2
local NodesTwo = {}
for i = 0, 6 do
NodesTwo[i] = {}
NodesTwo[i].value = 0
NodesTwo[i].NumberToAverage = 0
end
specie.LayerTwo = NodesTwo
genome[i] = specie
end
file:close()
--]]
print("File Loaded.")
return genome
end
-- Initialization
local genome = loadGenome(1) -- loadGenome() or getRandomGenome()
local Generation = 1
OldPlayerFitness = memory.readbyte(0x0086) + 256 * memory.readbyte(0x071A) - 40
savestate.load("SMB.State")
function saveGenome()
local filename = Generation .. ".gen"
local file = io.open(filename, "w")
for i = 1, 100 do
for j = 0, 16 do
for k = 0, 13 do
file:write(genome[i].LayerOne[j][k].WeightA .. "\n")
file:write(genome[i].LayerOne[j][k].WeightB .. "\n")
file:write(genome[i].LayerOne[j][k].out .. "\n")
end
end
end
file:close()
end
while true do
local IsAlive = (memory.readbyte(0x00E) ~= 0x0B)
if (memory.readbyte(0x00B5) > 1) then IsAlive = false end
if (IsAlive == false) then
savestate.load("SMB.State")
Generation = Generation + 1
genome = loadGenome(Generation)
CurrentSpecie = 1
StationaryFrames = 0
end
--memory.writebyte(0x0787, 0x02)
getPlayerLocation()
local blocks = {}
blocks = getBlocks(blocks)
blocks = getEnemies(blocks)
testSpecie(genome[CurrentSpecie], blocks)
display(blocks)
displayJoypad(joypad.getimmediate())
gui.text(0, 10, "Generation: " .. Generation)
gui.text(0, 25, "Species: " .. CurrentSpecie)
emu.frameadvance()
end
-- 7:45 PM Start Program