Как можно запустить компьютерные программы Академии Хана в автономном режиме или на моем собственном веб-сайте?

Я разработал программы на уроках компьютерного программирования Академии Хана, которые я хотел бы запустить за пределами Академии Хана. Как это сделать?


person Robert Tupelo-Schneck    schedule 16.08.2014    source источник


Ответы (3)


Академия Хана использует Processing.js, библиотеку JavaScript для взаимодействия с <canvas> элементами. Хотя Processing на самом деле является самостоятельным языком, Академия Хана использует код Processing.js только для JavaScript.

Итак, вам нужно настроить веб-страницу, которая импортирует Processing.js, настроить <canvas> и создать экземпляр Processing.js на холсте. Наконец, вам нужно убедиться, что в вашем коде Академии Хана есть все члены экземпляра Processing.js (я делаю это с with), а также некоторый эквивалент Небольшие модификации Khan Academy для Processing.js, например mouseIsPressed и getImage.

Вот какой-то шаблон, который работал у меня. Вероятно, потребуется дальнейшая разработка, чтобы заставить его работать для более сложных примеров; пожалуйста, оставляйте комментарии, когда вы найдете примеры, которые не работают.

<!DOCTYPE html>
<html>
<head>
  <title>JavaScript</title>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/processing.js/1.4.8/processing.min.js"></script>
</head>
<body>
  <canvas id="canvas"></canvas>
  <script>
    var canvas = document.getElementById("canvas");
    var processing = new Processing(canvas, function(processing) {
        processing.size(400, 400);
        processing.background(0xFFF);

        var mouseIsPressed = false;
        processing.mousePressed = function () { mouseIsPressed = true; };
        processing.mouseReleased = function () { mouseIsPressed = false; };

        var keyIsPressed = false;
        processing.keyPressed = function () { keyIsPressed = true; };
        processing.keyReleased = function () { keyIsPressed = false; };

        function getImage(s) {
            var url = "https://www.kasandbox.org/programming-images/" + s + ".png";
            processing.externals.sketch.imageCache.add(url);
            return processing.loadImage(url);
        }

        // use degrees rather than radians in rotate function
        var rotateFn = processing.rotate;
        processing.rotate = function (angle) {
            rotateFn(processing.radians(angle));
        };

        with (processing) {


            // INSERT YOUR KHAN ACADEMY PROGRAM HERE


        }
        if (typeof draw !== 'undefined') processing.draw = draw;
    });
  </script>
</body>
</html>
person Robert Tupelo-Schneck    schedule 16.08.2014
comment
Не могли бы вы опубликовать это на gist.github.com и написать преамбулу лицензии, чтобы я мог использовать ее в своем собственном коде? - person Ape-inago; 17.02.2015
comment
Я считаю, что в силу того, что он опубликован на Stack Overflow, он находится под лицензией CC BY-SA. Это работает для вас? - person Robert Tupelo-Schneck; 17.02.2015
comment
Я знал об этом, но мне не нравится предполагать, что другие знают об этом пункте. Спасибо за ваше разрешение :) - person Ape-inago; 18.02.2015
comment
Это прекрасно работает для большинства программ, которые я пробовал. Некоторые из них анимировались слишком быстро, но, надеюсь, я смогу понять, как это исправить. - person thinsoldier; 20.07.2015
comment
@thinsoldier: Я думаю, слишком быстрая анимация связана с тем, что processing.js имеет дело с радианами, а Академия Хана - с степенями. Если вы переключитесь в Khan Academy в режим радианов (angleMode = radians;), то все будет работать нормально. - person Michael; 19.10.2015

АДДОН к ответу Роберта:

Processing.js использует радианы по умолчанию для угловых значений, Khan Academy JS использует градусы. Если вы добавите следующие строки в приведенный выше код Роберта (перед оператором with), вы сможете использовать команды поворота, поскольку они исходят от KA.

var rotateFn = processing.rotate;
processing.rotate = function(angle) {
    rotateFn(processing.radians(angle));
}
person Michael    schedule 19.10.2015

