//Uses fixed-point.

RegDefault("ZealOS/RawHide", "F64 best_score=9999;\n");
RegExe("ZealOS/RawHide");

F64 t0, tf;
I64 outside_count;

#define MAP_WIDTH               1000
#define MAP_HEIGHT              1000
#define FENCE_WIDTH     320
#define FENCE_HEIGHT    200
#define MAP_BORDER              3
CDC *map_dc;

#define GATE_WIDTH              22
#define GATE_HEIGHT             7
F64 gate_theta, gate_t;



<1>/* Graphics Not Rendered in HTML */ <2>/* Graphics Not Rendered in HTML */ <3>/* Graphics Not Rendered in HTML */


<4>/* Graphics Not Rendered in HTML */ <5>/* Graphics Not Rendered in HTML */ <6>/* Graphics Not Rendered in HTML */


<7>/* Graphics Not Rendered in HTML */ <8>/* Graphics Not Rendered in HTML */


<9>/* Graphics Not Rendered in HTML */
                                <10>/* Graphics Not Rendered in HTML */








#define ANIMAL_WIDTH    20
#define ANIMAL_HEIGHT   16

U8      *cow_imgs[4]    = {<1>, <2>, <3>, <2>},
        *bull_imgs[4]   = {<4>, <5>, <6>, <5>},
        *horse_imgs[4]  = {<7>, <8>, <7>, <8>};

#define ANIMALS_NUM     100

class Animal
{
        I64      num, x, y, dx, dy;
        U8 **imgs;
        U32      buddy;
        U8       type, frame0;
        Bool dead, pad;
} *a;

//************************************
#define WATERFALL_HEIGHT                (MAP_HEIGHT / 20)
#define WATERFALL_DROPS                 512
#define WATERFALL_ACCELERATION  10
I32 *r_x, *r_width, *wfd_x;
F64 *wfd_t0, waterfall_tf;
I64 waterfall_x, waterfall_y, waterfall_width;

U0 RiverNew()
{
        r_x = MAlloc(MAP_HEIGHT * sizeof(I32));
        r_width = MAlloc(MAP_HEIGHT * sizeof(I32));
        wfd_x = MAlloc(WATERFALL_DROPS * sizeof(I32));
        wfd_t0 = MAlloc(WATERFALL_DROPS * sizeof(F64));
        waterfall_tf = Sqrt(2 * WATERFALL_HEIGHT / WATERFALL_ACCELERATION);
}

U0 RiverMake()
{
        I64 i, x = 2 * MAP_WIDTH << 32 / 3, y, dx = 0, w = 15 << 32;

        waterfall_y = (MAP_HEIGHT - WATERFALL_HEIGHT) / 2 * Rand + (MAP_HEIGHT - WATERFALL_HEIGHT) / 4;
        for (y = MAP_BORDER; y < MAP_HEIGHT-MAP_BORDER; y++)
        {
                r_x[y] = x.i32[1];
                r_width[y] = w.i32[1];
                if (waterfall_y - 5 < y < waterfall_y + WATERFALL_HEIGHT + 5)
                {
                        waterfall_width = r_width[y];
                        waterfall_x = r_x[y] - waterfall_width / 2;
                }
                else
                {
                        dx = ClampI64(dx + RandI32 / 64, -I32_MAX, I32_MAX) - I32_MAX / 256;
                        w = ClampI64(w + RandI32 * 2, 10 * U32_MAX, 150 * U32_MAX);
                        x += dx;
                }
        }
        for (i = 0; i < WATERFALL_DROPS; i++)
        {
                wfd_x[i] = Rand * waterfall_width + waterfall_x;
                wfd_t0[i] = tS - Rand * waterfall_tf;
        }

        //Plot waterfall cliff
        Sprite3B(map_dc, waterfall_x, waterfall_y, 0, <10>);

        //Plot sand bar
        x = 0;
        for (y = MAP_BORDER; y < MAP_HEIGHT - MAP_BORDER; y++)
        {
                if (!(waterfall_y - 9 < y < waterfall_y + WATERFALL_HEIGHT + 9))
                {
                        map_dc->color = YELLOW;
                        map_dc->thick = r_width[y] + 10;
                        GrPlot3(map_dc, r_x[y] + x.i32[1], y, 0);
                }
                x = ClampI64(x+RandI32, -6 * U32_MAX, 6 * U32_MAX);
        }

        //Plot water
        for (y = MAP_BORDER; y < MAP_HEIGHT - MAP_BORDER; y++)
        {
                map_dc->color = BLUE;
                map_dc->thick = r_width[y];
                GrPlot3(map_dc, r_x[y], y, 0);
        }
}

