//Uses fixed-point.

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

//Set snap to 4 and width to 4
//if you edit this map.

//Don't forget to change the
//starting pos.
#define MAN_START_X     0
#define MAN_START_Y     4.5

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














#define MONSTER_SCALE   2.0

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









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








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






#define PLANT_SCALE     2.0



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













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














#define SCREEN_SCALE            512
#define PLOT_GRID_WIDTH         24
#define PLOT_GRID_HEIGHT        24
#define MAN_HEIGHT                      125

#define MAP_SCALE                       4
I64 map_width, map_height;
U8 *map = NULL, *panels_processed_bitmap = NULL;

I64 man_xx, man_yy;
F64 man_theta;

F64 t0, tf;

#define MONSTERS_NUM            10
I64 monsters_left;
class Monster
{
        I64  x, y;
        Bool dead, pad[7];

} monsters[MONSTERS_NUM];

U0 CFTransform(CDC *dc, I64 *x, I64 *y, I64 *z)
{
        I64 zz;

        Mat4x4MulXYZ(dc->r, x, y, z);
        zz = SCREEN_SCALE / 3 + *z;
        if (zz < 1)
                zz = 1;
        *x = SCREEN_SCALE / 2 * *x / zz;
        *y = SCREEN_SCALE / 2 * (*y + MAN_HEIGHT) / zz;
        *x += dc->x;
        *y += dc->y;
        *z += dc->z;
}

#define LOS_SCALE       4

Bool LOSPlot(U8 *, I64 x, I64 y, I64)
{
        if (!map[(y / LOS_SCALE) * map_width + (x / LOS_SCALE)])
                return FALSE;
        else
                return TRUE;
}

Bool LOS(I64 x1, I64 y1, I64 x2, I64 y2)
{//Line of sight
        return Line(NULL, x1 * LOS_SCALE / SCREEN_SCALE, y1 * LOS_SCALE / SCREEN_SCALE, 0, 
                                          x2 * LOS_SCALE / SCREEN_SCALE, y2 * LOS_SCALE / SCREEN_SCALE, 0, &LOSPlot);
}

