Kid


title: Kid date: '2016-07-11' tags: ['code'] draft: false

己亥年的工作,接手之前未曾瞭解相關,特地調研數日,決定接下工作。其一,面向程式設計教育的程式設計工具,造福人類;其二,自我挑戰。世事如棋,不盡人意。在年關前結束了一系列工作,很可惜,很無奈。想來不會再做相關開發,在此對這段時期的工作做個經驗總結。唉,沒記性的人全靠筆記。


Google 出品 Blockly

# 根據專案根目錄的buld.py檔案提示,裝載對應版本的python
python2.7
# 在根據報錯提示,裝載對應版本的jdk
jdk1.8.0_251
# 以上裝載完成,若還有報錯,善用搜索,逐步排查。

編譯起來,檢視專案 demos 資料夾中的示例,基本無憂使用。

/**
 * 建立一個名稱空間 函式可確保其引數指示的 JavaScript 物件結構的存在。它檢查路徑表示式中的每個物件屬性是否存在,如果不存在,則將其初始化
 * utils/test.js */
goog.provide("Blockly.utils.test");
// 該模組的具體內容

/**
 * 從給定的名稱空間中“匯入”程式碼,以便閉包編譯器可以找到它。
 *  所需要的檔案.js */
goog.require("Blockly.utils.test");
// 具體使用。
Blockly.utils.test.testMethod();
Blockly.utils.test.isShow;

KID-CODE

Google 出品 BlocklyGames

該專案編譯成功需在 Linux 系統下,且連通國際網路。由於編譯後大部分程式碼依然不在本地,只好將該編譯後的該專案整個扔上伺服器,未作任何改動。

Microsoft 出品 pxt-blockly

微軟大廠,基於 Google 二開,修復大量 Edge 下的問題,並附帶一系列 pxt 擴充套件

把 Github 中幾個類似的庫進行比較,發現其實微軟的更適合二開。然而,該提議被駁回。


KID 系列

在著手二開 LLK 那套少兒程式設計工具時,看到有關新聞,為規避,更名為KID。這套開原始碼,網路上無數復刻版。還有專門為這套原始碼二開做付費諮詢的,可謂商機無限。但是隨著新聞爆出,一道網路遮蔽,不少復刻版網站,一片 404。而手上的專案,因為已經開發部分功能,直連自家伺服器,成功避開此次危機。

Blocks

基於 Google 出品的 Blockly 二次開發的積木工具。刪掉了原本的部分功能,比如積木工具的自動收縮、多種程式語言程式碼庫的生成等;改寫了部分功能,比如 svg 的資料構成形式等;也添加了各別功能,比如 checkbox 的積木。

按照需求,在該版本之上,二開出來專案所需的少兒版與幼兒版。其中幼兒版改動及追加功能較多,涉及到對 SVG 資料的變更、音樂及繪畫擴充套件提取到初始工具庫、積木全部圖示化、去文字,隱藏部分高階積木、重寫部分初級積木,如移動與方向、速度結合;跳躍與速度、方向結合;外觀的一些簡單指令積木等。二開的邏輯基本如下:

  • kid-blocks 中找到積木編輯的對應檔案,按照指定資料結構追加積木,注意命名;
  • kid-vm 中找到積木實現的對應檔案,按照積木命名建立函式,實現積木執行邏輯;
  • kid-gui 中找到 xml.js,新增新增的積木,注意 XML 結構及積木命名。

示例

/** msg\messages.js */
Blockly.Msg.PEN_CLEAR = "%1";

/** msg\scratch_msgs.js */
PEN_CLEAR: "%1",

/** blocks_vertical\extensions.js
 * 目前將新增的繪畫分類下的自定義積木塊,寫在該檔案內
 * 後期需想辦法,另起一個pen.js檔案,寫在內
 */
