├── .buildpath ├── .project ├── README.md ├── LICENSE └── src └── simulated-annealing.lua /.buildpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | lua 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.dltk.core.scriptbuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.koneki.ldt.nature 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fceux-lua-simulated-annealing 2 | ============================= 3 | 4 | An implementation of Simulated Annealing for FCEUX Lua Script to search NES input sequence automatically. 5 | 6 | Usage 7 | 1. Prepare FCEUX. 8 | 2. Open a ROM. 9 | 3. Open the Lua Script. 10 | 4. Stop the Lua Script. 11 | 5. Open the TAS Editor. 12 | 6. Run the Lua Script. 13 | 14 | Please note that the script does not push the START button. Please push the START button manually. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 nodchip 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/simulated-annealing.lua: -------------------------------------------------------------------------------- 1 | --- An implementation of Simulated Annealing for FCEUX Lua Script to search input sequence automatically. 2 | -- Written by nodchip 3 | -- 2014, December 24th. 4 | local START_FRAME = 196; 5 | local HARDNESS = 1e-0; 6 | local TIME_LIMIT_SECS = 10.0; 7 | local FRAMES_PER_WINDOW = 10; 8 | 9 | local MODE_UNKNOWN = 0; 10 | local MODE_CHANGE = 1; 11 | 12 | local INVALID = -1; 13 | 14 | local INPUT_A = 0x01; 15 | local INPUT_B = 0x02; 16 | local INPUT_SELECT = 0x04; 17 | local INPUT_START = 0x08; 18 | local INPUT_UP = 0x10; 19 | local INPUT_DOWN = 0x20; 20 | local INPUT_LEFT = 0x40; 21 | local INPUT_RIGHT = 0x80; 22 | 23 | local PROBABILITY_A = 0.9; 24 | local PROBABILITY_B = 0.9; 25 | local PROBABILITY_SELECT = 0.0; 26 | local PROBABILITY_START = 0.0; 27 | local PROBABILITY_UP = 0.0; 28 | local PROBABILITY_DOWN = 0.0; 29 | local PROBABILITY_LEFT = 0.0; 30 | local PROBABILITY_RIGHT = 0.9; 31 | 32 | local INPUT_TO_PROBABILITY = { 33 | [INPUT_A] = PROBABILITY_A, 34 | [INPUT_B] = PROBABILITY_B, 35 | [INPUT_SELECT] = PROBABILITY_SELECT, 36 | [INPUT_START] = PROBABILITY_START, 37 | [INPUT_UP] = PROBABILITY_UP, 38 | [INPUT_DOWN] = PROBABILITY_DOWN, 39 | [INPUT_LEFT] = PROBABILITY_LEFT, 40 | [INPUT_RIGHT] = PROBABILITY_RIGHT, 41 | }; 42 | 43 | local SEARCH_WINDOW_SIZE = 4 * 60; 44 | local SEARCH_STEP = 60; 45 | 46 | local NUMBER_OF_PLAYERS = 1; 47 | 48 | -------------------------------------------------------------------------------- 49 | -- Logging 50 | -------------------------------------------------------------------------------- 51 | 52 | local LEVEL_SEVERE = 0; 53 | local LEVEL_WARNING = 1; 54 | local LEVEL_INFO = 2; 55 | local LEVEL_CONFIG = 3; 56 | local LEVEL_FINE = 4; 57 | local LEVEL_FINER = 5; 58 | local LEVEL_FINEST = 6; 59 | 60 | local LOGGING_LEVEL = LEVEL_INFO; 61 | 62 | --- Prints the argument if the level <= LOGGING_LEVEL 63 | -- @param #number level Logging level 64 | local function log(level, ...) 65 | if level <= LOGGING_LEVEL then 66 | print(...) 67 | end 68 | end 69 | 70 | --- Prints the argument if the LEVEL_SEVERE <= LOGGING_LEVEL 71 | local function severe(...) 72 | log(LEVEL_SEVERE, ...); 73 | end 74 | 75 | --- Prints the argument if the LEVEL_WARNING <= LOGGING_LEVEL 76 | local function warning(...) 77 | log(LEVEL_WARNING, ...); 78 | end 79 | 80 | --- Prints the argument if the LEVEL_INFO <= LOGGING_LEVEL 81 | local function info(...) 82 | log(LEVEL_INFO, ...); 83 | end 84 | 85 | --- Prints the argument if the LEVEL_CONFIG <= LOGGING_LEVEL 86 | local function config(...) 87 | log(LEVEL_CONFIG, ...); 88 | end 89 | 90 | --- Prints the argument if the LEVEL_FINE <= LOGGING_LEVEL 91 | local function fine(...) 92 | log(LEVEL_FINE, ...); 93 | end 94 | 95 | --- Prints the argument if the LEVEL_FINER <= LOGGING_LEVEL 96 | local function finer(...) 97 | log(LEVEL_FINER, ...); 98 | end 99 | 100 | --- Prints the argument if the LEVEL_FINEST <= LOGGING_LEVEL 101 | local function finest(...) 102 | log(LEVEL_FINEST, ...); 103 | end 104 | 105 | -------------------------------------------------------------------------------- 106 | -- Input and input sequence library. 107 | -------------------------------------------------------------------------------- 108 | 109 | --- Generates a random input (the combination of pushed buttons) 110 | -- @return #number Random input. The return value can be passed to taseditor.submitinputchange(). 111 | local function generateRandomInput() 112 | finer("generateRandomInput()"); 113 | 114 | input = 0; 115 | for key, probability in pairs(INPUT_TO_PROBABILITY) do 116 | local r = math.random(); 117 | finest(key, probability, r); 118 | if r < probability then 119 | input = OR(input, key); 120 | end 121 | end 122 | finest(input); 123 | return input; 124 | end 125 | 126 | --- Generates the sequence of random input (the combinations of pushed buttons) for each joypad 127 | -- @param #number startFrame Frame number of the start frame. 128 | -- @param #number endFrame Frame number of the end frame. The input of this frame is not generated. 129 | -- @return #map<#number,#map<#number,#number> > Map from frame numbers to input for each joypad. 130 | local function generateRandomInputSequences(startFrame, endFrame) 131 | finer(string.format("generateRandomInputSequences(%d, %d)", startFrame, endFrame)); 132 | 133 | local inputSequences = {} 134 | for joypad = 1, NUMBER_OF_PLAYERS do 135 | local inputSequence = {}; 136 | for frame = startFrame, endFrame - 1, FRAMES_PER_WINDOW do 137 | inputSequence[frame] = generateRandomInput(); 138 | end 139 | inputSequences[joypad] = inputSequence; 140 | end 141 | 142 | return inputSequences; 143 | end 144 | 145 | --- Sets the sequence of input (the combinations of pushed buttons) to the TAS Editor for each joypad 146 | -- @param #number startFrame Frame number of the start frame. 147 | -- @param #number endFrame Frame number of the end frame. The input of this frame is not generated. 148 | local function setInputSequences(startFrame, endFrame, inputSequences) 149 | finer(string.format("setInputSequences(%d, %d, ...)", startFrame, endFrame)); 150 | 151 | -- Set the playback frame to startFrame 152 | -- to avoid the "cannot resume non-suspended coroutine" error. 153 | taseditor.setplayback(0); 154 | 155 | for joypad = 1, NUMBER_OF_PLAYERS do 156 | local inputSequence = inputSequences[joypad]; 157 | for frame = startFrame, endFrame - 1, FRAMES_PER_WINDOW do 158 | for delta = 0, FRAMES_PER_WINDOW - 1 do 159 | local input = inputSequence[frame]; 160 | -- Please uncomment out to fire the B button. 161 | -- input = AND(input, 0xff - INPUT_B); 162 | -- if (frame + delta) % 2 == 0 then 163 | -- input = OR(input, INPUT_B); 164 | -- end 165 | taseditor.submitinputchange(frame + delta, joypad, input); 166 | end 167 | end 168 | end 169 | 170 | taseditor.applyinputchanges(); 171 | end 172 | 173 | --- Gets the sequence of input (the combinations of pushed buttons) from the TAS Editor for each joypad 174 | -- @param #number startFrame Frame number of the start frame. 175 | -- @param #number endFrame Frame number of the end frame. The input of this frame is not set. 176 | -- @return #map<#number,#map<#number,#number> > Map from frame numbers to input for each joypad. 177 | local function getInputSequences(startFrame, endFrame) 178 | finer("getInputSequences()"); 179 | 180 | local inputSequences = {}; 181 | for joypad = 1, NUMBER_OF_PLAYERS do 182 | local inputSequence = {}; 183 | for frame = startFrame, endFrame - 1 do 184 | local input = taseditor.getinput(frame, joypad); 185 | finest(string.format("joypad:%d frame:%d input:%d", joypad, frame, input)); 186 | if input == -1 then 187 | input = 0; 188 | end 189 | inputSequence[frame] = input; 190 | end 191 | inputSequences[joypad] = inputSequence; 192 | end 193 | 194 | return inputSequences; 195 | end 196 | 197 | -------------------------------------------------------------------------------- 198 | -- Simulated Anneling library 199 | -------------------------------------------------------------------------------- 200 | 201 | --- Calculates the propbability that a state is transit to its neighbor state 202 | -- @param #number energy Energy of the current state 203 | -- @param #number energyNeighbor Energy of the neighbor state 204 | -- @param #number temperature Current temperature 205 | -- @return #number Probability 206 | local function calculateProbability(energy, energyNeighbor, temperature) 207 | finer("calculateProbability()"); 208 | 209 | if energyNeighbor < energy then 210 | return 1.0; 211 | else 212 | local result = math.exp((energy - energyNeighbor) / (temperature + 1e-9) * HARDNESS); 213 | fine(string.format("%f -> %f * %f = %f", energy, energyNeighbor, temperature, result)); 214 | return result; 215 | end 216 | end 217 | 218 | --- Generates an initial state. 219 | -- The initial state contains the frame numbers of the start frame, the frame 220 | -- number of the end frame and the sequence of input (the combinations of 221 | -- pushed buttons). 222 | -- @param #number startFrame Frame number of the start frame 223 | -- @param #number endFrame Frame number of the end frame 224 | -- @return #table Initial state 225 | local function generateInitialState(startFrame, endFrame) 226 | finer("generateInitialState()"); 227 | 228 | local inputSequences = getInputSequences(startFrame, endFrame); 229 | return { 230 | startFrame = startFrame, 231 | endFrame = endFrame, 232 | inputSequences = inputSequences; 233 | }; 234 | end 235 | 236 | --- Returns a random frame number between startFrame (inclusive) and endFrame (exclusive) 237 | -- @param #number startFrame Frame number of the start frame 238 | -- @param #number endFrame Frame number of the end frame 239 | -- @return #number Random frame number. (The return end frame number - startFrame) is divisible by FRAMES_PER_WINDOW. 240 | local function getRandomFrame(startFrame, endFrame) 241 | return math.random(0, (endFrame - startFrame) / FRAMES_PER_WINDOW - 1) * FRAMES_PER_WINDOW + startFrame; 242 | end 243 | 244 | --- Generates a neibor state of a given state. 245 | -- There are several pattern how the sequence of input are changed. 246 | -- 1. A input in the sequence is changed. 247 | -- 2. Two input in the sequence are swapped. 248 | -- 3. Three input in the sequence are swapped. 249 | -- 4. A input is inserted into the sequence. 250 | -- 5. A input is removed from the sequence. 251 | -- @param #state state Given state 252 | local function generateNeighborState(state) 253 | finer("generateNeighborState()"); 254 | 255 | local startFrame = state.startFrame; 256 | local endFrame = state.endFrame; 257 | local neighborState = nil; 258 | while true do 259 | local joypad = math.random(1, NUMBER_OF_PLAYERS); 260 | local mode = math.random(5); 261 | if mode == 1 then 262 | -- Change a input in inputSequence. 263 | local frame = getRandomFrame(startFrame, endFrame); 264 | local input = generateRandomInput(); 265 | if state.inputSequences[joypad][frame] ~= input then 266 | info(string.format("Change joypad:%d frame:%d %d -> %d", joypad, frame, state.inputSequences[joypad][frame], input)); 267 | local inputSequence = copytable(state.inputSequences[joypad]); 268 | inputSequence[frame] = input; 269 | local inputSequences = copytable(state.inputSequences); 270 | inputSequences[joypad] = inputSequence; 271 | return { 272 | startFrame = startFrame, 273 | endFrame = endFrame, 274 | inputSequences = inputSequences 275 | }; 276 | end 277 | 278 | elseif mode == 2 then 279 | -- Swap 2 input. 280 | local frame1 = getRandomFrame(startFrame, endFrame); 281 | local frame2 = getRandomFrame(startFrame, endFrame); 282 | if state.inputSequences[joypad][frame1] ~= state.inputSequences[joypad][frame2] then 283 | info(string.format("Swap joypad:%d frame:%d <-> %d", joypad, frame1, frame2)); 284 | local inputSequence = copytable(state.inputSequences[joypad]); 285 | local temp = inputSequence[frame1]; 286 | inputSequence[frame1] = inputSequence[frame2]; 287 | inputSequence[frame2] = temp; 288 | local inputSequences = copytable(state.inputSequences); 289 | inputSequences[joypad] = inputSequence; 290 | return { 291 | startFrame = startFrame, 292 | endFrame = endFrame, 293 | inputSequences = inputSequences 294 | }; 295 | end 296 | 297 | elseif mode == 3 then 298 | -- Swap 3 input. 299 | local frame1 = getRandomFrame(startFrame, endFrame); 300 | local frame2 = getRandomFrame(startFrame, endFrame); 301 | local frame3 = getRandomFrame(startFrame, endFrame); 302 | if state.inputSequences[joypad][frame1] ~= state.inputSequences[joypad][frame2] and 303 | state.inputSequences[joypad][frame2] ~= state.inputSequences[joypad][frame3] and 304 | state.inputSequences[joypad][frame3] ~= state.inputSequences[joypad][frame1] then 305 | info(string.format("Swap joypad:%d frame:%d <-> %d <-> %d", joypad, frame1, frame2, frame3)); 306 | local inputSequence = copytable(state.inputSequences[joypad]); 307 | local temp = inputSequence[frame1]; 308 | inputSequence[frame1] = inputSequence[frame2]; 309 | inputSequence[frame2] = inputSequence[frame3]; 310 | inputSequence[frame3] = temp; 311 | local inputSequences = copytable(state.inputSequences); 312 | inputSequences[joypad] = inputSequence; 313 | return { 314 | startFrame = startFrame, 315 | endFrame = endFrame, 316 | inputSequences = inputSequences 317 | }; 318 | end 319 | 320 | elseif mode == 4 then 321 | -- Insert a input. 322 | local insertionFrame = getRandomFrame(startFrame, endFrame); 323 | local input = generateRandomInput(); 324 | info(string.format("Insert joypad:%d frame:%d input:%d", joypad, insertionFrame, input)); 325 | local inputSequence = copytable(state.inputSequences[joypad]); 326 | for frame = endFrame - 1, insertionFrame + 1, -1 do 327 | inputSequence[frame] = inputSequence[frame - 1]; 328 | end 329 | inputSequence[insertionFrame] = input; 330 | local inputSequences = copytable(state.inputSequences); 331 | inputSequences[joypad] = inputSequence; 332 | return { 333 | startFrame = startFrame, 334 | endFrame = endFrame, 335 | inputSequences = inputSequences 336 | }; 337 | 338 | elseif mode == 5 then 339 | -- Remove a input. 340 | local removedFrame = getRandomFrame(startFrame, endFrame); 341 | local input = generateRandomInput(); 342 | info(string.format("Remove joypad:%d frame:%d input:%d", joypad, removedFrame, input)); 343 | local inputSequence = copytable(state.inputSequences[joypad]); 344 | for frame = removedFrame, endFrame - 2 do 345 | inputSequence[frame] = inputSequence[frame + 1]; 346 | end 347 | inputSequence[endFrame] = input; 348 | local inputSequences = copytable(state.inputSequences); 349 | inputSequences[joypad] = inputSequence; 350 | return { 351 | startFrame = startFrame, 352 | endFrame = endFrame, 353 | inputSequences = inputSequences 354 | }; 355 | end 356 | end 357 | end 358 | 359 | --- Calculates the energy of a given state. 360 | -- This functions calculates the energy by the following process. 361 | -- 1. Sets the sequence of input to the TAS Editor. 362 | -- 2. Sets the playback frame to startFrame. 363 | -- 3. Lets the emulator to emulate from startFrame (inclusive) to endFrame (exlusive). 364 | -- 4. Retrieves the coordinates of the Mario. 365 | -- 5. Returns the x-coordinates of the Mario multiplied by -1.0. 366 | -- Note that the state will be transit to lower energy. 367 | -- @param #number state Given state 368 | local function calculateEnergy(state) 369 | finer("calculateEnergy()"); 370 | finest("state", state); 371 | 372 | local startFrame = state.startFrame; 373 | local endFrame = state.endFrame; 374 | setInputSequences(startFrame, endFrame, state.inputSequences); 375 | 376 | finest("taseditor.setplayback(startFrame);"); 377 | taseditor.setplayback(startFrame); 378 | 379 | -- For Super Mario Bros. 380 | for frame = startFrame, endFrame - 1 do 381 | finest("emu.frameadvance();"); 382 | emu.frameadvance(); 383 | finest("emu.frameadvance(); exit"); 384 | end 385 | 386 | finest("mx"); 387 | local mx = memory.readbyte(0x0086)+(255*memory.readbyte(0x006D)); 388 | local my = memory.readbyte(0x00CE); 389 | 390 | return -1.0 * mx; 391 | 392 | -- For Balloon Fight. 393 | -- local previousC9 = memory.readbyte(0x00c9); 394 | -- finest("previousC9", previousC9); 395 | -- local shift = 0; 396 | -- for frame = startFrame, endFrame - 1 do 397 | -- finest("emu.frameadvance();"); 398 | -- emu.frameadvance(); 399 | -- finest("emu.frameadvance(); exit"); 400 | -- 401 | -- local c9 = memory.readbyte(0x00c9); 402 | -- if previousC9 == 0xff and c9 == 0 then 403 | -- shift = shift + 256; 404 | -- end 405 | -- previousC9 = c9; 406 | -- finest("previousC9", previousC9); 407 | -- end 408 | -- 409 | -- return -10.0 * (previousC9 + shift) + math.max(0, math.abs(y - 96) - 64); 410 | 411 | -- For TwinBee. 412 | -- for frame = startFrame, endFrame - 1 do 413 | -- finest("emu.frameadvance();"); 414 | -- emu.frameadvance(); 415 | -- finest("emu.frameadvance(); exit"); 416 | -- 417 | -- local rest = memory.readbyte(0x0080); 418 | -- finest("rest", rest); 419 | -- if rest ~= 3 then 420 | -- return -100.0 * frame; 421 | -- end 422 | -- 423 | -- local death = memory.readbyte(0x0012); 424 | -- finest("death", rest); 425 | -- if death == 51 then 426 | -- return -100.0 * frame; 427 | -- end 428 | -- end 429 | -- 430 | -- local x = memory.readbyte(0x00c3); 431 | -- local y = memory.readbyte(0x00c4); 432 | -- return -100.0 * endFrame + math.abs(x - 120) + math.abs(y - 200); 433 | 434 | -- TwinBee 3: Poko Poko Daimaō 435 | -- for frame = startFrame, endFrame - 1 do 436 | -- finest("emu.frameadvance();"); 437 | -- emu.frameadvance(); 438 | -- finest("emu.frameadvance(); exit"); 439 | -- 440 | -- local death1 = memory.readbyte(0x014a); 441 | -- if death1 ~= 0 and death1 ~= 128 then 442 | -- info("death1", death1); 443 | -- return -1e4 * frame + 1e6; 444 | -- end 445 | -- 446 | -- local death2 = memory.readbyte(0x014b); 447 | -- if death2 ~= 0 and death2 ~= 128 then 448 | -- info("death2", death2); 449 | -- return -1e4 * frame + 1e6; 450 | -- end 451 | -- end 452 | -- 453 | -- local score1 = (memory.readbyte(0x07e4) % 8) * 1 454 | -- + math.floor(memory.readbyte(0x07e4) / 8) * 10 455 | -- + (memory.readbyte(0x07e5) % 8) * 100 456 | -- + math.floor(memory.readbyte(0x07e5) / 8) * 1000 457 | -- + (memory.readbyte(0x07e6) % 8) * 10000 458 | -- + math.floor(memory.readbyte(0x07e6) / 8) * 100000; 459 | -- local score2 = (memory.readbyte(0x07e8) % 8) * 1 460 | -- + math.floor(memory.readbyte(0x07e8) / 8) * 10 461 | -- + (memory.readbyte(0x07e9) % 8) * 100 462 | -- + math.floor(memory.readbyte(0x07e9) / 8) * 1000 463 | -- + (memory.readbyte(0x07ea) % 8) * 10000 464 | -- + math.floor(memory.readbyte(0x07ea) / 8) * 100000; 465 | -- 466 | -- local x1 = memory.readbyte(0x0460); 467 | -- local y1 = memory.readbyte(0x0430); 468 | -- 469 | -- local x2 = memory.readbyte(0x0461); 470 | -- local y2 = memory.readbyte(0x0431); 471 | -- 472 | -- local weapon1 = memory.readbyte(0x0146); 473 | -- local weapon2 = memory.readbyte(0x0147); 474 | -- 475 | -- local dummy1 = memory.readbyte(0x016c); 476 | -- local dummy2 = memory.readbyte(0x016d); 477 | -- 478 | -- local speed1 = memory.readbyte(0x054c); 479 | -- local speed2 = memory.readbyte(0x054d); 480 | -- 481 | -- local arm1 = memory.readbyte(0x014c); 482 | -- local arm2 = memory.readbyte(0x014d); 483 | -- 484 | -- return -1e4 * endFrame 485 | -- - score1 486 | -- - score2 487 | -- + math.abs(x1 - 0x60) 488 | -- + math.abs(y1 - 0xb0) 489 | -- + math.abs(x2 - 0xa0) 490 | -- + math.abs(y2 - 0xb0) 491 | -- - 1e6 * weapon1 492 | -- - 1e6 * weapon2 493 | -- - 1e6 * dummy1 494 | -- - 1e6 * dummy2 495 | -- - 1e6 * math.abs(speed1 - 2) 496 | -- - 1e6 * math.abs(speed2 - 2) 497 | -- - 1e6 * arm1 498 | -- - 1e6 * arm2 499 | -- ; 500 | end 501 | 502 | --- Applies Simulated Annealing between startFrame (inclusive) and endFrame (eclusive) 503 | -- Pseudocode of the process is below (copied from Wikipedia). 504 | -- s ← s0; e ← E(s) // Initial state, energy. 505 | -- k ← 0 // Energy evaluation count. 506 | -- while k < kmax and e > emin // While time left & not good enough: 507 | -- T ← temperature(k/kmax) // Temperature calculation. 508 | -- snew ← neighbour(s) // Pick some neighbour. 509 | -- enew ← E(snew) // Compute its energy. 510 | -- if P(e, enew, T) > random() then // Should we move to it? 511 | -- s ← snew; e ← enew // Yes, change state. 512 | -- k ← k + 1 // One more evaluation done. 513 | -- @param #number startFrame Frame number of the start frame 514 | -- @param #number endFrame Frame number of the end frame 515 | local function anneal(startFrame, endFrame) 516 | finer("anneal()"); 517 | 518 | local timeStart = os.clock(); 519 | local timeEnd = timeStart + TIME_LIMIT_SECS; 520 | local state = generateInitialState(startFrame, endFrame); 521 | local energy = calculateEnergy(state) 522 | local result = copytable(state); 523 | local minEnergy = energy; 524 | local counter = 0; 525 | local timeCurrent = os.clock(); 526 | while timeCurrent < timeEnd do 527 | local neighborState = generateNeighborState(state); 528 | local energyNeighbor = calculateEnergy(neighborState); 529 | local random = math.random(); 530 | local temperature = 1.0 * (timeEnd - timeCurrent) / (timeEnd - timeStart) + 1e-8; 531 | local probability = calculateProbability(energy, energyNeighbor, temperature); 532 | if random < probability then 533 | -- Accept the neighbor state. 534 | state = neighborState; 535 | if minEnergy > energyNeighbor then 536 | info(string.format("minEnergy updated! %.5f -> %.5f", minEnergy, energyNeighbor)); 537 | minEnergy = energyNeighbor; 538 | result = copytable(state); 539 | end 540 | info(string.format("+++ Accepted %.5f -> %.5f : minEnergy=%.5f", energy, energyNeighbor, minEnergy)); 541 | energy = energyNeighbor; 542 | else 543 | -- Decline 544 | info(string.format("--- Declined %.5f -> %.5f : minEnergy=%.5f", energy, energyNeighbor, minEnergy)); 545 | end 546 | counter = counter + 1; 547 | timeCurrent = os.clock(); 548 | end 549 | info(string.format("counter:%d minEnergy:%.5f", counter, minEnergy)); 550 | info(); 551 | return result; 552 | end 553 | 554 | -------------------------------------------------------------------------------- 555 | -- Entry point 556 | -------------------------------------------------------------------------------- 557 | 558 | --local duration = 60 * 10; 559 | --local inputSequence = generateRandomInputSequence(START_FRAME, START_FRAME + duration); 560 | --setInputSequence(START_FRAME, START_FRAME + duration, inputSequence); 561 | --anneal(START_FRAME, START_FRAME + duration); 562 | 563 | --local initialInputSequence = generateRandomInputSequence(initialFrame, initialFrame + SEARCH_WINDOW_SIZE); 564 | --setInputSequence(initialFrame, initialFrame + SEARCH_WINDOW_SIZE, initialInputSequence); 565 | finest("emu.speedmode();"); 566 | emu.speedmode("turbo"); 567 | emu.speedmode("maximum"); 568 | 569 | local initialInputSequences = generateRandomInputSequences(START_FRAME, START_FRAME + SEARCH_WINDOW_SIZE); 570 | setInputSequences(START_FRAME, START_FRAME + SEARCH_WINDOW_SIZE, initialInputSequences); 571 | 572 | for step = 0, 0xffff do 573 | local startFrame = START_FRAME + SEARCH_STEP * step; 574 | local endFrame = startFrame + SEARCH_WINDOW_SIZE; 575 | info(string.format("step:%d startFrame:%d endFrame:%d", step, startFrame, endFrame)); 576 | local inputSequences = generateRandomInputSequences(endFrame - SEARCH_STEP, endFrame); 577 | setInputSequences(endFrame - SEARCH_STEP, endFrame, inputSequences); 578 | local state = anneal(startFrame, endFrame); 579 | 580 | setInputSequences(startFrame, endFrame, state.inputSequences) 581 | 582 | -- Playback all the frames to avoid pause. 583 | finest("taseditor.setplayback(startFrame);"); 584 | taseditor.setplayback(startFrame); 585 | 586 | for frame = startFrame, endFrame - 1 do 587 | finest("emu.frameadvance();"); 588 | emu.frameadvance(); 589 | finest("emu.frameadvance(); exit"); 590 | end 591 | end 592 | 593 | taseditor.stopseeking(); 594 | --------------------------------------------------------------------------------