View Page Source

Back to Page
Revision 3 (current)
Edited by Unknown on 1/1/2022 6:13 PM

%%SRC_EMBED c
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;
%%END_EMBED

%%SRC_EMBED c
namespace LunarballLaunchManager2ns{
unsigned MAX_FRAMES;
unsigned BALLS_REMAINING;
}
%%END_EMBED

%%SRC_EMBED c
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) 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 bool SetBest(unsigned num_pocketed, unsigned nframes, unsigned model, bool Force=true)
        {
            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 = num_pocketed  / (double)(nframes  ?nframes:1);
                double ratio_old = best_pocketed / (double)(best_time?best_time:1);
                
                /* If the "now" is subpar, reject it. */
                if(ratio_now < ratio_old || ratio_now == 0.0)
                {
                    /* If regardless we got a better number of pocketing */
                    if(num_pocketed != best_pocketed && num_pocketed > 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 = ref_pocketed / (double)(ref_time?ref_time:1);
                        
                        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) return false;
                if(num_pocketed == best_pocketed && nframes >= 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) return false;
                    if(now_pre_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 + 160;
            else
                max_hope += 3;

            if(max_hope < MAX_FRAMES)
            {
                fprintf(stderr, "[%d]Setting max frame limit to %u\n",
                    (int)getpid(), max_hope);
                MAX_FRAMES = max_hope;
            }
        }
    
    private:
        Winner();
    };

    #undef RunFrameWith
    #define RunFrameWith(k) do { CurInput = (k); FCEUI_FrameAdvance(); scrReturn(1); if(!Active)goto EndLoop; } while(0)
    
    struct AimingMethod
    {
        unsigned char key;
        bool autofire;
    public:
        AimingMethod() : key(0),autofire(false) { }
        
        void Decide(unsigned wanted, unsigned orig_angle = RAM[0x3C0])
        {
            unsigned plus_dist  = (wanted+256-orig_angle) & 255;
            unsigned minus_dist = (orig_angle+256-wanted) & 255;
            int dir = plus_dist > 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.key*2+b.autofire); }
    
        int Run(unsigned angle, unsigned frno, unsigned& frames_begin, unsigned maxframes)
        {
            scrBegin;
            
            // autofire: repeatedly press to the direction
            // !autofire: hold button until angle is right

            while(RAM[0x3C0] != angle)
            {
                RunFrameWith( (autofire && (frno&1)) ? 0x00 : key);
                if(++frames_begin >= 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
                    && RAM[0x300+b] >= 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 bool transition;
        transition = false;
        
        if(verbose) fprintf(stderr, "Waiting until next shot can be shot\n");
    NextFrameWaiter:
        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"; 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) mindist=dist;
                    }
                    if(mindist>maximumdistance) maximumdistance=mindist;
                }
        }
        unsigned n_pockets = pocket_x.size();
        
        double result = 0;
        for(unsigned n=1; n<16; ++n)
        {
            unsigned char BallState = RAM[0x300+n];
            if(RAM[0x310+n] == 0 || RAM[0x310+n] >= 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) mindistsquared = distsquared;
            }
            double dist = std::sqrt(mindistsquared);
            
            /*
            fprintf(stderr, "\n[%u/%u][0:%02X 3:%02X 7:%02X] ballx=%6.3f bally=%6.3f dist=%6.3f, maxdist=%g;",
                n, framecount,
                RAM[0x300+n], RAM[0x330+n], RAM[0x370+n],
                ballx,bally,dist, maximumdistance);*/
            
            result += (1.0 - (dist / maximumdistance)) * 0.5;
          #endif
        }
        //fprintf(stderr, "\n");
        
        return result / 6.0;
    }
};
%%END_EMBED

%%SRC_EMBED c
struct LunarballLaunchManager2: public LunarballMethods
{
    // Define the range of parameters to be passed to Mutate().
    static const double MINMUTAATIO = 0.0010; // Minimum value, given to first candidates
    static const double MAXMUTAATIO = 0.7800; // Maximum value, given to last candidates
    // This controls the curve of mutation distribution 
    // (larger value = more big mutations, smaller value = more small mutations).
    // 1 = even distribution
    static const double LOGMUTAATIO = 0.243;
    
    static const unsigned WINNERS = 16; // number of winners to take each round for refinement