/** 繪畫分類 之 清理 */
Blockly.Blocks["pen_clear"] = {
  /**
   * 清理
   * @this Blockly.Block
   */
  init: function () {
    this.jsonInit({
      // 明文內容:對應msg/messages.js檔案中對應常量
      message0: Blockly.Msg.PEN_CLEAR,
      // 該積木塊所需的引數對應設定
      args0: [
        // 第一個引數
        {
          type: "field_image", // 欄位_圖片 型別
          // 圖片的src,通常採用 media\xxxxx.svg下的svg檔案
          src: Blockly.mainWorkspace.options.pathToMedia + "pen.svg",
          width: 24, // 圖示的寬度
          height: 24, // 圖示的高度
        },
      ],
      // 分類:分類.運動分類
      category: Blockly.Categories.pen,
      // :[設定運動類相簿的顏色,設定本積木塊的形狀]
      extensions: ["colours_pen", "shape_statement"],
    });
  },
};

/** blocks_vertical\default_toolbox.js */
  '<category name="%{BKY_CATEGORY_PEN}" id="pen" colour="#0FBD8C" secondaryColour="#0DA57A" iconURI="../media/pen.svg">' +
  // 新增加的積木塊
  '<block type="pen_clear"></block>' +
  "</category>" +
/** core\colours.js */
// 追加 音樂分類圖示三色設定
  music: {
    primary: "#0FBD8C",
    secondary: "#0DA57A",
    tertiary: "#DB6E00",
  },

/** msg\messages.js
 *  再次檔案中,定義新增的分類
 */
Blockly.Msg.CATEGORY_MUSIC = "Music"; // 音樂
Blockly.Msg.CATEGORY_PEN = "Pen"; // 繪畫

/** msg\scratch_msgs.js
 * 在此檔案中,追加以定義的分類,明文國際化
 */
  CATEGORY_MUSIC: "音樂",
  CATEGORY_PEN: "繪畫",

/** blocks_vertical\vertical_extensions.js
 *  該檔案主要註冊所有積木塊
 */
 Blockly.ScratchBlocks.VerticalExtensions.registerAll=()=>{
   // 在分類名字陣列中,新增以下[音樂、繪畫]分類名
    var categoryNames = [ "music","pen" ];
 }
import ScratchBlocks from "scratch-blocks";
ScratchBlocks.Blocks["control_if_else"] = {
  init: function () {
    this.jsonInit({
      type: "control_if_else",
      // 積木如果要支援多語言,就需要用 ScratchBlocks.Msg 物件來設定 message
      message0: ScratchBlocks.Msg.CONTROL_IF, // zh: 如果 1% 那麼
      message1: "%1",
      message2: ScratchBlocks.Msg.CONTROL_ELSE, // zh: 否則
      message3: "%1",
      args0: [
        {
          type: "input_value",
          name: "CONDITION",
          check: "Boolean",
        },
      ],
      args1: [
        {
          type: "input_statement",
          name: "SUBSTACK",
        },
      ],
      args3: [
        {
          type: "input_statement",
          name: "SUBSTACK2",
        },
      ],
      // 分類也用 ScratchBlocks.Categories 物件來設定,避免不一致
      category: ScratchBlocks.Categories.control,
      extensions: ["colours_control", "shape_statement"],
    });
  },
};
/** core\toolbox.js
 * 給一個分類建立DOM(原來的藍色圓形)
 * Create the DOM for a category in the toolbox.
 */
Blockly.Toolbox.Category.prototype.createDom = function () {
  /**
   * 該函式中的this指向每個分類圓形圖示,即(bubble)
   * 透過是否有 iconURI,為該分類圖示設定不同class樣式
   * 自定義時,在內部邏輯中實現
   * 此內部函式也可以繼續建立元素,做出更復雜的分類圖示比如以下程式碼
  */
if (this.iconURI_) {
    // bubble 即
    this.bubble_ = goog.dom.createDom("div", {
      class: "scratchCategoryItemIcon",
    });
    // this.bubble_.style.backgroundImage = "url(" + this.iconURI_ + ")";
    // 追加程式碼:即使設定了.svg檔案,也依然保留背景色
    this.bubble_.style.backgroundColor = this.colour_;

    // 此處添加了一個div ,並將icon.svg作為該div的背景圖渲染出來
    this.icon_svg = goog.dom.createDom("div", {
      class: "scratchCategoryItemIcon",
    });
    this.icon_svg.style.backgroundImage = "url(" + this.iconURI_ + ")";
    this.bubble_.appendChild(this.icon_svg);
  }
}