U0 DrawIt(CTask *task, CDC *dc)
{
        I64              i, j, *r1, *r2, *r3, *s2w, xx, yy, zz, x, y, x1, y1, z1, c, x1w, y1w, x1h, y1h, xh, yh, zh, 
                         cx = task->pix_width  / 2, 
                         cy = task->pix_height / 2;
        U8              *tmps;
        F64              tt;
        CD3I32   poly[4];
        Monster *tmpm;

        DCDepthBufAlloc(dc);
        MemSet(panels_processed_bitmap, 0, (map_width * map_height + 7) >> 3);

        //World to screen
        Mat4x4RotZ(dc->r, man_theta + pi / 2);
        Mat4x4RotX(dc->r, pi / 2);
        DCMat4x4Set(dc, dc->r);

        xh = -man_xx / SCREEN_SCALE;
        yh = -man_yy / SCREEN_SCALE;
        zh = 0;
        Mat4x4MulXYZ(dc->r, &xh, &yh, &zh);
        Mat4x4TranslationEqu(dc->r, xh, yh, zh);

        //Screen to world
        s2w = Mat4x4IdentNew(task);
        Mat4x4RotX(s2w, -pi / 2);
        Mat4x4RotZ(s2w, -man_theta - pi / 2);
        xh = 0;
        yh = 0;
        zh = SCREEN_SCALE;
        Mat4x4MulXYZ(s2w, &xh, &yh, &zh);

        //Rotate light source
        xx =  dc->ls.x;
        yy =  dc->ls.y;
        zz = -dc->ls.z;
        (*dc->transform)(dc, &xx, &yy, &zz);
        dc->ls.x = xx;
        dc->ls.y = yy;
        dc->ls.z = zz;

        dc->flags |= DCF_TRANSFORMATION;
        dc->transform = &CFTransform;
        dc->x = cx;
        dc->y = cy;
        r1 = Mat4x4IdentNew(task);
        Mat4x4RotX(r1, -pi / 2);
        Mat4x4RotZ(r1, tS);
        Mat4x4Scale(r1, MONSTER_SCALE);

        r2 = Mat4x4IdentNew(task);
        Mat4x4Scale(r2, MONSTER_SCALE);

        r3 = Mat4x4IdentNew(task);
        Mat4x4RotX(r3, -pi / 2);
        Mat4x4Scale(r3, PLANT_SCALE);

        Seed(1);
        x1h = man_xx + yh * PLOT_GRID_WIDTH / 2 + xh * (PLOT_GRID_HEIGHT - 1);
        y1h = man_yy - xh * PLOT_GRID_WIDTH / 2 + yh * (PLOT_GRID_HEIGHT - 1);
        xh >>= 1;
        yh >>= 1;
        for (j = 0; j < PLOT_GRID_HEIGHT * 2; j++)
        {
                x1w = x1h;
                y1w = y1h;
                for (i = 0; i < PLOT_GRID_WIDTH * 4; i++)
                {
                        xx = x1w / SCREEN_SCALE;
                        yy = y1w / SCREEN_SCALE;
                        x = xx * SCREEN_SCALE - man_xx;
                        y = yy * SCREEN_SCALE - man_yy;
                        if (1 <= xx < map_width - 1 && 1 <= yy < map_height - 1 && !LBts(panels_processed_bitmap, yy * map_width + xx))
                        {
                                if ((c = map[yy * map_width + xx]) &&
                                        LOS(xx * SCREEN_SCALE + SCREEN_SCALE / 2, yy * SCREEN_SCALE + SCREEN_SCALE / 2, man_xx, man_yy))
                                {
                                        if (c == YELLOW)
                                                dc->color = DKGRAY;
                                        else
                                                dc->color = c;
                                        poly[0].x = x;
                                        poly[0].y = y;
                                        poly[0].z = 0;
                                        poly[1].x = x + SCREEN_SCALE;
                                        poly[1].y = y;
                                        poly[1].z = 0;
                                        poly[2].x = x + SCREEN_SCALE;
                                        poly[2].y = y + SCREEN_SCALE;
                                        poly[2].z = 0;
                                        poly[3].x = x;
                                        poly[3].y = y + SCREEN_SCALE;
                                        poly[3].z = 0;
                                        GrFillPoly3(dc, 4, poly);
                                        if (c == GREEN)
                                        {
                                                x1 = x + SCREEN_SCALE / 2;
                                                y1 = y + SCREEN_SCALE / 2;
                                                z1 = 0;
                                                DCTransform(dc, &x1, &y1, &z1);
                                                if (z1 > 0)
                                                        Sprite3Mat4x4B(dc, x + SCREEN_SCALE / 2, y + SCREEN_SCALE / 2, 0, <5>, r3);
                                        }
                                        else if (c == YELLOW)
                                        {
                                                x1 = x + SCREEN_SCALE / 2;
                                                y1 = y + SCREEN_SCALE / 2;
                                                z1 = 0;
                                                DCTransform(dc, &x1, &y1, &z1);
                                                if (z1 > 0)
                                                        Sprite3Mat4x4B(dc, x + SCREEN_SCALE / 2, y + SCREEN_SCALE / 2, 0, <6>, r3);
                                        }

                                        if (!map[(yy + 1) * map_width + xx])
                                        {
                                                dc->color = LTGRAY;
                                                poly[0].x = x;
                                                poly[0].y = y + SCREEN_SCALE;
                                                poly[0].z = 0;
                                                poly[1].x = x + SCREEN_SCALE;
                                                poly[1].y = y + SCREEN_SCALE;
                                                poly[1].z = 0;
                                                poly[2].x = x + SCREEN_SCALE;
                                                poly[2].y = y + SCREEN_SCALE;
                                                poly[2].z = SCREEN_SCALE;
                                                poly[3].x = x;
                                                poly[3].y = y + SCREEN_SCALE;
                                                poly[3].z = SCREEN_SCALE;
                                                GrFillPoly3(dc, 4, poly);
                                        }
                                        if (!map[yy * map_width + xx + 1])
                                        {
                                                dc->color = WHITE;
                                                poly[0].x = x + SCREEN_SCALE;
                                                poly[0].y = y;
                                                poly[0].z = 0;
                                                poly[1].x = x + SCREEN_SCALE;
                                                poly[1].y = y + SCREEN_SCALE;
                                                poly[1].z = 0;
                                                poly[2].x = x + SCREEN_SCALE;
                                                poly[2].y = y + SCREEN_SCALE;
                                                poly[2].z = SCREEN_SCALE;
                                                poly[3].x = x + SCREEN_SCALE;
                                                poly[3].y = y;
                                                poly[3].z = SCREEN_SCALE;
                                                GrFillPoly3(dc, 4, poly);
                                        }
                                        if (!map[(yy - 1) * map_width + xx])
                                        {
                                                dc->color =LTGRAY;
                                                poly[0].x = x;
                                                poly[0].y = y;
                                                poly[0].z = 0;
                                                poly[1].x = x + SCREEN_SCALE;
                                                poly[1].y = y;
                                                poly[1].z = 0;
                                                poly[2].x = x + SCREEN_SCALE;
                                                poly[2].y = y;
                                                poly[2].z = SCREEN_SCALE;
                                                poly[3].x = x;
                                                poly[3].y = y;
                                                poly[3].z = SCREEN_SCALE;
                                                GrFillPoly3(dc, 4, poly);
                                        }
                                        if (!map[yy * map_width + xx - 1])
                                        {
                                                dc->color =WHITE;
                                                poly[0].x = x;
                                                poly[0].y = y;
                                                poly[0].z = 0;
                                                poly[1].x = x;
                                                poly[1].y = y + SCREEN_SCALE;
                                                poly[1].z = 0;
                                                poly[2].x = x;
                                                poly[2].y = y + SCREEN_SCALE;
                                                poly[2].z = SCREEN_SCALE;
                                                poly[3].x = x;
                                                poly[3].y = y;
                                                poly[3].z = SCREEN_SCALE;
                                                GrFillPoly3(dc, 4, poly);
                                        }
                                }
                        }
                        x1w -= yh;
                        y1w += xh;
                }
                x1h -= xh;
                y1h -= yh;
        }

        //Draw Monsters
        for (i = 0, tmpm = monsters; i < MONSTERS_NUM; i++, tmpm++)
        {
                x = tmpm->x;
                y = tmpm->y;
                if (LOS(x, y, man_xx, man_yy))
                {
                        x -= man_xx;
                        y -= man_yy;
                        xx = x;
                        yy = y;
                        zz = 0;
                        DCTransform(dc, &xx, &yy, &zz);
                        if (zz > 0)
                        {
                                if (tmpm->dead)
                                        Sprite3Mat4x4B(dc, x, y, 0, <2>, r2);
                                else
                                {
                                        tt = Tri(tS, 1.0);
                                        tmps = SpriteInterpolate(tt, <3>, <4>);
                                        Sprite3Mat4x4B(dc, x, y, 0, tmps, r1);
                                        Free(tmps);
                                }
                        }
                }
        }
        Free(r1);
        Free(r2);
        Free(r3);

        //Draw Map heads-up display, scaled 2 pixs
        Free(dc->r);
        DCMat4x4Set(dc, Mat4x4IdentNew(task));
        dc->x = task->pix_width  - 2 * map_width;
        dc->y = task->pix_height - 2 * map_height;
        dc->z = 0;
        dc->transform = &DCTransform;
        dc->thick = 2;
        for (i = 0; i < map_height; i++)
                for (j = 0; j < map_width; j++)
                {
                        dc->color = map[(map_height - 1 - i) * map_width + j];
                        GrPlot3(dc, 2 * j, 2 * i, 0);
                }

                //Draw Things on heads-up Map
        dc->color = LTPURPLE;
        for (i = 0, tmpm = monsters; i < MONSTERS_NUM; i++, tmpm++)
                if (!tmpm->dead)
                        GrPlot3(dc, 2 * (tmpm->x / SCREEN_SCALE), 
                                                2 * (map_height - 1 - tmpm->y / SCREEN_SCALE), 0);
        dc->color = LTCYAN;
        GrPlot3(dc, 2 * (man_xx / SCREEN_SCALE), 2 * (map_height - 1 - man_yy / SCREEN_SCALE), 0);

        if (tf)
        {
                dc->color = LTRED;
                if (Blink)
                        GrPrint(dc, cx - (FONT_WIDTH * 14) / 2, cy - FONT_HEIGHT / 2, "Game Completed");
                tt = tf;
        }
        else
        {
                dc->color = LTGREEN;
                GrLine(dc, cx - 5, cy, cx + 5, cy);
                GrLine(dc, cx, cy - 5, cx, cy + 5);
                tt = tS;
        }
        GrPrint(dc, 0, 0, "Enemy:%d Time:%3.2f Best:%3.2f", monsters_left, tt - t0, best_score);
        Free(s2w);
        Seed(0);
}

