//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;
    PaletteSetLight(FALSE);
    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;