/** blocks_vertical\default_toolbox.js
 * 該xml結構中,追加 iconURI屬性,並設定其icon.svg指向
*/
  '<xml id="toolbox-categories" style="display: none">' +
  // 分類 分類名 id  該分類(圓圈)的顏色 第二色
  // '<category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="#4C97FF" secondaryColour="#3373CC">' +
  '<category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="#4C97FF" secondaryColour="#3373CC" ' +
  // 此處指定iconURI,即可達到將原有圓形圖示替換為自定義.svg圖示
  'iconURI="../media/move-left.svg" showStatusButton="true">' +

/** core\css.js
 * 在該檔案中,更改每個分類div的樣式設定,以及其子元素樣式設定,如下:
 */

// 注:以上變更並編譯完成後,需在gui庫下lib\make-toolbox-xml.js
//  注:iconURI的地址為 ./static/blocks-media/icon.svg
    return `
    <category name="%{BKY_CATEGORY_MOTION}" id="motion" colour="#4C97FF" secondaryColour="#3373CC" iconURI="./static/blocks-media/move-left.svg" >………………`;
/** msg/messages.js */
// Motion blocks
Blockly.Msg.MOTION_MOVESTEPS = "%1 %2";
Blockly.Msg.MOTION_TURNLEFT = "%1 %2";
Blockly.Msg.MOTION_TURNRIGHT = "%1 %2";
/** msg/scratch_msgs.js */
Blockly.ScratchMsgs.locales["en"] = {
  MOTION_MOVESTEPS: "%1 %2",
  MOTION_TURNLEFT: "%1 %2",
  MOTION_TURNRIGHT: "%1 %2",
};
/** blocks_vertical\motion.js */
// 以移動為例
Blockly.Blocks["motion_movesteps"] = {
  /**
   * Block to move steps.
   * @this Blockly.Block
   */
  init: function () {
    this.jsonInit({
      // 明文內容:對應msg/messages.js檔案中對應常量
      message0: Blockly.Msg.MOTION_MOVESTEPS,
      // 該積木塊所需的引數對應設定
      args0: [
        // 第一個引數
        {
          type: "field_image", // 欄位_圖片 型別
          // 圖片的src,通常採用 media\xxxxx.svg下的svg檔案
          src: Blockly.mainWorkspace.options.pathToMedia + "move-left.svg", // 目前該svg不存在
          width: 24, // 圖示的寬度
          height: 24, // 圖示的高度
        },
        // 第二個引數
        {
          type: "input_value", // 輸入值型別
          name: "STEPS", // 步,可以看作單位?
        },
      ],
      // 分類:分類.運動分類
      category: Blockly.Categories.motion,
      // :[設定運動類相簿的顏色,設定本積木塊的形狀]
      extensions: ["colours_motion", "shape_statement"],
    });
    console.log(
      "blocks_vertical建立運動類下第一個積木塊:",
      Blockly.mainWorkspace
    );
  },
};

// 其中的pathToMedia在core\options.js中定義

var pathToMedia = "https://blockly-demo.appspot.com/static/media/";
if (options["media"]) {
  pathToMedia = options["media"];
} else if (options["path"]) {
  // 'path'為已棄用選項,由'media'代替
  pathToMedia = options["path"] + "media/";
}
/** blocks_vertical\default_toolbox.js */
Blockly.Blocks.defaultToolbox = "在這裡註釋掉不需要的預設積木塊xml結構";
/**
 * 運動 之 重置 即舞臺角色迴歸至 0,0
 */