U0 RiverDel()
{
        Free(r_x);
        Free(r_width);
        Free(wfd_x);
        Free(wfd_t0);
}

//************************************
class RiverDrop
{
        RiverDrop  *next, *last;
        I64                     y, dx, dy;

} rd_head;
Bool rd_lock;

U0 RiverDropsDel()
{
        while (LBts(&rd_lock, 0))
                Yield;
        QueueDel(&rd_head, TRUE);
        QueueInit(&rd_head);
        LBtr(&rd_lock, 0);
}

U0 RiverDropsNext(CTask *mem_task)
{
        RiverDrop *tmpr, *tmpr1;

        while (LBts(&rd_lock, 0))
                Yield;
        tmpr = rd_head.next;
        while (tmpr != &rd_head)
        {
                tmpr1 = tmpr->next;
                if (++tmpr->y >= MAP_HEIGHT - MAP_BORDER)
                {
                        QueueRemove(tmpr);
                        Free(tmpr);
                }
                else
                {
                        do
                        {
                                if (RandU16 & 1 && GrPeek(map_dc, r_x[tmpr->y] + tmpr->dx, tmpr->y + tmpr->dy) == BLUE)
                                        break;
                                tmpr->dx = ClampI64(tmpr->dx + RandU16 % 3 - 1, -r_width[tmpr->y] / 2, r_width[tmpr->y] / 2);
                                tmpr->dy = ClampI64(tmpr->dy + RandU16 % 3 - 1, -r_width[tmpr->y] / 2, r_width[tmpr->y] / 2);
                        }
                        while (GrPeek(map_dc, r_x[tmpr->y] + tmpr->dx, tmpr->y + tmpr->dy) != BLUE &&
                                   GrPeek(map_dc, r_x[tmpr->y], tmpr->y) == BLUE);//Might be reiniting
                }
                tmpr = tmpr1;
        }
        tmpr = MAlloc(sizeof(RiverDrop), mem_task);
        tmpr->y = MAP_BORDER;
        tmpr->dx = 0;
        tmpr->dy = 0;
        QueueInsert(tmpr, rd_head.last);
        LBtr(&rd_lock, 0);
}

U0 RiverDropsDraw(CDC *dc, I64 cx, I64 cy)
{
        I64                     i;
        F64                     t = tS;
        RiverDrop  *tmpr;

        while (LBts(&rd_lock, 0))
                Yield;
        tmpr = rd_head.next;
        dc->color = LTBLUE;
        while (tmpr != &rd_head)
        {
                GrPlot(dc, r_x[tmpr->y] + tmpr->dx-cx, tmpr->y + tmpr->dy-cy);
                tmpr = tmpr->next;
        }
        LBtr(&rd_lock, 0);

        dc->color = WHITE;
        for (i = 0; i < WATERFALL_DROPS; i++)
                GrPlot(dc, wfd_x[i] - cx,
                           waterfall_y + 0.5 * WATERFALL_ACCELERATION * Sqr(waterfall_tf * Saw(t - wfd_t0[i], waterfall_tf)) - cy);
}

//************************************
U0 DrawIt(CTask *task, CDC *dc)
{
        static I64      last_pos_x = 0;
        static Bool     left = TRUE;
        F64                     t;
        I64                     i, frame=4 * tS, 
                                cx = (MAP_WIDTH  - task->pix_width)  / 2, 
                                cy = (MAP_HEIGHT - task->pix_height) / 2;

        if (task->scroll_x + cx < 0)
                task->scroll_x = -cx;

        if (task->scroll_x + cx > MAP_WIDTH - task->pix_width)
                task->scroll_x = MAP_WIDTH - task->pix_width - cx;

        if (task->scroll_y + cy < 0)
                task->scroll_y = -cy;

        if (task->scroll_y + cy > MAP_HEIGHT - task->pix_height)
                task->scroll_y = MAP_HEIGHT - task->pix_height - cy;

        map_dc->flags |= DCF_NO_TRANSPARENTS;
        GrBlot(dc, -cx, -cy, map_dc);

        RiverDropsDraw(dc, cx, cy);

        for (i = 0; i < ANIMALS_NUM; i++)
                if (!a[i].dead)
                {
                        if (a[i].dx < 0)
                        {
                                dc->flags |= DCF_JUST_MIRROR | DCF_SYMMETRY;
                                DCSymmetrySet(dc, a[i].x.i32[1] - cx, 0, a[i].x.i32[1] - cx, 1);
                        }
                        Sprite3(dc, a[i].x.i32[1] - cx, a[i].y.i32[1] - cy, 0, a[i].imgs[(frame + a[i].frame0) & 3]);
                        dc->flags &= ~(DCF_JUST_MIRROR|DCF_SYMMETRY);
                }

        if (mouse.pos.x - last_pos_x > 0)
                left = FALSE;
        else if (mouse.pos.x - last_pos_x < 0)
                left = TRUE;
        if (left)
        {
                dc->flags |= DCF_JUST_MIRROR | DCF_SYMMETRY;
                DCSymmetrySet(dc, mouse.pos.x - task->pix_left - task->scroll_x, 0, 
                                                  mouse.pos.x - task->pix_left - task->scroll_x, 1);
        }
        Sprite3(dc, mouse.pos.x - task->pix_left - task->scroll_x, 
                                mouse.pos.y - task->pix_top  - task->scroll_y, 0, horse_imgs[frame & 3]);
        dc->flags &= ~(DCF_JUST_MIRROR|DCF_SYMMETRY);
        last_pos_x = mouse.pos.x;

        if (tf)
        {
                dc->color = RED;
                t = tf - t0;
                if (Blink)
                        GrPrint(dc, (task->pix_width  - FONT_WIDTH * 14) >> 1 - task->scroll_x, 
                                                (task->pix_height - FONT_HEIGHT)     >> 1 - task->scroll_y, 
                                                "Game Completed");
        }
        else
        {
                dc->color = BLACK;
                t = tS - t0;
        }
        GrPrint(dc, -task->scroll_x, -task->scroll_y, "Outside:%03d Time:%7.2fs Best:%7.2fs", outside_count, t, best_score);
}

U0 BuddySel(I64 i)
{
        I64 b, best_b = i, score, best_score = I64_MAX;

        for (b = 0; b < ANIMALS_NUM; b++)
        {
                if (b != i && !a[b].dead)
                {
                        score = RandU32 % (512 * 512) + SqrI64(a[b].x.i32[1] - a[i].x.i32[1]) + SqrI64(a[b].y.i32[1] - a[i].y.i32[1]);
                        if (score < best_score)
                        {
                                best_score = score;
                                best_b = b;
                        }
                }
        }
        a[i].buddy = best_b;
}


U0 RedrawGate()
{
        F64 tt = tS - gate_t;
        I64 x1 = FENCE_WIDTH - 63, y1 = FENCE_HEIGHT - 1, dx, dy;

        if (tt < 0.5)
                gate_theta = Clamp(gate_theta + 0.02, 0, pi / 2);
        else if (tt > 5.0)
                gate_theta = Clamp(gate_theta - 0.02, 0, pi / 2);

        dx = GATE_WIDTH * Cos(gate_theta);
        dy = -0.8 * GATE_WIDTH * Sin(gate_theta);

        map_dc->color = LTGREEN;
        GrRect(map_dc, x1, y1 - 0.8 * GATE_WIDTH - GATE_HEIGHT, 46, 0.8 * GATE_WIDTH + GATE_HEIGHT + 3);

        map_dc->color = BLACK;

        GrLine(map_dc, x1,              y1,                                     x1 + dx, y1 + dy);
        GrLine(map_dc, x1,              y1,                                     x1,              y1 - GATE_HEIGHT);
        GrLine(map_dc, x1 + dx, y1 + dy,                        x1 + dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1,              y1 - GATE_HEIGHT,       x1 + dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1,              y1,                                     x1 + dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1,              y1 - GATE_HEIGHT,       x1 + dx, y1 + dy);

        GrLine(map_dc, x1 + 45,                 y1,                                     x1 + 45 - dx, y1 + dy);
        GrLine(map_dc, x1 + 45,                 y1,                                     x1 + 45,          y1 - GATE_HEIGHT);
        GrLine(map_dc, x1 + 45 - dx,    y1 + dy,                        x1 + 45 - dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1 + 45,                 y1 - GATE_HEIGHT,       x1 + 45 - dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1 + 45,                 y1,                                     x1 + 45 - dx, y1 + dy - GATE_HEIGHT);
        GrLine(map_dc, x1 + 45,                 y1 - GATE_HEIGHT,       x1 + 45 - dx, y1 + dy);
}

Bool CheckMap(I64 x, I64 y)
{
        I64 i, j, c;

        if (SqrI64(x - (waterfall_x + waterfall_width / 2)) >> 1 + SqrI64(y - (waterfall_y + WATERFALL_HEIGHT / 2)) < 2500)
                return FALSE;
        for (j = -4; j <= 2; j++)
                for (i = -4; i <= 4; i++)
                {
                        c = GrPeek(map_dc, x + i, y + j);
                        if (c == LTGRAY || c == BLACK)
                                return FALSE;
                }

        return TRUE;
}