    // number of variations per each generation (PLUS WINNERS, which are preserved for competition)
    // larger number increases the chances of finding the Right Thing
    static const unsigned NUM_VARIATIONS = 1900;
    
    // number of entirely randomly generated candidates to add each round
    static const unsigned NUM_RANDOMS = 300;

    // accept rule when no improvements have been found within this number of generations
    // larger number decreases the chances of missing the Right Thing
    static const unsigned min_generations_no_improvement = 80;

    static const unsigned NUM_FORKS = 4;
    static const unsigned NUM_CANDIDATES_PER_PROCESS = 200;

    static const unsigned INIT_MAX_FRAMES = 600+3;
    
    static const bool DO_DUALSHOT = true;
    
    /*
        Input:
         For all
           angle
           preangle
           
           try some: prevelocity
           try all: velocity
           
           find best, then refine it
           genetically
    */
    
    struct CandidateType
    {
    private:
        float angle;
        float velocity;
        float preangle;
        float prevelocity;
    public:
        float scoring;
        uint_least16_t confidence;
        bool dummy_first;
        uint_least32_t key;
    public:
        CandidateType(): angle(0),velocity(0),preangle(0),prevelocity(0),
                         scoring(0),confidence(0),dummy_first(false), key(0)
                         { }
        
        void Mutate(double mut_amount)
        {
        retry: ;
            unsigned r = lrand48();
            unsigned which = 1+(r%15), change = r/15;
            unsigned oldmodel = GetModel();
            if(dummy_first) { which = 12 + 1+(r%3); change = r/3;}
            if(which & 1) angle       = fmod(angle       + 1 + ((change&1)?1:-1)*mut_amount, 1);
            if(which & 2) velocity    = fmod(velocity    + 1 + ((change&2)?1:-1)*mut_amount, 1);
            if(which & 4) preangle    = fmod(preangle    + 1 + ((change&4)?1:-1)*mut_amount, 1);
            if(which & 8) prevelocity = fmod(prevelocity + 1 + ((change&8)?1:-1)*mut_amount, 1);
            if(dummy_first)
            {
                if((GetModel() >> 16) == (oldmodel >> 16))
                {
                    if(mut_amount < 0.4) mut_amount += 0.1;
                    goto retry;
                }
                dummy_first = false;
            }
        }
        unsigned GetModel() const
        {
            unsigned char ang = (unsigned)(angle*256)& 0xFF;
            unsigned char vel = (unsigned)(velocity*256) & 0xFF;
            unsigned char preang = (unsigned)(preangle*256) & 0xFF;
            unsigned char prevel = (unsigned)(prevelocity*256) & 0xFF;
            
            /* Game's velocity gauge goes in increments of 3 */
            vel = (vel/3)*3;       if(vel < 0x12)    vel = 0x12;
            prevel = (prevel/3)*3; if(prevel < 0x12) prevel = 0x12;
            
            return (ang + vel*256U) + 65536U * (preang + prevel*256U);
        }
        CandidateType& SetModel(unsigned model, bool dummy=false)
        {
            unsigned cur = model%65536;
            unsigned pre = model/65536;
            
            angle    = (cur%256) / 256.0; preangle    = (pre%256) / 256.0;
            velocity = (cur/256) / 256.0; prevelocity = (pre/256) / 256.0;
            dummy_first = dummy;
            return *this;
        }
        void CreateRandom()
        {
            angle    = drand48(); preangle = drand48();
            velocity = drand48(); prevelocity = drand48();
        }
        
        static unsigned CountSeeds() { return 0; }
        CandidateType& SetSeed(unsigned)
        {
            return *this;
        }

        bool BetterScoring(const CandidateType& b) const
        {
            return scoring > b.scoring;
        }
        void SaveRes()
        {
            char candfn[128]; sprintf(candfn, "/tmp/cand%u.res", key);
            FILE*fp = fopen(candfn, "wt");
            if(fp)
            {
                fprintf(fp, "%g,%d,%d\n",
                    scoring,
                    confidence,
                    (int)dummy_first);
                fclose(fp);
            }
            else
                perror(candfn);
        }
        void LoadRes()
        {
            char candfn[128]; sprintf(candfn, "/tmp/cand%u.res", key);
            if(FILE*fp = fopen(candfn, "rt"))
            {
                int dummy, cf;
                double sc;
                fscanf(fp, "%lg,%d,%d",
                    &sc,
                    &cf,
                    &dummy);
                scoring=sc;
                confidence=cf;
                dummy_first = dummy;
                fclose(fp);
            }
            else
            {
                perror(candfn);
            }
            unlink(candfn);
        }
    };

