Skip to content

Instantly share code, notes, and snippets.

@rymate1234
Last active January 29, 2016 19:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rymate1234/edf417846affd4db10db to your computer and use it in GitHub Desktop.
Save rymate1234/edf417846affd4db10db to your computer and use it in GitHub Desktop.
----------------------------------------------------------------------------------------------------------------------------------------------------
--Super Mario Bros.
--Neural Network Learning
--by Michael Roberts
--06/2015
--
--Use with FCEUX v2.2.2
----------------------------------------------------------------------------------------------------------------------------------------------------
--RAM Addresses for variables
----------------------------------------------------------------------------------------------------------------------------------------------------
saveState = savestate.object(1)
savestate.save(saveState)
RamNametableHi = 0x20
RamNametableLow = 0x01
RamNametableSize = 0x2BF
RamNametableSolidStart = 0x01
--Screen attributes
RamObjectMapStart = 0x500
MapTileNum = 208
--Player attributes
RamPlayerX = 0x86
RamPlayerY = 0x3B8
RamPlayerScreenX = 0x6D
RamLives = 0x75A
RamCoins = 0x75E
--Game attributes
RamWorld = 0x75F
RamLevel = 0x760
--Score
RamScoreDigit1 = 0x7D8 --10^5
RamScoreDigit2 = 0x7D9 --10^4
RamScoreDigit3 = 0x7DA --10^3
RamScoreDigit4 = 0x7DB --10^2
RamScoreDigit5 = 0x7DC --10^1
TimeDigit1 = 0x07F8
TimeDigit2 = 0x07F9
TimeDigit3 = 0x07FA
--Enemy attributes
RamEnemyX = 0x87 --starting address in memory for multiple enemies
RamEnemyY = 0xCF
RamEnemyScreenX = 0x6E
EnemyNumSlots = 5
RamEnemyFlag = 0xF --In Zelda, it's enemy direction used as flag
--Projectile attributes
--RamProjectileX = 0x87 --starting address in memory for multiple enemies
--RamProjectileY = 0xCF
--RamProjectileScreenX = 0x6E
--ProjectileNumSlots = 5
--RamProjectileFlag = 0xF --In Zelda, it's enemy direction used as flag
------------------------------------------------------------------------------------------------------------------------------------------------------
--timer = 40
index = 1
--AReady = true
--BReady = true
vblankFlag = 0
vblankOff = 0
--PPU2002 = {}
generation = 1
fitness = 0
maxFitness = 0
startFitness = 0
timeStuck = 0
maxStuckTime = 1000 --220
medStuckTime = maxStuckTime / 2 --220
lastX = 0
lastY = 0
lastLives = 0
timerHold = 0
lastScreen = 0
screenTimeStuck = 0
maxScreenStuckTime = maxStuckTime
playerX = 0
playerY = 0
playerRoomX = 0
playerRoomY = 0
playerMapX = 0
playerMapY = 0
playerLives = 0
playerScore = 0
currentTime = 0
lastTime = 0
resetGame = 0
room = {}
map = {}
for i=1,2*MapTileNum do
map[i] = 0
end
enemyX = {}
enemyY = {}
enemyRoomX = {}
enemyRoomY = {}
enemyMapX = {}
enemyMapY = {}
enemyDir = {}
projectileX = {}
projectileY = {}
projectileRoomX = {}
projectileRoomY = {}
drawRoom = false
drawMap = false
drawView = false
drawCoord = true
drawNeurons = false
drawWeights = false
drawController = false
drawPopulation = true
viewRadius = 7
viewDiameter = 2*viewRadius+1
viewBox = {}
for i=1,viewDiameter*viewDiameter do
viewBox[i] = 0
end
inputNum = viewDiameter*viewDiameter + 1
outputNum = 6 --6 buttons on the controller
layerNum = 2
layerSize = {inputNum, 12, outputNum}
--layerSize array has layerNum+1 entries. (inputNum layer is counted as layer 0)
populationSize = 25
populationFitness = {}
for i=1, populationSize do
populationFitness[i] = nil
end
currentChild = 1
currentParent1 = 0
currentParent2 = 0
mutationProbability = .05
meanFitness = 0
stdvFitness = 0
inputViewNum = MapTileNum --this is the number of inputs to X that are from the view or map
--inputSigmoidStrength = 4.394/inputNum
--hiddenSigmoidStrength = 4.394/layerSize
outputStepStrength = 0 --layerSize/4
--sigmoid strength = 4ln3/N --4ln3 = 4.394
--where N=number of neurons input into current layer being calculated.
--X[1][] = input
--X[layerNum+1][] = output
--Y = output bool
--
--S[l] = W[l]*X[l-1]
--X[l] = f(S[l])
--f is shaping function (tanh)
Y = {}
for i=1, outputNum do
Y[i] = false
end
X = {}
S = {}
delta = {}
for l=1,layerNum+1 do
X[l] = {}
S[l] = {}
delta[l] = {}
for i=1, layerSize[l] do
X[l][i] = 0
S[l][i] = 0
delta[l][i] = 0
end
end
W = {}
memW = {}
for p=1, populationSize do
W[p] = {}
for l=1,layerNum do
W[p][l] = {}
memW[l] = {}
for i=1,layerSize[l+1] do
W[p][l][i] = {}
memW[l][i] = {}
for j=1,layerSize[l] do
W[p][l][i][j] = 2*math.random() - 1 --math.random(3)-2
memW[l][i][j] = W[p][l][i][j]
end
end
end
end
--Training parameters
--trainingMode = 1 -> begin in training mode. 0 is game running mode
geneticMode = true
trainingMode = false
recordMode = 1
keyPressReady = true
randomChangeSize = .1
stepSize = .1
sampleRate = 100
sampleTimer = 0
controllerInput = {}
errorVal = 0
Y_Train = {}
for i=1, outputNum do
Y_Train[i] = 0
end
--------------------------------------------------------------------------------------------------------------------------------------------
--Functions
--------------------------------------------------------------------------------------------------------------------------------------------
function readPPU()
memory.writebyte(0x2006, RamNametableHi)
memory.writebyte(0x2006, RamNametableLow)
j=1
for i=0,RamNametableSize do
local val = memory.readbyte(0x2007)
room[i+1] = val
if (math.floor(i/32))%2 == 0 then
if i%2 == 0 then
map[j] = val
j = j+1
end
end
--if val <= RamNametableSolidStart then
-- room[i+1] = 0
--else
-- room[i+1] = 1
--end
end
end
function inputView()
for i = -viewRadius, viewRadius do
for j = -viewRadius, viewRadius do
local x = playerMapX+i-1
local y = playerMapY+j-1
local page = math.floor(x/16)
local xAddress = x - 16*page+1
local yAddress = y + 13*(page%2)
if xAddress >= 1 and xAddress < 32 and yAddress >= 1 and yAddress <= 25 then
viewBox[(i+viewRadius+1)+viewDiameter*(j+viewRadius)] = map[xAddress + 16*yAddress]
else
viewBox[(i+viewRadius+1)+viewDiameter*(j+viewRadius)] = 0
end
end
end
end
function inputMap()
for i=1, 2*MapTileNum do
if memory.readbyte(0x500 + i-1) ~= 0 then
if memory.readbyte(0x500 + i-1) == 193 then
map[i] = 2
elseif memory.readbyte(0x500 + i-1) == 192 then
map[i] = 2
elseif memory.readbyte(0x500 + i-1) <= 21 and memory.readbyte(0x500 + i-1) >= 18 then
map[i] = 3
else
map[i] = 1
end
else
map[i] = 0
end
end
for i=1, EnemyNumSlots do
if memory.readbyte(RamEnemyFlag+(i-1)) ~= 0 then
local page = math.floor(enemyMapX[i]/16)
local xAddress = enemyMapX[i] - 16*page
local yAddress = enemyMapY[i] - 1 + 13*(page%2)
if xAddress >= 1 and xAddress < 32 and yAddress >= 1 and yAddress <= 25 then
map[xAddress + 16*yAddress] = -1
end
end
end
end
function inputPlayer()
playerX = memory.readbyte(RamPlayerX) + memory.readbyte(RamPlayerScreenX)*0x100 + 4
playerY = memory.readbyte(RamPlayerY) + 16
playerRoomX = math.floor(playerX/8)+1
playerRoomY = math.floor(playerY/7.5)-7
playerMapX = math.floor((playerX%512)/16)+1
playerMapY = math.floor((playerY-32)/16)+1
playerScore = (memory.readbyte(RamScoreDigit1)*10^5)+(memory.readbyte(RamScoreDigit2)*10^4)+(memory.readbyte(RamScoreDigit3)*10^3)+(memory.readbyte(RamScoreDigit4)*10^2)+(memory.readbyte(RamScoreDigit5)*10^1)
currentTime = (memory.readbyte(TimeDigit1)*10^2)+(memory.readbyte(TimeDigit2)*10^1)+(memory.readbyte(TimeDigit3))
end
function inputEnemies()
for i=1,EnemyNumSlots do
if memory.readbyte(RamEnemyFlag+(i-1)) ~= 0 then
enemyX[i] = memory.readbyte(RamEnemyX+(i-1)) + memory.readbyte(RamEnemyScreenX+(i-1))*0x100
enemyY[i] = memory.readbyte(RamEnemyY+(i-1)) + 24
else
enemyX[i] = -1
enemyY[i] = -1
end
enemyRoomX[i] = math.floor(enemyX[i]/8)+1
enemyRoomY[i] = math.floor(enemyY[i]/7.5)-7
enemyMapX[i] = math.floor((enemyX[i]%512)/16)+1
enemyMapY[i] = math.floor((enemyY[i]-32)/16)
end
--for i=1,ProjectileNumSlots do
-- if memory.readbyte(RamProjectileFlag-(i-1)) ~= 0 then
-- projectileX[i] = memory.readbyte(RamProjectileX-(i-1))
-- projectileY[i] = memory.readbyte(RamProjectileY-(i-1))
-- else
-- projectileX[i] = -1
-- projectileY[i] = -1
-- end
-- projectileRoomX[i] = math.floor(projectileX[i]/8)
-- projectileRoomY[i] = math.floor(projectileY[i]/7.5)
--end
end
function inputViewToX(startPosition)
for i=1,(viewDiameter)*(viewDiameter) do
X[1][startPosition-1+i] = viewBox[i]
end
end
function inputMapToX(startPosition)
for i=1,MapTileNum do
if map[i] <= RamNametableSolidStart then
X[1][startPosition-1+i] = 0
else
X[1][startPosition-1+i] = 1
end
end
end
function inputCoordToX(startPosition)
X[1][startPosition] = playerMapX/32
X[1][startPosition+1] = playerMapY/13
--X[1][startPosition+2] = playerHealth
end
function inputEnemyToX(startPosition)
for i=0,EnemyNumSlots-1 do
X[1][startPosition+2*i] = enemyMapX[i+1]/32
X[1][startPosition+2*i+1] = enemyMapY[i+1]/13
end
--for i=0,ProjectileNumSlots-1 do
-- X[1][startPosition+2*i+14] = projectileX[i+1]/256
-- X[1][startPosition+2*i+15] = projectileY[i+1]/240
--end
end
function inputNoise(val)
for i=1,inputNum do
X[1][i] = X[1][i] + val*(2*math.random()-1)
if X[1][i] > 1 then
X[1][i] = 1
end
if X[1][i] < -1 then
X[1][i] = -1
end
end
end
function drawGui()
local mapDrawX = -8
local mapDrawY = 0
local mapScale = 8
local viewDrawX = 56
local viewDrawY = 56
local neuronDrawSpacing = 32
local neuronVerticalSpacing = 4
local controllerDrawX = 176
local controllerDrawY = 48 --200
local drawNeuronsX = 88
if drawRoom == true then
for i=0,RamNametableSize do
local x = i%32
local y = math.floor(i/32)
if tunicColor%2 == 0 then
gui.text(x*8, 64+y*8, room[i+1]%16)
else
gui.text(x*8, 64+y*8, math.floor(room[i+1]/16))
end
--if room[i+1] <= 0x77 then
-- gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+x*mapScale+mapScale, mapDrawY+y*mapScale+mapScale, "black")
--else
-- gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+x*mapScale+mapScale, mapDrawY+y*mapScale+mapScale, "blue")
--end
end
gui.box(mapDrawX+playerMapX*mapScale, mapDrawY+mapScale*(playerMapY-8), mapDrawX+mapScale*(playerMapX+1.5), mapDrawY+mapScale*(playerMapY-6.5), "green")
for i=1,EnemyNumSlots do
if enemyX[i] ~= -1 then
gui.box(mapDrawX+enemyRoomX[i]*mapScale, mapDrawY+mapScale*(enemyRoomY[i]-8), mapDrawX+mapScale*(enemyRoomX[i]+1.5), mapDrawY+mapScale*(enemyRoomY[i]-6.5), "red")
end
end
--for i=1,ProjectileNumSlots do
-- if projectileX[i] ~= -1 then
-- gui.box(mapDrawX+projectileRoomX[i]*mapScale, mapDrawY+mapScale*(projectileRoomY[i]-8), mapDrawX+mapScale*(projectileRoomX[i]+1), mapDrawY+mapScale*(projectileRoomY[i]-7), "red")
-- end
--end
end
if drawMap == true then
--gui.box(0,0,256,240,"black")
for i=0,2*MapTileNum-1 do
local x = 16*math.floor(i/208) + i%16 + 1
local y = math.floor(i/16) - 13*math.floor(i/208) + 1
--local x = i%16 + 1 -- + 16*(i%208)
--local y = math.floor(i/16) + 1 - 13*(i%208)
--gui.text(mapDrawX+x*16, mapDrawY++y*16, map[i+1]%16)
if map[i+1] == 0 then
gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+(x+1)*mapScale-1, mapDrawY+(y+1)*mapScale-1, "black")
elseif map[i+1] == 1 then
gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+(x+1)*mapScale-1, mapDrawY+(y+1)*mapScale-1, "blue")
elseif map[i+1] == 2 then
gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+(x+1)*mapScale-1, mapDrawY+(y+1)*mapScale-1, "cyan")
elseif map[i+1] == 3 then
gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+(x+1)*mapScale-1, mapDrawY+(y+1)*mapScale-1, "#458B00")
elseif map[i+1] == -1 then
gui.box(mapDrawX+x*mapScale, mapDrawY+y*mapScale, mapDrawX+(x+1)*mapScale-1, mapDrawY+(y+1)*mapScale-1, "red")
end
end
gui.box(mapDrawX+playerMapX*mapScale, mapDrawY+mapScale*playerMapY, mapDrawX+mapScale*(playerMapX+1)-1, mapDrawY+mapScale*(playerMapY+1)-1, "green")
gui.box(mapDrawX+(playerMapX-viewRadius)*mapScale, mapDrawY+mapScale*(playerMapY-viewRadius), mapDrawX+(playerMapX+viewRadius+1)*mapScale, mapDrawY+mapScale*(playerMapY+viewRadius+1), "clear", "white")
--Draw enemies on map. Replaced by inputing enemies into map[i] as -1 values
--for i=1,EnemyNumSlots do
-- if enemyX[i] ~= -1 then
-- gui.box(mapDrawX+enemyMapX[i]*mapScale, mapDrawY+mapScale*enemyMapY[i], mapDrawX+mapScale*(enemyMapX[i]+1)-1, mapDrawY+mapScale*(enemyMapY[i]+1)-1, "red")
-- end
--end
--for i=1,4 do
-- if projectileX[i] ~= -1 then
-- gui.box(mapDrawX+projectileRoomX[i]*mapScale, mapDrawY+mapScale*(projectileRoomY[i]-8), mapDrawX+mapScale*(projectileRoomX[i]+1), mapDrawY+mapScale*(projectileRoomY[i]-7), "red")
-- end
--end
end
if drawView == true then
for i=1,viewDiameter*viewDiameter do
local x = (i-1)%viewDiameter - viewRadius
local y = math.floor((i-1)/viewDiameter) - viewRadius
if viewBox[i] == 1 then
gui.box(viewDrawX+8*x, viewDrawY+8*y, viewDrawX+8*(x+1)-1, viewDrawY+8*(y+1)-1, "blue")
elseif viewBox[i] == 0 then
gui.box(viewDrawX+8*x, viewDrawY+8*y, viewDrawX+8*(x+1)-1, viewDrawY+8*(y+1)-1, "black")
elseif viewBox[i] == -1 then
gui.box(viewDrawX+8*x, viewDrawY+8*y, viewDrawX+8*(x+1)-1, viewDrawY+8*(y+1)-1, "red")
end
end
gui.box(viewDrawX, viewDrawY, viewDrawX+8-1, viewDrawY+8-1, "green")
end
if drawCoord == true then
gui.text(192,8,playerX)
gui.text(224,8,playerY)
gui.text(192,16,playerMapX)
gui.text(224,16,playerMapY)
--gui.text(168,48,playerHealth)
end
if drawNeurons == true then
for l=1,layerNum+1 do
local layerDrawSpacing = (neuronVerticalSpacing*inputNum) / layerSize[l]
for i=1, layerSize[l] do
if X[l][i] >= -0.5 and X[l][i] <= 0.5 then
gui.box(drawNeuronsX+neuronDrawSpacing*(l-1), 10+(i-1)*layerDrawSpacing, drawNeuronsX+3+neuronDrawSpacing*(l-1), 10+i*layerDrawSpacing, "gray", "black")
elseif X[l][i] > 0.5 then
gui.box(drawNeuronsX+neuronDrawSpacing*(l-1), 10+(i-1)*layerDrawSpacing, drawNeuronsX+3+neuronDrawSpacing*(l-1), 10+i*layerDrawSpacing, "green", "black")
elseif X[l][i] < -0.5 then
gui.box(drawNeuronsX+neuronDrawSpacing*(l-1), 10+(i-1)*layerDrawSpacing, drawNeuronsX+3+neuronDrawSpacing*(l-1), 10+i*layerDrawSpacing, "red", "black")
end
end
end
end
if drawController == true then
gui.box(controllerDrawX-1, controllerDrawY-1, controllerDrawX+64, controllerDrawY+24, "black")
--A
if Y[1] == true then
gui.box(controllerDrawX+56, controllerDrawY+8, controllerDrawX+63, controllerDrawY+15, "blue")
else
gui.box(controllerDrawX+56, controllerDrawY+8, controllerDrawX+63, controllerDrawY+15, "gray")
end
--up
if Y[2] == true then
gui.box(controllerDrawX+8, controllerDrawY, controllerDrawX+15, controllerDrawY+7, "blue")
else
gui.box(controllerDrawX+8, controllerDrawY, controllerDrawX+15, controllerDrawY+7, "gray")
end
--left
if Y[3] == true then
gui.box(controllerDrawX, controllerDrawY+8, controllerDrawX+7, controllerDrawY+15, "blue")
else
gui.box(controllerDrawX, controllerDrawY+8, controllerDrawX+7, controllerDrawY+15, "gray")
end
--B
if Y[4] == true then
gui.box(controllerDrawX+40, controllerDrawY+8, controllerDrawX+47, controllerDrawY+15, "blue")
else
gui.box(controllerDrawX+40, controllerDrawY+8, controllerDrawX+47, controllerDrawY+15, "gray")
end
--right
if Y[5] == true then
gui.box(controllerDrawX+16, controllerDrawY+8, controllerDrawX+23, controllerDrawY+15, "blue")
else
gui.box(controllerDrawX+16, controllerDrawY+8, controllerDrawX+23, controllerDrawY+15, "gray")
end
--down
if Y[6] == true then
gui.box(controllerDrawX+8, controllerDrawY+16, controllerDrawX+15, controllerDrawY+23, "blue")
else
gui.box(controllerDrawX+8, controllerDrawY+16, controllerDrawX+15, controllerDrawY+23, "gray")
end
end
if drawWeights == true then
for l=1,layerNum do
local layerInSpacing = (neuronVerticalSpacing*inputNum) / layerSize[l]
local layerOutSpacing = (neuronVerticalSpacing*inputNum) / layerSize[l+1]
for i=1,layerSize[l+1] do
for j=1,layerSize[l] do
if W[currentChild][l][i][j] >= .6 then
gui.line(drawNeuronsX+3+neuronDrawSpacing*(l-1), 10+(j-.5)*layerInSpacing, drawNeuronsX-1+neuronDrawSpacing*l, 10+(i-.5)*layerOutSpacing, {0,0,255,48})
end
if W[currentChild][l][i][j] <= -.6 then
gui.line(drawNeuronsX+3+neuronDrawSpacing*(l-1), 10+(j-.5)*layerInSpacing, drawNeuronsX-1+neuronDrawSpacing*l, 10+(i-.5)*layerOutSpacing, {255,0,0,48})
end
end
end
end
end
if drawPopulation == true then
if currentParent1 > 0 then
gui.box(4, 8*currentParent1, 10, 8*(currentParent1+1)-2, "blue")
end
if currentParent2 > 0 then
gui.box(4, 8*currentParent2, 10, 8*(currentParent2+1)-2, "blue")
end
gui.box(4, 8*currentChild, 10, 8*(currentChild+1)-2, "green")
for i=1, populationSize do
if populationFitness[i] ~= nil then
gui.text(12, 8*i, populationFitness[i])
end
end
end
end
function multiply(matrixIn, vectorIn, rows, collumns)
local vectorOut = {}
for i=1,rows do
vectorOut[i] = 0
for j=1,collumns do
vectorOut[i] = vectorOut[i] + matrixIn[i][j]*vectorIn[j]
end
end
return vectorOut
end
function sigmoid(inputVector, inputLength, strength)
local vectorOut = {}
for i=1,inputLength do
vectorOut[i] = 2/(1+math.exp(-strength*inputVector[i])) - 1
end
return vectorOut
end
function tanh(inputVector, inputLength)
local vectorOut = {}
for i=1,inputLength do
vectorOut[i] = (math.exp(inputVector[i]) - math.exp(-inputVector[i])) / (math.exp(inputVector[i]) + math.exp(-inputVector[i]))
end
return vectorOut
end
function stepFunction(inputVector, inputLength)
local vectorOut = {}
for i=1,inputLength do
if inputVector[i] >= outputStepStrength then
vectorOut[i] = true
else
vectorOut[i] = false
end
end
return vectorOut
end
function doublePressNegate(button1, button2)
if Y[button1] == true and Y[button2] == true then
if X[layerNum+1][button1] > X[layerNum+1][button2] then
Y[button2] = false
else
Y[button1] = false
end
end
end
function outputToController()
local controllerInput = {}
controllerInput = {A=Y[1], up=Y[2], left=Y[3], B=Y[4], select=nil, right=Y[5], down=Y[6], start=nil}
joypad.write(1, controllerInput)
end
function controllerOverride()
local controllerInput = {}
controllerInput = joypad.read(1)
Y[1] = controllerInput["A"]
Y[2] = controllerInput["up"]
Y[3] = controllerInput["left"]
Y[4] = controllerInput["B"]
Y[5] = controllerInput["right"]
Y[6] = controllerInput["down"]
end
function updateWeightsFromMem()
for l=1,layerNum do
for i=1,layerSize[l+1] do
for j=1,layerSize[l] do
--if math.random() < stepSize then
-- W[l][i][j] = (memW[l][i][j] + 2)%3 - 1
--else
-- W[l][i][j] = memW[l][i][j]
--end
W[currentChild][l][i][j] = memW[l][i][j] + randomChangeSize*(2*math.random() - 1)
--if W[i][j][k] > 1 then
-- W[i][j][k] = 1
--end
--if W[i][j][k] < -1 then
-- W[i][j][k] = -1
--end
end
end
end
end
function updateWeightsRandom()
for l=1,layerNum do
for i=1,layerSize[l+1] do
for j=1,layerSize[l] do
W[currentChild][l][i][j] = 2*math.random() - 1 --math.random(3)-2
memW[l][i][j] = W[currentChild][l][i][j]
end
end
end
end
function updateMemWeights()
for l=1,layerNum do
for i=1,layerSize[l+1] do
for j=1,layerSize[l] do
memW[l][i][j] = W[currentChild][l][i][j]
end
end
end
end
function restartOld()
if fitness > maxFitness then
maxFitness = fitness
updateMemWeights()
end
updateWeightsFromMem()
if fitness <= minFitness then
if maxFitness <= minFitness then
updateWeightsRandom()
end
end
generation = generation + 1
fitness = 0
timeStuck = 0
savestate.load(saveState)
inputPlayer()
inputMap()
inputView()
end
function inputController()
--controllerInput = {A=true, up=false, left=false, B=false, select=nil, right=false, down=false, start=nil}
controllerInput = joypad.read(1)
for i=1, outputNum do
Y_Train[i] = 0
end
if controllerInput["A"] == true then
Y_Train[1] = 1
end
if controllerInput["up"] == true then
Y_Train[2] = 1
end
if controllerInput["left"] == true then
Y_Train[3] = 1
end
if controllerInput["B"] == true then
Y_Train[4] = 1
end
if controllerInput["right"] == true then
Y_Train[5] = 1
end
if controllerInput["down"] == true then
Y_Train[6] = 1
end
if controllerInput["select"] == true then
trainingMode = false
timeStuck = 0
screenTimeStuck = 0
savestate.load(saveState)
inputPlayer()
inputEnemies()
inputMap()
inputView()
fitness = playerX
end
if controllerInput["up"] == true and keyPressReady == true then
recordMode = (recordMode+1)%2
keyPressReady = false
end
if controllerInput["up"] == false then
keyPressReady = true
end
end
function forwardPropogate()
X[1][1] = -1
for l=1, layerNum do
S[l+1] = multiply(W[currentChild][l], X[l], layerSize[l+1], layerSize[l])
X[l+1] = tanh(S[l+1], layerSize[l+1])
if l < layerNum then
X[l+1][1] = -1
end
end
Y = stepFunction(X[layerNum+1], outputNum)
--make it so up/down etc. can't be hit at same time
doublePressNegate(3,5)
doublePressNegate(2,6)
doublePressNegate(5,6)
doublePressNegate(2,5)
doublePressNegate(3,6)
end
function backPropogate()
for i=1, outputNum do
delta[layerNum+1][i] = (1-X[layerNum+1][i]*X[layerNum+1][i]) * (X[layerNum+1][i] - Y_Train[i])
end
for l=1, layerNum-2 do
for j=1, layerSize[layerNum-l] do
local matrixProduct = 0
for k=1, layerSize[layerNum-l+1] do
emu.print(l)
emu.print(W[layerNum-l][k][j][l])
emu.print(W[layerNum-l][k][j])
emu.print("Delta: " .. delta[layerNum-l+1][k])
matrixProduct = matrixProduct + W[layerNum-l][k][j][layerNum] * delta[layerNum-l+1][k]
end
delta[layerNum-l][j] = (1 - X[layerNum-l][j]*X[layerNum-l][j]) * matrixProduct
end
end
end
function batchGradientDescent()
for l=1, layerNum do
for i=1, layerSize[l+1] do
for j=1, layerSize[l] do
--l = math.random(layerNum)
--i = math.random(layerSize[l+1])
--j = math.random(layerSize[l])
W[currentChild][l][i][j] = W[currentChild][l][i][j] - stepSize * delta[l+1][i] * X[l][j]
end
end
end
end
function restart()
fitness = round(fitness, 0)
generation = generation + 1/populationSize
if populationFitness[currentChild] == nil then
populationFitness[currentChild] = fitness
elseif fitness > populationFitness[currentChild] then
populationFitness[currentChild] = fitness
end
if #populationFitness < populationSize then
currentChild = currentChild + 1
else
currentChild = minimum(populationFitness)
if math.random() <= .5 then
currentParent1 = maximum(populationFitness)
if currentParent1 == currentChild then
currentParent1 = math.random(populationSize-1)
if currentParent1 >= currentChild then
currentParent1 = currentParent1+1
end
end
else
currentParent1 = math.random(populationSize-1)
if currentParent1 >= currentChild then
currentParent1 = currentParent1+1
end
end
currentParent2 = math.random(populationSize-2)
if currentParent2 >= currentChild then
currentParent2 = currentParent2+1
end
if currentParent2 >= currentParent1 then
currentParent2 = currentParent2+1
end
if currentParent2 == currentChild then
currentParent2 = currentParent2+1
end
meanFitness = mean(populationFitness)
stdvFitness = standardDev(populationFitness)
if stdvFitness <= meanFitness/6 then
uniformCrossover(mutationProbability*2)
else
uniformCrossover(mutationProbability)
end
end
timeStuck = 0
screenTimeStuck = 0
fitness = 0
savestate.load(saveState)
inputPlayer()
startFitness = playerX
end
function minimum(vectorIn)
local val = vectorIn[1]
local index = 1
for i=1, #vectorIn do
if vectorIn[i] < val then
index = i
val = vectorIn[i]
end
end
return index
end
function maximum(vectorIn)
local val = vectorIn[1]
local index = 1
for i=1, #vectorIn do
if vectorIn[i] > val then
index = i
val = vectorIn[i]
end
end
return index
end
function uniformCrossover(probability)
for l=1, layerNum do
for i=1, layerSize[l+1] do
for j=1, layerSize[l] do
if math.random() < probability then
W[currentChild][l][i][j] = 2*math.random()-1
else
if math.random() <=0.5 then
W[currentChild][l][i][j] = W[currentParent1][l][i][j]
else
W[currentChild][l][i][j] = W[currentParent2][l][i][j]
end
end
end
end
end
end
function mean(arrayIn)
local sum = 0
--if #arrayIn > 0 then
for i=1, #arrayIn do
sum = sum + arrayIn[i]
end
return sum / #arrayIn
--else
-- return 0
--end
end
function standardDev(arrayIn)
local sum = 0
local stdv = 0
local meanVal = mean(arrayIn)
--if #arrayIn > 1 then
for i=1, #arrayIn do
sum = sum + (arrayIn[i] - meanVal) * (arrayIn[i] - meanVal)
end
stdv = math.sqrt(sum / (#arrayIn-1))
return stdv
--else
-- return 0
--end
end
function round(num, idp)
local mult = 10^(idp or 0)
return math.floor(num * mult + 0.5) / mult
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
--Main Code
------------------------------------------------------------------------------------------------------------------------------------------------------------
inputPlayer()
lastX = playerX
lastY = playerY
lastScreen = memory.readbyte(RamPlayerScreenX)
startFitness = playerX
playerLives = memory.readbyte(RamLives)
lastLives = playerLives
lastScore = playerScore
while (true) do
--read PPU only when changing rooms
--vblankFlag = memory.readbyte(0xE3)
--if vblankFlag == 1 then
-- vblankOff = 1
--elseif vblankOff == 1 then
-- readPPU()
-- vblankOff = 0
--end
inputPlayer()
inputEnemies()
inputMap()
inputView()
index = 2 --first neuron slot is for constant X[1] = 1
inputViewToX(index)
index = index + viewDiameter*viewDiameter
--inputCoordToX(index)
--index = index + 2
--inputEnemyToX(index)
--index = index + 2*EnemyNumSlots
--inputNoise(.1)
forwardPropogate()
if lastScore < playerScore then
fitness = fitness + ((playerScore - lastScore) / 2)
end
if lastX < playerX then
fitness = fitness + (playerX - lastX)
elseif lastX > playerX then
fitness = fitness - 1
end
if trainingMode == true then
inputController()
if recordMode == 1 then
sampleTimer = sampleTimer + 1
if sampleTimer >= sampleRate then
sampleTimer = 0
backPropogate()
batchGradientDescent()
errorVal = 0
for i=1, 6 do
errorVal = errorVal + ((X[layerNum+1][i] - Y_Train[i])*(X[layerNum+1][i] - Y_Train[i]))/2
end
end
-- gui.text(0,8,errorVal)
--outputToController()
end
else
outputToController()
--timeStuck = 0
--screenTimeStuck = 0
-- fitness += playerX - startFitness + playerScore --player score increases fitness
if timerHold == 0 then
if math.abs(playerX - lastX) < 2 then
timeStuck = timeStuck + 1
else
timeStuck = 0
end
if memory.readbyte(RamPlayerScreenX) == lastScreen then
screenTimeStuck = screenTimeStuck + 1
else
screenTimeStuck = 0
end
end
if (fitness < -500 or timeStuck > maxStuckTime or screenTimeStuck > maxScreenStuckTime) then
if geneticMode == true then
emu.print(fitness)
restart()
resetGame = 0
else
restartOld()
end
end
playerLives = memory.readbyte(RamLives)
if lastLives ~= playerLives then
--timerHold = 200
--timeStuck = 0
--screenTimeStuck = 0
if playerLives == 255 then
savestate.load(saveState)
restart()
fitness = 0
elseif playerLives < 2 then
emu.print("lives left: " .. playerLives .. " last lives: " .. lastLives)
fitness = fitness - 200
screenTimeStuck = 0
timeStuck = 0
timerHold = 0
end
end
if timerHold > 0 then
timerHold = timerHold - 1
end
--Cause seizures if he sits still for too long
if timeStuck >= medStuckTime then
inputNoise(1)
end
if (currentTime < lastTime) then
emu.print(currentTime)
fitness = fitness - 1
end
end
drawGui()
gui.text(220, 8, math.floor(generation))
gui.text(48, 8, fitness)
--gui.text(48, 16, startFitness)
--gui.text(48, 24, timerHold)
--gui.text(64, 24, timeStuck)
gui.text(48, 16, math.floor(meanFitness))
--gui.text(48, 24, math.floor(stdvFitness))
lastX = playerX
lastY = playerY
lastScreen = memory.readbyte(RamPlayerScreenX)
lastLives = playerLives
lastScore = playerScore
lastTime = currentTime
emu.frameadvance()
end
------------------------------------------------------------------------------------------------------------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment