Submission Text Full Submission Page
Lunar Ball / Lunar Pool. It is a minigolf-esque pocket billiards game.

Game objectives

  • Emulator used: FCEU version 0.98.ihavenoidea
  • Complete all 60 boards starting from 1
    • It is possible to begin from any board, including 60, but you have to complete all 60 to get the ending
  • Single player
  • Friction setting: 0 (NONE)
    • See below for notes on this
  • Fastest time
  • Uses death to save time
  • Does not utilize glitches (there exists one, but it was not invoked in this movie)

N/A game objectives

  • This game is completely deterministic. There is no "manipulates luck".
  • There are no dinosaurs in this game.

Comments

This movie was created mostly using a computer program, the development of which took several months and several iterations.
Lunar Ball has this interesting feature, that on the configuration screen, its friction setting can be arbitrarily selected from integer values 0 to 255. The default value is 32. The value 0 in particular is very interesting: It literally means "no friction whatsoever. This movie was created in the frictionless mode.
A pool game played in completely frictionless setup is a very different game from a normal pool game.
In a frictionless game, it is more than possible, that a careless shot makes the game completely unwinnable. Indeed, it is easy to demonstrate such a shot that will leave the game into an infinite loop, much resembling the effect that happens in the popular gadget, "Newton's cradle". In such a loop, there is no choice but to reset the console, terminating the game.
From a computer player's viewpoint, this means that the searching space for valid moves is drastically smaller than it is for a regular game: most shots produce either an infinite loop, or they result in the eventual demise of the cue ball. Due to the law of conservation of energy, so called "preparation shots" simply cannot be done, either. This also means that unless you "suicide" once in a while (intentionally pocket the cue ball), you get a "PERFECT" message in all boards, and that the scores skyrocket. Also, the timeout for shots should be set considerably longer than in a default-friction game, because the balls can be gliding for a long time before they eventually find a pocket.

Score and RATE


In Lunar Ball, each successful shot increases the "RATE" value of the player; each unsuccessful shot resets it. The higher the "RATE" value, the more points the player is awarded for the next successful shot. Points are awarded at a rate of 10 points per frame.
The RATE value maxes at 99, at which point completing a single 6-ball board gives 20790 points ((6+5+4+3+2+1)*99*10), plus the PERFECT bonus. Tallying that amount takes more than half a minute. Because nobody wants to watch 15 minutes of score tallying, it is a good idea to reset the RATE value every once in a while.
However, because this game mode is frictionless, the only possible method that a finite shot can be unsuccessful, is when it shoots the cue ball into a pocket. This I call "suicide", and this movie features that action every once in a while, and it is completely intentional for the aforementioned reason.

Board by board comments

Board 1

Board 1 is completed by depositing each ball on the board in any of the pockets on the board.

Board 2

Board 2 is completed by depositing each ball on the board in any of the pockets on the board.

Board 3

Board 3 is completed by depositing each ball on the board in any of the pockets on the board.

Board 4

Board 4 is completed by depositing each ball on the board in any of the pockets on the board.

Board 5

Board 5 is completed by depositing each ball on the board in any of the pockets on the board.

Board 6

Board 6 is completed by depositing each ball on the board in any of the pockets on the board.

Board 7

Board 7 is completed by depositing each ball on the board in any of the pockets on the board.

Board 8

Board 8 is completed by depositing each ball on the board in any of the pockets on the board.

Board 9

Board 9 is completed by depositing each ball on the board in any of the pockets on the board.

Board 10

Board 10 is completed by depositing each ball on the board in any of the pockets on the board.

Board 11

Board 11 is completed by depositing each ball on the board in any of the pockets on the board.

Board 12

Board 12 is completed by depositing each ball on the board in any of the pockets on the board.

Board 13

Board 13 is completed by depositing each ball on the board in any of the pockets on the board.

Board 14

Board 14 is completed by depositing each ball on the board in any of the pockets on the board.

Board 15

Board 15 is completed by depositing each ball on the board in any of the pockets on the board.