    typedef
      std::map<unsigned char/*preang*/,
               std::map<unsigned char/*prevel*/,
                   std::map<unsigned char/*ang*/,
                      std::map<unsigned char/*vel*/, CandidateType*>
              > > > CandidateOptMapType;
    
    struct LunarState: public SaveState
    {
        unsigned nmp, nmf, *nmpp, *nmfp;
        LunarState(): SaveState(), nmp(0), nmf(0), nmpp(&nmp), nmfp(&nmf) { }
        
        void Save(unsigned& np, unsigned& nf)
            { SaveState::Create(); nmp=*(nmpp=&np); nmf=*(nmfp=&nf); }
        
        void Load()
            { SaveState::Load(); *nmpp=nmp; *nmfp=nmf; }
    };
    
    bool AnalyzeShotResult(CandidateType& cand, unsigned& num_pocketed, unsigned& nframes,
        unsigned ang,unsigned vel,unsigned preang,unsigned prevel)
    {
        using namespace LunarballLaunchManager2ns;
    
        scrBegin;
        
        static const char* failreason; failreason = 0;
        static unsigned nframes_transition; nframes_transition = 0;
        
      #if 1 /* Don't do this, it hinders the finding of good candidates */
        if(num_pocketed > 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)
          {
            /*fprintf(stderr, "estimated %u, got %u\n",
                best_time_this_pocket_count, nframes);*/
            failreason = "pessimistic";
            goto Fail;
          }
        }
      #endif
      
        static double prospects; prospects = CalculateProspects();
        
        if(num_pocketed > 0)
        {
            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) nframes_transition=0;
        
    EndLoop: ;
        // confidence is not increased if a fail happens
        fprintf(stderr, "\rk[%04d]ang[%3d]vel[%3d]preang[%3u]prevel[%3u]: p=%u, f=%u(ef=%u), %2.4f, %-18s%s",
            cand.key,
            ang,vel,preang,prevel,
            num_pocketed, nframes-nframes_transition, nframes_transition,
            cand.scoring,
            failreason ? failreason : "",
            (/*num_pocketed &&*/ !failreason) ? "\n" : ""
        );
        
