LoginSignup
3
4

More than 3 years have passed since last update.

Blocklyに基づきビジュアルプログラミングの入門級の実例(二、ブロック毎のステップ実行及びブロックのハイライト表示の方法)

Last updated at Posted at 2019-12-26

Interpreterとは

Blocklyのコードを実行する時、簡単なソースコードなら eval() を使えばいいんですけれど、ユーザーブロックを安全に走らせる必要がある場合、独自のJavaScriptインタープリタを組み込みます。
JavaScriptインタープリタは、Google Blockly本家が推奨しています。Blockごとのステップ実行・中断・再開が可能で、実行中のBlockをハイライトすることができるようになっています。
以下の記事は、信号機のライトの点滅を制御することを例として、Blocklyでプログラミングする時、ブロック毎のステップの実行やブロックをハイライトに表示する方法などを説明して見ます。

カスタムブロックの作成

Blocklyの開発ツールを利用して、以下のブロックを作成してみましょう。ブロックの名称は「stoplightswitch」となっております。
スクリーンショット 2019-12-26 21.43.04.png
ブロックの作成と同時、生成されたJavaScript定義コードを「stoplightblocks.js」に保存しましょう。
中身は以下となります。
スクリーンショット 2019-12-26 21.45.12.png

Webプロジェクトを作成

カスタムブロックを実装するために、下図ような簡単なWebプロジェクトを作成しましょう。
スクリーンショット 2019-12-26 21.47.48.png
プロジェクトの中に、以下のファイルがあります

  • stoplight.html → Blocklyのステップ実行とハイライト表示を説明するためのファイル
  • stoplightblocks.js → カスタムブロックの定義コード、関数を記載するファイル
  • stoplight.pnt → 信号機のイメージファイル
【stoplight.html】

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8"/>
    <title> blockly game</title>
    <script src="./js-interpreter/acorn_interpreter.js"></script>
    <script src="./blockly/blockly_compressed.js"></script>
    <script src="./blockly/blocks_compressed.js"></script>
    <script src="./blockly/javascript_compressed.js"></script>
    <script src="./blockly/msg/js/en.js"></script>
    <script src="./stoplightblocks.js"></script>
    <style type="text/css">
        body {
            background-color: #fff;
            font-family: sans-serif;
        }
        h1 {
            font-weight: normal;
            font-size: 140%;
        }
        #light {
            position: absolute;
            left: 650px;
            top: 100px;
        }
        .circle {
            position: absolute;
            height: 59px;
            width: 59px;
            border-radius:50%;
            background-color: white;
            border: solid 1px black;
        }
    </style>
</head>
<body>
    <!-- button on run code -->
    <p>
        <button id="stepButton" onclick="stepCode()">Step Code</button>
    </p>
    <!-- blockly workspace -->
    <div id="blocklyDiv" style="height:480px;width:600px;"></div>
    <xml id="toolbox" style="display:none;">
        <category name="logic">
            <block type="stoplightswitch"></block>
            <block type="math_number"></block>
        </category>
    </xml>
    <!-- added to the page -->
     <img id="light" src="stoplight.png" />
    <div id="light0" class="circle" style="left:770px;top:132px;"></div>
    <div id="light1" class="circle" style="left:770px;top:209px;"></div>
    <div id="light2" class="circle" style="left:770px;top:287px;"></div>
