在前文 "中文編程語言之Z語言初嘗試: ZLOGO 4" 與相關討論後, 萌生了用JavaScript編寫類似語言以便線上編程的想法. 於是使用 @TKT2016 (知乎賬號)的ZLOGO語法設計, 在 "編程語言試驗之Antlr4+JavaScript實現"圈4"" 基礎上, 通過p5js的繪圖功 ...
在前文中文編程語言之Z語言初嘗試: ZLOGO 4與相關討論後, 萌生了用JavaScript編寫類似語言以便線上編程的想法. 於是使用 @TKT2016 (知乎賬號)的ZLOGO語法設計, 在編程語言試驗之Antlr4+JavaScript實現"圈4"基礎上, 通過p5js的繪圖功能, 實現了基本的兩個ZLOGO功能. 如圖(動態效果看起來更爽一點, 當然要耐心等它畫完, 請自行嘗試):
源碼庫: program-in-chinese/quan3, 導出代碼到本地後, 在瀏覽器中打開"圈3.html"即可在本地實踐編程.
線上演示: 地址.
由於還不支持迴圈, 實現這個五角星的代碼很重影:
開始
前進200
左轉144度
前進200
左轉144度
前進200
左轉144度
前進200
左轉144度
前進200
結束
下麵是編程語言試驗之Antlr4+JavaScript實現"圈4"之後添加的主要部分:
語法文件(圈3.g4):
聲明 : 前進 | 轉向;
前進 : '前進' T數 ;
轉向 : T轉向 '轉' T數 '度' ;
T轉向 : '左' | '右' ;
主要修改在"定製監聽器.js":
命名還比較粗糙, 一些用語最好更加一致(比如"長度","距離","前進量"雖在不同上下文, 實際指的是一個東西), 需要改進. 當然演算法肯定也可以改進, 暫時是實現功能優先.
var 常量_指令名_前進 = "前進";
var 常量_指令名_轉向 = "轉向";
var 序號 = 0;
var 畫布尺寸 = {x: 1000, y: 800};
var 原點 = {x: 畫布尺寸.x/2, y: 畫布尺寸.y/2};
var 前進角度 = 90; // 預設向上, 對應弧度: 90 * Math.PI / 180
// 指令格式: 名稱 (轉向, 前進, 筆色等等); 參數 (轉向角度--右為負,左為正; 前進長度-像素數等等);
var 指令序列 = [];
定製監聽器.prototype.enter程式 = function(ctx) {
重置狀態();
// 只需調用一次
// https://p5js.org/reference/#/p5/setup
構圖 = function() {
新畫布(畫布尺寸.x, 畫布尺寸.y);
}
};
function 重置狀態() {
序號 = 0;
原點 = {x: 畫布尺寸.x/2, y: 畫布尺寸.y/2};
前進角度 = 90;
指令序列 = [];
}
// 根據指令序列, 生成路徑分段描述(段起止點坐標, 顏色等等)
// 如: 前進50, 左轉90度, 前進50 應返回(假設起點為{x: 200, y: 200}):
// {起點: {x: 200, y: 200}, 終點: {x: 200, y: 150}, 長度: 50},
// {起點: {x: 200, y: 150}, 終點: {x: 150, y: 150}, 長度: 50}
function 生成路徑表(指令序列) {
// 段: {起點: {x, y}, 終點: {x, y}, 長度, 顏色}
var 路徑表 = [];
var 起點 = 原點;
for(var i = 0; i < 指令序列.length; i++ ){
var 指令 = 指令序列[i];
var 指令名 = 指令.名稱;
var 段 = {起點: 起點};
if (指令名 === 常量_指令名_前進) {
var 距離 = 指令.參數;
var x增量 = Math.cos(前進角度 * Math.PI / 180);
var y增量 = Math.sin(前進角度 * Math.PI / 180);
段.終點 = {x: 起點.x + x增量 * 距離, y: 起點.y - y增量 * 距離};
段.長度 = 距離;
路徑表.push(段);
起點 = 段.終點;
} else if (指令名 === 常量_指令名_轉向) {
前進角度 += 指令.參數;
}
}
return 路徑表;
}
定製監聽器.prototype.exit程式 = function(ctx) {
var 路徑表 = 生成路徑表(指令序列);
繪製 = function() {
var 當前序號 = 序號;
background(255, 255, 255);
for (var i = 0; i < 路徑表.length; i++ ) {
var 段 = 路徑表[i];
var 起點 = 段.起點;
var 終點 = 段.終點;
var 距離 = 段.長度;
if (當前序號 < 距離) {
line(起點.x, 起點.y, 起點.x + (終點.x - 起點.x) * 當前序號 / 距離, 起點.y + (終點.y - 起點.y) * 當前序號 / 距離);
break;
} else {
line(起點.x, 起點.y, 終點.x, 終點.y);
當前序號 = 當前序號 - 段.長度;
}
}
序號 ++;
}
};
定製監聽器.prototype.exit前進 = function(上下文) {
var 前進量 = 上下文.getChild(1).getText()
指令序列.push({名稱: 常量_指令名_前進, 參數: parseInt(前進量)});
};
定製監聽器.prototype.exit轉向 = function(上下文) {
var 方向 = 上下文.getChild(0).getText();
var 角度 = parseInt(上下文.getChild(2).getText());
角度 = 角度 * (方向 === "左" ? 1 : -1);
指令序列.push({名稱: 常量_指令名_轉向, 參數: 角度});
};