Blockly.Blocks["motion_reset"] = {
  /**
   * Block to move steps. 向右移動
   * @this Blockly.Block
   */
  init: function () {
    this.jsonInit({
      // 明文內容:對應msg/messages.js檔案中對應常量
      message0: Blockly.Msg.MOTION_RESET,
      // 該積木塊所需的引數對應設定
      args0: [
        // 第一個引數
        {
          type: "field_image", // 欄位_圖片 型別
          // 圖片的src,通常採用 media\xxxxx.svg下的svg檔案
          src: Blockly.mainWorkspace.options.pathToMedia + "reset.svg",
          width: 24, // 圖示的寬度
          height: 24, // 圖示的高度
        },
      ],
      // 分類:分類.運動分類
      category: Blockly.Categories.motion,
      // :[設定運動類相簿的顏色,設定本積木塊的形狀]
      extensions: ["colours_motion", "shape_statement"],
    });
    console.log(
      "blocks_vertical建立運動類下第一個積木塊:",
      Blockly.mainWorkspace
    );
  },
};
'<block type="motion_reset" id="motion_reset">' +
  "</block>" +

core\block_render_svg_vertical.js // 處理渲染 svg 成型的檔案

$ npm run translate # 更改msg\messages.js檔案後,執行該命令

常見錯誤及解決方案

C:\Python27
C:\Python27\Scripts
# 錯誤程式碼如下:
Traceback (most recent call last):
  File "build.py", line 574, in <module>
    test_proc = subprocess.Popen(test_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  File "C:\Python27\lib\subprocess.py", line 394, in __init__
    errread, errwrite)
  File "C:\Python27\lib\subprocess.py", line 644, in _execute_child
    startupinfo)
WindowsError: [Error 2]

# 解決方式 https://github.com/LLK/scratch-blocks/issues/1620
build.py檔案約 331行 程式碼替換
      # proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
      outfile = open("dash_args.txt","w+")
      outfile.write("\n".join(args[11:]))
      outfile.close()
      args =  args[:11]
      args.extend(['--flagfile','dash_args.txt'])
      proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, shell = True)

build.py檔案約 579行 程式碼替換
    # test_proc = subprocess.Popen(test_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    test_proc = subprocess.Popen(test_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,shell=True)
Could not find "java" in your PATH.
Using remote compiler: closure-compiler.appspot.com ...

Error: Closure not found.  Read this:
  developers.google.com/blockly/guides/modify/web/closure

解決:裝jdk8
環境變數 系統使用者新建
變數名:JAVA_HOME
變數值:C:\Program Files\Java\jdk1.8.0_251
PATH追加:%JAVA_HOME%\bin,移動到頂部

Chrome 等現代瀏覽器,呼叫攝像頭及錄音功能需要在 HTTPS 模式下,將該專案設定為 HTTPS 請求方式後,需將內中使用外鏈資源及 API 皆改為 HTTPS


這段工作經歷,不但涉獵了程式設計+教育,而且對教育界有一些瞭解。從而悟出,工具放在那裡,使用者如何使用,非是他人能夠左右。許多人性化設計,不入使用者法眼,倒是那些懶人模式的操作,更得使用者喜歡。程式設計+教育,面對的群體特殊老師家長學生。三個群體之中,老師則透過程式設計工具及學生獲得自身利益,家長訴求簡單,透過孩子獲知相關資訊,看到孩子與眾不同;學生則是一個被動的角色,基本指哪兒打哪兒,參加比賽,拿到好成績。僅僅是程式設計工具,根本無法吸引學生,即便有了BlocklyGames,或者亥廠的codecombat(漢化版)。由於三方之中各有利益,就會達成某些無需說明的一致。老話說得好上有政策下有對策,開發方向從打造優秀的程式設計工具及平臺,服務學生,潛移默化地過渡到為了虛假繁榮。當察覺程式設計+教育的真實操作情況,會懷疑自己在助紂為虐。


帶新需謹慎,徒勞一場空。 人生中又多幾名過客,既是過客,不提也罷。