U0 Fire()
{
        I64              i, x, y;
        F64              d, dx, dy, xx = Cos(man_theta), yy = Sin(man_theta);
        Monster *tmpm;

        Noise(100, 53, 74);
        for (i = 0, tmpm = monsters; i < MONSTERS_NUM; i++, tmpm++)
        {
                x = tmpm->x;
                y = tmpm->y;
                if (!tmpm->dead && LOS(x, y, man_xx, man_yy))
                {
                        dx = x - man_xx;
                        dy = man_yy - y;
                        if (d = Sqrt(dx * dx + dy * dy))
                        {
                                dx /= d;
                                dy /= d;
                                if (dx * xx + dy * yy > 0.995)
                                {
                                        tmpm->dead = TRUE;
                                        if (!--monsters_left)
                                        {
                                                tf = tS;
                                                if (tf - t0 < best_score)
                                                        best_score = tf - t0;
                                        }
                                }
                        }
                }
        }
}

U0 Init()
{
        I64              i, x, y;
        CDC             *dc;
        Monster *tmpm;

        DocClear;
        "$BG,BLACK$%h*c", TEXT_ROWS / 2, '\n';

        dc = Sprite2DC(<1>);
        map_width  = dc->width  / MAP_SCALE + 2;
        map_height = dc->height / MAP_SCALE + 2;
        Free(map);
        Free(panels_processed_bitmap);
        map = CAlloc(map_width * map_height * sizeof(U8));
        panels_processed_bitmap = MAlloc((map_width * map_height + 7) >> 3);
        for (y = 0; y < map_height - 2; y++)
                for (x = 0; x < map_width - 2; x++)
                        map[(map_height - 2 - y) * map_width + x + 1] = GrPeek(dc, x * MAP_SCALE, y * MAP_SCALE);
        DCDel(dc);
        man_xx = (1 + MAN_START_X) * SCREEN_SCALE;
        man_yy = (map_height - 1 - MAN_START_Y) * SCREEN_SCALE;
        man_theta = 0;

        for (i = 0, tmpm = monsters; i < MONSTERS_NUM; i++, tmpm++)
        {
                tmpm->dead = FALSE;
                do
                {
                        tmpm->x = RandU64 % ((map_width  - 2) * SCREEN_SCALE) + SCREEN_SCALE;
                        tmpm->y = RandU64 % ((map_height - 2) * SCREEN_SCALE) + SCREEN_SCALE;
                }
                while (!map[(tmpm->y / SCREEN_SCALE) * map_width + tmpm->x / SCREEN_SCALE]);
        }
        monsters_left = MONSTERS_NUM;
        tf = 0;
        t0 = tS;
}