Да, извините, я немного опоздал с этим вопросом. Но я сам разобрался...

Вы можете клонировать этот репозиторий или следовать приведенным ниже инструкциям:

Или вы можете сделать следующее: В вашем index.html поместите исходный код всех трех файлов:

Обработка.js:

https://raw.githubusercontent.com/Khan/processing-js/66bec3a3ae88262fcb8e420f7fa581b46f91a052/processing.js

loadKa.js:

Это производственный код для загрузки нескольких отдельных файлов вместе:

!(function(window, JSON, localStorage)
{
    function createProcessing()
    {
        var args = Array.prototype.slice.call(arguments);
        args.push({ beginCode: "with(processing)\n{", endCode: "}"});
        var any = combine.apply(this, args);

        this.cache = window.cache = {};
        this.cache.loadedImages = window.cache.loadedImages = {};
        this.cache.imageNames = window.cache.imageNames = [
            "avatars/aqualine-sapling", 
            "avatars/aqualine-seed", 
            "avatars/aqualine-seedling", 
            "avatars/aqualine-tree", 
            "avatars/aqualine-ultimate", 
            "avatars/avatar-team", 
            "avatars/duskpin-sapling", 
            "avatars/duskpin-seed", 
            "avatars/duskpin-tree", 
            "avatars/duskpin-ultimate", 
            "avatars/leaf-blue", 
            "avatars/leaf-green", 
            "avatars/leaf-grey", 
            "avatars/leaf-orange", 
            "avatars/leaf-red", 
            "avatars/leaf-yellow", 
            "avatars/leafers-sapling", 
            "avatars/leafers-seed", 
            "avatars/leafers-seedling", 
            "avatars/leafers-tree", 
            "avatars/leafers-ultimate", 
            "avatars/marcimus", 
            "avatars/marcimus-orange", 
            "avatars/marcimus-purple", 
            "avatars/marcimus-red", 
            "avatars/mr-pants", 
            "avatars/mr-pants-green", 
            "avatars/mr-pants-orange", 
            "avatars/mr-pants-pink", 
            "avatars/mr-pants-purple", 
            "avatars/mr-pants-with-hat", 
            "avatars/mr-pink", 
            "avatars/mr-pink-green", 
            "avatars/mr-pink-orange", 
            "avatars/old-spice-man", 
            "avatars/old-spice-man-blue", 
            "avatars/orange-juice-squid", 
            "avatars/piceratops-sapling", 
            "avatars/piceratops-seed", 
            "avatars/piceratops-seedling", 
            "avatars/piceratops-tree", 
            "avatars/piceratops-ultimate", 
            "avatars/primosaur-sapling", 
            "avatars/primosaur-seed", 
            "avatars/primosaur-seedling", 
            "avatars/primosaur-tree", 
            "avatars/primosaur-ultimate", 
            "avatars/purple-pi", 
            "avatars/purple-pi-pink", 
            "avatars/purple-pi-teal", 
            "avatars/questionmark", 
            "avatars/robot_female_1", 
            "avatars/robot_female_2", 
            "avatars/robot_female_3", 
            "avatars/robot_male_1", 
            "avatars/robot_male_2", 
            "avatars/robot_male_3", 
            "avatars/spunky-sam", 
            "avatars/spunky-sam-green", 
            "avatars/spunky-sam-orange", 
            "avatars/spunky-sam-red", 
            "avatars/starky-sapling", 
            "avatars/starky-seed", 
            "avatars/starky-seedling", 
            "avatars/starky-tree", 
            "avatars/starky-ultimate", 
            "creatures/Hopper-Happy", 
            "creatures/Hopper-Cool", 
            "creatures/Hopper-Jumping", 
            "creatures/OhNoes", 
            "creatures/OhNoes-Happy", 
            "creatures/OhNoes-Hmm", 
            "cute/Blank", 
            "cute/BrownBlock", 
            "cute/CharacterBoy", 
            "cute/CharacterCatGirl", 
            "cute/CharacterHornGirl", 
            "cute/CharacterPinkGirl", 
            "cute/CharacterPrincessGirl", 
            "cute/ChestClosed", 
            "cute/ChestLid", 
            "cute/ChestOpen", 
            "cute/DirtBlock", 
            "cute/DoorTallClosed", 
            "cute/DoorTallOpen", 
            "cute/EnemyBug", 
            "cute/GemBlue", 
            "cute/GemGreen", 
            "cute/GemOrange", 
            "cute/GrassBlock", 
            "cute/Heart", 
            "cute/Key", 
            "cute/PlainBlock", 
            "cute/RampEast", 
            "cute/RampWest", 
            "cute/Rock", 
            "cute/RoofEast", 
            "cute/RoofNorth", 
            "cute/RoofNorthEast", 
            "cute/RoofNorthWest", 
            "cute/RoofSouth", 
            "cute/RoofSouthEast", 
            "cute/RoofSouthWest", 
            "cute/RoofWest", 
            "cute/Selector", 
            "cute/ShadowEast", 
            "cute/ShadowNorth", 
            "cute/ShadowNorthEast", 
            "cute/ShadowNorthWest", 
            "cute/ShadowSideWest", 
            "cute/ShadowSouth", 
            "cute/ShadowSouthEast", 
            "cute/ShadowSouthWest", 
            "cute/ShadowWest", 
            "cute/WoodBlock",
            "cute/Star", 
            "cute/StoneBlock", 
            "cute/StoneBlockTall", 
            "cute/TreeShort", 
            "cute/TreeTall", 
            "space/girl2", 
            "space/girl3", 
            "space/girl4", 
            "space/girl5", 
            "space/healthheart", 
            "space/minus", 
            "space/octopus", 
            "space/planet", 
            "space/plus", 
            "space/rocketship", 
            "space/star", 
            "space/3", 
            "space/4", 
            "space/5", 
            "space/6", 
            "space/7", 
            "space/8", 
            "space/9"
        ];

        window.links = {
            proxyUrl : "https://cors-anywhere.herokuapp.com/",
            image : ["https://www.kasandbox.org/third_party/javascript-khansrc/live-editor/build/images/", 
                     "https://github.com/Khan/live-editor/tree/master/images",
                     "https://www.kasandbox.org/programming-images/"],
        };

        var self = this;

        this.setup = function()
        {
            function code(processing)
            {
                processing.size(400, 400);
                processing.background(255, 255, 255);
                processing.angleMode = "degrees";

                processing.mousePressed = function() {};
                processing.mouseReleased = function() {};
                processing.mouseMoved = function() {};
                processing.mouseDragged = function() {};
                processing.mouseOver = function() {};
                processing.mouseOut = function() {};
                processing.keyPressed = function() {};
                processing.keyReleased = function() {};
                processing.keyTyped = function() {};

                processing.getSound = function(name)
                { 
                    return "Sound"; 
                };
                processing.playSound = function(sound) 
                { 
                    console.log(sound + " is not supported yet..."); 
                };

                processing.getImage = function(name)
                {
                    return (window.cache || self.cache).loadedImages[name] || processing.get(0, 0, 1, 1);
                };

                var lastGet = processing.get;
                processing.get = function()
                {
                    try{
                        return lastGet.apply(this, arguments);
                    }
                    catch(e)
                    {
                        if(arguments[2] !== 0 && arguments[3] !== 0)
                        {
                            console.log(e);
                        }else{
                            throw e;
                        }
                    }
                };

                processing.debug = function(event) 
                {
                    try{
                        return window.console.log.apply(this, arguments);
                    } 
                    catch(e) 
                    {
                        processing.println.apply(this, arguments);
                    }
                };
                processing.Program = {
                    restart: function() 
                    {
                        window.location.reload();
                    },
                    assertEqual: function(equiv) 
                    {
                        if(!equiv) 
                        {
                            console.warn(equiv);
                        }
                    },
                };
            }

            code = combine(new Function("return " + code.toString().split("\n").join(" "))(), any);

            var matched = code.toString().match("this[ ]*\[[ ]*\[[ ]*(\"KAInfiniteLoopSetTimeout\")[ ]*\][ ]*\][ ]*\([ ]*\d*[ ]*\);*");

            if(matched)
            {
                code = new Function("return " + code.toString().replace(matched[0], ""))();
            }

            window.canvas = document.getElementById("canvas"); 
            window.processing = new Processing(canvas, code);
        };

        this.imageProcessing = new Processing(canvas, function(processing)
        {
            try{
                processing.imageCache = JSON.parse(localStorage.getItem("imageCache"));
            }
            catch(e)
            {
                console.log(e);
            }

            if(!processing.imageCache)
            {
                processing.imageCache = {};
            }

            processing.getImage = function(name, callback, url)
            {
                if(name === undefined) { return get(0, 0, 1, 1); }

                url = url || window.links.image[0] + name.split(".")[0] + ".png";
                callback = callback || function() {};

                if(!processing.imageCache)
                {
                    var img = processing.loadImage(url);
                    callback(img, name);
                    return img;
                }
                if(processing.imageCache[name])
                {
                    var img = processing.loadImage(processing.imageCache[name]);
                    callback(img, name);
                    return img;
                }

                toDataURL(window.links.proxyUrl + url, function(dataUrl)
                {
                    processing.imageCache[name] = dataUrl; 
                    localStorage.setItem("imageCache", JSON.stringify(processing.imageCache));
                    callback(processing.imageCache[name], name);
                });

                return processing.loadImage(processing.imageCache[url] || url);
            };

            window.cache.imageNames.forEach(function(element, index, array)
            {
                processing.getImage(element, function(img, name)
                {
                    window.cache.loadedImages[name] = img;

                    if(index === array.length - 1)
                    {
                        (window.setTimeout || function(func)
                        {
                            return func.apply(this, arguments);
                        })
                        (function()
                        {
                            self.setup();
                        }, 50);
                    }
                });
            });
        });
    }

    function combine(a, c)
    {
        var args = Array.prototype.slice.call(arguments);
        var config = {};

        var funcArgs = "";
        var join = "";
        for(var i = 0; i < args.length; i++)
        {
            if(typeof args[i] === "object")
            {
                config = args[i];
                continue;
            }

            var to = args[i].toString();

            var temp = to.substring(to.indexOf('(') + 1, to.indexOf(')'));

            if(temp !== "" && temp !== " ")
            {
                funcArgs += temp + ",";
            }

            join += to.slice(to.indexOf('{') + 1, -1);
        }

        funcArgs = funcArgs.slice(0, -1);

        return new Function("return function any(" + funcArgs + "){" + (config.beginCode || "").replace("\n", "") + join + (config.endCode || "") + "}")();
    }

    function toDataURL(url, callback) 
    {
        var xhr = new XMLHttpRequest();
        xhr.onload = function() 
        {
            var reader = new FileReader();
            reader.onloadend = function() 
            {
                callback(reader.result);
            }
            reader.readAsDataURL(xhr.response);
        };
        xhr.open('GET', url);
        xhr.responseType = 'blob';
        xhr.send();
    }

    return {
        createProcessing: window.createProcessing = this.createProcessing = createProcessing,
        toDataURL: window.toDataURL = this.toDataURL = toDataURL,
        combine: window.combine = this.combine = combine,
    };
}( 
    (window || {}), 
    (JSON || { stringify: function() { return "{}"; }, parse: function() { return {}; } }), 
    (localStorage || { getItem: function() { return {} }, setItem: function() {}, removeItem: function() {} })
));

Индекс.js:

В вашем файле index.js Использование:

function main()
{
      //Your code here (Trust me it really works!)
}

createProcessing(main);

Вы также можете добавить дополнительные аргументы в качестве функций в функцию createProcessing. Да, у меня есть все, кроме звуков и иногда (редко) раскрученного управления!

person Community    schedule 26.03.2019
comment
Я ищу это для моих детских проектов. Этот работает и решает проблему асинхронной загрузки изображений. - person Yiyu Jia; 23.12.2020