U0 AnimateTask(CTask *parent)
{
        I64              i, cx, cy, cursor_x, cursor_y, dd, ddx, ddy, count, max_speed = I64_MAX, updates = 0, my_outside_count;
        F64              f, d, dx, dy, s, stress;
        Animal  *tmpa, *tmpa1;

        while (TRUE)
        {
                max_speed = ClampU64(max_speed, U32_MAX / 3, 200 * U32_MAX);
                cx = (MAP_WIDTH - parent->pix_width) / 2, cy = (MAP_HEIGHT - parent->pix_height) / 2;
                cursor_x = mouse.pos.x + cx - parent->pix_left - parent->scroll_x;
                cursor_y = mouse.pos.y + cy - parent->pix_top  - parent->scroll_y;
                count = 0;
                stress = 0;
                my_outside_count = 0;
                if (cursor_x < FENCE_WIDTH && cursor_y < FENCE_HEIGHT)
                        gate_t = tS;
                RedrawGate;
                for (i = 0; i < ANIMALS_NUM; i++)
                {
                        tmpa = &a[i];
                        if (!tmpa->dead)
                        {//Move away from horse
                                ddx = tmpa->x.i32[1]-cursor_x;
                                ddy = tmpa->y.i32[1]-cursor_y;
                                if (dd = SqrI64(ddx) + SqrI64(ddy))
                                {
                                        d = Sqrt(dd);
                                        dx = ddx / d;
                                        dy = ddy / d;
                                        f = 5.0e2 * U32_MAX / dd;
                                        tmpa->dx += f * dx;
                                        tmpa->dy += f * dy;
                                }

                                //Resel buddy about every ANIMALS_NUM*10ms=5.12 seconds
                                tmpa1 = &a[tmpa->buddy];
                                if (tmpa1->dead || i == updates % ANIMALS_NUM)
                                {
                                        BuddySel(i);
                                        tmpa1 = &a[tmpa->buddy];
                                }

                                //Move toward buddy
                                ddx = tmpa->x.i32[1] - tmpa1->x.i32[1];
                                ddy = tmpa->y.i32[1] - tmpa1->y.i32[1];
                                if (dd = SqrI64(ddx) + SqrI64(ddy))
                                {
                                        d = Sqrt(dd);
                                        s = d ` 1.25 - 80;
                                        stress += Abs(s);
                                        dx = ddx / d;
                                        dy = ddy / d;
                                        f = -0.001 * s * U32_MAX;
                                        tmpa->dx += f * dx;
                                        tmpa->dy += f * dy;
                                }

                                //Make velocity similar to buddy
                                tmpa->dx += 0.1 * (tmpa1->dx - tmpa->dx);
                                tmpa->dy += 0.1 * (tmpa1->dy - tmpa->dy);

                                //Add random movement,  limit speed and dampen speed
                                tmpa->dx = 0.995 * ClampI64(tmpa->dx + RandI32 / 32, -max_speed, max_speed);
                                tmpa->dy = 0.995 * ClampI64(tmpa->dy + RandI32 / 32, -max_speed, max_speed);

                                //Slow in river
                                if (GrPeek(map_dc, tmpa->x.i32[1], tmpa->y.i32[1]) != LTGREEN)
                                {
                                        tmpa->dx /= 2;
                                        tmpa->dy /= 2;
                                }

                                if (CheckMap((tmpa->x + tmpa->dx) >> 32, (tmpa->y + tmpa->dy) >> 32))
                                {
                                        tmpa->x += tmpa->dx;
                                        tmpa->y += tmpa->dy;
                                }

                                //Keep on map
                                if (!(MAP_BORDER + ANIMAL_WIDTH / 2 <= tmpa->x.i32[1] < MAP_WIDTH - MAP_BORDER - ANIMAL_WIDTH / 2))
                                {
                                        tmpa->x  -= tmpa->dx;
                                        tmpa->dx = -tmpa->dx;
                                }
                                if (!(MAP_BORDER + ANIMAL_HEIGHT <= tmpa->y.i32[1] < MAP_HEIGHT - MAP_BORDER))
                                {
                                        tmpa->y  -= tmpa->dy;
                                        tmpa->dy = -tmpa->dy;
                                }
                                count++;
                                if (tmpa->x >> 32 >= FENCE_WIDTH || tmpa->y >> 32 >= FENCE_HEIGHT)
                                        my_outside_count++;
                        }
                }
                outside_count = my_outside_count;

                if (!(updates & 15))
                        RiverDropsNext(parent);

                if (!tf && !outside_count)
                {
                        tf = tS;
                        music.mute = TRUE;
                        Sound(86);
                        Sleep(200);
                        Sound;
                        Sleep(100);
                        if (tf - t0 < best_score)
                        {
                                best_score = tf - t0;
                                Sound(86);
                                Sleep(200);
                                Sound;
                                Sleep(100);
                        }
                        music.mute = FALSE;
                }

                updates++;

                if (count)
                        stress /= count;
                else
                        stress = 0;
                if (stress > 100.0)
                {
                        Yield;
                        max_speed = stress / 5.0 * U32_MAX; //Converge faster at start-up
                }
                else
                {
                        Sleep(10);
                        max_speed = 0; //Will be set to normal max speed
                }
        }
}

U0 SongTask(I64)
{//Randomly generated (by God :-)
        Fs->task_end_cb = &SoundTaskEndCB;
        MusicSettingsReset;
        while (TRUE)
        {
                Play("5qC4etG5DC4B5DCECFqFC4sA5D4A5D4qB");
                Play("5C4etG5DC4B5DCECFqFC4sA5D4A5D4qB");
                Play("4sGAGA5qG4etG5GD4eBBqB5F4eBA5qE");
                Play("4sGAGA5qG4etG5GD4eBBqB5F4eBA5qE");
        }
}

U0 ReInit()
{
        I64 i;

        RiverDropsDel;
        map_dc->color = LTGREEN;
        GrRect(map_dc, 2, 2, MAP_WIDTH - 4, MAP_HEIGHT - 4);

        RiverMake;

        //Plot fence
        for (i = FENCE_WIDTH; i > 0; i -= 16)
                Sprite3(map_dc, i, FENCE_HEIGHT, 0, <9>);
        map_dc->thick = 1;
        map_dc->color = BROWN;
        for (i = 0; i < FENCE_HEIGHT - 16; i += 16)
                GrLine(map_dc, FENCE_WIDTH - 1, i, FENCE_WIDTH - 1, i + 7);
        map_dc->color = LTGRAY;
        GrLine(map_dc, FENCE_WIDTH, 0, FENCE_WIDTH, FENCE_HEIGHT - 6);
        RedrawGate;

        map_dc->thick = MAP_BORDER;
        map_dc->color = RED;
        GrBorder(map_dc, MAP_BORDER / 2, MAP_BORDER / 2, MAP_WIDTH - (MAP_BORDER + 1) / 2, MAP_HEIGHT - (MAP_BORDER + 1) / 2);

        for (i = MAP_BORDER; i <= MAP_HEIGHT - MAP_BORDER; i++)
                RiverDropsNext(Fs);

        MemSet(a, 0, ANIMALS_NUM * sizeof(Animal));
        for (i = 0; i < ANIMALS_NUM; i++)
        {
                a[i].num = i;
                do
                {
                        a[i].x = (64 + RandU32 % (MAP_WIDTH - 128)) << 32;
                        a[i].y = (64 + RandU32 % (MAP_WIDTH - 128)) << 32;
                }
                while (!CheckMap(a[i].x >> 32, a[i].y >> 32));

                if (i & 1)
                        a[i].imgs = cow_imgs;
                else
                        a[i].imgs = bull_imgs;
                a[i].frame0 = RandU16 & 3;
                BuddySel(i);
        }
        outside_count = ANIMALS_NUM;
        gate_t = 0;
        gate_theta = 0;
        t0 = tS;
        tf = 0;
}

U0 Init()
{
        RiverNew;
        rd_lock = 0;
        QueueInit(&rd_head);
        map_dc = DCNew(MAP_WIDTH, MAP_HEIGHT);
        a = MAlloc(ANIMALS_NUM * sizeof(Animal));
        ReInit;
}

U0 CleanUp()
{
        DCDel(map_dc);
        Free(a);
        RiverDropsDel;
        RiverDel;
}

U0 RawHide()
{
        I64 message_code, arg1, arg2;
        SettingsPush; //See SettingsPush
        MenuPush(
                                "File {"
                                "  Abort(,CH_SHIFT_ESC);"
                                "  Exit(,CH_ESC);"
                                "}"
                                "Play {"
                                "  Restart(,'\n');"
                                "}"
                                );
        Fs->song_task = Spawn(&SongTask, NULL, "Song",, Fs);

        PopUpOk(
                                "Coral the cattle.      The coral is in the\n"
                                "upper-left corner if you scroll.\n\n"
                                "Keep holding the $GREEN$<CTRL>$FG$ key and\n"
                                "scroll with $GREEN${CTRL-Left Grab}$FG$.");

        Fs->win_inhibit = WIG_TASK_DEFAULT - WIF_SELF_FOCUS - WIF_SELF_BORDER - WIF_SELF_GRAB_SCROLL - WIF_FOCUS_TASK_MENU;
        AutoComplete;
        WinBorder;
        WinMax;
        DocCursor;
        DocClear;
        Init;
        Fs->animate_task = Spawn(&AnimateTask, Fs, "Animate",, Fs);
        Fs->draw_it              = &DrawIt;
        try
        {
                while (TRUE)
                {
                        message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_KEY_DOWN + 1 << MESSAGE_MS_L_DOWN + 1 << MESSAGE_MS_R_DOWN);
                        switch (message_code)
                        {
                                case MESSAGE_MS_L_DOWN:  //Doesn't do anything, yet.
                                        break;

                                case MESSAGE_MS_R_DOWN:  //Doesn't do anything, yet.
                                        break;

                                case MESSAGE_KEY_DOWN:
                                        switch (arg1)
                                        {
                                                case '\n':
                                                        ReInit;
                                                        break;

                                                case CH_SHIFT_ESC:
                                                case CH_ESC:
                                                        goto rh_done;
                                        }
                                        break;
                        }
                }
rh_done:
                MessageGet(,, 1 << MESSAGE_KEY_UP);
        }
        catch
                PutExcept;
        SettingsPop;
        CleanUp;
        MenuPop;
        RegWrite("ZealOS/RawHide", "F64 best_score=%5.4f;\n", best_score);
}

RawHide;