U0 AnimateTask(I64)
{
        I64              i, x, y, dd;
        Monster *tmpm;

        while (TRUE)
        {
                dd = 0.25 * SCREEN_SCALE * Sin(tS / 2);
                for (i = 0, tmpm = monsters; i < MONSTERS_NUM; i++, tmpm++)
                        if (!tmpm->dead)
                        {
                                x = tmpm->x;
                                y = tmpm->y;
                                if (i & 1)
                                        x += dd;
                                else
                                        y += dd;
                                if (0 <= x <= map_width  * SCREEN_SCALE &&
                                        0 <= y <= map_height * SCREEN_SCALE &&
                                        map[(y / SCREEN_SCALE) * map_width + x / SCREEN_SCALE])
                                {
                                        if (!map[(y / SCREEN_SCALE) * map_width + x / SCREEN_SCALE + 1]         &&
                                                        x - RoundI64(x, SCREEN_SCALE) > SCREEN_SCALE / 2                        ||
                                                        !map[(y / SCREEN_SCALE) * map_width + x / SCREEN_SCALE - 1] &&
                                                        x - RoundI64(x, SCREEN_SCALE) < SCREEN_SCALE / 2)
                                                x = RoundI64(x, SCREEN_SCALE) + SCREEN_SCALE / 2;

                                        if (!map[(y / SCREEN_SCALE + 1) * map_width + x / SCREEN_SCALE]         &&
                                                        y - RoundI64(y, SCREEN_SCALE) > SCREEN_SCALE / 2                        ||
                                                        !map[(y / SCREEN_SCALE - 1) * map_width + x / SCREEN_SCALE] &&
                                                        y - RoundI64(y, SCREEN_SCALE) < SCREEN_SCALE / 2)
                                                y = RoundI64(y, SCREEN_SCALE) + SCREEN_SCALE / 2;
                                        tmpm->x = x;
                                        tmpm->y = y;
                                }
                        }
                Sleep(20);
        }
}

