Bisqwit / Source / Bots / Lunar Ball

<< Bisqwit / Source / Bots
<< Bisqwit / Source
<< Bisqwit

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) 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