<script>
    //set options
    var options = {
        toolbox: toolbox,
        collapse: true,
        comments: true,
        disable: true,
        maxBlocks: Infinity,
        trashcan: true,
        horizontalLayout: false,
        toolboxPosition: 'start',
        css: true,
        rtl: false,
        scrollbars: true,
        sounds: true,
        oneBasedIndex: true,
        grid: {
            spacing: 20,
            length: 1,
            colour: '#888',
            snap: true
        }
    }
    //put the toolbox in the workspace
    var workspace = Blockly.inject('blocklyDiv', options);
    var stepButton = document.getElementById('stepButton');
    var myInterpreter = null;
    var highlightPause = false;
    var code = '';
    function highlightBlock(id) {
        workspace.highlightBlock(id);
        highlightPause = true;
    }
    function generateCodeAndLoadIntoInterpreter() {
        // Generate JavaScript code and parse it.
        Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
        Blockly.JavaScript.addReservedWords('highlightBlock');
        code = Blockly.JavaScript.workspaceToCode(workspace);
    }
    // Load the interpreter now, and upon future changes.
    generateCodeAndLoadIntoInterpreter();
    workspace.addChangeListener(function(event) {
        if (!(event instanceof Blockly.Events.Ui)) {
            // Something changed. Parser needs to be reloaded.
            generateCodeAndLoadIntoInterpreter();
        }
    });
    function initApi(interpreter, scope) {
        // Add an API function for highlighting blocks.
        wrapper = function(id) {
            id = id ? id.toString() : '';
            return interpreter.createPrimitive(highlightBlock(id));
        };
        interpreter.setProperty(
            scope,
            'highlightBlock',
            interpreter.createNativeFunction(wrapper)
        );
        // 信号機のライト点滅制御ためのAPI関数を追加
        wrapper = function(lightNo, lightColor) {
            lightNo = lightNo ? lightNo.toString() : '';
            lightColor = lightColor ? lightColor.toString() : '';
            return interpreter.createPrimitive(setLightColor(lightNo, lightColor));
        };
        interpreter.setProperty(
            scope,
            'setLightColor',
            interpreter.createNativeFunction(wrapper)
        )
    }
    // 信号機のライト点滅の制御関数
    function setLightColor(lightNo, lightColor) {
        document.getElementById("light" + lightNo).style.backgroundColor= lightColor ;
    }
    function stepCode() {
        if (!myInterpreter) {
            // First statement of this code.
            myInterpreter = new Interpreter(code, initApi);
            // do the first statement
            setTimeout(function() {
              highlightPause = true;
              stepCode();
            }, 1);
            return;
        }
        highlightPause = false;
        try {
            var ok = myInterpreter.step();
            var node = myInterpreter.stateStack[myInterpreter.stateStack.length - 1].node;
            console.log(node);
        } finally {
            if (!ok) {
                document.getElementById('stepButton').disabled = 'disabled';
            }
        }
    }
</script>
</body>
</html>
stoplightblocks.js

//ブロック定義コード
//stoplight switch block - turn color on or off
Blockly.Blocks['stoplightswitch'] = {
  init: function() {
    this.appendValueInput("lightno")
        .setCheck("Number")
        .appendField("turn")
        .appendField(new Blockly.FieldDropdown([["red","R"], ["green","G"], ["yellow","Y"]]), "colorlist")
        .appendField(new Blockly.FieldDropdown([["on","T"], ["off","F"]]), "switch");
    this.setInputsInline(true);
    this.setPreviousStatement(true, "String");
    this.setNextStatement(true, "String");
    this.setColour(230);
    this.setTooltip("信号機");
    this.setHelpUrl("http://");
  }
};
//ブロックのGenerator
Blockly.JavaScript['stoplightswitch'] = function(block) {
    var dropdown_colorlist = block.getFieldValue('colorlist');
    var dropdown_switch = block.getFieldValue('switch');
    var value_lightno = Blockly.JavaScript.valueToCode(block, 'lightno', Blockly.JavaScript.ORDER_ATOMIC);
    // TODO: Assemble JavaScript into code variable.
    if (dropdown_colorlist === "R") {
        var color = "red";
    } else if (dropdown_colorlist === "G") {
        var color = "green";
    } else if (dropdown_colorlist === "Y") {
        var color = "yellow";
    }
    if (dropdown_switch === "T") {
        var code = "setLightColor('"+  value_lightno +"','"+ color +"');";
    } else if (dropdown_switch === "F") {
        var code = "setLightColor('"+  value_lightno +"','"+ color +"');";
    }
    return code;
};

実行結果

Step Codeボタンを連続に押下された場合、以下の順に信号機を点灯する同時に、対応するブロックにもハイライトに表示することが行われています。
【図一】赤ライトを点灯、1番目のブロックをハイライト表示
スクリーンショット 2019-12-26 22.07.06.png
【図二】青ライトを点灯、2番目のブロックをハイライト表示
スクリーンショット 2019-12-26 22.08.45.png
【図三】黄ライトを点灯、3番目のブロックをハイライト表示
スクリーンショット 2019-12-26 22.11.40.png

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4