├── .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 |
--------------------------------------------------------------------------------