        if(!failreason && num_pocketed > 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 VelT, typename NextT>
    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 AngT, typename NextT>
    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/*angle*/, NextT> AimList;
        typedef std::map<AimingMethod, AimList> 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(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(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->dummy_first = 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 "dummy_first" */
            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->dummy_first = 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;
        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, unsigned>& 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, unsigned>::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; ++candno)
                {
                    CandidateType* candit = &CandidateList[candno];
                    candit->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, unsigned>& 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)
            {
                perror("fork");
                goto end;
            }
        }
        
        // child
        
        MovieForkIntoTempFiles();
        
        static unsigned end_candno;
        end_candno = first_candno + NUM_CANDIDATES_PER_PROCESS;
        if(end_candno > CandidateList.size())
            end_candno = CandidateList.size();
        
        static CandidateOptMapType optmap;
        optmap.clear();
        for(unsigned candno=first_candno; candno < end_candno; ++candno)
        {
            CandidateType* candit = &CandidateList[candno];
            unsigned model = candit->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;
        
        static std::map<int, unsigned> 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.size(); candno+=NUM_CANDIDATES_PER_PROCESS)
        {
            do {
                WaitCandidate(CandidateList, pid_list, pid_list.size() >= 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);
    }
    
    int Run()
    {
        scrBegin;
        
        static CandidateListType WinnerList;
        static std::bitset<0x100000000ULL> covered_candidates;
        static std::bitset<0x10000> covered_preshots;
        
BeginNewShot:
        Winner::SetBest(0,0,0);
        
        LunarballLaunchManager2ns::MAX_FRAMES = INIT_MAX_FRAMES;
        LunarballLaunchManager2ns::BALLS_REMAINING = RAM[0x18E];
        
        WinnerList.clear();
        covered_candidates.reset();
        covered_preshots.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) \
          /*if(preang >= 110 && preang < 0x90)*/ \
          /*if(preang >= 0x75 && ang >= 0x75)*/ \
          /*if(ang < 40 || ang > 170)*/ \
          do{ \
            CandidateType tmp; \
            unsigned model = (ang+vel*256U) + (preang + prevel*256U)*65536U; \
            if(!covered_preshots[model >> 16] \
            && !covered_candidates[model]) \
            { \
                WinnerList.push_back(tmp.SetModel(model, is_oneshot)); \
                if(is_oneshot) covered_preshots[model >> 16] = true; \
                covered_candidates[model] = true; \
            } \
            /*Winner::SetBest(pok,tim,model);*/ \
          }while(0)
        
        fprintf(stderr, "Beginning new shot. Table=%u, remaining balls=%u\n",
            RAM[0x187], RAM[0x18E]);

        if(RAM[0x187] == 1 && RAM[0x18E] == 6)
        {
            CreateSeed(4, 488,  228,240, 208,138, false);
        }
        if(RAM[0x187] == 1 && RAM[0x18E] == 2)
        {
            CreateSeed(2, 346,   56,177,  36,0, false);
            CreateSeed(1, 225,   59,174,  35,6, false);
        }
        if(RAM[0x187] == 2 && RAM[0x18E] == 6)
        {
            CreateSeed(4, 550,   51,255,  44, 54, false);
            CreateSeed(3, 378,   97,252, 102, 36, false);
            CreateSeed(1, 127,  245, 60,  33,222, false);
            CreateSeed(1, 156,  246,241, 105,141, true);
            CreateSeed(2, 276,    0,255, 100,144, true);
            CreateSeed(2, 250,    0,255, 161,252, true);
            CreateSeed(2, 245,   46,204,  26, 45, false);
        }
        if(RAM[0x187] == 2 && RAM[0x18E] == 2)
        {
            CreateSeed(1, 286,   0x9D,0x60,0x81,0x99, false);
            CreateSeed(1, 251,  157,189, 34,153, true);
            CreateSeed(2, 420,   0x61,0xCC,0x48,0x30, false);
            CreateSeed(2, 420,   97,204,77,27, false);
            CreateSeed(2, 420,   97,207,77,27, false);
            CreateSeed(2, 422,   97,204,77,33, false);
            CreateSeed(2, 422,   97,207,72,24, false);
            CreateSeed(2, 422,   97,222,72,48, false);
            CreateSeed(2, 422,   97,225,77,21, false);
            
            CreateSeed(2, 430,   97,225,77,42, false);
            CreateSeed(2, 439, 24,177,40,18, false);
            CreateSeed(2, 439, 53,234,13,27, false);
            CreateSeed(2, 439, 98,225,53,27, false);
            CreateSeed(2, 439, 107,210,57,39, false);
            CreateSeed(2, 439, 95,234,97,30, false);
            CreateSeed(2, 464, 102,246,109,33, false);
            CreateSeed(2, 464,  26,225,14,24, false);
        }
        if(RAM[0x187] == 4 && RAM[0x18E] == 5)
        {/*
            CreateSeed(3, 373,  134,  243,  126,  33, false);
            CreateSeed(3, 373,  134,  243,  126,  36, false);
            CreateSeed(3, 373,  134,  243,  126,  48, false);
            CreateSeed(3, 373,  134,  243,  127,  45, false);*/
        }
        if(RAM[0x187] == 5 && RAM[0x18E] == 7)
        {
            CreateSeed(1, 191, 84,255, 84,207, true);
            CreateSeed(2, 301, 80,255, 80,213, true);
            CreateSeed(3, 372, 86,255, 86,255, true);
        }
        if(RAM[0x187] == 5 && RAM[0x18e] == 4)
        {
            CreateSeed(3,534,155,255,155,45,false);
            CreateSeed(3,506,155,255,155,42,false);
            CreateSeed(3,506,155,255,155,39,false);
            CreateSeed(3,506,155,255,155,27,false);
            CreateSeed(1,303,168,255,168,243,false);
            CreateSeed(2,324,170,255,170,249,false);
            CreateSeed(3,446,169,255,169,240,false);
            CreateSeed(1,303,168,255,168,243,false);
            CreateSeed(2,324,170,255,170,249,false);
            CreateSeed(4,696,136,246,76,18,false);
            CreateSeed(4,693,136,240,76,21,false);
            CreateSeed(4,693,136,243,76,21,false);
            CreateSeed(4,693,136,240,76,24,false);
            CreateSeed(4,693,136,243,76,24,false);
            CreateSeed(4,692,136,237,76,45,false);
            CreateSeed(4,641,74,246,81,51,false);
            CreateSeed(4,572,68,240,88,18,false);
            CreateSeed(4,569,68,237,88,21,false);
            CreateSeed(4,568,67,243,93,45,false);
            CreateSeed(4,568,67,246,93,45,false);
            CreateSeed(4,567,67,240,93,51,false);
            CreateSeed(4,553,63,210,95,42,false);
        }
        if(RAM[0x187] == 6 && RAM[0x18E] == 6)
        {
            CreateSeed(0,400,7,255,7,174,false);
            CreateSeed(0,398,7,255,7,168,false);
            CreateSeed(0,373,12,255,12,132,false);
            CreateSeed(0,364,12,255,12,129,false);
            CreateSeed(0,364,12,255,12,126,false);
            CreateSeed(0,302,15,255,15,192,false);
            CreateSeed(0,302,15,255,15,189,false);
            CreateSeed(0,294,15,255,15,174,false);
            CreateSeed(0,422,21,255,21,210,false);
            CreateSeed(0,379,46,255,46,228,false);
            CreateSeed(0,420,40,255,40,255,false);
            CreateSeed(0,273,45,255,45,231,false);
            CreateSeed(0,264,45,255,45,141,false);
            CreateSeed(0,378,47,255,47,228,false);
            CreateSeed(0,298,50,255,50,216,false);
            CreateSeed(0,241,62,255,62,132,false);
            CreateSeed(0,240,62,255,62,117,false);
            CreateSeed(0,132,64,255,64,177,false);
            CreateSeed(0,365,81,255,81,216,false);
            CreateSeed(0,345,81,255,81,243,false);
            CreateSeed(0,291,229,255,229,255,false);
            CreateSeed(0,132,64,255,64,177,false);
            CreateSeed(0,291,229,255,229,255,false);
        }
        if(RAM[0x187] == 7 && RAM[0x18E] == 7)
        {
            CreateSeed(0,0, 45,252, 73,24,false);
            CreateSeed(0,0, 45,255, 73,24,false);
            CreateSeed(0,0, 45,252, 73,27,false);
            CreateSeed(0,0, 45,255, 73,27,false);
            CreateSeed(0,0, 59,234, 56,42,false);
            CreateSeed(0,0, 59,237, 56,42,false);
        }
        if(RAM[0x187] == 7 && RAM[0x18E] == 2)
        {
            CreateSeed(2,325, 30,207,39,30, false);
            CreateSeed(2,325, 30,207,39,33, false);
            CreateSeed(2,325, 30,204,39,45, false);
            CreateSeed(1,154, 48,255,48,240, false);
        }
        if(RAM[0x187] == 8 && RAM[0x18E] == 3)
        {
            CreateSeed(1,125, 50,255,50,171, false);
            CreateSeed(2,203, 33,255,33,234, false);
            CreateSeed(3,389, 15,237,27,24, false);
            CreateSeed(3,389, 15,240,27,33, false);
            CreateSeed(3,389, 15,237,27,33, false);
            CreateSeed(3,389, 15,240,27,36, false);
            CreateSeed(3,389, 15,237,27,36, false);
            CreateSeed(3,389, 15,240,28,21, false);
            CreateSeed(3,389, 15,237,28,33, false);
            CreateSeed(3,389, 15,240,28,33, false);
        }
        if(RAM[0x187] == 9 && RAM[0x18E] == 6)
        {
            CreateSeed(3,263, 7,255, 7,84, false);
            CreateSeed(3,263, 246,240, 10,54, false);
            //CreateSeed(3,250, 64,210, 34,30, false);
        }
        
        /**/
        
        /*
        CreateSeed(0,0, 0x9A,0xFC, 0x9F,0x1B, false);
        CreateSeed(0,0, 0x9A,0xFF, 0x9F,0x1B, false);
        CreateSeed(0,0, 0x9A,0xFC, 0x9F,0x1E, false);
        CreateSeed(0,0, 0x9A,0xFF, 0x9F,0x1E, false);
        CreateSeed(0,0, 140,237, 57,255, false);*/

        /* This ensures that all pre-shots are covered. */
        /**/
        for(unsigned preang=0; preang<256; preang += 1)
        for(unsigned prevel=18; prevel<256; prevel += 3)
            CreateSeed(0,0, preang,255, preang, prevel, false);
        /**/

        static SaveState itbegin;
        itbegin.Create();

        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(generationno == 2)
            {
                if(DO_DUALSHOT)
                {
                    /* Each plausible two-shot combination... */
                    for(unsigned preang=0; preang<256; preang += 1)
                    for(unsigned prevel=18; prevel<=63; prevel += 3)
                    {
                        /*
                        if(preang==51) preang=52;
                        if(preang==164) preang=168;
                        */
                    
                        static const int dists[] = {
                            50,1,/*
                            20,1,
                            34,2,
                            40,3,
                            70,5,
                            100,7,
                            128,14,*/ -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 = 198; //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; vel+=3)
                                    CreateSeed(0, 0, ((preang+prevdist)&255), vel, (preang&0xFF), prevel, false);
                            }
                            prevdist = dist;
                        }
                    }
                }
            }
            CandidateList = WinnerList;
            if(generationno > 2) CandidateList.clear();
            /*
            for(unsigned candno=0; candno<CandidateList.size(); ++candno)
                while(candno < CandidateList.size()
                && !covered_candidates[candno])
                {
                    CandidateList.erase(CandidateList.begin() + candno);
                }
            */

            if(DO_DUALSHOT && !WinnerList.empty() && generationno > 2)
            for(unsigned a=0; a<NUM_VARIATIONS; ++a)
            {
                // Take one of the best as a model, and mutate a copy of it
                unsigned randi = lrand48() % WinnerList.size();
                CandidateType tmp = WinnerList[randi];

                static const double minlog  = pow(LOGMUTAATIO, MINMUTAATIO);
                static const double maxlog  = pow(LOGMUTAATIO, MAXMUTAATIO);
                const double logpos = minlog + (a*(maxlog-minlog))/(NUM_VARIATIONS-1);
                const double mut_amount = log(logpos) / log(LOGMUTAATIO);
                
                //if(generationno == 1)
                //    fprintf(stderr, "Gen %u: mut_amount=%g\n", a, mut_amount);

                tmp.confidence = 0;
                tmp.Mutate(mut_amount);

                unsigned c = tmp.GetModel();
                if(covered_candidates[c]) continue;
                if(covered_preshots[c >> 16]) continue;
                covered_candidates[c] = true;

                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; ++a)
            {
                CandidateType tmp; tmp.CreateRandom();

                unsigned c = tmp.GetModel();
                if(covered_candidates[c]) continue;
                if(covered_preshots[c >> 16]) continue;
                covered_candidates[c] = true;

                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\n",
                generationno, (unsigned)CandidateList.size(), (unsigned)WinnerList.size(),
                (unsigned) covered_preshots.count(),
                (unsigned long) covered_candidates.count() );
            }

            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.size(); ++candno)
            {
                CandidateType* candit = &CandidateList[candno];
                candit->key = candno;

                if(candit->dummy_first)
                {
                    covered_preshots[candit->GetModel() >> 16] = true;
                }
            }

            // 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.size(); ++candno)
            {
                static CandidateType* candit;
                candit = &CandidateList[candno];

                if(candit->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, time = Winner::GetBestTimeWithPocketCount(n, model);
                if(time == 0) continue;
                CandidateType tmp;
                tmp.SetModel(model, false); // don't know if it's a dummy_first 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; ++n)
            {
                static const double minlog  = pow(LOGMUTAATIO, MINMUTAATIO);
                static const double maxlog  = pow(LOGMUTAATIO, MAXMUTAATIO);
                const double logpos = minlog + (n*(maxlog-minlog))/(WINNERS-1);
                const double mut_amount = log(logpos) / log(LOGMUTAATIO);
                
                unsigned sel_winner = (unsigned)(mut_amount * CandidateList.size());
                sel_winner %= CandidateList.size();
                
                WinnerList.push_back(CandidateList[sel_winner]);
            }
             
            Averaging quality_all, quality_best;
            for(CandidateListType::const_iterator i=WinnerList.begin();
                  i!=WinnerList.end(); ++i)
                quality_best.Cumulate(i->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
};
%%END_EMBED