Board 16

Board 16 is completed by depositing each ball on the board in any of the pockets on the board.

Board 17

Board 17 is completed by depositing each ball on the board in any of the pockets on the board.

Board 18

Board 18 is completed by depositing each ball on the board in any of the pockets on the board.
At this point, 43.6 % of viewers have started fastforwarding the movie.

Board 19

Board 19 is completed by depositing each ball on the board in any of the pockets on the board.

Board 20

Board 20 is completed by depositing each ball on the board in any of the pockets on the board.

Board 21

Board 21 is completed by depositing each ball on the board in any of the pockets on the board.

Board 22

Board 22 is completed by depositing each ball on the board in any of the pockets on the board.

Board 23

Board 23 is completed by depositing each ball on the board in any of the pockets on the board.

Board 24

Board 24 is completed by depositing each ball on the board in any of the pockets on the board.

Board 25

Board 25 is completed by depositing each ball on the board in any of the pockets on the board.

Board 26

Board 26 is completed by depositing each ball on the board in any of the pockets on the board.

Board 27

Board 27 is completed by depositing each ball on the board in any of the pockets on the board.

Board 28

Board 28 is completed by depositing each ball on the board in any of the pockets on the board.

Board 29

Board 29 is completed by depositing each ball on the board in any of the pockets on the board.

Board 30

Board 30 is completed by depositing each ball on the board in any of the pockets on the board.

Board 31

Board 31 is completed by depositing each ball on the board in any of the pockets on the board.

Board 32

Board 32 is completed by depositing each ball on the board in any of the pockets on the board.

Board 33

Board 33 is completed by depositing each ball on the board in any of the pockets on the board.

Board 34

Board 34 is completed by depositing each ball on the board in any of the pockets on the board.

Board 35

Board 35 is completed by depositing each ball on the board in any of the pockets on the board.

Board 36

Board 36 is completed by depositing each ball on the board in any of the pockets on the board.

Board 37

Board 37 is completed by depositing each ball on the board in any of the pockets on the board.

Board 38

Board 38 is completed by depositing each ball on the board in any of the pockets on the board.

Board 39

Board 39 is completed by depositing each ball on the board in any of the pockets on the board.

Board 40

Board 40 is completed by depositing each ball on the board in any of the pockets on the board.

Board 41

Board 41 is completed by depositing each ball on the board in any of the pockets on the board.

Board 42

Board 42 is completed by depositing each ball on the board in any of the pockets on the board.

Board 43

Board 43 is completed by depositing each ball on the board in any of the pockets on the board.

Board 44

Board 44 is completed by depositing each ball on the board in any of the pockets on the board.

Board 45

Board 45 is completed by depositing each ball on the board in any of the pockets on the board.

Board 46

Board 46 is completed by depositing each ball on the board in any of the pockets on the board.

Board 47

Board 47 is completed by depositing each ball on the board in any of the pockets on the board.

Board 48

Board 48 is completed by depositing each ball on the board in any of the pockets on the board.

Board 49

Board 49 is completed by depositing each ball on the board in any of the pockets on the board.

Board 50

Board 50 is completed by depositing each ball on the board in any of the pockets on the board.

Board 51

Board 51 is completed by depositing each ball on the board in any of the pockets on the board.

Board 52

Board 52 is completed by depositing each ball on the board in any of the pockets on the board.

Board 53

Board 53 is completed by depositing each ball on the board in any of the pockets on the board.

Board 54

Board 54 is completed by depositing each ball on the board in any of the pockets on the board.

Board 55

Board 55 is completed by depositing each ball on the board in any of the pockets on the board.

Board 56

Board 56 is completed by depositing each ball on the board in any of the pockets on the board.

Board 57

Board 57 is completed by depositing each ball on the board in any of the pockets on the board.

Board 58

Board 58 is completed by depositing each ball on the board in any of the pockets on the board.

Board 59

Board 59 is completed by depositing each ball on the board in any of the pockets on the board.

Board 60