U0 CleanUp()
{
        Free(map);
        Free(panels_processed_bitmap);
        map = NULL;
        panels_processed_bitmap = NULL;
}

U0 SongTask(I64)
{//Song by Terry A. Davis
        Fs->task_end_cb = &SoundTaskEndCB;
        MusicSettingsReset;
        while (TRUE)
        {
                Play("3q.A#eGAeA#qAq.A#eGeAeA#qA");
                Play("3q.A#eGA#AqGq.A#eGA#AqG");
                Play("4eA#AqGeA#AqGeA#AGAA#AqG");
        }
}

U0 MoveMan(F64 theta)
{
        I64 x, y, color, step=SCREEN_SCALE / 2;

        do
        {
                x = man_xx + step * Cos(theta);
                y = man_yy - step * Sin(theta);
                x = Clamp(x, 0, map_width  * SCREEN_SCALE);
                y = Clamp(y, 0, map_height * SCREEN_SCALE);
                color = map[y / SCREEN_SCALE * map_width + x / SCREEN_SCALE];
                if (color == DKGRAY || color == GREEN) {
                        man_xx = x;
                        man_yy = y;
                        break;
                }
                else
                        step >>= 1;
        }
        while (step);
}

//#define MICRO_STEPS   4
#define MICRO_STEPS     32
U0 RotateMan(F64 d)
{
        I64 i;

        for (i = 0; i < MICRO_STEPS; i++)
        {
                man_theta += d / MICRO_STEPS;
//              Sleep(15);
                Sleep(1);
        }
}

U0 CastleFrankenstein()
{
        I64 sc;

        MenuPush(
                                "File {"
                                "  Abort(,CH_SHIFT_ESC);"
                                "  Exit(,CH_ESC);"
                                "}"
                                "Play {"
                                "  Restart(,'\n');"
                                "  Fwd(,,SC_CURSOR_UP);"
                                "  Bwd(,,SC_CURSOR_DOWN);"
                                "  Left(,,SC_CURSOR_LEFT);"
                                "  Right(,,SC_CURSOR_RIGHT);"
                                "  Fire(,CH_SPACE);"
                                "}"
                                );

        SettingsPush; //See SettingsPush
        Fs->text_attr = DKGRAY << 4 + WHITE;
        AutoComplete;
        WinBorder;
        WinMax;
        DocCursor;
        Init;
        Fs->animate_task = Spawn(&AnimateTask, NULL, "Animate",, Fs);
        Fs->song_task    = Spawn(&SongTask, NULL, "Song",, Fs);
        Fs->draw_it              = &DrawIt;

        try
        {
                while (TRUE)
                {
                        switch (KeyGet(&sc))
                        {
                                case CH_SPACE:
                                        Fire;
                                        break;

                                case '\n':
                                        Init;
                                        break;

                                case CH_ESC:
                                case CH_SHIFT_ESC:
                                        goto fs_done;

                                case 0:
                                        switch (sc.u8[0])
                                        {
                                                case SC_CURSOR_RIGHT:
//                                                      Spawn(&RotateMan, (pi / 32)(I64));
                                                        RotateMan(pi / 32);
                                                        FlushMessages;
                                                        break;

                                                case SC_CURSOR_LEFT:
//                                                      Spawn(&RotateMan, (-pi / 32)(I64));
                                                        RotateMan(-pi / 32);
                                                        FlushMessages;
                                                        break;

                                                case SC_CURSOR_UP:
                                                        MoveMan(man_theta);
                                                        break;

                                                case SC_CURSOR_DOWN:
                                                        MoveMan(man_theta + pi);
                                                        break;
                                        }
                                        break;
                        }
                }
fs_done:
        }
        catch
                PutExcept;
        DocClear;
        SettingsPop;
        CleanUp;
        MenuPop;
        RegWrite("ZealOS/CastleFrankenstein", "F64 best_score=%5.4f;\n", best_score);
}

CastleFrankenstein;