[
  {
    "path": "README.md",
    "content": "# Mario Kart 64 - NEAT Algorithm\n\nThis is an implementation of the NEAT algorithm in Lua for Mario Kart 64 and the BizHawk emulator. To use this code you need to create a save state at the beginning of a level. The code expects the name of the save state file to be BB150.state, however you can change this by changing line 54 of the code (state_file = \"BB150.state\"). Make sure the save state file is in the Lua folder (in the BizHawk folder) in order to use it.\n\n[NEAT paper](http://nn.cs.utexas.edu/downloads/papers/stanley.ec02.pdf)\n\n[More information on NEAT](https://www.cs.ucf.edu/~kstanley/neat.html)\n\n### After about 2.5 days of training:\n<a href=\"https://youtu.be/tmltm0ZHkHw\" target=\"_blank\"><img src=\"http://img.youtube.com/vi/tmltm0ZHkHw/0.jpg\" \nalt=\"IMAGE ALT TEXT HERE\" width=\"240\" height=\"180\" border=\"10\" /></a>\n"
  },
  {
    "path": "neat.lua",
    "content": "--===========================================================================--\n---------------------------NEAT algorithm for MK64-----------------------------\n--===========================================================================--\n--                                                                           --\n-- Author: Nick Nelson                                                       --\n-- November, 2016                                                            --\n-- You may freely use this code, but please give credit to the original      --\n--   author.                                                                 --\n--                                                                           --\n-- Setup: create a save state at the beginning of a level. Call it\n--   LR150.state and save it in the lua folder. Note: So far I have only\n--   tested this on Luigi Raceway with 150cc.\n--===========================================================================--\n\n--[[\nKnown bugs:\n- replay best, then reload, in_cell is now nil. update: this only happens\n  sometimes, just do it again\n\nPossible bugs:\n- in at least one case a single species took over the entire population, which\n  could mean i have something wrong in the code that is supposed to prevent\n  this...\n\nHaven't tested:\n- load a backup file instead of the saved file\n- spawning code could use more testing\n\nAreas for improvement:\n- adjust mutation code to better suit the problem\n\nSome unknown object types:\n- 43 hot air balloon in luigi raceway?\n- 42 blue shell I think\n- 21 dead banana?\n- 22 dead banana?\n- 14\n]]\n\nfunction p(line)\n\tconsole.write(line)\nend\n\nfunction pn(line)\n\tconsole.writeline(line)\nend\n\nfunction initialize_things()\n\tconsole.clear()\n\n\t-- pn(game)\n\tpn('MK64 NEAT')\n\n\tstate_file = \"BB150.state\"\n\n\t-- TODO add bumpers?\n\tbutton_input_names = {\n\t-- \"Start\",\n\t\"P1 B\",\n\t\"P1 A\",\n\t\"P1 Z\",\n\t\"P1 A Down\",\n\t\"P1 A Left\",\n\t\"P1 A Right\",\n\t\"P1 A Up\",\n\t\"P1 L\",\n\t\"P1 R\" }\n\n\tbutton_actual_names = {\n\t-- \"Start\",\n\t\"B\",\n\t\"A\",\n\t\"Z\",\n\t\"Down\",\n\t\"Left\",\n\t\"Right\",\n\t\"Up\",\n\t\"L\",\n\t\"R\" }\n\tnum_buttons = #button_actual_names\n\n\tcourse = {}\n\n\t-- collision address\n\tcourse.col_addresses = {\n\t0x1D65A0,  -- Mario Raceway\n\t0x1D4280,  -- Choco Mountain\n\t\"\",\n\t0x1D8380,  -- Banshee Boardwalk\n\t0x1E5170,  -- Yoshi Valley\n\t0x1D4650,  -- Frappe Snowland\n\t0x1E6380,  -- Koopa Troopa Beach\n\t0x1DAF50,  -- Royal Raceway\n\t0x1DD1C0,  -- Luigi Raceway\n\t0x1E1500,  -- Moo Moo Farm\n\t0x1F0AC0,  -- Toad's Turnpike\n\t0x1F0120,  -- Kalamari Desert\n\t0x1D6A70,  -- Sherbet Land\n\t0x1E3080,  -- Rainbow Road\n\t0x1D9AE0,  -- Wario Stadium\n\t\"\",\n\t\"\",\n\t\"\",\n\t0x1E1300,\n\t\"\"}  --\n\n\t-- the collision attribute for each track\n\tcourse.track_attribute = {\n\t\"\",  -- Mario Raceway\n\t\"\",  -- Choco Mountain\n\t\"\",\n\t6,\n\t2,  -- Yoshi Valley\n\t5,  -- Frappe Snowland\n\t3,  -- Koopa Troopa Beach\n\t1,  -- Royal Raceway\n\t1,  -- Luigi Raceway\n\t2,  -- Moo Moo Farm\n\t1,  -- Toad's Turnpike\n\t2,  -- Kalamari Desert\n\t\"\",  -- Sherbet Land\n\t1,  -- Rainbow Road\n\t\"\",  -- Wario Stadium\n\t\"\",\n\t\"\",\n\t\"\",\n\t2,\n\t\"\"}\n\n\tcourse.names = {\n\t\"Mario Raceway      \",\n\t\"Choco Mountain     \",\n\t\"Bowser's Castle    \",\n\t\"Banshee Boardwalk  \",\n\t\"Yoshi Valley       \",\n\t\"Frappe Snowland    \",\n\t\"Koopa Troopa Beach \",\n\t\"Royal Raceway      \",\n\t\"Luigi Raceway      \",\n\t\"Moo Moo Farm       \",\n\t\"Toad's Turnpike    \",\n\t\"Kalimari Desert    \",\n\t\"Sherbet Land       \",\n\t\"Rainbow Road       \",\n\t\"Wario Stadium      \",\n\t\"Block Fort         \",\n\t\"Skyscraper         \",\n\t\"Double Deck        \",\n\t\"DK's Jungle Parkway\",\n\t\"Big Donut          \"}\n\n\tsavestate.load(state_file)\n\n\tcourse.selected_addr = 0xDC5A0\n\tcourse.number = mainmemory.read_u16_be(course.selected_addr) + 1\n\tcourse.name = course.names[course.number]\n\tcourse.col_start = course.col_addresses[course.number]\n\t-- course.col_fin = course.col_addr_fins[course.number]\n\tcourse.col_step = 0x2C\n\tcourse.col_attr_offset = 0x02\n\tcourse.p1_offset = 0x11\n\tcourse.p2_offset = 0x15\n\tcourse.p3_offset = 0x19\n\tcourse.tr_attr = course.track_attribute[course.number]\n\n\t-- this loads the map, just the track sections though\n\tload_map()\n\n\t-- kart data\n\tkart = {}\n\tkart.x_addr = 0x0F69A4\n\tkart.xv_addr = 0x0F69C4\n\tkart.y_addr = 0xF69A8\n\tkart.yv_addr = 0x0F69C8\n\tkart.z_addr = 0x0F69AC\n\tkart.zv_addr = 0x0F69CC\n\tdist_addr = 0x16328A\n\tkart.sin = 0xF6B04\n\tkart.cos = 0xF6B0C\n\n\tcharacter_addr = 0x0DC53B\n\tcharacter = mainmemory.read_u8(character_addr)\n\n\t-- object addresses\n\tobj = {}\n\tobj.addr = 0x15F9B8\n\tobj.stop = 0x162578\n\tobj.step = 0x70\n\tobj.x_offset = 0x18\n\tobj.y_offset = 0x1C\n\tobj.z_offset = 0x20\n\n\t-- some colors\n\tblack  = 0xFF000000\n\twhite  = 0xFFFFFFFF\n\tred    = 0xFFFF0000\n\tbred   = 0x60FF0000\n\tgreen  = 0xFF00FF00\n\tsgreen = 0xFF009900\n\tblue   = 0xFF0000FF\n\tyellow = 0xFFFFFF00\n\tfbox   = 0xFF00007F\n\tbblue  = 0x200000FF\n\tl_off  = 0x500000FF\n\tbwhite = 0x60FFFFFF\n\tback   = 0x40808080\n\tnone   = 0x00000000\n\tb_on   = 0xFF1d2dc1\n\tb_off  = 0x90000000\n\tplayer = {\n\t0x80FF0000, -- mario\n\t0x0, -- luigi\n\t0x0, --\n\t0x0, --\n\t0x0, --\n\t0x0, --\n\t0x0, --\n\t0x0} --\n\n\tmath.randomseed(os.time())\n\tbox_radius = 6\n\tnum_inputs = box_radius*box_radius*4\n\tmax_nodes = 1000000\n\tfitness = 0\n\tgain = 0\n\thighest_fitness = 0\n\thighest_distance = 0\n\tglobal_max_fitness = 0\n\tglobal_best_genome = new_genome()\n\treplay_best_genome = false\n\tnot_advanced = 20\n\tglobal_innovation = 0\n\tcurrent_species = 0\n\tpopulation = 200\n\tcompatibility_threshold = 1.0\n\tc1 = 1\n\tc2 = 1\n\tc3 = 1\n\tmutate_weights_chance   = 0.25\n\tweight_perturbation     = 2\n\t-- must be at least one so all genomes have at least one gene\n\tmutate_structure_chance = 1\n\tadd_node_chance         = 0.66\n\n\tclear_controller()\n\n\t-- initialize population\n\tinitialize_population()\nend -- end initialize_things\n\nfunction create_form()\n\tform = forms.newform(250, 400, \"MK64 NEAT\")\n\thide_net = forms.checkbox(form, \"Hide Network\", 5, 5)\n\thide_xyz_data = forms.checkbox(form, \"Hide Info\", 5, 25)\n\tload_save_label = forms.label(form, \"Save/Load file:\", 5, 50)\n\tload_save_backup = forms.textbox(\n\t\tform, state_file..\"_frequent_backup.txt\", 155, 30, nil, 5, 73\n\t\t)\n\t-- see next_genome() function\n\tsave_button = forms.button(form, \"Save\", save_here, 5, 95)\n\tload_button = forms.button(form, \"Load\", load_generation, 85, 95)\n\treplay_best_button = forms.button(\n\t\tform, \"Replay Best\", replay_best, 5, 125\n\t\t)\n\trestart_button = forms.button(form, \"Restart\", initialize_things, 85, 125)\n\n\tl_generation = forms.label(form, \"Generation: 0\", 5, 160)\n\tl_species = forms.label(form, \"Species: 0\", 5, 185)\n\tl_genome = forms.label(form, \"Genome: 0\", 5, 210)\n\tl_fitness = forms.label(form, \"Fitness: 0\", 5, 235)\n\tl_max_fitness = forms.label(form, \"Max Fitness: 0\", 5, 260)\n\tl_member = forms.label(form, \"Member: \", 5, 285)\n\nend\n\nfunction game_over()\n\tforms.destroy(form)\nend\n\nfunction round(num, idp)\n\tlocal mult = 10^(idp or 0)\n\treturn math.floor(num * mult + 0.5) / mult\nend\n\nfunction display_info()\n\tgui.drawBox(-1, 214, 320, 240, none, bwhite)\n\t-- gui.drawText(-2, 212, course.name, black, none)\n\tgui.drawText(-2, 212, \"Generation:\"..pop.generation, black, none)\n\tgui.drawText(120, 212, \"Species:\"..pop.current_species, black, none)\n\tgui.drawText(240, 212, \"Genome:\"..pop.current_genome, black, none)\n\n\tgui.drawText(-1, 224, \"Fitness:\".. round(fitness), black, none)\n\tgui.drawText(\n\t\t90, 224, \"Max Fitness:\"..round(global_max_fitness), black, none\n\t\t)\n\tgui.drawText(215, 224, \"Member:\"..pop.member..\"/\"..population, black, none)\n\n\tif pop.frame_count % 10 == 0 then\n\t\tforms.settext(l_generation, \"Generation: \"..pop.generation)\n\t\tforms.settext(l_species, \"Species: \"..pop.current_species)\n\t\tforms.settext(l_genome, \"Genome: \"..pop.current_genome)\n\t\tforms.settext(l_fitness, \"Fitness: \"..round(fitness))\n\t\tforms.settext(l_max_fitness, \"Max Fitness: \"..round(global_max_fitness))\n\t\tforms.settext(l_member, \"Member: \"..pop.member..\"/\"..population)\n\tend\n\n-------------------------------------------------------------------------------\n\t-- gui.text(100, 0, \"Distance: \" .. distance, \"white\", \"black\")\n\t-- gui.text(0, 0, course.name, \"white\", \"topright\")\n\n\t-- gui.text(0,17, \"X \".. string.format(\"%.3f\", kart_x),\"white\",\"bottomleft\")\n\t-- gui.text(0,2, \"Xv \".. string.format(\"%.3f\", kart_xv),\"white\",\"bottomleft\")\n\n\t-- gui.text(120,17, \"Y \".. string.format(\"%.3f\", kart_y),\"white\",\"bottomleft\")\n\t-- gui.text(120,2, \"Yv \".. string.format(\"%.3f\", kart_yv),\"white\",\"bottomleft\")\n\n\t-- gui.text(240,17, \"Z \".. string.format(\"%.3f\", kart_z),\"white\",\"bottomleft\")\n\t-- gui.text(240,2, \"Zv \".. string.format(\"%.3f\", kart_zv),\"white\",\"bottomleft\")\n\n\t-- gui.text(360,17, \"XYv \" .. string.format(\"%.3f\", XYspeed) .. \" km/h\",\"white\",\"bottomleft\")\n\t-- gui.text(360,2, \"XYZv \" .. string.format(\"%.3f\", round(XYZspeed)) .. \" km/h\",\"white\",\"topleft\")\n\n\t-- gui.text(530,17, \"sin \" .. string.format(\"%.9f\", k_sin),\"white\",\"bottomleft\")\n\t-- gui.text(530,2, \"cos \" .. string.format(\"%.9f\", k_cos),\"white\",\"bottomleft\")\nend\n\nfunction handle_form()\n\t-- handle form options\n\tif not forms.ischecked(hide_net) then\n\t\tshow_network()\n\tend\n\n\tif not forms.ischecked(hide_xyz_data) then\n\t\tdisplay_info()\n\tend\nend\n\nfunction load_map()\n\tthe_course = {}\n\t-- TODO find where the collision addresses end for each track\n\tfor addr = course.col_start, course.col_start + 0x9000, course.col_step do\n\t\tlocal section = {}\n\t\tsection.p1 = {}\n\t\tsection.p2 = {}\n\t\tsection.p3 = {}\n\t\tlocal the_attribute = mainmemory.read_s16_be(\n\t\t\taddr + course.col_attr_offset\n\t\t\t)\n\t\tif the_attribute == course.tr_attr then\n\t\t\tsection.attribute = the_attribute\n\n\t\t\tlocal p1_addr = mainmemory.read_s24_be(addr + course.p1_offset)\n\t\t\tsection.p1.x = mainmemory.read_s16_be(p1_addr)\n\t\t\tsection.p1.y = mainmemory.read_s16_be(p1_addr + 0x2)\n\t\t\tsection.p1.z = mainmemory.read_s16_be(p1_addr + 0x4)\n\n\t\t\tlocal p2_addr = mainmemory.read_s24_be(addr + course.p2_offset)\n\t\t\tsection.p2.x = mainmemory.read_s16_be(p2_addr)\n\t\t\tsection.p2.y = mainmemory.read_s16_be(p2_addr + 0x2)\n\t\t\tsection.p2.z = mainmemory.read_s16_be(p2_addr + 0x4)\n\n\t\t\tlocal p3_addr = mainmemory.read_s24_be(addr + course.p3_offset)\n\t\t\tsection.p3.x = mainmemory.read_s16_be(p3_addr)\n\t\t\tsection.p3.y = mainmemory.read_s16_be(p3_addr + 0x2)\n\t\t\tsection.p3.z = mainmemory.read_s16_be(p3_addr + 0x4)\n\n\t\t\tthe_course[#the_course + 1] = section\n\t\tend\n\tend\nend\n\nfunction initialize_population()\n\tpop = new_pop()\n\n\tfor i = 1, pop.size do\n\t\tlocal genome = new_genome()\n\t\tmutate(genome)\n\t\tspeciate(genome)\n\tend\n\n\trefresh()\n\tnext_genome(false)\n\tinitialize_run(false)\n\tcreate_backup(\"backup_\"..state_file..\"_gen_0.txt\")\nend\n\nfunction new_pop()\n\tlocal pop = {}\n\tpop.size = population\n\tpop.species = {}\n\tpop.generation = 1\n\tpop.current_species = 1\n\tpop.current_genome = 0\n\tpop.member = 0\n\tpop.frame_count = -1\n\treturn pop\nend\n\nfunction new_species()\n\tlocal species = {}\n\tspecies.genomes = {}\n\tspecies.fitness = 0\n\tspecies.last_fitness = 0\n\tspecies.improvement_age = 0\n\treturn species\nend\n\nfunction new_genome()\n\tlocal genome = {}\n\t-- gonna try to do this with just one genes table,\n\t-- instead of nodes and connections\n\tgenome.genes = {}\n\t-- keep track of how many nodes are in the network (not including outputs)\n\tgenome.num_neurons = num_inputs\n\tgenome.fitness = 0\n\tgenome.shared_fitness = 0\n\tgenome.network = {}\n\tgenome.received_trial = false\n\treturn genome\nend\n\nfunction new_gene(the_in, the_out, the_weight, the_enable, the_innovation)\n\tlocal gene = {}\n\tgene.in_node = the_in\n\tgene.out_node = the_out\n\tgene.weight = the_weight\n\tgene.enable = the_enable\n\tgene.innovation = the_innovation\n\treturn gene\nend\n\nfunction copy_all_species()\n\tlocal species_copy = {}\n\tfor s = 1, #pop.species do\n\t\tlocal species = new_species()\n\t\tfor g = 1, #pop.species[s].genomes do\n\t\t\ttable.insert(\n\t\t\t\tspecies.genomes, copy_genome(pop.species[s].genomes[g])\n\t\t\t\t)\n\t\tend\n\t\ttable.insert(species_copy, species)\n\tend\n\treturn species_copy\nend\n\nfunction copy_genome(genome)\n\tg2 = new_genome()\n\tfor g = 1, #genome.genes do\n\t\ttable.insert(g2.genes, copy_gene(genome.genes[g]))\n\tend\n\tg2.num_neurons = genome.num_neurons\n\tg2.fitness = genome.fitness\n\tg2.shared_fitness = genome.shared_fitness\n\tg2.received_trial = genome.received_trial\n\treturn g2\nend\n\nfunction copy_gene(gene)\n\tlocal g2 = {}\n\tg2.in_node = gene.in_node\n\tg2.out_node = gene.out_node\n\tg2.weight = gene.weight\n\tg2.enable = gene.enable\n\tg2.innovation = gene.innovation\n\treturn g2\nend\n\nfunction new_neuron(genome)\n\tgenome.num_neurons = genome.num_neurons + 1\n\treturn genome.num_neurons\nend\n\nfunction new_innovation(n1, n2)\n\t-- TODO check for existing innovation\n\tglobal_innovation = global_innovation + 1\n\t-- pn(global_innovation)\n\treturn global_innovation\nend\n\nfunction mutate(genome)\n\t-- can mutate each weight\n\tif math.random() < mutate_weights_chance then\n\t\tmutate_weights(genome)\n\tend\n\n\t-- structure mutations\n\t-- can add connection\n\t-- if math.random() <= mutate_structure_chance then\n\tfor i = 1, math.random(1, 2) do\n\t\tadd_connection(genome)\n\tend\n\t-- end\n\n\t-- can add node\n\tif math.random() < add_node_chance then\n\t\tfor i = 1, math.random(1,1) do\n\t\t\tadd_node(genome)\n\t\tend\n\tend\n\n\t-- enable / disable\n\tif math.random() < 0.5 then\n\t\tmutate_enable(genome)\n\tend\nend\n\nfunction mutate_weights(genome)\n\t-- mutate the weights\n\tfor i = 1, #genome.genes do\n\t\tlocal n = math.random(-1,1)\n\t\twhile n == 0 do\n\t\t\tn = math.random(-1,1)\n\t\tend\n\t\tgenome.genes[i].weight = (\n\t\t\tgenome.genes[i].weight + n * math.random() * weight_perturbation\n\t\t\t)\n\tend\nend\n\nfunction add_connection(genome)\n\t-- add a new connection gene with a random weight\n\n\t-- find two random neurons to connect, only one can be an input,\n\t-- and they cannot be connected already\n\tlocal n1\n\tlocal n2\n\tn1, n2 = two_random_neurons(genome)\n\n\t-- generate a new gene with random weight, and in and out neurons\n\t-- TODO check for existing innovation\n\tlocal new_gene = new_gene(\n\t\tn1, n2, -- in_node, out_node\n\t\tmath.random(), -- weight\n\t\ttrue, -- enable bit\n\t\tnew_innovation(n1, n2)\n\t\t)\n\n\tfor k, gene in pairs(genome.genes) do\n\t\tif (gene.in_node == new_gene.in_node and\n\t\t\tgene.out_node == new_gene.out_node) then\n\t\t\treturn  -- TODO make this work better so it just tries again\n\t\tend\n\tend\n\n\ttable.insert(genome.genes, new_gene)\nend\n\nfunction add_node(genome)\n\tif #genome.genes == 0 then\n\t\treturn\n\tend\n\n\t-- pick a connection to split\n\tlocal old_connection = genome.genes[math.random(1, #genome.genes)]\n\told_connection.enable = false\n\tlocal n1 = old_connection.in_node\n\tlocal n2 = old_connection.out_node\n\tlocal new_node_id = new_neuron(genome)\n\n\t-- create two new connections\n\tlocal new_connection_1 = new_gene(n1, new_node_id,\n\t\t1.0,\n\t\ttrue,\n\t\tnew_innovation(n1, new_node_id)\n\t\t)\n\tlocal new_connection_2 = new_gene(new_node_id, n2,\n\t\told_connection.weight,\n\t\ttrue,\n\t\tnew_innovation(new_node_id, n2)\n\t\t)\n\tgenome.num_neurons = genome.num_neurons + 1\n\ttable.insert(genome.genes, new_connection_1)\n\ttable.insert(genome.genes, new_connection_2)\nend\n\nfunction mutate_enable(genome)\n\tfor g = 1, #genome.genes do\n\t\tif math.random() < 0.3 then\n\t\t\tif genome.genes[g].enable then\n\t\t\t\tgenome.genes[g].enable = false\n\t\t\telse\n\t\t\t\tgenome.genes[g].enable = true\n\t\t\tend\n\t\tend\n\tend\nend\n\nfunction two_random_neurons(genome)\n\tlocal n1\n\tlocal n2\n\n\tlocal neurons = {}\n\tfor i = 1, num_inputs do\n\t\tneurons[i] = true\n\tend\n\tfor o = 1, num_buttons do\n\t\tneurons[max_nodes + o] = true\n\tend\n\tfor i = 1, #genome.genes do\n\t\tif genome.genes[i].in_node > num_inputs then\n\t\t\tneurons[genome.genes[i].in_node] = true\n\t\tend\n\t\tif genome.genes[i].out_node > num_inputs then\n\t\t\tneurons[genome.genes[i].out_node] = true\n\t\tend\n\tend\n\tlocal num_neurons = 0\n\tfor _,_ in pairs(neurons) do\n\t\tnum_neurons = num_neurons + 1\n\tend\n\n\t-- choose if n2 will be output neuron or some other neuron\n\tif num_neurons - 9 > num_inputs then\n\t\tlocal excess_neurons = num_neurons - num_inputs - 9\n\t\tlocal rando = math.random()\n\t\tif rando < 0.33 then\n\t\t\tn1 = math.random(1, 144)\n\t\t\tn2 = math.random(145, 145 + excess_neurons)\n\t\telseif rando > 0.33 and rando < 0.66 then\n\t\t\tn1 = math.random(145, 145 + excess_neurons)\n\t\t\tn2 = math.random(1000001, 1000009)\n\t\telse\n\t\t\tn1 = math.random(1, num_neurons - 9)\n\t\t\tn2 = math.random(1000001, 1000009)\n\t\tend\n\telse -- we don't have any other neurons but outputs\n\t\tn1 = math.random(1, num_neurons - 9)\n\t\tn2 = math.random(1000001, 1000009)\n\tend\n\n\treturn n1, n2\nend\n\nfunction get_neurons(genome)\n\tlocal neurons = {}\n\tfor i = 1, num_inputs do\n\t\tneurons[i] = true\n\tend\n\tfor o = 1, num_buttons do\n\t\tneurons[max_nodes + o] = true\n\tend\n\tfor i = 1, #genome.genes do\n\t\tif genome.genes[i].in_node > num_inputs then\n\t\t\tneurons[genome.genes[i].in_node] = true\n\t\tend\n\t\tif genome.genes[i].out_node > num_inputs then\n\t\t\tneurons[genome.genes[i].out_node] = true\n\t\tend\n\tend\n\treturn neurons\nend\n\nfunction speciate(baby_genome)\n\tlocal matched_a_species = false\n\tlocal s = 0\n\tfor i = 1, #pop.species do\n\t\tlocal genes1 = pop.species[i].genomes[1].genes\n\t\tlocal genes2 = baby_genome.genes\n\t\tif #genes2 < #genes1 then -- make sure the shorter genome is first\n\t\t\tgenes1, genes2 = genes2, genes1\n\t\tend\n\t\tcd = compatibility_distance(genes1, genes2)\n\t\tif cd < compatibility_threshold then\n\t\t\ttable.insert(pop.species[i].genomes, baby_genome)\n\t\t\tmatched_a_species = true\n\t\t\ts = i\n\t\t\tbreak\n\t\tend\n\tend\n\n\tif not matched_a_species then\n\t\tlocal unique_species = new_species()\n\t\ttable.insert(unique_species.genomes, baby_genome)\n\t\ttable.insert(pop.species, unique_species)\n\tend\n\treturn s\nend\n\nfunction compatibility_distance(genes1, genes2)\n\tlocal E = excess_genes(genes1, genes2)\n\tlocal D = disjoint_genes(genes1, genes2)\n\tlocal N = math.max(#genes1, #genes2)\n\tif N < 20 then N = 1 end\n\tlocal W = sum_of_weight_differences(genes1, genes2) / (\n\t\t#genes1 + #genes2 - E - D\n\t\t)\n\treturn c1*E/N + c2*D/N + c3*W\nend\n\nfunction excess_genes(genes1, genes2)\n\tlocal excess = 0\n\n\tlocal highest_1 = 0\n\tfor i = 1, #genes1 do\n\t\tif genes1[i].innovation > highest_1 then\n\t\t\thighest_1 = genes1[i].innovation\n\t\tend\n\tend\n\n\tlocal highest_2 = 0\n\tfor i = 1, #genes2 do\n\t\tif genes2[i].innovation > highest_2 then\n\t\t\thighest_2 = genes2[i].innovation\n\t\tend\n\tend\n\n\tif highest_1 > highest_2 then\n\t\tfor i = 1, #genes1 do\n\t\t\tif genes1[i].innovation > highest_2 then\n\t\t\t\texcess = excess + 1\n\n\t\t\tend\n\t\tend\n\telse\n\t\tfor i = 1, #genes2 do\n\t\t\tif genes2[i].innovation > highest_1 then\n\t\t\t\texcess = excess + 1\n\t\t\tend\n\t\tend\n\tend\n\n\treturn excess\nend\n\nfunction disjoint_genes(genes1, genes2)\n\tlocal disjoint = 0\n\n\tfor i = 1, #genes1 do\n\t\tlocal found = false\n\t\tfor j = 1, #genes2 do\n\t\t\tif genes1[i].innovation == genes2[j].innovation then\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\tend\n\t\tend\n\t\tif not found then\n\t\t\tdisjoint = disjoint + 1\n\t\tend\n\tend\n\n\tfor i = 1, #genes2 do\n\t\tlocal found = false\n\t\tfor j = 1, #genes1 do\n\t\t\tif genes2[i].innovation == genes2[j].innovation then\n\t\t\t\tfound = true\n\t\t\t\tbreak\n\t\t\tend\n\t\tend\n\t\tif not found then\n\t\t\tdisjoint = disjoint + 1\n\t\tend\n\tend\n\n\treturn disjoint\nend\n\nfunction sum_of_weight_differences(genes1, genes2)\n\tlocal sum_of_differences = 0\n\n\tfor i = 1, #genes1 do\n\t\tfor j = 1, #genes1 do\n\t\t\tif genes1[i].innovation == genes2[j].innovation then\n\t\t\t\tsum_of_differences = sum_of_differences + math.abs(\n\t\t\t\t\tgenes1[i].weight - genes2[j].weight\n\t\t\t\t\t)\n\t\t\tend\n\t\tend\n\tend\n\n\treturn sum_of_differences\nend\n\nfunction get_objects()\n\tobjects = {}\n\tfor addr = obj.addr, obj.stop, obj.step do\n\t\tlocal object = {}\n\t\tobject.t = mainmemory.read_s16_be(addr) * 0.1\n\n\t\t-- hmmm\n\t\tif object.t > 1.1 and object.t < 1.3 then\n\t\t\tobject.t = 1.2\n\t\tend\n\t\tif object.t > 0.5 and object.t < 0.7 then\n\t\t\tobject.t = 0.6\n\t\tend\n\t\tif object.t > 0.6 and object.t < 0.8 then\n\t\t\tobject.t = 0.7\n\t\tend\n\t\tif object.t > 1.8 and object.t < 2 then\n\t\t\tobject.t = 1.9\n\t\tend\n\n\t\t-- make bad objects a negative value\n\t\tif object.t == 2.6 or object.t == 1.3 or\n\t\t\tobject.t == 0.8 or object.t == 0.7 or\n\t\t\tobject.t == 0.6 or object.t == 3.1 or\n\t\t\tobject.t == 3.2 or object.t == 3 or\n\t\t\tobject.t == 1.9 then\n\t\t\tobject.t = -object.t\n\t\tend\n\n\t\tobject.x = mainmemory.readfloat(addr + obj.x_offset, true)\n\t\tobject.y = mainmemory.readfloat(addr + obj.y_offset, true)\n\t\tobject.z = mainmemory.readfloat(addr + obj.z_offset, true)\n\t\tif object.x == object.x and object.t ~= 0 and\n\t\t\tobject.t ~= 4.3 and in_box(object) then\n\t\t\tobjects[#objects + 1] = object\n\t\tend\n\tend\nend\n\nfunction get_box()\n\tbox = {}\n\tbox.tl    = {}\n\tbox.tr    = {}\n\tbox.bl    = {}\n\tbox.br    = {}\n\tbox.tl.x  = 359 * k_cos + 180 * k_sin + kart_x\n\tbox.tl.z  = 359 * k_sin - 180 * k_cos + kart_z\n\tbox.bl.x  =  -1 * k_cos + 180 * k_sin + kart_x\n\tbox.bl.z  =  -1 * k_sin - 180 * k_cos + kart_z\n\tbox.br.x  =  -1 * k_cos - 180 * k_sin + kart_x\n\tbox.br.z  =  -1 * k_sin + 180 * k_cos + kart_z\n\tbox.tr.x  = 359 * k_cos - 180 * k_sin + kart_x\n\tbox.tr.z  = 359 * k_sin + 180 * k_cos + kart_z\n\t-- gui.text(\n\t-- \t220,130, string.format(\"%.3f\", box.tl.x) .. \",Y,\" .. string.format(\n\t-- \t\t\"%.3f\", box.tl.z\n\t-- \t\t),\"white\",\"topleft\")\n\t-- gui.text(\n\t-- \t480,130, string.format(\"%.3f\", box.tr.x) .. \",Y,\" .. string.format(\n\t-- \t\t\"%.3f\", box.tr.z),\n\t-- \t\"white\",\"topleft\")\n\t-- gui.text(\n\t-- \t220,430, string.format(\"%.3f\", box.bl.x) .. \",Y,\" .. string.format(\n\t-- \t\t\"%.3f\", box.bl.z),\n\t-- \t\"white\",\"topleft\")\n\t-- gui.text(\n\t-- \t480,430, string.format(\"%.3f\", box.br.x) .. \",Y,\" .. string.format(\n\t-- \t\t\"%.3f\", box.br.z),\n\t-- \t\"white\",\"topleft\")\nend\n\nfunction in_box(o)\n\t-- top left to bottom left\n\tlocal a = -(box.bl.z - box.tl.z)\n\tlocal b = box.bl.x - box.tl.x\n\tlocal c = -(a * box.tl.x + b * box.tl.z)\n\tlocal b1 = s_sign(o, a, b, c) < 0\n\t-- pn(b1)\n\n\t-- bottom left to bottom right\n\ta = -(box.br.z - box.bl.z)\n\tb = box.br.x - box.bl.x\n\tc = -(a * box.bl.x + b * box.bl.z)\n\tlocal b2 = s_sign(o, a, b, c) < 0\n\n\t-- bottom right to top right\n\ta = -(box.tr.z - box.br.z)\n\tb = box.tr.x - box.br.x\n\tc = -(a * box.br.x + b * box.br.z)\n\tlocal b3 = s_sign(o, a, b, c) < 0\n\n\t-- top right to top left\n\ta = -(box.tl.z - box.tr.z)\n\tb = box.tl.x - box.tr.x\n\tc = -(a * box.tr.x + b * box.tr.z)\n\tlocal b4 = s_sign(o, a, b, c) < 0\n\n\treturn ((b1 == b2) and (b2 == b3) and (b3 == b4))\nend\n\nfunction s_sign(o, a, b, c)\n\treturn a * o.x + b * o.z + c\nend\n\nfunction get_tiles()\n\ttiles = {}\n\tfor z = -165, 165, 30 do\n\t\tfor x = 344, 13, -30 do\n\t\t\tlocal tile = {}\n\t\t\ttile.n = #tiles + 1\n\t\t\ttile.x = x * k_cos - z * k_sin + kart_x\n\t\t\ttile.z = x * k_sin + z * k_cos + kart_z\n\t\t\ttile.t = get_tile_attribute(tile.x, tile.z)\n\t\t\ttiles[#tiles + 1] = tile\n\t\tend\n\tend\nend\n\nfunction get_tile_attribute(x, z)\n\t-- could be an object, could be track, could be something else\n\n\t-- objects\n\tfor i, o in ipairs(objects) do\n\t\tlocal o_box = get_o_box(o)\n\t\tif in_o_box(x, z, o_box) then\n\t\t\treturn o.t\n\t\tend\n\tend\n\n\t-- track\n\tfor i, section in ipairs(the_course) do\n\t\tif in_section(x, z, section) then\n\t\t\treturn section.attribute\n\t\tend\n\tend\n\n\t-- something else\n\treturn -1\nend\n\nfunction get_o_box(o)\n\tlocal o_box = {}\n\to_box.tl = {}\n\to_box.bl = {}\n\to_box.br = {}\n\to_box.tr = {}\n\to_box.tl.x =  15 * k_cos + 15 * k_sin + o.x\n\to_box.tl.z =  15 * k_sin - 15 * k_cos + o.z\n\to_box.bl.x = -15 * k_cos + 15 * k_sin + o.x\n\to_box.bl.z = -15 * k_sin - 15 * k_cos + o.z\n\to_box.br.x = -15 * k_cos - 15 * k_sin + o.x\n\to_box.br.z = -15 * k_sin + 15 * k_cos + o.z\n\to_box.tr.x =  15 * k_cos - 15 * k_sin + o.x\n\to_box.tr.z =  15 * k_sin + 15 * k_cos + o.z\n\to_box.y = o.y\n\treturn o_box\nend\n\nfunction in_o_box(x, z, o_box)\n\tlocal o = {}\n\to.x = x\n\to.z = z\n\n\t-- top left to bottom left\n\tlocal a = -(o_box.bl.z - o_box.tl.z)\n\tlocal b = o_box.bl.x - o_box.tl.x\n\tlocal c = -(a * o_box.tl.x + b * o_box.tl.z)\n\tlocal b1 = s_sign(o, a, b, c) < 0\n\n\t-- bottom left to bottom right\n\ta = -(o_box.br.z - o_box.bl.z)\n\tb = o_box.br.x - o_box.bl.x\n\tc = -(a * o_box.bl.x + b * o_box.bl.z)\n\tlocal b2 = s_sign(o, a, b, c) < 0\n\n\t-- bottom right to top right\n\ta = -(o_box.tr.z - o_box.br.z)\n\tb = o_box.tr.x - o_box.br.x\n\tc = -(a * o_box.br.x + b * o_box.br.z)\n\tlocal b3 = s_sign(o, a, b, c) < 0\n\n\t-- top right to top left\n\ta = -(o_box.tl.z - o_box.tr.z)\n\tb = o_box.tl.x - o_box.tr.x\n\tc = -(a * o_box.tr.x + b * o_box.tr.z)\n\tlocal b4 = s_sign(o, a, b, c) < 0\n\n\tlocal b5 = o_box.y > kart_y - 74 and o_box.y < kart_y + 74\n\n\treturn ((b1 == b2) and (b2 == b3) and (b3 == b4)) and b5\nend\n\nfunction in_section(x, z, s)\n\tlocal b1 = t_sign(x, z, s.p1,   s.p2)    < 0\n\tlocal b2 = t_sign(x, z, s.p2,   s.p3)  < 0\n\tlocal b3 = t_sign(x, z, s.p3, s.p1)    < 0\n\tlocal b4 = s.p1.y   > kart_y - 74 and s.p1.y   < kart_y + 74\n\tlocal b5 = s.p2.y   > kart_y - 74 and s.p2.y   < kart_y + 74\n\tlocal b6 = s.p3.y > kart_y - 74 and s.p3.y < kart_y + 74\n\tlocal b7 = ((b1 == b2) and (b2 == b3)) and b4 and b5 and b6\n\treturn b7\nend\n\nfunction t_sign(x, z, p2, p3)\n\treturn (x - p3.x) * (p2.z - p3.z) - (p2.x - p3.x) * (z - p3.z)\nend\n\nfunction get_inputs()\n\tget_box()\n\tget_objects()\n\tget_tiles()\nend\n\nfunction get_outputs()\n\tget_inputs()\n\tlocal outputs = evaluate_network()\n\treturn outputs\nend\n\nfunction basic_ai()\n\t-- this demonstrates how simple a solution can be\n\t-- an interesting this about this is that it will play out the same every\n\t-- single time. the game may seem random, but if the input to the game is\n\t-- always the same, then the game will always play exactly the same.\n\t-- the opponents will always drive in the same place, the same items will\n\t-- received from item boxes, etc.\n\tget_inputs()\n\n\tlocal outputs = {}\n\tlocal attr = course.track_attribute[course.number]\n\n\tclear_controller()\n\toutputs = controller\n\n\tlocal button_a = \"P1 A\"\n\toutputs[button_a] = true\n\n\tlocal button_z = \"P1 Z\"\n\tif pop.frame_count % 23 == 0 then\n\t\toutputs[button_z] = true\n\telse\n\t\toutputs[button_z] = false\n\tend\n\n\tlocal button_left = \"P1 A Left\"\n\tlocal button_right = \"P1 A Right\"\n\n\tif tiles[39].t == attr or tiles[31].t == attr then\n\t\toutputs[button_left] = true\n\telse\n\t\toutputs[button_left] = false\n\tend\n\n\tif tiles[99].t == attr or tiles[115].t == attr then\n\t\toutputs[button_right] = true\n\t\toutputs[button_left] = false\n\telse\n\t\toutputs[button_right] = false\n\tend\n\n\treturn outputs\nend\n\nfunction show_network()\n\tlocal text_color = white\n\tlocal cell_border = black\n\tlocal cell_fill = white\n\tlocal line_color = back\n\tlocal object_color = black\n\tlocal track_cell_border = black\n\tlocal track_cell_fill = white\n\n\tlocal genome = {}\n\tif replay_best_genome then\n\t\tgenome = copy_genome(global_best_genome)\n\telse\n\t\tgenome = pop.species[pop.current_species].genomes[pop.current_genome]\n\tend\n\n\t-- aerial view\n\t-- scale is 6\n\tgui.drawBox( -- 60x60 box\n\t\t92-box_radius*5-1, 80-box_radius*5-1,\n\t\t92+box_radius*5+1, 80+box_radius*5+1,\n\t\tblack, bblue)\n\tlocal i = 1\n\tfor x = -box_radius, box_radius - 1 do -- the tiles\n\t\tfor z = -box_radius, box_radius - 1 do\n\t\t\tcell_border = black\n\t\t\tif tiles[i].t == course.tr_attr then  -- track attribute\n\t\t\t\tcell_fill = white\n\t\t\telseif tiles[i].t == -2.6  or tiles[i].t == -3.2 or\n\t\t\ttiles[i].t == -3.1 or tiles[i].t == -3\n\t\t\tor tiles[i].t == 1.9 then  -- tree, cactus\n\t\t\t\tcell_fill = green\n\t\t\telseif tiles[i].t == 1.2 then  -- item box\n\t\t\t\tcell_fill = blue\n\t\t\telseif tiles[i].t == -0.7 then  -- green shell\n\t\t\t\tcell_fill = sgreen\n\t\t\telseif tiles[i].t == -0.8 then  -- red shell\n\t\t\t\tcell_fill = red\n\t\t\telseif tiles[i].t == -1.3 then  -- fake item box\n\t\t\t\tcell_fill = fbox\n\t\t\telseif tiles[i].t == -0.6 then  -- banana\n\t\t\t\tcell_fill = yellow\n\t\t\telseif tiles[i].t > 0 then  -- unknown\n\t\t\t\tcell_fill = black\n\t\t\t\tpn('unknown object: '..tiles[i].t)\n\t\t\telse\n\t\t\t\tcell_border = none\n\t\t\t\tcell_fill = none\n\t\t\tend\n\t\t\tgui.drawBox(\n\t\t\t\t92+x*5, 80+z*5,\n\t\t\t\t92+x*5+5, 80+z*5+5,\n\t\t\t\tcell_border, cell_fill\n\t\t\t\t)\n\t\t\ti = i + 1\n\t\tend\n\tend\n\n\tlocal cells = {}\n\tlocal cell = {}\n\n\tlocal i = 1\n\tfor x = -box_radius, box_radius - 1 do -- the tiles\n\t\tfor y = -box_radius, box_radius - 1 do\n\t\t\tcell = {}\n\t\t\tcell.x = 95+x*5\n\t\t\tcell.y = 83+y*5\n\t\t\tcell.activated = false -- no activation needed on these nodes\n\t\t\tcells[i] = cell\n\t\t\ti = i + 1\n\t\tend\n\tend\n\n\t-- buttons\n\tgui.drawBox(275, 38, 315, 129, black, bblue)\n\tfor o, b in ipairs(button_input_names) do\n\t\tcell = {}\n\t\tcell.x = 270\n\t\tcell.y = 34+10*o\n\t\tif controller[b] == true then\n\t\t\tcell.activated = true\n\t\t\ttext_color = white\n\t\t\tcell_border = black\n\t\t\tcell_fill = white\n\t\telse\n\t\t\tcell.activated = false\n\t\t\ttext_color = b_off\n\t\t\tcell_border = b_off\n\t\t\tcell_fill = back\n\t\tend\n\t\tcells[o + max_nodes] = cell\n\t\tgui.drawText(275, 26+10*o, button_actual_names[o], text_color, 9)\n\t\tgui.drawBox(268, 32+10*o, 272, 36+10*o, cell_border, cell_fill)\n\n\t\t-- this is for the basic AI\n\t\t-- if o == 5 and controller[b] == true then\n\t\t-- \tgui.drawLine(80, 49, 270, 84, blue)\n\t\t-- elseif o == 5 and controller[b] == false then\n\t\t-- \tgui.drawLine(80, 49, 270, 84, bblue)\n\t\t-- else\n\t\t-- \tgui.drawLine(80, 49, 270, 84, none)\n\t\t-- end\n\n\t\t-- if o == 6 and controller[b] == true then\n\t\t-- \tgui.drawLine(105, 49, 270, 92, blue)\n\t\t-- elseif o == 6 and controller[b] == false then\n\t\t-- \tgui.drawLine(105, 49, 270, 92, bblue)\n\t\t-- else\n\t\t-- \tgui.drawLine(105, 49, 270, 92, none)\n\t\t-- end\n\tend\n\n\tfor n, node in pairs(current_network.nodes) do\n\t\tif n > num_inputs and n <= max_nodes then\n\t\t\tcell = {}\n\t\t\tcell.x = 160\n\t\t\tcell.y = 50\n\t\t\tif node.value > 1 then\n\t\t\t\tcell.activated = true\n\t\t\telse\n\t\t\t\tcell.activated = false\n\t\t\tend\n\t\t\tcells[n] = cell\n\t\tend\n\tend\n\n\t-- try to reset the x and y of the hidden nodes so the network looks nice\n\t-- this code closely follows sethbling's code\n\tfor i = 1, 4 do\n\t\tfor _, gene in pairs(genome.genes) do\n\t\t\tif gene.enable then\n\t\t\t\tlocal in_cell = cells[gene.in_node]\n\t\t\t\tlocal out_cell = cells[gene.out_node]\n\t\t\t\tif gene.in_node > num_inputs and\n\t\t\t\t\tgene.in_node <= max_nodes then\n\t\t\t\t\tin_cell.x = 0.75 * in_cell.x + 0.25 * out_cell.x\n\t\t\t\t\tif in_cell.x >= out_cell.x then\n\t\t\t\t\t\tin_cell.x = in_cell.x - 40\n\t\t\t\t\tend\n\t\t\t\t\tif in_cell.x < 150 then\n\t\t\t\t\t\tin_cell.x = 150\n\t\t\t\t\tend\n\n\t\t\t\t\tif in_cell.x > 260 then\n\t\t\t\t\t\tin_cell.x = 260\n\t\t\t\t\tend\n\t\t\t\t\tin_cell.y = 0.75*in_cell.y + 0.25*out_cell.y\n\t\t\t\tend\n\t\t\t\tif gene.out_node > num_inputs and\n\t\t\t\t\tgene.out_node <= max_nodes then\n\t\t\t\t\tout_cell.x = 0.25 * in_cell.x + 0.75 * out_cell.x\n\t\t\t\t\tif in_cell.x >= out_cell.x then\n\t\t\t\t\t\tout_cell.x = out_cell.x + 40\n\t\t\t\t\tend\n\t\t\t\t\tif out_cell.x < 150 then\n\t\t\t\t\t\tout_cell.x = 150\n\t\t\t\t\tend\n\t\t\t\t\tif out_cell.x > 260 then\n\t\t\t\t\t\tout_cell.x = 260\n\t\t\t\t\tend\n\t\t\t\t\tout_cell.y = 0.25 * in_cell.y + 0.75 * out_cell.y\n\t\t\t\tend\n\t\t\tend -- if enabled\n\t\tend -- for loop\n\tend -- do 4 times\n\n\t-- draw hidden nodes, and connections\n\tfor i, cell in pairs(cells) do\n\t\tif i > num_inputs and i <= max_nodes then\n\t\t\tif cell.activated then\n\t\t\t\tcell_border = black\n\t\t\t\tcell_fill = white\n\t\t\telse\n\t\t\t\tcell_border = b_off\n\t\t\t\tcell_fill = back\n\t\t\tend\n\t\t\tgui.drawBox(\n\t\t\t\tcell.x - 2, cell.y - 2,\n\t\t\t\tcell.x + 2, cell.y + 2,\n\t\t\t\tcell_border, cell_fill\n\t\t\t\t)\n\t\tend\n\tend\n\n\t-- draw connections\n\tfor _, gene in pairs(genome.genes) do\n\t\tif gene.enable then\n\t\t\tlocal in_cell = cells[gene.in_node]\n\t\t\tlocal out_cell = cells[gene.out_node]\n\n\t\t\tif out_cell.activated then\n\t\t\t\tline_color = red\n\t\t\telse\n\t\t\t\tline_color = bred\n\t\t\tend\n\n\t\t\tgui.drawLine(\n\t\t\t\tin_cell.x, in_cell.y,\n\t\t\t\tout_cell.x, out_cell.y,\n\t\t\t\tline_color\n\t\t\t\t)\n\t\tend\n\tend\n\n\t-- this is the kart\n\tgui.drawBox(90,106,94,110,none,player[character])\nend\n\nfunction save_here()\n\tlocal file_name = forms.gettext(load_save_backup)\n\tcreate_backup(file_name)\nend\n\nfunction load_generation()\n\tlocal file_name = forms.gettext(load_save_backup)\n\tload_backup(file_name)\nend\n\nfunction replay_best()\n\tsave_here()\n\tload_generation()\n\treplay_best_genome = true\n\tinitialize_run(true)\nend\n\nfunction create_backup(file_name)\n\tlocal file = assert(io.open(file_name, \"w\"))\n\n\t-- first save the best genome\n\tfile:write(global_best_genome.fitness..\" \")\n\tfile:write(global_best_genome.num_neurons..\" \")\n\tfile:write(#global_best_genome.genes.. \"\\n\")\n\tfor g, gene in pairs(global_best_genome.genes) do\n\t\tfile:write(gene.in_node..\" \")\n\t\tfile:write(gene.out_node..\" \")\n\t\tfile:write(gene.weight..\" \")\n\t\tfile:write(gene.innovation..\" \")\n\t\tif gene.enable then\n\t\t\tfile:write(\"1\\n\")\n\t\telse\n\t\t\tfile:write(\"0\\n\")\n\t\tend\n\tend\n\n\t-- then save the entire population\n\tfile:write(pop.generation..\" \")\n\tfile:write(global_max_fitness..\" \")\n\tfile:write(#pop.species..\"\\n\")\n\tfor s, species in pairs(pop.species) do\n\t\tfile:write(species.fitness..\" \")\n\t\tfile:write(species.improvement_age..\" \")\n\t\tfile:write(#species.genomes..\"\\n\")\n\t\tfor g, genome in pairs(species.genomes) do\n\t\t\tfile:write(genome.fitness..\" \")\n\t\t\tfile:write(genome.num_neurons..\" \")\n\t\t\tif genome.received_trial then\n\t\t\t\tfile:write(\"1 \")\n\t\t\telse\n\t\t\t\tfile:write(\"0 \")\n\t\t\tend\n\t\t\tfile:write(#genome.genes..\"\\n\")\n\t\t\tfor h, gene in pairs(genome.genes) do\n\t\t\t\tfile:write(gene.in_node..\" \")\n\t\t\t\tfile:write(gene.out_node..\" \")\n\t\t\t\tfile:write(gene.weight..\" \")\n\t\t\t\tfile:write(gene.innovation..\" \")\n\t\t\t\tif gene.enable then\n\t\t\t\t\tfile:write(\"1\\n\")\n\t\t\t\telse\n\t\t\t\t\tfile:write(\"0\\n\")\n\t\t\t\tend\n\t\t\tend\n\t\tend\n\tend\n\n\tfile:close()\nend\n\nfunction load_backup(file_name)\n\tlocal file = assert(io.open(file_name, \"r\"))\n\n\t-- first read the best genome\n\tglobal_best_genome = {}\n\tglobal_best_genome = new_genome()\n\tlocal num_genes = 0\n\tglobal_best_genome.fitness,\n\tglobal_best_genome.num_neurons,\n\tnum_genes = file:read(\"*number\", \"*number\", \"*number\")\n\tfor g = 1, num_genes do\n\t\tlocal genes = new_gene()\n\t\ttable.insert(global_best_genome.genes, genes)\n\t\tgenes.in_node,\n\t\tgenes.out_node,\n\t\tgenes.weight,\n\t\tgenes.innovation,\n\t\tgenes.enable = file:read(\n\t\t\t\"*number\", \"*number\", \"*number\", \"*number\", \"*number\"\n\t\t\t)\n\t\tif genes.enable == 1 then\n\t\t\tgenes.enable = true\n\t\telse\n\t\t\tgenes.enable = false\n\t\tend\n\tend\n\n\t-- then read the rest of the population\n\tpop = {}\n\tpop = new_pop()\n\tlocal num_species\n\tpop.generation,\n\tglobal_max_fitness,\n\tnum_species = file:read(\"*number\", \"*number\", \"*number\")\n\tfor s = 1, num_species do\n\t\tlocal species = new_species()\n\t\ttable.insert(pop.species, species)\n\t\tlocal num_genomes\n\t\tspecies.fitness,\n\t\tspecies.improvement_age,\n\t\tnum_genomes = file:read(\"*number\", \"*number\", \"*number\")\n\t\tfor g = 1, num_genomes do\n\t\t\tlocal genome = new_genome()\n\t\t\ttable.insert(species.genomes, genome)\n\t\t\tlocal received_trial\n\t\t\tgenome.fitness,\n\t\t\tgenome.num_neurons,\n\t\t\treceived_trial,\n\t\t\tnum_genes = file:read(\n\t\t\t\t\"*number\", \"*number\", \"*number\", \"*number\"\n\t\t\t\t)\n\t\t\tif received_trial == 1 then\n\t\t\t\tgenome.received_trial = true\n\t\t\telse\n\t\t\t\tgenome.received_trial = false\n\t\t\tend\n\t\t\tfor h = 1, num_genes do\n\t\t\t\tlocal gene = new_gene()\n\t\t\t\ttable.insert(genome.genes, gene)\n\t\t\t\tgene.in_node,\n\t\t\t\tgene.out_node,\n\t\t\t\tgene.weight,\n\t\t\t\tgene.innovation,\n\t\t\t\tgene.enable = file:read(\n\t\t\t\t\t\"*number\", \"*number\", \"*number\", \"*number\", \"*number\"\n\t\t\t\t\t)\n\t\t\t\tif gene.enable == 1 then\n\t\t\t\t\tgene.enable = true\n\t\t\t\telse\n\t\t\t\t\tgene.enable = false\n\t\t\t\tend\n\t\t\tend -- end gene loop\n\t\tend -- end genome loop\n\tend -- end species loop\n\n\tfile:close()\n\n\t-- we need to advance to the genome we were on when we saved this file\n\tpop.current_species = 1\n\tpop.current_genome = 1\n\tpop.member = 1\n\tlocal received_trial = true\n\twhile received_trial do\n\t\tnext_genome(false)\n\t\tlocal species = pop.species[pop.current_species]\n\t\tlocal genome = species.genomes[pop.current_genome]\n\t\treceived_trial = genome.received_trial\n\tend\n\n\tinitialize_run(false)\nend\n\nfunction is_dead()\n\tlocal species = pop.species[pop.current_species]\n\tlocal genome = species.genomes[pop.current_genome]\n\n\tif distance < 1 then\n\t\tfitness = XYZspeed * 0.001\n\telseif distance > highest_distance then\n\t\thighest_distance = distance\n\t\tgain = fitness - distance\n\t\tif gain > 0 then\n\t\t\tfitness = distance + gain + round(XYZspeed / 25)\n\t\telse\n\t\t\tfitness = distance + round(XYZspeed / 25)\n\t\tend\n\tend\n\n\tif fitness > highest_fitness then\n\t\thighest_fitness = fitness\n\t\tgenome.fitness = round(highest_fitness)\n\t\tnot_advanced = 20\n\tend\n\n\tif fitness > species.fitness then\n\t\tspecies.fitness = fitness\n\tend\n\n\tif fitness > global_max_fitness then\n\t\tglobal_max_fitness = highest_fitness\n\tend\n\n\t -- cap the frame count bonus\n\tif pop.frame_count < 1250 then\n\t\tadvanced = pop.frame_count * 0.2\n\telse\n\t\tadvanced = 250\n\tend\n\tnot_advanced = not_advanced - 1\n\n\tif advanced + not_advanced <= 0 then\n\t\tgenome.received_trial = true\n\t\tif replay_best_genome then\n\t\t\treplay_best_genome = false\n\t\t\tload_backup(state_file..\"_frequent_backup.txt\")\n\t\tend\n\t\treturn true\n\tend\n\n\treturn false\nend\n\nfunction clear_controller()\n\tcontroller = {}\n\tfor i = 1, #button_input_names do\n\t\tlocal button = button_input_names[i]\n\t\tcontroller[button] = false\n\tend\nend\n\nfunction remove_under_performers()\n\tfor s = 1, #old_pop.species do\n\t\ttable.sort(old_pop.species[s].genomes, function(a,b)\n\t\t\treturn (a.fitness > b.fitness)\n\t\tend)\n\t\t-- retain the top 20% to be parents\n\t\tlocal stop = round(#old_pop.species[s].genomes * 0.2)\n\t\tif stop < 2 then stop = 2 end\n\t\tfor g = #old_pop.species[s].genomes, stop, -1 do\n\t\t\ttable.remove(old_pop.species[s].genomes)\n\t\tend\n\tend\nend\n\nfunction remove_non_improvers()\n\t-- drop species that haven't received an improved fitness in 4 generations\n\tlocal not_killed_off = {}\n\n\tfor s = 1, #old_pop.species do\n\t\tif old_pop.species[s].fitness > old_pop.species[s].last_fitness then\n\t\t\told_pop.species[s].last_fitness = old_pop.species[s].fitness\n\t\t\told_pop.species[s].improvement_age = 0\n\t\telse\n\t\t\told_pop.species[\n\t\t\ts\n\t\t\t].improvement_age = old_pop.species[s].improvement_age + 1\n\t\tend\n\n\t\tif old_pop.species[s].improvement_age < 30 or\n\t\t\told_pop.species[s].fitness >= global_max_fitness then\n\t\t\ttable.insert(not_killed_off, old_pop.species[s])\n\t\tend\n\tend\n\n\told_pop.species = not_killed_off\nend\n\nfunction adjust_fitnesses()\n\tfor s = 1, #old_pop.species do\n\t\tfor g = 1, #old_pop.species[s].genomes do\n\t\t\tlocal species = old_pop.species[s]\n\t\t\tlocal genome = species.genomes[g]\n\t\t\tgenome.fitness = genome.fitness / #species.genomes\n\t\tend\n\tend\nend\n\nfunction calculate_average_fitness()\n\tlocal sum = 0\n\tfor s = 1, #old_pop.species do\n\t\tfor g = 1, #old_pop.species[s].genomes do\n\t\t\tsum = sum + old_pop.species[s].genomes[g].fitness\n\t\tend\n\tend\n\treturn sum / population\nend\n\nfunction calculate_spawn_levels()\n\tlocal spawn_total = 0 -- debug\n\tlocal average_fitness = calculate_average_fitness()\n\tfor s = 1, #old_pop.species do\n\t\tlocal species = old_pop.species[s]\n\t\tspecies.spawn_number = 0\n\t\tfor g = 1, #old_pop.species[s].genomes do\n\t\t\tlocal genome = species.genomes[g]\n\t\t\tgenome.spawn_number = genome.fitness / average_fitness\n\t\t\tspecies.spawn_number = species.spawn_number + genome.spawn_number\n\t\tend\n\t\tspecies.spawn_number = round(species.spawn_number)\n\t\tif species.spawn_number > 0 then\n\t\t\tpn('species spawn number: '..species.spawn_number) -- debug\n\t\tend\n\t\tspawn_total = spawn_total + species.spawn_number -- debug\n\tend\n\tpn('spawn total: '..spawn_total) -- debug\nend\n\nfunction contains_innovation(genes, i)\n\tfor g = 1, #genes do\n\t\tif genes[g].innovation == i then return true end\n\tend\n\treturn false\nend\n\nfunction cross_over(g1, g2)\n\tlocal baby = new_genome()\n\tfor g = 1, #g1.genes do\n\t\tif not contains_innovation(baby.genes, g1.genes[g].innovation) then\n\t\t\ttable.insert(baby.genes, copy_gene(g1.genes[g]))\n\t\tend\n\tend\n\tfor g = 1, #g2.genes do\n\t\tif not contains_innovation(baby.genes, g2.genes[g].innovation) then\n\t\t\ttable.insert(baby.genes, copy_gene(g2.genes[g]))\n\t\tend\n\tend\n\tbaby.num_neurons = math.max(g1.num_neurons, g2.num_neurons)\n\treturn baby\nend\n\nfunction current_pop_size()\n\tlocal size = 0\n\tfor s = 1, #pop.species do\n\t\tfor g = 1, #pop.species[s].genomes do\n\t\t\tsize = size + 1\n\t\tend\n\tend\n\treturn size\nend\n\nfunction make_babies()\n\tpop.species = {}\n\n\t-- put the best genome in the population without mutation\n\tspeciate(global_best_genome)\n\n\tfor s = 1, #old_pop.species do\n\t\tlocal chosen_best_yet = false\n\t\tlocal species = old_pop.species[s]\n\n\t\twhile species.spawn_number >= 1 and current_pop_size() < population do\n\n\t\t\tif not chosen_best_yet then\n\t\t\t\t-- put the best genome in the new pop first for per\n\t\t\t\t-- species elitism (Ai book, page 395)\n\t\t\t\tlocal best_in_species = {}\n\t\t\t\tbest_in_species.fitness = 0\n\t\t\t\tfor i = 1, #species.genomes do\n\t\t\t\t\tif species.genomes[i].fitness > best_in_species.fitness then\n\t\t\t\t\t\tbest_in_species = species.genomes[i]\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\tspeciate(best_in_species)\n\t\t\t\tchosen_best_yet = true\n\t\t\telse\n\t\t\t\t-- only one genome in this species, so just mutate\n\t\t\t\tif #species.genomes == 1 then\n\t\t\t\t\tlocal baby = copy_genome(species.genomes[1])\n\t\t\t\t\tmutate(baby)\n\t\t\t\t\tbaby.received_trial = false\n\t\t\t\t\tspeciate(baby)\n\t\t\t\t-- enough genomes to have parents\n\t\t\t\telseif #species.genomes > 1 then\n\t\t\t\t\tif math.random() < 0.8 then -- crossover chance\n\t\t\t\t\t\tlocal g1_i = math.random(1, #species.genomes)\n\t\t\t\t\t\tlocal g2_i = math.random(1, #species.genomes)\n\t\t\t\t\t\tlocal number_of_attempts = 5\n\t\t\t\t\t\twhile g1_i == g2_i and number_of_attempts > 0 do\n\t\t\t\t\t\t\tg2_i = math.random(1, #species.genomes)\n\t\t\t\t\t\t\tnumber_of_attempts = number_of_attempts - 1\n\t\t\t\t\t\tend\n\t\t\t\t\t\tlocal baby = {}\n\t\t\t\t\t\tif g1_i ~= g2_i then\n\t\t\t\t\t\t\tlocal genome_1 = species.genomes[g1_i]\n\t\t\t\t\t\t\tlocal genome_2 = species.genomes[g2_i]\n\t\t\t\t\t\t\tbaby = cross_over(genome_1, genome_2)\n\t\t\t\t\t\t\tmutate(baby)\n\t\t\t\t\t\t\tspeciate(baby)\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tbaby = species.genomes[g1_i]\n\t\t\t\t\t\t\tmutate(baby)\n\t\t\t\t\t\t\tspeciate(baby)\n\t\t\t\t\t\tend -- if we have two different genomes\n\t\t\t\t\tend -- crossover chance\n\t\t\t\tend -- if 1 or more\n\t\t\tend -- if chosen best\n\t\t\tspecies.spawn_number = species.spawn_number - 1\n\t\tend -- end while\n\tend -- next species\n\n\t-- make sure the population is full\n\t-- TODO force this to choose from genomes that have a fitness above zero\n\tlocal i = 0\n\twhile current_pop_size() < population do\n\t\ti = i + 1\n\t\tlocal k = 10\n\t\tlocal r_species\n\t\tlocal r_genome\n\t\tlocal winner = new_genome()\n\t\twinner.fitness = 0\n\t\tfor j = 1, k do\n\t\t\tr_species = old_pop.species[math.random(1, #old_pop.species)]\n\t\t\tr_genome = copy_genome(\n\t\t\t\tr_species.genomes[math.random(1, #r_species.genomes)]\n\t\t\t\t)\n\t\t\tif r_genome.fitness > winner.fitness then\n\t\t\t\twinner = r_genome\n\t\t\tend\n\t\tend\n\t\tmutate(winner)\n\t\twinner.received_trial = false\n\t\tspeciate(winner)\n\tend\n\tif i > 0 then pn(\"fill: \"..i) end\nend\n\nfunction new_generation()\n\t-- pn('new_generation()')\n\tcreate_backup(\"backup_\"..state_file..\"_gen_\"..pop.generation..\".txt\")\n\n\tmath.randomseed(os.time())\n\n\tpop.current_species = 1\n\tpop.member = 1\n\tpop.generation = pop.generation + 1\n\n\told_pop = {}\n\told_pop.species = copy_all_species(pop.species)\n\n\tremove_under_performers()\n\tremove_non_improvers()\n\tadjust_fitnesses()\n\tcalculate_spawn_levels()\n\tmake_babies()\nend\n\nfunction next_genome(check_for_best)\n\tlocal genome = pop.species[pop.current_species].genomes[pop.current_genome]\n\n\tif check_for_best and genome.fitness > global_best_genome.fitness then\n\t\tglobal_best_genome = {}\n\t\t-- global_best_genome = new_genome()\n\t\tglobal_best_genome = copy_genome(genome)\n\t\tcreate_backup( -- the save button is kind of obsolete when this is here\n\t\t\tstate_file..\"_\"..pop.generation..\"_\"..pop.current_species..\"_\"\n\t\t\t..pop.current_genome..\"_best.txt\"\n\t\t\t)\n\tend\n\n\tif pop.generation > 1 and pop.member % 5 == 0 then\n\t\tcreate_backup(state_file..\"_frequent_backup.txt\")\n\tend\n\n\t-- go to next genome, species, or generation\n\tpop.member = pop.member + 1\n\tpop.current_genome = pop.current_genome + 1\n\tif pop.current_genome > #pop.species[pop.current_species].genomes then\n\t\tpop.current_genome = 1\n\t\tpop.current_species = pop.current_species + 1\n\t\tif pop.current_species > #pop.species then\n\t\t\tnew_generation()\n\t\tend\n\tend\nend\n\nfunction initialize_run(best_run)\n\tsavestate.load(state_file)\n\thighest_fitness = 0\n\thighest_distance = 0\n\tfitness = 0\n\tgain = 0\n\tpop.frame_count = -1\n\tadvanced = 0\n\tnot_advanced = 20\n\tclear_controller()\n\tif best_run then\n\t\tcurrent_network = create_network(global_best_genome)\n\telse\n\t\tcurrent_network = create_network(\n\t\t\tpop.species[pop.current_species].genomes[pop.current_genome]\n\t\t\t)\n\tend\n\tcontroller = get_outputs()\nend\n\nfunction new_node()\n\tlocal node = {}\n\tnode.connection_genes = {}\n\tnode.value = 0.0\n\treturn node\nend\n\nfunction create_network(genome)\n\tlocal network = {}\n\tnetwork.nodes = {}\n\n\t-- create input nodes\n\tfor i = 1, num_inputs do\n\t\tnetwork.nodes[i] = new_node()\n\tend\n\n\t-- create output nodes\n\tfor o = 1, num_buttons do\n\t\tnetwork.nodes[max_nodes + o] = new_node()\n\tend\n\t-- console.clear()\n\ttable.sort(genome.genes, function (a,b)\n\t\treturn a.out_node < b.out_node\n\t\tend)\n\t-- create nodes from genes, if they don't exist yet\n\tfor g = 1, #genome.genes do\n\t\tlocal gene = genome.genes[g]\n\t\t-- pn(gene)\n\t\tif gene.enable then\n\t\t\t-- does this gene's out node exist?\n\t\t\tif network.nodes[gene.out_node] == nil then\n\t\t\t\tnetwork.nodes[gene.out_node] = new_node()\n\t\t\tend\n\t\t\t-- put this gene into it's out_node's connection_genes table so we\n\t\t\t-- know the weights and the values\n\t\t\tlocal node = network.nodes[gene.out_node]\n\t\t\ttable.insert(node.connection_genes, gene)\n\t\t\t-- does this gene's in_node exist?\n\t\t\tif network.nodes[gene.in_node] == nil then\n\t\t\t\tnetwork.nodes[gene.in_node] = new_node()\n\t\t\tend\n\t\tend\n\tend\n\n\treturn network\nend\n\nfunction sigmoid(x)\n\treturn 2 / (1 + math.exp(-x))\nend\n\nfunction evaluate_network()\n\tlocal outputs = {}\n\n\tfor i = 1, num_inputs - 1 do\n\t\tcurrent_network.nodes[i].value = tiles[i].t\n\tend\n\n\tfor i, node in pairs(current_network.nodes) do\n\t\tlocal sum = 0\n\t\tfor g = 1, #node.connection_genes do\n\t\t\tlocal connection = node.connection_genes[g]\n\t\t\tlocal in_value = current_network.nodes[connection.in_node].value\n\t\t\tsum = sum + connection.weight * in_value\n\t\tend\n\n\t\tif #node.connection_genes > 0 then\n\t\t\tnode.value = sigmoid(sum)\n\t\tend\n\tend\n\n\tfor o = 1, num_buttons do\n\t\tlocal button = button_input_names[o]\n\t\tif current_network.nodes[max_nodes + o].value > 1 then\n\t\t\toutputs[button] = true\n\t\telse\n\t\t\toutputs[button] = false\n\t\tend\n\tend\n\n\treturn outputs\nend\n\nfunction refresh()\n\t-- http://tinyclouds.org/rant.html (warning: profanity)\n\t-- because he criticizes those who space things out nicely\n\t-- i don't agree with him entirely, but at least it is kind of funny\n\tpop.frame_count = pop.frame_count + 1\n\n\tdistance = mainmemory.read_s16_be(dist_addr)\n\n\tk_sin    = mainmemory.readfloat(kart.sin,     true)\n\tk_cos    = mainmemory.readfloat(kart.cos,     true)\n\n\tkart_x   = mainmemory.readfloat(kart.x_addr,  true)\n\tkart_xv  = mainmemory.readfloat(kart.xv_addr, true) * 12\n\tkart_y   = mainmemory.readfloat(kart.y_addr,  true)\n\tkart_yv  = mainmemory.readfloat(kart.yv_addr, true) * 12\n\tkart_z   = mainmemory.readfloat(kart.z_addr,  true)\n\tkart_zv  = mainmemory.readfloat(kart.zv_addr, true) * 12\n\n\tXYspeed  = math.sqrt (kart_xv^2+kart_yv^2)\n\tXYZspeed = math.sqrt (kart_xv^2+kart_yv^2+kart_zv^2)\nend\n\n\n-------------------------------------------------------------------------------\n-------------------------------------    --------------------------------------\n-------------------------------------------------------------------------------\nevent.onexit(game_over)\ngame = gameinfo.getromname()\nright_game = \"Mario Kart 64 (USA)\" == game\n\nif right_game then\n\tinitialize_things()\n\tcreate_form()\nelse\n\tpn(\"wrong game\")\nend\n\nwhile right_game do\n\trefresh()\n\n\tif pop.frame_count % 5 == 0 then\n\t\tclear_controller()\n\t\t-- controller = basic_ai()\n\t\tcontroller = get_outputs()\n\tend\n\n\thandle_form()\n\tjoypad.set(controller)\n\n\tif is_dead() then\n\t\tnext_genome(true)\n\t\tinitialize_run(false)\n\tend\n\n\temu.frameadvance()\nend\n"
  }
]