Board 60 is completed by depositing each ball on the board in any of the pockets on the board.
After shooting the last shot, the input is terminated. The triumphal ending is seen after the board is completed.

Other comments

Thanks to:
  • qFox
  • Derakon
  • Xkeeper
  • Adelikat
  • Nach
  • Bisqwit
  • Jemini, Teramoto, Miyamoto, Yunoki and Niitani -- the Compile crew credited in the game

Baxter: A very novel concept. Not only impossible for a regular player, but also for a regular TASer. Very different to any other TAS on the site, accepting for publication.
Aktan: processing...

TASVideoAgent
They/Them
Experienced Forum User, Moderator
Joined: 8/3/2004
Posts: 12513
Location: 127.0.0.1
This topic is for the purpose of discussing #2609: Bisqwit's NES Lunar Ball "no friction" in 36:52.00
Experienced Forum User
Joined: 11/4/2007
Posts: 1772
Location: Australia, Victoria
Voting no because there is 1 hour and 16 minutes to go until April Fools day in my timezone at the time of writing.
Experienced Forum User, Expert player (2503)
Joined: 6/2/2009
Posts: 1182
Location: Teresópolis - Rio de Janeiro - Brazil
Sega TASer of 2013SNES TASer of 2012SNES TASer of 2010
Voting Yes because... um... because I'm not a fool!
I am old enough to know better, but not enough to do it.
Experienced Forum User
Joined: 7/2/2007
Posts: 3960
Flygon wrote:
Voting no because there is 1 hour and 16 minutes to go until April Fools day in my timezone at the time of writing.
Thanks to the miracle of time zones, April First is 36 hours long and started in Fiji at what is 9AM March 31st on the west coast of the USA. Hooray for Useless Internet Day!
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Experienced Forum User, Skilled player (1592)
Joined: 9/17/2009
Posts: 4884
Location: ̶C̶a̶n̶a̶d̶a̶ "Kanatah"
GBA TASer of 2010
Experienced Forum User
Joined: 1/26/2009
Posts: 558
Location: Canada - Québec
jlun2 wrote:
Voting no because ofthis.
There no luck manipulation in this run, this means there still some hope. So you have to kill yourself, if you don't want to wait for the score..? I like this challenge. The overall optimisation seem midly good. I suppose that 0 friction is hardest setting, but I would like to know if this run could be faster with an another friction value?
Active player, Experienced Forum User (264)
Joined: 3/4/2006
Posts: 341
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?
Experienced Forum User
Joined: 7/2/2007
Posts: 3960
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). Nitrodon: I haven't watched the run, but if a ball hits another ball at rest full-on, then I suspect it will transfer all of its momentum to the other ball, leaving itself at rest. There are other, more complicated ways to achieve rest in a perfectly-elastic frictionless universe with limited precision, too. For example, the ball the first ball hits can be moving perpendicular to the first ball's path (assuming that spin is not considered in these kinds of collisions), or the first ball could hit multiple balls in the same frame.
Pyrel - an open-source rewrite of the Angband roguelike game in Python.
Active player, Editor, Experienced Forum User (288)
Joined: 3/8/2004
Posts: 7468
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.
Editor, Experienced Forum User, Player (51)
Joined: 6/22/2005
Posts: 1001
Didn't watch the movie, but the description sounds good enough for a publication. Besides, since this will be the last April 1 to grace TASVideos :'( :'(, at least one such joke submission should be accepted. In fact, due to the recently demonstrated impossibility of accurate emulation, they should all be accepted.
Current Projects: TAS: Wizards & Warriors III.
Experienced Forum User
Joined: 5/24/2004
Posts: 262
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? Edit: Bisqwit posted the source below. Thanks for the code, Bisqwit!
Limne
Any
Experienced Forum User
Joined: 2/24/2010
Posts: 153
I'd love to see an encode of this. Even though the death of the site has ruined my life forever.
Experienced Forum User
Joined: 11/9/2008
Posts: 108
Location: New Orleans, LA U.S.A.
Limne wrote:
I'd love to see an encode of this. Even though the death of the site has ruined my life forever.
ditto
__--N0ISE
Tub
Experienced Forum User
Joined: 6/25/2005
Posts: 1377
interesting TAS. Despite today's date, I think it's an excellent TAS, even though it's longer than needed. Can't vote anything but yes. nice to know you still got the time and the heart for TASing :)
m00
mklip2001
He/Him
Editor, Experienced Forum User
Joined: 6/23/2009
Posts: 2206
Location: Georgia, USA
Oh man, Bisqwit has so many balls, he actually has to lose some in order to stay fast! In all seriousness, the frictionless category wasn't as entertaining by the end of the run. However, some courses were masterful, and the whole run is quite impressive technically. Yes vote.
Used to be a frequent submissions commenter. My new computer has had some issues running emulators, so I've been here more sporadically. Still haven't gotten around to actually TASing yet... I was going to improve Kid Dracula for GB. It seems I was beaten to it, though, with a recent awesome run by Hetfield90 and StarvinStruthers. (http://tasvideos.org/2928M.html.) Thanks to goofydylan8 for running Gargoyle's Quest 2 because I mentioned the game! (http://tasvideos.org/2001M.html) Thanks to feos and MESHUGGAH for taking up runs of Duck Tales 2 because of my old signature! Thanks also to Samsara for finishing a Treasure Master run. From the submission comments:
Shoutouts and thanks to mklip2001 for arguably being the nicest and most supportive person on the forums.
Active player, Editor, Experienced Forum User (288)
Joined: 3/8/2004
Posts: 7468
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: source code
Active player, Editor, Experienced Forum User (288)
Joined: 3/8/2004
Posts: 7468
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
Active player, Editor, Experienced Forum User (288)
Joined: 3/8/2004
Posts: 7468
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.
Experienced Forum User
Joined: 11/4/2007
Posts: 1772
Location: Australia, Victoria
On a more serious note, I am encoding this in High Definition now. I'll be making a Standard Definition encode of no one makes one by the time I am finished with the High Definition one. I really need to get that second computer, a single CPU isn't enough for the huge backlog that has happened.
sgrunt
He/Him
Emulator Coder, Experienced Forum User
Joined: 10/28/2007
Posts: 1360
Location: The dark horror in the back of your mind
This run is a conundrum. In a reversal of another recently submitted run, this is technically extremely impressive, but has negligible entertainment value - you're basically watching the same thing over and over and over again with very little variety in the game play. I am torn as to whether this should be published or not. I think that I will vote Meh. Encode is in progress, since quite a few people have asked for one. EDIT: http://www.archive.org/download/NesLunarBallnoFrictionIn3652.0ByBisqwit/lunarball-tas-nofriction-bisqwit.mp4 Stream: http://www.zexsoft.com/aktan/?NesLunarBallnoFrictionIn3652.0ByBisqwit/lunarball-tas-nofriction-bisqwit_512kb
Banned User
Joined: 12/23/2004
Posts: 1850
I'd much rather see a hacked ROM that tallies bonuses immediately and scores a Perfect on every stage in the least number of shots. It would be more exciting to watch... at least, I think so. Edit: If it counts for anything, I watched the entire movie sans turbo. Yeah!
Perma-banned
Limne
Any
Experienced Forum User
Joined: 2/24/2010
Posts: 153
Awesome, awesome, awesome. I loved every minute of it. All forty of them. Just, mind boggling puzzle action, especially after I went and tried this on no friction myself. I don't care how much of a nerd I might seem, pulling this off is just too mind-blowingly crazy.
Experienced Forum User
Joined: 11/4/2007
Posts: 1772
Location: Australia, Victoria
Experienced Forum User, Player (142)
Joined: 11/27/2004
Posts: 686
Location: WA State, USA
Watched it all with no fast forward. Loved every minute of it. Some of those shots are just crazy. Yes vote.
Nach wrote:
I also used to wake up every morning, open my curtains, and see the twin towers. And then one day, wasn't able to anymore, I'll never forget that.