Чистый
// FPS игры
// Эта игра была написана на TypeScript

fps(60);
var song = new Audio();
song.src = 'https://storage.geekclass.ru/images/d13ad405-4f14-4239-af5d-582cf55320e1.ogg';
song.loop = true;
song.play();

var completed = false;

interface Drawable
{
    draw(ctx: CanvasRenderingContext2D) : void;
}

class Scene implements Drawable
{
    public objects: Array<Drawable>;
    public constructor()
    {
        this.objects = new Array<Drawable>();
    }
    public draw(ctx: CanvasRenderingContext2D) : void
    {
        this.objects.forEach(function(i)
        {
            i.draw(ctx);
        });
    }
}

class Mesh
{
    public mesh: any;

    public constructor(maker: () => any)
    {
        this.mesh = maker();
    }
};

var MeshRefs = {
    playerImage: new Mesh(() => {
        var img = new Image(40, 40);
        img.src = "https://storage.geekclass.ru/images/be68a4c3-479a-4f21-b9da-08f27fb3b833.jpeg";
        return img;
    }),
    wallImage: new Mesh(() => {
        var img = new Image(40, 40);
        img.src = "https://storage.geekclass.ru/images/b6dc49bf-c53b-48a4-a196-49c2bc0ea192.jpeg";
        return img;
    }),
    flagImage: new Mesh(() => {
        var img = new Image(40, 40);
        img.src = "https://storage.geekclass.ru/images/3375898c-19ae-45d9-a5be-5bd791069de7.png";
        return img;
    })
};

class Wall implements Drawable
{
    public x: number;
    public y: number;
    mesh: Mesh;

    public constructor()
    {
        this.mesh = MeshRefs.wallImage;
        this.x = 0;
        this.y = 0;
    }
    
    public draw(ctx: CanvasRenderingContext2D) : void
    {
        ctx.drawImage(
            this.mesh.mesh, 
            this.x, 
            this.y, 
            this.mesh.mesh.width, 
            this.mesh.mesh.height
        );
    }
}

class Flag implements Drawable
{
    public x: number;
    public y: number;
    mesh: Mesh;

    public constructor()
    {
        this.mesh = MeshRefs.flagImage;
        this.x = 0;
        this.y = 0;
    }
    
    public draw(ctx: CanvasRenderingContext2D) : void
    {
        ctx.drawImage(
            this.mesh.mesh, 
            this.x, 
            this.y, 
            this.mesh.mesh.width, 
            this.mesh.mesh.height
        );
    }
}

class GameMap implements Drawable
{
    x: number;
    y: number;
    wall: Wall;
    flag: Flag;
    map: Array<Array<number>>;

    public fromArray(arr: Array<Array<number>>) : void
    {
        this.map = new Array<Array<number>>();
        arr[0].forEach((i, ii) => {
            this.map.push(Array<number>());
        });        
        arr.forEach((i, ii) => {
            i.forEach((j, ij) => {
                this.map[ij].push(j);
            });
        });
    }

    public constructor()
    {
        this.flag = new Flag();
        this.wall = new Wall();
        this.x = 0;
        this.y = 0;
    }
    
    public draw(ctx: CanvasRenderingContext2D) : void
    {
        this.map.forEach((i, ii) => {
            i.forEach((j, ij) => {
                if (this.map[ii][ij] == 1)
                {
                    this.wall.x = this.x + (ii * 40);
                    this.wall.y = this.y + (ij * 40);
                    this.wall.draw(ctx);
                }
                if (this.map[ii][ij] == 2)
                {
                    this.flag.x = this.x + (ii * 40);
                    this.flag.y = this.y + (ij * 40);
                    this.flag.draw(ctx);
                }
            });
        });
    }
}


class Player implements Drawable
{
    x: number;
    y: number;
    vy: number;
    vx: number;
    mesh: Mesh;

    public constructor()
    {
        this.mesh = MeshRefs.playerImage;
        this.x = 0;
        this.y = 0;
        this.vx = 0;
        this.vy = 0;
    }

    private singleCollision(i, j)
    {
        i = i*40;
        j = j*40;
        return i < this.x + 37 &&
               i + 40 > this.x + 3 &&
               j < this.y + 37 &&
               j + 40 > this.y + 3;
    }

    public update(dt: number, level: GameMap)
    {

        var scalar = Math.sqrt(this.vx*this.vx+this.vy*this.vy);
        if (scalar == 0) return;
        this.x += this.vx/scalar*dt;

        if (this.x < 0)
        {
            this.x = 0;
            this.vy = 0;
        }
        this.collision(level, 'x');
        this.y += this.vy/scalar*dt;

        if (this.y < 0)
        {
            this.vy = 0;
            this.y = 0;
        }
        this.collision(level, 'y');




    }

    public collision(map: GameMap, dir: string)
    {
        var end = false;
        map.map.forEach((i, ii) => {
            if (end) return;
            i.forEach((j, ij) => {
                if (j == 1)
                {
                    if (this.singleCollision(ii, ij))
                    {
                        if (this.vx < 0 && dir == 'x')
                        {
                            this.vx = 0;
                            this.x = ii * 40 + 37;
                        }
                        else if (this.vx > 0 && dir == 'x')
                        {
                            this.vx = 0;
                            this.x = ii * 40 - 37;
                        }
                        if (this.vy < 0 && dir == 'y')
                        {
                            this.vy = 0;
                            this.y = ij * 40 + 37;
                        }
                        else if (this.vy > 0 && dir == 'y')
                        {
                            this.vy = 0;
                            this.y = ij * 40 - 37;
                        }
                        end = true;
                        return;
                        
                    }
                }
                else if (j == 2 && this.singleCollision(ii, ij))
                {
                    completed = true;
                }
            });
        });    
        
        if (dir == 'x') this.vx = 0;
        if (dir == 'y') this.vy = 0;    
    }

    public draw(ctx: CanvasRenderingContext2D) : void
    {
        //Нарисовать меш игрока
        ctx.drawImage(
            this.mesh.mesh, 
            this.x, 
            this.y, 
            this.mesh.mesh.width, 
            this.mesh.mesh.height
        );
    }
}

var scene: Scene = new Scene();
var level: GameMap = new GameMap();
var player: Player = new Player();
var lastTime = Date.now();
var currentLevel = 0;
var deltaTime = 0;
var levels = [
    [
        [0, 1, 1, 2, 1],
        [0, 0, 0, 0, 1],
        [0, 1, 1, 1, 1],
        [1, 1, 0, 0, 0],
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 1, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 1, 2, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1]
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1, 0, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 2, 1, 0, 0, 0, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1]
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1],
        [1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1],
        [1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1],
        [1, 2, 1, 0, 0, 0, 1, 0, 1, 0, 1],
        [1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1],
        [1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 1],
        [1, 0, 1, 0, 0, 2, 1, 0, 1, 0, 1],
        [1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1],
        [1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1],
        [1, 0, 1, 0, 0, 0, 1, 2, 1, 1, 1],
        [1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1],
        [1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1],
        [1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ],
    [
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
    ]
]
level.fromArray(levels[currentLevel]);

scene.objects.push(player);
scene.objects.push(level);
context.clearColor = '#7ddbe0';



function update()
{
    var now = Date.now();
    deltaTime = (now-lastTime)/10;
    lastTime = now;

    if (engine.keys['a'] || engine.keys['ф'] ||
        engine.keys['A'] || engine.keys['Ф'])
    {
        player.vx = -30;
    }
    if (engine.keys['d'] || engine.keys['в'] ||
        engine.keys['D'] || engine.keys['В'])
    {
        player.vx = 30;
    }
    if (engine.keys['w'] || engine.keys['ц'] ||
        engine.keys['W'] || engine.keys['Ц'])
    {
        player.vy = -30;
    }
    if (engine.keys['s'] || engine.keys['ы'] ||
        engine.keys['S'] || engine.keys['Ы'])
    {
        player.vy = 30;
    }


    player.update(deltaTime, level);
    if (completed)
    {
        currentLevel++;
        level.fromArray(levels[currentLevel]);
        completed = false;
    }
    draw();
}

function draw()
{
    context.clear();
    scene.draw(context);
}