Posts for Bisqwit


Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Chef Stef wrote:
I enjoyed this movie because it was a lot like watching somebody solve a bunch of puzzles. Knowing that a bot created most of the input added some entertainment as well. Certain stages (e.g. 52) had some unexpectedly clever shots that pocketed all the balls at once. This gets a yes vote from me. I only have a couple of questions: 1. On stage 43, why did you suicide twice? I couldn't see any obvious reason for it. 2. What exactly is going on when a (very) slowly-moving ball hits another and they both stop? Shouldn't one or both of them keep moving because of the frictionless setting?
Thanks for the feedback. Here are answers to your questions: 1a) Upon stage 43 begin, the RATE value was 30. Shooting the first shot as-is would have given a bonus of 30*32+20*31+10*30 points, that is 1880. The first suicide changed it to 30*3+20*2+10*1, that is 140. The difference is 1740 points, e.g. 174 frames, or 2.9 seconds. Which was a larger a saving than the delay of the suicide. 1b) At the beginning of the second shot, the RATE value was 8. Shooting the second shot as-is would have given a bonus of 60*10+50*9+40*8 points, that is 1370. The second suicide changed it to 60*3+50*2+40*1 points, that is 320. The difference is 1050 points, e.g. 105 frames, or 1.75 seconds. Which was a again apparently larger a saving than the delay of the suicide. 2) On impact, the kinetic power is divided between the two colliding balls in a manner that depends on the angle of collision. If the kinetic power is low enough, it may be that after dividing it, it is below the minimum value positive expressible with the fixed point mathematics used in the game. E.g. When you divide 0.01 (hex) by 2, you get 0.008 (hex), which gets saved as 0.00, that is interpreted as halt. The threshold for halt may actually be even higher in this game.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
henke37 wrote:
Oh, and there should probably be some sort of optimization for the text mode screens, lossy encodings really suck for text.
x264 is quite good with text and patterned video in general. Large motion vectors and reference frames enable the screens to be encoded very optimally. Of course, pixels are still subject to the same chroma supersampling as regular graphics, but text usually has quite good contrast, unless you have dark dark blue on black background -- but that is not a problem particular to text; any content where the major details are in the subtle contrasts of blue or red hues is going to be blurry / blocky. The screen resolution changing is a more significant problem. Changing from 720x400 (or 640x400 if 9-pixel text displays are not supported) to 640x480 requires some clever ideas. Changes between 640x400 and 320x200 are more trivial. This problem is not specific to DOS in particular. PSX has the same problem. For example, Chrono Cross has content in 320x240, 512x480 and 256x240 in order of commonness. In any case, you will have to pick one of these two: A) Produce video that really changes the resolution (i.e. each screenmode change begins a new video stream). Not many players support this. B) Pick the most commonly occurring resolution and scale all video to that resolution. E.g. in case of CC, above, 320x240. C) Pick a resolution that is an integer multiple of all input resolutions (e.g. 640x400 for mode-13 VGA games, 2560x480 for CC) and scale all video to that resolution. D) Pick a resolution that is an integer multiple of the largest resolution, and display all content within that window, with integer-ratio scaling at most; i.e. black borders around the video in other cases. For example, with Chrono Cross, use 512x480 for the video. Display 320x240 with borders (96 pixels on each side, 120 pixels on top and bottom), 512x480 verbatim and 256x240 with 2x scaling. Or use 640x480 for the video; display 320x240 with 2x scaling, 512x480 with borders and 256x240 with 2x scaling and borders. This is likely the least preferable solution, for a number of reasons (ugliness, aspect ratio ignorance). (Yet, this is what was done with several Gens encodes.) When scaling the video, you should probably use a point filter for content that is intended to be pixelated (unless your scaling ratio is uneven) and a high-quality lanczos filter for content that is not intended to appear pixelated.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
BadPotato wrote:
So I suppose that someone should edith this part.
Since I'm at fault for writing that paragraph, let me clarify that the idea is to display each distinct, consecutively unique frame once. Reducing the frame rate, if it still results in the exact same visible appearance as the original video (including the timing of screen content change), is okay. I clarify this "timing" thing, because it may be not obvious. For simplicity, let's consider this example, Say if your console runs at 8 fps (I know 60 is a typical value, but I'm not going to type 2*60 lines, so I say "8 fps"), and you have observed that it updates the screen as such: 0.000s update 0.125s update 0.250s non update 0.375s update 0.500s update 0.625s non update 0.750s update 0.875s update 1.000s update 1.125s update 1.250s non update 1.375s update 1.500s update 1.625s non update 1.750s update 1.875s update You may observe that the actual screen update rate is about 6 fps (6 out of each 8 frames update the screen). You are free to delete the duplicate frames, as follows: 0.000s update 0.125s update 0.250s non update 0.375s update 0.500s update 0.625s non update 0.750s update 0.875s update 1.000s update 1.125s update 1.250s non update 1.375s update 1.500s update 1.625s non update 1.750s update 1.875s update But you must not change the global fps into 6, because it will incur the following type of change: 0.000 update 0.167 update 0.333 update 0.500 update 0.667 update 0.833 update 1.000 update 1.167 update 1.333 update 1.500 update 1.667 update 1.833 update This would change the timestamps (and intervals) when frames appear. Exception: A lowest-common-denominator type of fps reduction is allowed. But then you must be certain that no update-frame will be dropped or rescheduled. Example is changing 60 fps to 30 fps, if each and every even frame in the video is a non-update. If there is one update-frame that occurs on an even frame number, then you must not change the global fps. An algorithm for reducing the frame rate could be: ̨̟͚̙̝̍̎̽̚ ̑̍̔͗̓̑́͏̠̦̲̞͖̼̣ي̶͍̳̄̋̕ز̡͈͇̯͉̮̑̔͋̄͂ا̫̠̮̬ͯ̓ͯ̾̇ͭ̍̏̋ل̖̠̟̩͍͚͙̹̔͐̽ͪ͢ ̢̞̺͔̤ͣ̾͒ͣ̀͜ي̷͇̱̩̖͚̰ͦͤ͒ͣ͊̍ͨͅم̛͖̳̰̘̪͉̞̌ͩ͊͗̍̊̃͌ك̸̮̳̲̜̟̼̜͐͒̿̅̂͑̋͢͞ن͇̻̬̂̉͞نͮ̊͛ͮ̌́ͪ̃͏͔̺̯ي͓̣̰̩̤͈ͦ͒̇̍̔͟͠ͅ ̖͓͈̔̂̔̅̋ͫ͡أ̋ͮͩ̚̚҉͕̝̣͚̙̩̼̥ͅن͕̙̺̯̝ͮ̌̏̆ͯ̒ͪ̉ ̶̞͇̭͓̝͗̉̄̚أ̶̗̝͉̭̭͔̣͒̓́ف̥̘̪̋̎̀͘̕ه̥̖͎̞͖̹̖̦̦̀̀̓͡م̸̻͍͔̬͈̘̳̪̎̎̄͊͑͒̚ ̸̨̞̝͎̰͔̖ͬ̆ͫ͗͠ͅم͍̍̂͘͢ا̳̠̝͋͆ͮ͡ ̡̩͈͖̺̹̝͈̻ͩ̀ي͒̐̃̆̏҉̶̗͎̦̟ق̨͓͓̝̬̜̘̈́̔͒͛̓̊̔̎̈͝ͅو̮̦͆ͮ̾ͯ͆̈́ل̿ͦ̋̎̂ͨ̌͗̚͏͚̪͕̟̦ Scratch that, I could not figure out the algorithm in 30 minutes. In any case, I think the goal is to get the smallest fractional number that is an integer multiple of all input update-frame timestamps modulo 1. The nominator of said fraction is the desired fps. Examples: ― If the list of input update-frame timestamps modulo 1 is 0; 0.25; 0.5; 0.75, the smallest fractional number is 4/1, and the fps is 4. ― If the list is 0; 0.125; 0.25; 0.5; 0.75, the fps is 8. If unsure, keep the original fps and just drop no-op frames.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Frenom, why did you not ladder-zip to the left at 9:27 like you did in the Cutman stage? In Wily2, why did you take a small fire refill? You had exactly that amount in excess in the end. In Wily3 boss, why did not luck manipulate the first contraption to appear from the top exit? Bleh, this game does not have a clone Rock/Roll/Light flash in the mountains when the sun appears...
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
NitroGenesis wrote:
Here you go PreddY: http://www.archive.org/details/TasFrenomRockman
Windows Media? In MP4 container? With slight A/V desync and framedroppy appearance? You can tell who did not read encoder instructions... In any case, thanks, it's considerably better than nothing!
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Out of nonparticular interest, I created a hacked ROM that makes the score tallying very quick. To those who wonder whether it is possible and better to pocket all six balls in the first board, here's the running log of this new attempt:
$ tail lunar2.log -f |egrep --line-buffered 'eneration|Beginning|Pocketed|limit|eath|angle|skip|SKIP'|grep --line-buffered -v skipped.because|while read l;do echo "`date` $l";done
Sat Apr  3 15:27:41 EEST 2010 Beginning new shot. Table=1, remaining balls=6; 0 on 16-bit donelist, 0 on 32-bit, 0 deaths
Sat Apr  3 15:27:41 EEST 2010 Generation 1: 20480 candidates based on 20480; 0 on 16-bit donelist, 20480 on 32-bit, 0 deaths, frame limit 3503
Sat Apr  3 15:28:46 EEST 2010 Record: Pocketed 6 (time 2189, angle 2, velo 255, preangle 2, prevelo 237) (ball at 80,8C)
Sat Apr  3 15:28:46 EEST 2010 [1108]Setting max frame limit to 2192
k[0238]ang[  2]vel[255]preang[  2]prevel[252]: p=6, f=2193(ef=0), 0.0000, timeout           [1096]Setting max frame limit to 2192
Sat Apr  3 16:02:09 EEST 2010 Ignoring: Pocketed 3 (time 1742, angle 107, velo 255, preangle 107, prevelo 51) (ball at AF,A3)
Sat Apr  3 16:02:10 EEST 2010 Ignoring: Pocketed 3 (time 1742, angle 107, velo 255, preangle 107, prevelo 48) (ball at AF,A3)
Sat Apr  3 16:08:41 EEST 2010 Record: Pocketed 5 (time 1727, angle 128, velo 255, preangle 128, prevelo 156) (ball at 96,8C)
Sat Apr  3 16:38:31 EEST 2010 Ignoring: Pocketed 3 (time 1625, angle 234, velo 255, preangle 234, prevelo 42) (ball at C1,77)
Sat Apr  3 16:38:31 EEST 2010 Ignoring: Pocketed 3 (time 1625, angle 234, velo 255, preangle 234, prevelo 45) (ball at C1,77)
Sat Apr  3 16:38:51 EEST 2010 Ignoring: Pocketed 3 (time 1339, angle 234, velo 255, preangle 234, prevelo 111) (ball at C1,77)
Sat Apr  3 16:38:52 EEST 2010 Ignoring: Pocketed 3 (time 1339, angle 234, velo 255, preangle 234, prevelo 114) (ball at C1,77)
Sat Apr  3 16:39:07 EEST 2010 Ignoring: Pocketed 2 (time 1633, angle 234, velo 255, preangle 234, prevelo 171) (ball at BF,83)
It shows how it found a way to pocket 6 balls, but then it found a way to pocket 5 balls that is faster per-ball than the 6-ball way.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
arflech wrote:
It looks like you sometimes just shoot the cue ball straight into a pocket without touching the other balls...why?
For the aforementioned reason.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
JXQ wrote:
I enjoyed this quite a bit. One shot on board 38 seemed questionable to my untrained eye, though.
The last shot in board 38 was done in such a convoluted way because there was no direct angle that would have resulted in the complete halt of the cue ball, due to numerical inaccuracies in the game. Only 256 possible aiming directions after all. andrewg: The point was a bit of both, but with emphasis on speed. At each round, the shot or pair-of-shots was chosen that resulted in the greatest value for the expression "num_pocketed / (nframes+6)", where "num_pocketed" is the number of balls pocketed in the end, excluding the cue ball, and "nframes" is the number of frames before the next shot is available, including the time spent tallying scores and any waits for the board to reset. Usually the number of frames saved by a death, compared by a deathless shot (which would have otherwise accomplished the same result, but with more score), was around 200. This does not mean that the deaths accounted to only ~2000 frames of saving though. Skipping the death means exponential accumulation of score, not linear. AngerFist: Improving the run is a matter of devising new algorithms or new rules to play by. There is one drawback to the algorithm used by my bot: It always optimizes the *current* shot for the best speed as shown above. It is theoretically possible for a number of reasons, that shooting a less optimal shot *now* allows you to shoot an even more optimal shot *next*. In fact, I corrected two or three such situations in this movie manually (and a dozen of those in the upcoming standard friction run), but it is mathematically impractical to optimize them consistently, due to the exponential increase of the search space. That said, I sort of agree with Xkeeper. The score tallying delay is evil.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Wow. This game is amazing. A. For its "Japanese are crazy" factor. B. Because it quite well reflects the mindset one gets after being tired at work. The stage that started at 2:30 spoke words. C. There's a Finnish flag there! D. Does the サラリーマンチャンプ title have a part of the Mandelbrot fractal on its background? Maybe not, it may just be a regular lightning art...
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Sorry about the triple post.
Derakon wrote:
BadPotato: Bisqwit is also working on a standard-friction run, but optimization takes much longer because of the possibility for setup shots (which leave the balls in a good position to pocket everything rapidly).
Yes. It is currently about to complete board 51 (which it began tackling about 8 days ago). At this rate, it should be finished somewhere in the 5th or 6th month.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
>90 % of what I wrote regarding the happenstances were honest thoughts from my mind, even if not the foremost ones, even if I was intentionally misleading with them. Nach, I congratulate you for the well thought post that is above.
Post subject: source code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Download bot.cpp
Language: cpp

#include <stdio> #include <stdarg> #include <stdlib> #include <string> #include <time> #include <algorithm> #include <sstream> #include <vector> #include <bitset> #include <deque> #include <map> #include <sys> /* For file locking */ #include <sys> #include <sys> #include <unistd> #include <boost> #include <boost> #include <boost> static boost::mt19937 rng; template<unsigned> static inline unsigned GetRnd() { static boost::uniform_int<int> g(0, mod-1); return g(rng); } static inline unsigned GetRnd(unsigned mod) { return boost::uniform_int<int> (0,mod-1) (rng); } static class BitSet16bit { unsigned* data; unsigned setcount; enum { bitsper = 8*sizeof(unsigned) }; enum { max = 0x10000U / bitsper }; public: BitSet16bit() : data(0) { reset(); } void reset() { delete[] data; data = new unsigned[ max ]; for(size_t a=0; a<max; ++a) data[a] = 0; setcount = 0; } void set(size_t index) { if(!operator[] (index)) { data[ offset(index) ] |= mask(index); ++setcount; } } inline bool operator[] (size_t index) const { return data [ offset(index) ] & mask(index); } inline size_t count() const { return setcount; } private: BitSet16bit(const BitSet16bit&); void operator=(const BitSet16bit&); size_t offset(size_t index) const { return index / (bitsper); } size_t mask(size_t index) const { return 1UL << (index % bitsper); } } covered_preshots; static class BitSet32bit { unsigned* data; unsigned setcount; enum { bitsper = 8*sizeof(unsigned) }; enum { max = (0x10000000U / bitsper)*0x10U }; public: BitSet32bit() : data(0) { reset(); } void reset() { delete[] data; data = new unsigned[ max ]; for(size_t a=0; a<max; ++a) data[a] = 0; setcount = 0; } void set(size_t index) { if(!operator[] (index)) { data[ offset(index) ] |= mask(index); ++setcount; } } inline bool operator[] (size_t index) const { return data [ offset(index) ] & mask(index); } inline size_t count() const { return setcount; } private: BitSet32bit(const BitSet32bit&); void operator=(const BitSet32bit&); size_t offset(size_t index) const { return index / bitsper; } size_t mask(size_t index) const { return 1UL << (index % bitsper); } } covered_candidates; static class deathset { int fd; public: deathset() { fd = open("/tmp/endcueset.dat", O_RDWR | O_CREAT | O_TRUNC, 0644); reset(); } void reset() { unsigned buf[0x10000]; for(size_t n=0; n<0x10000; ++n) buf[n] = 0; flock(fd, LOCK_EX); pwrite(fd, buf, 0x10000*sizeof(unsigned), 0); flock(fd, LOCK_UN); } unsigned operator[] (size_t n) const { unsigned b = 0; flock(fd, LOCK_EX); pread(fd, &b, sizeof(unsigned), n*sizeof(unsigned)); flock(fd, LOCK_UN); return b; } void set(size_t n, unsigned nframes) { unsigned b=0; flock(fd, LOCK_EX); pread(fd, &b, sizeof(unsigned), n*sizeof(unsigned)); if(b == 0 || nframes < b) pwrite(fd, &nframes, sizeof(unsigned), n*sizeof(unsigned)); fdatasync(fd); flock(fd, LOCK_UN); } size_t count() const { size_t result = 0; unsigned buf[0x10000]; for(size_t n=0; n<0x10000; ++n) buf[n] = 0; flock(fd, LOCK_EX); pread(fd, buf, 0x10000*sizeof(unsigned), 0); flock(fd, LOCK_UN); for(size_t n=0; n<0x10000>= 9899 /* fceux */ # include "../../../types.h" # include "../../../state.h" # include "../../../movie.h" # include "../../../utils/memorystream.h" # include "../../../fceu.h" # include "../../../driver.h" # include "../coroutine.h" #else extern "C" { # include "types.h" # include "state.h" # include "movie.h" # include "fceu.h" } # include "coroutine.h" #endif #define BISQBOT_INTERNAL #include "bisqbotdefs.hh" #include "bisqbot.hh" int BotFrontDisableVideo=0; #define INCLUDE_BOT_IN_SAVESTATE 0 struct SaveState { std::vector<unsigned> Data; #if INCLUDE_BOT_IN_SAVESTATE const BisqBotStateBase* Bot; SaveState() : Bot(0) { } ~SaveState() { delete Bot; } #endif void Create() { #if INCLUDE_BOT_IN_SAVESTATE delete Bot; #endif #if defined(FCEU_VERSION_NUMERIC) && FCEU_VERSION_NUMERIC >= 9899 /* fceux */ memorystream ms; FCEUSS_SaveMS(&ms, 0); Data.resize(ms.size()); memcpy(&Data[0], ms.buf(), Data.size()); #else FILE*fp = tmpfile(); FCEUSS_SaveFP(fp); rewind(fp); fseek(fp,0,SEEK_END); long pos = ftell(fp); rewind(fp); SaveState result; Data.resize(pos); fread(&Data[0], 1, pos, fp); fclose(fp); #endif #if INCLUDE_BOT_IN_SAVESTATE Bot = BisqBotNewSaveState(); #endif } void Load() const { #if defined(FCEU_VERSION_NUMERIC) && FCEU_VERSION_NUMERIC >= 9899 /* fceux */ memorystream ms; ms.write( (const char*) &Data[0], Data.size()); ms.seekg(0); FCEUSS_LoadFP(&ms, SSLOADPARAM_NOBACKUP); #else FILE*fp = tmpfile(); fwrite(&Data[0], 1, Data.size(), fp); rewind(fp); FCEUSS_LoadFP(fp, 0); fclose(fp); #endif #if INCLUDE_BOT_IN_SAVESTATE BisqBotLoadState(Bot); #endif } #if INCLUDE_BOT_IN_SAVESTATE private: SaveState(const SaveState& b); void operator=(const SaveState& b); #endif }; static unsigned char CurInput; static bool Active=false; static void DisplaySeq(const std::vector<unsigned>& Seq) { std::stringstream msg; for(unsigned a=0; a<Seq.size(); ) { unsigned c=1, ch=Seq[a]; while(a+c < Seq.size() && Seq[a+c] == ch) ++c; #if 1 if(c) { msg << "d(" << c << ","; unsigned b=0; if(ch & K_A) { if(b++)msg << '|'; msg << ("K_A"); } if(ch & K_B) { if(b++)msg << '|'; msg << ("K_B"); } if(ch & K_SE) { if(b++)msg << '|'; msg << ("K_SE"); } if(ch & K_ST) { if(b++)msg << '|'; msg << ("K_ST"); } if(ch & K_U) { if(b++)msg << '|'; msg << ("K_U"); } if(ch & K_D) { if(b++)msg << '|'; msg << ("K_D"); } if(ch & K_L) { if(b++)msg << '|'; msg << ("K_L"); } if(ch & K_R) { if(b++)msg << '|'; msg << ("K_R"); } if(!b) msg << "0"; msg << (") "); a += c; } #else msg << c; msg << '*'; unsigned b=0; if(ch & K_A) { if(b++) msg << ("_"); msg << ("A"); } if(ch & K_B) { if(b++) msg << ("_"); msg << ("B"); } if(ch & K_SE) { if(b++) msg << ("_"); msg << ("SE"); } if(ch & K_ST) { if(b++) msg << ("_"); msg << ("ST"); } if(ch & K_U) { if(b++) msg << ("_"); msg << ("U"); } if(ch & K_D) { if(b++) msg << ("_"); msg << ("D"); } if(ch & K_L) { if(b++) msg << ("_"); msg << ("L"); } if(ch & K_R) { if(b++) msg << ("_"); msg << ("R"); } if(!b) msg << ("00"); msg << (", "); a += c; #endif } BotFrontMsg("%s", msg.str().c_str()); } static std::vector<unsigned> GlobalSeqSample; void BisqBotDisplaySeqSample() { DisplaySeq(GlobalSeqSample); BotFrontMsg("\n"); } void BotFrontMsg(const char*fmt, ...) { FILE*fp; va_list ap; va_start(ap,fmt); vfprintf(stderr, fmt, ap); va_end(ap); fflush(stderr); fp = fopen("rockman.log", "at"); if(!fp) fp = fopen("rockman.log", "wt"); if(!fp) return; flock(fileno(fp), LOCK_EX); fseek(fp,0,SEEK_END); va_start(ap,fmt); fprintf(fp, "[%d]", (int)getpid()); vfprintf(fp, fmt, ap); va_end(ap); fclose(fp); } void BotFrontStart() { fprintf(stderr,"BotFrontStart\n"); BotFrontDisableVideo=1; Active=true; } void BotFrontEnd() { fprintf(stderr,"BotFrontEnd\n"); BisqBotReset(); BotFrontDisableVideo=0; Active=false; } void BotFrontToggle() { if(!Active) BotFrontStart(); else BotFrontEnd(); } int BotFrontActive() { return Active ? 1 : 0; } unsigned BotFrontGetKeymask() { if(Active) { //fprintf(stderr, "CurInput: %02X\n", CurInput); return CurInput; } //fprintf(stderr, "Nul keymask\n"); return 0; } /* FCEU main loop: 1. Emulate frame 2. Read keyboard input (BotFrontIter) 3. Update joypad input (BotFrontGetKeymask) It is actually possible for keyboard input to be read multiple times per frame... Currently we hope this don't happen. */ #if defined(FCEU_VERSION_NUMERIC) && FCEU_VERSION_NUMERIC >= 9899 /* fceux */ # define framecount currFrameCounter #else extern "C" { extern uint32_t framecount; } #endif #include "averages.hh" #include "mutation.hh" #include <tr1> static struct LunarFilenameManager { char LockFileName[512]; char StateFileName[512]; LunarFilenameManager() { int botfrontpid = getpid(); sprintf(LockFileName, "/tmp/botfront-lock-%d", botfrontpid); sprintf(StateFileName, "botfrontL-middle-%d", botfrontpid); } } LunarFilenames; namespace LunarballLaunchManager2ns{ unsigned MAX_FRAMES; unsigned BALLS_REMAINING; } struct LunarballMethods { struct Winner { /* number pocketed best time best_model savestate */ static void GetBest(unsigned& best_pocketed, unsigned& best_time) { best_pocketed=0; best_time=65535; int flags = O_RDWR | O_CREAT; int fd = open(LunarFilenames.LockFileName, flags, 0600); if(fd < 0) return; struct autocloser { int f; autocloser(int fd):f(fd){} ~autocloser(){close(f);} } au(fd); flock(fd, LOCK_EX); char Buf[sizeof(unsigned)*30]; if(pread(fd, Buf, sizeof(Buf), 0) < sizeof(Buf)) return; unsigned* const best_pptr = (unsigned*)&Buf[0]; unsigned* const best_tptr = (unsigned*)&Buf[sizeof(unsigned)*10]; //unsigned* const best_mptr = (unsigned*)&Buf[sizeof(unsigned)*20]; best_pocketed = best_pptr[0]; best_time = best_tptr[0]; } static unsigned GetBestTimeWithPocketCount(unsigned count, unsigned& model) { int flags = O_RDWR | O_CREAT; int fd = open(LunarFilenames.LockFileName, flags, 0600); if(fd < 0) { model=0; return 65535; } struct autocloser { int f; autocloser(int fd):f(fd){} ~autocloser(){close(f);} } au(fd); flock(fd, LOCK_EX); char Buf[sizeof(unsigned)*30]; if(pread(fd, Buf, sizeof(Buf), 0) < sizeof(Buf)) return 65535; //unsigned* const best_pptr = (unsigned*)&Buf[0]; unsigned* const best_tptr = (unsigned*)&Buf[sizeof(unsigned)*10]; unsigned* const best_mptr = (unsigned*)&Buf[sizeof(unsigned)*20]; model = best_mptr[count]; return best_tptr[count]; } static double GenRatio(double num_pocketed, double nframes) { if(nframes < 1) nframes = 1; return num_pocketed / (nframes+6); } static bool SetBest(unsigned num_pocketed, unsigned nframes, unsigned model, bool Force) { using namespace LunarballLaunchManager2ns; int flags = O_RDWR | O_CREAT | (Force ? O_TRUNC : 0); int fd = open(LunarFilenames.LockFileName, flags, 0600); struct autocloser { int f; autocloser(int fd):f(fd){} ~autocloser(){close(f);} } au(fd); flock(fd, LOCK_EX); char Buf[sizeof(unsigned)*30] = { 0 }; pread(fd, Buf, sizeof(Buf), 0); unsigned* const best_pptr = (unsigned*)&Buf[0]; // 0x00 unsigned* const best_tptr = (unsigned*)&Buf[sizeof(unsigned)*10]; // 0x28 unsigned* const best_mptr = (unsigned*)&Buf[sizeof(unsigned)*20]; // 0x50 unsigned& best_pocketed = best_pptr[0]; unsigned& best_time = best_tptr[0]; unsigned& best_model = best_mptr[0]; if(!Force) { double ratio_now = GenRatio(num_pocketed, nframes); double ratio_old = GenRatio(best_pocketed, best_time); /* If the "now" is subpar, reject it. */ if(ratio_now <ratio_old> 0) { unsigned& ref_pocketed = best_pptr[num_pocketed]; unsigned& ref_time = best_tptr[num_pocketed]; unsigned& ref_model = best_mptr[num_pocketed]; double ratio_ref = GenRatio(ref_pocketed, ref_time); if(ratio_now >= ratio_ref || ref_pocketed == 0 || ratio_ref == 0.0) { unsigned cur = model%65536; unsigned pre = model/65536; fprintf(stderr, "Ignoring: Pocketed %u (time %u, angle %u, velo %u, preangle %u, prevelo %u) (ball at %02X,%02X)\n", num_pocketed, nframes, cur%256, cur/256, pre%256, pre/256, RAM[0x370], RAM[0x330] ); ref_pocketed = num_pocketed; ref_time = nframes; ref_model = model; pwrite(fd, Buf, sizeof(Buf), 0); } } return false; } /* if(num_pocketed <best_pocketed>= best_time) return false; */ if(ratio_now == ratio_old) { unsigned now_cur = model%65536, old_cur = best_model%65536; unsigned now_pre = model/65536, old_pre = best_model/65536; unsigned now_cur_vel = now_cur/256, old_cur_vel = old_cur/256; unsigned now_pre_vel = now_pre/256, old_pre_vel = old_pre/256; if(num_pocketed < best_pocketed) return false; if(now_cur_vel <old_cur_vel> old_pre_vel) return false; if(model == best_model) return false; } } best_pocketed = num_pocketed; best_time = nframes; best_model = model; best_pptr[best_pocketed] = best_pocketed; best_tptr[best_pocketed] = best_time; best_mptr[best_pocketed] = best_model; pwrite(fd, Buf, sizeof(Buf), 0); if(!Force) { unsigned cur = model%65536; unsigned pre = model/65536; fprintf(stderr, "Record: Pocketed %u (time %u, angle %u, velo %u, preangle %u, prevelo %u) (ball at %02X,%02X)\n", best_pocketed, best_time, cur%256, cur/256, pre%256, pre/256, RAM[0x370], RAM[0x330] ); //beststate.Create(); FCEUSS_Save(LunarFilenames.StateFileName); if(RAM[0x18E] == 0) // no remaining balls MAX_FRAMES = std::min(MAX_FRAMES, best_time+3); } return true; } static void Compete(unsigned num_pocketed, unsigned nframes, unsigned model) { SetBest(num_pocketed, nframes, model, false); ContestLimit(num_pocketed, nframes); } static void Load() { FCEUSS_Load(LunarFilenames.StateFileName); } static void LoadLimit() { int flags = O_RDONLY; int fd = open(LunarFilenames.LockFileName, flags, 0600); if(fd < 0) return; struct autocloser { int f; autocloser(int fd):f(fd){} ~autocloser(){close(f);} } au(fd); flock(fd, LOCK_EX); char Buf[sizeof(unsigned)*30]; if(pread(fd, Buf, sizeof(Buf), 0) < sizeof(Buf)) return; //unsigned* const best_pptr = (unsigned*)&Buf[0]; unsigned* const best_tptr = (unsigned*)&Buf[sizeof(unsigned)*10]; //unsigned* const best_mptr = (unsigned*)&Buf[sizeof(unsigned)*20]; for(unsigned num_pocketed=1; num_pocketed<10; ++num_pocketed) { unsigned nframes = best_tptr[num_pocketed]; ContestLimit(num_pocketed, nframes); } } static void ContestLimit(unsigned num_pocketed, unsigned nframes) { if(!num_pocketed || !nframes) return; using namespace LunarballLaunchManager2ns; unsigned max_hope = nframes * BALLS_REMAINING / num_pocketed; if(num_pocketed < BALLS_REMAINING) max_hope += 7 + 230; else max_hope += 3; if(max_hope <MAX_FRAMES> minus_dist ? -1 : 1; unsigned dist = std::min(plus_dist, minus_dist); key = dir == 1 ? K_R : K_L; unsigned elapse_norepeat = 16 + (dist-1)*1; unsigned elapse_repeat = 1 + 2*(dist-1); autofire = elapse_repeat <= elapse_norepeat; } bool operator==(const AimingMethod& b) const { return key==b.key && autofire==b.autofire; } bool operator!=(const AimingMethod& b) const { return !operator==(b); } bool operator<(const AimingMethod& b) const { return (key*2+autofire) <b>= maxframes) goto Fail; } EndLoop: Fail: ; scrFinish(0); } }; static bool DoAiming(unsigned angle, unsigned& frames_begin, unsigned maxframes) { //fprintf(stderr, "#1 aiming for angle %u, f(%u)\n", angle, frames_begin); scrBegin; //fprintf(stderr, "#2 aiming for angle %u, f(%u)\n", angle, frames_begin); static AimingMethod method; method.Decide(angle); /*fprintf(stderr, "#M aiming for angle %u, f(%u): %02X,%s, 0x3C0=%u\n", angle, frames_begin, method.key, method.autofire?"true":"false", RAM[0x3C0]);*/ static unsigned frno; frno=0; while(method.Run(angle, frno++, frames_begin, maxframes)) scrReturn(1); //fprintf(stderr, "#3 aiming for angle %u, f(%u)\n", angle, frames_begin); scrFinish(0); } static bool DoFiring(unsigned& frames_begin, unsigned maxframes) { scrBegin; if(!frames_begin) RunFrameWith(0); do { RunFrameWith(K_B); ++frames_begin; // If white was pocketed or maxframes hit if(WhitePocketed() || frames_begin > maxframes) goto Fail; } while(RAM[0x3D0] == 0 || RAM[0x3D0] == 0xFF); EndLoop: Fail:; scrFinish(0); } static bool DoWaitBallsStop(unsigned& nframes, const char*& failreason, unsigned maxframes) { scrBegin; while(RAM[0x3D0] != 0 && RAM[0x3D0] != 0xFF) { if(RAM[0x3A0] == 0) { // If white ball is not moving, check if all other balls // have been pocketed for(unsigned b=1; b<16; ++b) if(RAM[0x310+b] <0x10>= 4) goto NotAllPocketed; break; // yay! NotAllPocketed:; } if(WhitePocketed()) { /*failreason="blunder";*/ goto Fail; } if(++nframes > maxframes) { failreason="timeout"; goto Fail; } //if(frames_begin+nframes >= 300) goto Fail; RunFrameWith(0); } EndLoop: Fail:; scrFinish(0); } static bool DoWaitNextShotPossible( unsigned& nframes_total, unsigned& nframes_transition, bool verbose, unsigned maxframes) { /* After the shot has been shot, wait until the cursor can be moved again. */ scrBegin; static SaveState tmp; static unsigned was_angle, became_angle1, became_angle2; static unsigned n_attempts; static bool transition; transition = false; if(verbose) fprintf(stderr, "Waiting until next shot can be shot\n"); n_attempts = 0; NextFrameWaiter: if(++n_attempts >= 1000) goto Fail; if(!transition && RAM[0x18F] == 0) transition = true; if(nframes_total-nframes_transition > maxframes) goto Fail; tmp.Create(); was_angle = RAM[0x3C0]; RunFrameWith(K_R); ++nframes_total; if(transition) ++nframes_transition; became_angle1 = RAM[0x3C0]; if(became_angle1 == was_angle) goto NextFrameWaiter; // K_R affected angle, check if K_L also affects angle tmp.Load(); --nframes_total; if(transition) --nframes_transition; RunFrameWith(K_L); ++nframes_total; if(transition) ++nframes_transition; became_angle2 = RAM[0x3C0]; if(became_angle2 == was_angle) goto NextFrameWaiter; // K_R and K_L both worked // If Right and Left produced the *same* change, reject this action if(became_angle1 == became_angle2) goto NextFrameWaiter; // K_R and K_L produced different changes, so we're ready. tmp.Load(); --nframes_total; if(transition) --nframes_transition; EndLoop: Fail: ; scrFinish(0); } static bool DoProfileShot(unsigned& num_pocketed, unsigned& nframes, const char*& failreason, unsigned maxframes) { scrBegin; failreason = 0; // Count how many balls were pocketed before playing static unsigned pocketed_begin; pocketed_begin = GetPocketedCount(); // Fire (and wait until it registers the action) while(DoFiring(nframes, maxframes)) scrReturn(1); // Wait until all balls stop moving. while(DoWaitBallsStop(nframes, failreason, maxframes)) scrReturn(1); if(failreason) goto Fail; num_pocketed = GetPocketedCount(); if(WhitePocketed()) { /*failreason="blunder2";*/ num_pocketed=0; goto Fail; } /*fprintf(stderr, "frame %u, pocketed %u, begin %u\n", (unsigned)framecount, num_pocketed, pocketed_begin); */ if(num_pocketed >= pocketed_begin) num_pocketed -= pocketed_begin; else num_pocketed=0; /*EndLoop:*/ Fail: ; scrFinish(0); } static unsigned GetPocketedCount() { unsigned result = 0; for(unsigned a=1; a<16;++a) { if(RAM[0x300+a]==0xC0) break; if((RAM[0x300+a] & 15) != 2 && (RAM[0x300+a] & 15) != 1) { //fprintf(stderr, "%u:%02X;", a,RAM[0x300+a]); ++result; } } ++result; return result; } static bool WhitePocketed() { return (RAM[0x300] & 15) != 2 && (RAM[0x300] & 15) != 1; } static double CalculateProspects() { /* Returns a value 0..1 describing how good the board state is */ static std::vector<double> pocket_x; static std::vector<double> pocket_y; static int LastInitPockets=-1; static double maximumdistance; if(LastInitPockets != RAM[0x187]) { /* To save CPU time, only regenerate * the pocket list when the table changes */ pocket_x.clear(); pocket_y.clear(); LastInitPockets = RAM[0x187]; for(unsigned y=1; y<19; ++y) { for(unsigned x=1; x<25; ++x) { unsigned char c = RAM[0x600 + y*26+x]; //fprintf(stderr, "%02X ", c); #define b(n) (((n)&~0xCC)==0) if((c & 0x10) == 0x10 // this indicates a pocket-type tile && (b(RAM[0x600 + (y-1)*26+x]) // must be next to field || b(RAM[0x600 + (y+1)*26+x]) || b(RAM[0x600 + y*26+(x-1)]) || b(RAM[0x600 + y*26+(x+1)]))) { pocket_x.push_back(x); pocket_y.push_back(y); //fprintf(stderr, "\npocket at %u,%u", x,y); } #undef b } //fprintf(stderr, "\n"); } maximumdistance=0; unsigned n_pockets = pocket_x.size(); for(unsigned y=1; y<19; ++y) for(unsigned x=1; x<25; ++x) { double mindist=9e39; for(unsigned p=0; p<n_pockets; ++p) { double xd = x-pocket_x[p], yd = y-pocket_y[p]; double dist = std::sqrt(xd*xd+yd*yd); if(dist <mindist>maximumdistance) maximumdistance=mindist; } } unsigned n_pockets = pocket_x.size(); double result = 0; for(unsigned n=1; n<16>= 0x10) continue; // non-balls if(BallState == 0x83 || BallState == 0x03 || BallState == 0x00) { // The ball does not exist anymore. Perfect. //result += 1.0; continue; } #if 1 // Find out how close the ball is to the nearest pocket double ballx = int(RAM[0x370 + n] / 8) - 3; double bally = int(RAM[0x330 + n] / 8) - 8; double mindistsquared = 9e39; for(unsigned p=0; p<n_pockets; ++p) { double xdist = ballx - pocket_x[p], ydist = bally - pocket_y[p]; double distsquared = xdist*xdist + ydist*ydist; if(distsquared <mindistsquared>> 16) == (oldmodel >> 16)) { if(mut_amount <0> 0) { unsigned dummy_model, best_time_this_pocket_count = Winner::GetBestTimeWithPocketCount(num_pocketed,dummy_model); if(best_time_this_pocket_count > 0 && best_time_this_pocket_count*5/3 <nframes> 0) { if(IGNORE_SCORE_DELAYS) { static unsigned nf1, nf2; nf1=nf2=0; while(DoWaitNextShotPossible( nf1, nf2, false, 9999)) scrReturn(1); } else { while(DoWaitNextShotPossible( nframes, nframes_transition, false, MAX_FRAMES)) scrReturn(1); if(nframes-nframes_transition >= MAX_FRAMES) goto Fail; } } static unsigned nframes_scoring; nframes_scoring = nframes - nframes_transition; cand.scoring = num_pocketed; cand.scoring += prospects; cand.scoring /= (double)(nframes_scoring ? nframes_scoring : 0); cand.scoring *= 131072; // arbitrary number to make the scores more readable ++cand.confidence; goto EndLoop; Fail: if(!failreason && nframes-nframes_transition >= MAX_FRAMES) failreason="timeout"; if(!failreason && !num_pocketed) failreason="dummy"; if(!failreason) failreason="fail"; if(nframes <nframes_transition> 0) Winner::Compete(num_pocketed, nframes_scoring, cand.GetModel()); fflush(stderr); scrFinish(0); } typedef CandidateOptMapType i1s; typedef i1s::iterator i1; // i, preang -> *prevel typedef i1::value_type::second_type i2s; typedef i2s::iterator i2; // j, prevel -> *ang typedef i2::value_type::second_type i3s; typedef i3s::iterator i3; // k, ang -> *vel typedef i3::value_type::second_type i4s; typedef i4s::iterator i4; // l, vel -> *candidate typedef i4::value_type::second_type i5s; template<typename> int RunCandidates_VelocityLoop(VelT& list, NextT& j, unsigned& num_pocketed, unsigned& nframes, unsigned& vel) { using namespace LunarballLaunchManager2ns; scrBegin; /* For each prevelocity / velocity */ while(!list.empty()) { for(;;) { vel = RAM[0x3A0]; j = list.find(vel); if(j != list.end()) break; if(nframes++ >= MAX_FRAMES) goto Fail; RunFrameWith(0x00); } /* got vel and j */ /* Shoot */ static LunarState shot_state; shot_state.Save(num_pocketed,nframes); scrReturn(2); list.erase(j); if(!list.empty()) shot_state.Load(); } EndLoop: Fail: ; scrFinish(0); } template<typename> int RunCandidates_AngleLoop(AngT& list, NextT& k, unsigned& num_pocketed, unsigned& nframes, unsigned& ang) { using namespace LunarballLaunchManager2ns; scrBegin; /* For each preangle / angle. */ /* Divide the angles into groups per what kind of input * is needed to create them. After that, for each group, * one just need to extend one frame at time until the * desired frame is met. */ typedef std::map<unsigned> AimList; typedef std::map<AimingMethod> AimMap; static AimMap aims; aims.clear(); for(k = list.begin(); k != list.end(); ++k) { AimingMethod method; method.Decide(k->first); aims[method][k->first] = k; } static LunarState begin; begin.Save(num_pocketed,nframes); static typename AimMap::iterator ai; for(ai = aims.begin(); ai != aims.end(); ++ai) { static unsigned aim_framecount; aim_framecount = 0; if(ai != aims.begin()) begin.Load(); while(!ai->second.empty()) { for(;;) { ang = RAM[0x3C0]; { typename AimList::iterator j = ai->second.find(ang); if(j != ai->second.end()) { k = j->second; ai->second.erase(j); break; } } if(nframes++ >= MAX_FRAMES) goto FailAim; RunFrameWith( (ai->first.autofire && (aim_framecount++&1)) ? 0x00 : ai->first.key ); } /* got ang and k */ /* Now each velocity for this angle.... */ static LunarState aim_state; aim_state.Save(num_pocketed,nframes); scrReturn(2); if(!ai->second.empty()) aim_state.Load(); } FailAim: ; } EndLoop: ; scrFinish(0); } /* Actual shot */ bool RunCandidates_4do(unsigned& num_pocketed, unsigned& nframes, i5s& lsecond, unsigned preang, unsigned prevel, unsigned ang, unsigned vel) { using namespace LunarballLaunchManager2ns; scrBegin; static const char* failreason; failreason = 0; while(DoProfileShot(num_pocketed, nframes, failreason, MAX_FRAMES)) scrReturn(1); if(WhitePocketed() || failreason) { /*fprintf(stderr, "ang[%3d]vel[%3d]preang[%3u]prevel[%3u]: %s\n", ang,vel,preang,prevel, failreason);*/ goto Fail; } while(AnalyzeShotResult(*lsecond, num_pocketed, nframes, ang,vel,preang,prevel)) scrReturn(1); /*EndLoop:*/ Fail: ; scrFinish(0); } /* Preshot */ bool RunCandidates_2do(unsigned& num_pocketed, unsigned& nframes, i3s& jsecond, unsigned preang, unsigned prevel) { using namespace LunarballLaunchManager2ns; scrBegin; static const char* failreason; failreason = 0; while(DoProfileShot(num_pocketed, nframes, failreason, MAX_FRAMES)) scrReturn(1); if(DO_DEATH && WhitePocketed()) { unsigned model = preang + prevel*256; fprintf(stderr, "!"); fflush(stderr); deathset.set(model, nframes == 0 ? 1 : nframes); num_pocketed = 0; } if(failreason) { /* Second shot is irrelevant now */ for(i3 k = jsecond.begin(); k != jsecond.end(); ++k) for(i4 l = k->second.begin(); l != k->second.end(); ++l) l->second->first_shot_is_enough = true; /*fprintf(stderr, "ang[*]vel[*]preang[%3u]prevel[%3u]: %s\n", preang,prevel, failreason);*/ goto Fail; } if(num_pocketed > 0) { /* Second shot is irrelevant now */ /* Just analyze the shot, put it in one of the candidates * and mark all of them as "first_shot_is_enough" */ static bool first; first=true; static i3 k; static i4 l; for(k = jsecond.begin(); k != jsecond.end(); ++k) for(l = k->second.begin(); l != k->second.end(); ++l) { l->second->first_shot_is_enough = true; if(first) { static unsigned ang, vel; ang = k->first; vel = l->first; while(AnalyzeShotResult(*l->second, num_pocketed, nframes, ang,vel,preang,prevel)) scrReturn(1); first = false; } } goto EndLoop; } if(!DO_DUALSHOT) goto EndLoop; static unsigned nextshot; nextshot = 0; if(IGNORE_SCORE_DELAYS) { static unsigned nf1; nf1=0; while(DoWaitNextShotPossible(nf1, nextshot, false, 9999)) scrReturn(1); } else { while(DoWaitNextShotPossible(nframes, nextshot, false, MAX_FRAMES)) scrReturn(1); } // ignore transition time, it shouldn't transition here if(nextshot) { failreason="??transition"; goto Fail; } /* actual shot */ while(RunCandidates_3(num_pocketed, nframes, jsecond, preang, prevel)) scrReturn(1); EndLoop: Fail: ; scrFinish(0); } /* Velocity loop */ bool RunCandidates_4(unsigned& num_pocketed, unsigned& nframes, i4s& ksecond, unsigned preang, unsigned prevel, unsigned ang) { scrBegin; for(;;) { static i4 l; static unsigned vel; { int c = RunCandidates_VelocityLoop(ksecond, l, num_pocketed, nframes, vel); if(!c) break; if(c == 1) goto Idle; } /* got vel and l */ /* Shoot the shot */ while(RunCandidates_4do(num_pocketed, nframes, l->second, preang, prevel, ang, vel)) scrReturn(1); continue; Idle: scrReturn(1); } scrFinish(0); } /* Angle loop */ bool RunCandidates_3(unsigned& num_pocketed, unsigned& nframes, i3s& jsecond, unsigned preang, unsigned prevel) { scrBegin; for(;;) { static i3 k; static unsigned ang; { int c = RunCandidates_AngleLoop(jsecond, k, num_pocketed, nframes, ang); if(!c) break; if(c == 1) goto Idle; } /* got ang and k */ /* Check each velocity */ while(RunCandidates_4(num_pocketed, nframes, k->second, preang, prevel, ang)) scrReturn(1); continue; Idle: scrReturn(1); } scrFinish(0); } /* Prevelocity */ bool RunCandidates_2(unsigned& num_pocketed, unsigned& nframes, i2s& isecond, unsigned preang) { scrBegin; for(;;) { static i2 j; static unsigned prevel; { int c = RunCandidates_VelocityLoop(isecond, j, num_pocketed, nframes, prevel); if(!c) break; if(c == 1) goto Idle; } /* got prevel and j */ /* Shoot the preshot -> Check each angle */ while(RunCandidates_2do(num_pocketed, nframes, j->second, preang, prevel)) scrReturn(1); continue; Idle: scrReturn(1); } scrFinish(0); } /* Preangle */ bool RunCandidates_1(unsigned& num_pocketed, unsigned& nframes, i1s& optmap) { scrBegin; for(;;) { static i1 i; static unsigned preang; { int c = RunCandidates_AngleLoop(optmap, i, num_pocketed, nframes, preang); if(!c) break; if(c == 1) goto Idle; } /* got preang and i */ /* Check each velocity */ while(RunCandidates_2(num_pocketed, nframes, i->second, preang)) scrReturn(1); continue; Idle: scrReturn(1); } scrFinish(0); } bool RunCandidates(const SaveState& itbegin_state, CandidateOptMapType& optmap) { scrBegin; itbegin_state.Load(); static unsigned num_pocketed; num_pocketed = 0; static unsigned nframes; nframes = 0; while(RunCandidates_1(num_pocketed, nframes, optmap)) scrReturn(1); scrFinish(0); } typedef std::deque<CandidateType> CandidateListType; void WaitCandidate(CandidateListType& CandidateList, std::map<int>& pid_list, bool block) { for(int waitflag = block ? 0 : WNOHANG; ; waitflag = WNOHANG) { int status = 0; int pid = waitpid(-1, &status, waitflag); if(pid <= 0) break; std::map<int>::iterator i = pid_list.find(pid); if(i == pid_list.end()) { fprintf(stderr, "Unknown child %d died\n", pid); } else { const unsigned first_candno = i->second; unsigned candno = first_candno; unsigned end_candno = candno + NUM_CANDIDATES_PER_PROCESS; if(end_candno > CandidateList.size()) end_candno = CandidateList.size(); for(; candno <end_candno>LoadRes(); } pid_list.erase(i); } if(WIFSIGNALED(status)) { fprintf(stderr, "Child died at signal %d\n", WTERMSIG(status)); } } } int LaunchNCandidates (CandidateListType& CandidateList, const unsigned first_candno, std::map<int>& pid_list, const SaveState& itbegin) { scrBegin; if(true) // parent { Winner::LoadLimit(); int pid; fflush(stdout); fflush(stderr); pid = fork(); if(pid > 0) { pid_list[pid] = first_candno; fprintf(stderr, "."); fflush(stderr); goto end; } if(pid <0> CandidateList.size()) end_candno = CandidateList.size(); static CandidateOptMapType optmap; optmap.clear(); for(unsigned candno=first_candno; candno <end_candno>GetModel(); unsigned cur = model%65536, ang=cur%256, vel=cur/256; unsigned pre = model/65536, preang=pre%256, prevel=pre/256; optmap[preang][prevel][ang][vel] = candit; candit->scoring = 0; // Assume it does really badly } while(RunCandidates(itbegin, optmap)) scrReturn(1); optmap.clear(); for(unsigned candno=first_candno; candno < end_candno; ++candno) CandidateList[candno].SaveRes(); // terminate child fflush(stdout); fflush(stderr); _exit(0); end:; scrFinish(0); } int RunCandidates(CandidateListType& CandidateList, const SaveState& itbegin) { scrBegin; if(CandidateList.empty()) return 0; static std::map<int> pid_list; pid_list.clear(); BotFrontDisableVideo=2; // Sort the new-round candidates, in an order that // is fastest to execute // std::sort(CandidateList.begin(), CandidateList.end(), // std::mem_fun_ref(&CandidateType::SimilarityCompare)); /* As much as I'd like to use OpenMP here, it wouldn't work because it's * not compatible with coroutines: an omp loop may only have one exit. */ static unsigned candno; for(candno=0; candno<CandidateList>= NUM_FORKS); } while(pid_list.size() >= NUM_FORKS); while(LaunchNCandidates(CandidateList, candno, pid_list, itbegin)) scrReturn(1); } BotFrontDisableVideo=1; while(!pid_list.empty()) { WaitCandidate(CandidateList, pid_list, true); } scrFinish(0); } static void CreateSeedF( CandidateListType& WinnerList, unsigned model, bool is_oneshot) { /*if(preang >= 110 && preang <0x90>= 0x75 && ang >= 0x75)*/ /*if(ang <40> 170)*/ CandidateType tmp; bool skip = false; if(covered_preshots[model >> 16]) (skip=true)/*,fprintf(stderr, "model %08X skipped because of covered_preshots[%04X]\n", (unsigned)model, (unsigned)(model >> 16))*/; if(covered_candidates[model]) (skip=true)/*,fprintf(stderr, "model %08X skipped because of covered_candidates[%08X]\n", (unsigned)model, (unsigned)(model))*/; if(deathset[model >> 16] && deathset[model & 0xFFFF]) (skip=true)/*,fprintf(stderr, "model %08X skipped because of deathset[%04X]\n", (unsigned)model, (unsigned)(model & 0xFFFF))*/; if(!skip) { WinnerList.push_back(tmp.SetModel(model, is_oneshot)); if(is_oneshot) covered_preshots.set(model >> 16); covered_candidates.set(model); } /*Winner::SetBest(pok,tim,model);*/ } int Run() { scrBegin; static CandidateListType WinnerList; BeginNewShot: Winner::SetBest(0,0,0, true); LunarballLaunchManager2ns::MAX_FRAMES = INIT_MAX_FRAMES; LunarballLaunchManager2ns::BALLS_REMAINING = RAM[0x18E]; WinnerList.clear(); covered_candidates.reset(); covered_preshots.reset(); deathset.reset(); /* Append the seed group to this list */ for(unsigned a=0; a< CandidateType::CountSeeds(); ++a) WinnerList.push_back(CandidateType().SetSeed(a)); #define CreateSeed(pok,tim,ang,vel,preang,prevel, is_oneshot) \ CreateSeedF(WinnerList, \ (ang+vel*256U) + (preang + prevel*256U)*65536U, is_oneshot); fprintf(stderr, "Beginning new shot. Table=%u, remaining balls=%u; %u on 16-bit donelist, %lu on 32-bit, %u deaths\n", RAM[0x187], RAM[0x18E], (unsigned) covered_preshots.count(), (unsigned long) covered_candidates.count(), (unsigned) deathset.count() ); static SaveState itbegin; itbegin.Create(); /**/ /* This ensures that all pre-shots are covered. */ /**/ {unsigned n_skipped = 0; for(unsigned preang=0; preang<256; preang += 1) for(unsigned prevel=18; prevel<256; prevel += 3) { size_t a = WinnerList.size(); CreateSeed(0,0, preang,255, preang, prevel, false); n_skipped += WinnerList.size() == a; // This will also map out single-shots that cause "death" (cue ball pocketings) } if(n_skipped) fprintf(stderr, "- %u CHOICES SKIPPED??\n", (unsigned)n_skipped);} /**/ static unsigned generationno; static unsigned n_generations_no_improvement; n_generations_no_improvement = 0; static double best_scoring; static CandidateType best_candidate; best_scoring = -999999; /* Generate NUM_VARIATIONS for as many generations as needed */ for(generationno=1; Active; ++generationno) { static CandidateListType CandidateList; /* generate new scorings from models */ if(DO_DEATH && generationno == 2) { for(unsigned angle_begin = 0; angle_begin < 256; angle_begin += 32) { std::vector< std::pair<unsigned> > deaths; for(unsigned d=0; d<65536; ++d) { if((d%256) <angle_begin>= angle_begin+32) continue; if(deathset[d]) deaths.push_back( std::pair<unsigned> ( deathset[d], d ) ); } std::sort(deaths.begin(), deaths.end()); fprintf(stderr, "Chosen deaths:\n"); for(unsigned dn=0; dn<deaths.size() && dn < 4; ++dn) { unsigned d = deaths[dn].second; fprintf(stderr, "- angle %u, velocity %u, %u frames\n", d%256, d/256, deaths[dn].first); for(unsigned ang=0; ang<256; ang += 1) for(unsigned vel=18; vel<256> 250) LunarballLaunchManager2ns::MAX_FRAMES = 250; if(DO_DUALSHOT) { /* Each plausible two-shot combination... */ for(unsigned preang=0; preang<256; preang += 1) for(unsigned prevel=18; prevel<=140; prevel += 3) { if(RAM[0x187] == 23 && RAM[0x18E] == 4) if(preang < 156)continue; if(RAM[0x187] == 26 && RAM[0x18E] == 4) if(preang < 9)continue; if(RAM[0x187] == 30 && RAM[0x18E] == 3) if(preang < 193)continue; /* if(preang==51) preang=52; if(preang==164) preang=168; */ //if(preang==200) preang=247; static const int dists[] = { 35,1, 50,2, 90,4, 128,6, -1}; int prevdist=0; for(int n=0; dists[n*2] != -1; ++n) { int dist = dists[n*2]; int skip = dists[n*2+1]; for(; prevdist <= dist; prevdist += skip) { const unsigned minvel = 171; for(unsigned vel=minvel; vel<256; vel+=3) CreateSeed(0, 0, ((preang-prevdist)&255), vel, (preang&0xFF), prevel, false); if(prevdist != 0) for(unsigned vel=minvel; vel<256> 2) CandidateList.clear(); /* for(unsigned candno=0; candno<CandidateList.size(); ++candno) while(candno <CandidateList> 2) for(unsigned a=0; a<NUM_VARIATIONS>> 16]) continue; covered_candidates.set(c); CandidateList.push_back(tmp); } //if(generationno==1){Active=0;} if(DO_DUALSHOT && (CandidateList.empty() || n_generations_no_improvement > 0 /*|| generationno==1*/)) for(unsigned a=0; a<NUM_RANDOMS>> 16]) continue; covered_candidates.set(c); CandidateList.push_back(tmp); } ++n_generations_no_improvement; { fprintf(stderr, "Generation %u: %u candidates based on %u; %u on 16-bit donelist, %lu on 32-bit, %u deaths, frame limit %u\n", generationno, (unsigned)CandidateList.size(), (unsigned)WinnerList.size(), (unsigned) covered_preshots.count(), (unsigned long) covered_candidates.count(), (unsigned) deathset.count(), LunarballLaunchManager2ns::MAX_FRAMES ); } if(CandidateList.empty()) { fprintf(stderr, "Out of candidates, ending this generation\n"); break; } WinnerList.clear(); // list of old winners not needed at this point for(unsigned candno=0; candno<CandidateList.size(); ++candno) CandidateList[candno].key = candno; // Run the candidates while(RunCandidates(CandidateList, itbegin)) scrReturn(1); fprintf(stderr, "\r"); for(unsigned candno=0; candno<CandidateList>key = candno; if(candit->first_shot_is_enough) { covered_preshots.set( candit->GetModel() >> 16 ); } } // Sort the new-round candidates, best-first std::sort(CandidateList.begin(), CandidateList.end(), std::mem_fun_ref(&CandidateType::BetterScoring)); /* Check if there was a new improvement */ for(unsigned candno=0; candno<CandidateList>scoring > best_scoring) { // reset the no-progress counter best_scoring = candit->scoring; best_candidate = *candit; n_generations_no_improvement = 0; } break; // No need to check further, as they're sorted by score } /* Always add the winners of each kind for seeding the next breed */ for(unsigned n=1; n<10; ++n) { unsigned model=0; unsigned time = Winner::GetBestTimeWithPocketCount(n, model); if(time == 0 || time == 65535) continue; CandidateType tmp; tmp.SetModel(model, false); // don't know if it's a first_shot_is_enough or not tmp.confidence = 1; tmp.scoring = (n / (double)time); tmp.scoring += n * 1e-5; tmp.scoring += ((model/0x100)&0xFF) * (1e-5/256.0); tmp.scoring += ((model/0x1000000)&0xFF) * (1e-5/65536.0); tmp.scoring *= 131072; WinnerList.push_front(tmp); } for(unsigned n=0; n<WINNERS>scoring); for(CandidateListType::const_iterator i=CandidateList.begin(); i!=CandidateList.end(); ++i) quality_all.Cumulate(i->scoring); fprintf(stderr, "Average quality of generation %u: %g (all), %g (inheritors)\n", generationno, quality_all.GetValue(), quality_best.GetValue()); if(n_generations_no_improvement >= min_generations_no_improvement || !DO_DUALSHOT) break; } // proceed to next generation #undef CreateSeed Winner::Load(); if(true) { fprintf(stderr, "Beginning new shot\n"); FCEUSS_Save("botfrontL-newshotbegin"); goto BeginNewShot; } /*EndLoop: ; */ BotFrontEnd(); Winner::Load(); FCEUSS_Save("botfrontL-final"); scrFinish(0); } #undef RunFrameWith }; void BotFrontIter() { scrBegin; Restart: while(!Active) { BisqBotEvaluate(0); BisqBotReset(); scrReturnV; } struct timeval tv; gettimeofday(&tv,NULL); rng.seed(tv.tv_usec + tv.tv_sec*1000000ULL); if(!FCEUI_EmulationPaused()) FCEUI_ToggleEmulationPause(); #if 1 /* LUNAR BALL PLAYER */ static LunarballLaunchManager2 code; while(Active) { code.Run(); scrReturnV; } BotFrontEnd(); FCEUSS_Save("botfrontL-end"); goto Restart; #endif goto Restart; scrFinishV; }
Edited by feos: Replaced with a regular code embed. Original code is backed up at https://files.tasvideos.org/2095/archives/injection.7z
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Andypro wrote:
What a novel idea for a TAS. Big yes vote here. I'm impressed that you wrote a program for this. I would love to see the source code! Was it a lua script or something else?
It was not a Lua script, because would have been way too slow, and Lua cannot multithread either. I'll post the source code below!
Post subject: Re: #2620: Baxter's NES Tetris "playaround" in 02:17.36
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Not only the holes, but even the brick colors! Well done entering the name at end.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Nitrodon wrote:
The following balls stopped moving somehow: (shot numbers exclude suicides) Stage 22, shot 1: 3 ball Stage 46, shot 1: 1 ball Stage 46, shot 3: cue ball Stage 47, shot 1: cue ball Stage 50, shot 1: 1 ball and cue ball (and several honorable mentions) Stage 51, shot 1: cue ball Stage 58, shot 2: cue ball How is this consistent with the law of conservation of energy?
You have a keen eye. This is very true (I did not verify the stage numbers, but I know there were such instances). In Lunar Ball, when a ball hits another ball, a transfer of kinetic energy happens, the magnitude of which depends on the angle on which the balls collide. When the kinetic energy is small enough to begin with, it may happen that the transferred energy falls below the epsilon value (or rather, fixed point math accuracy), and thus it is treated that the ball stopped completely. In short, this inconsistency with the law of conservation of energy is due to inaccurate math calculations carried out by the game.
Post subject: Re: Joke Submissions
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Oh well. Way to kill the mood. Well, I suppose you will do what is necessary. I guess it is better to do it on such a festive occassion than on some other time. It gives people the benefit of doubt, too; it will be easier to discuss the dilemma than if it is done on some “serious” day. I am concerned mildly of this because I am also going to submit one such submission, in the usual tempting fashion. I know I should not, but… It is most difficult for me to just throw away some work I have done.
Post subject: Re: Video Game Music Quiz (Noise edition)
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Arne_the_great wrote:
There are 15 tracks, all from Super Nintendo. They are all from quite well known games. But I warn you, they are quite noisy and hard to recognize. There are only one track from each game so same game won't come twice!
I recognized FF6 Figaro in an instant, the others were too annoying to even listen to.
Gunty wrote:
I have never noticed this before, but in this context track00 suddenly shows a great (and somewhat disturbing) resemblance to Robo's theme :)
Wow! That is so true. I have never before made that connection.
Post subject: Isn't it a good thing?
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Hasn't the site already outlived its purpose though? The original purpose was to provide high-quality video game entertainment in the Internet where it was standard (in 2003--2004) to provide much worse quality. Nowadays, others are providing good quality too. Look at SDA for example. And Youtube with its HD streaming. In my view, the concept of TAS was only a temporary goal of this site. The reason it was narrowed down to TASes was because there were other sites that did other types of video game movies. Even now, there are slowplays, speeddemosarchive, and many others. Also, the purpose of the site was to educate people on how the concept is applied to create entertainment. Haven't we educated people enough? Just about every speedrunner and their sister knows by now what a TAS is. Obviously I cannot cast any vote you might draw, and also I have kind of promised to never do anything that directly hurts this site (it just isn't in my nature), but just in a very slight way I may suggest that some rethinking might be called for.
Post subject: Confirmation and thoughts
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
I realize I don't really have a place in this discussion considering my changed position which is self-imposed, but let me just say these words anyway. It was indeed part of my original fascination on this TAS concept that the performance shown in the movie can, just so theoretically, be performed on the real console as well, given perfect knowledge and perfect skill. I never intended for the site to showcase emulator artifacts. I mean, savestates and slowdown do not make for "emulator artifacts" because they only alter the perception of the time, not the game itself, but the emulation itself should be consistent with what the actual console performs. Admittedly, the first emulator we used, Famtasia, did not really do well on any aspect as far as authenticity goes, but by now, we are pretty close there. But is the whole concept flawed, based on incorrect assumptions? That is a big "bummer" if so. It pains me greatly to see, as it indeed turns out, that the very things I fought to teach to people, regarding TASes, were incorrect to begin with. I have no sufficient words with which to apologize to everyone, whose life I am responsible of indirectly stealing months, even years, of. On the other hand, I am glad I was able to produce a coherent sentence that ends with the word "of" directly preceded by a comma.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Okay, hmm. I was apparently underwater fishing for some 1000 or so things which were actually quiz items that must be answered correctly by the partner in team above the surface in order to win a prize. We won. I have no idea about the prize though, nor about who was the partner, who was actually a she. I slept quite long though, almost 12 hours, so I dreamed a lot more than that, but that's one thing I remember.
Post subject: Re: crazy promotion idea...
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
WebNations wrote:
i think the most efficient way would be repeating a mario with star sequence because the music repeats way faster than the usual music.
Currently used audio codecs provide no temporal compression at all. They work on psychoacoustic detail removal. What this means is that repetive audio does not compress any better than non-repetitive audio. The same goes for video codecs too, except with H.264's reference frame mechanism (which can efficiently repeat content from a maximum of 16 frames ago).
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Bablo wrote:
The tas shows some interesting (new) glitches, but isn't very well executed. It looks quite unoptimized most of the time. Also the game is sort of uninteresting. No.
I'm with Bablo on this one. The execution of the movie looks suboptimal to me.
Post subject: Re: Another Youtube video of writing C++ code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
creaothceann wrote:
EDIT: Ah, nevermind.
... Well, thanks for sharing that.
Post subject: Another Youtube video of writing C++ code
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
I created another programming-related Youtube video. In this video, I create a MOD S3M player from scratch. Link to video In order to make it fit in Youtube's 10 minute length limit, I recorded this in the same style we make TASes: by running the emulator slower (in some parts) so I get to type faster. I call this a tool-assisted education video.
Editor, Experienced Forum User, Published Author, Active player (296)
Joined: 3/8/2004
Posts: 7469
Location: Arzareth
Bisqwit's idea wrote:
Ever wish you knew in advance how everything around you would happen? To the tiniest detail Ever wish you could change what you knew would happen? Subtly manipulating seemingly irrelevant, chaotic factors Ever wish you could go back and fix that mistake? And in case you fail to fix it, try again And again Ever wish you could have made something just perfect? Perfectly coreographed To the tiniest detail This is TASVideos, to video games.
tl, dr! EDIT: Inspired by the (trailer thereof and the) movie Primer, which I enjoyed recently.