diff --git a/demo/turtle2.html b/demo/turtle2.html
index 00991e7a..66ba5bb0 100644
--- a/demo/turtle2.html
+++ b/demo/turtle2.html
@@ -88,33 +88,6 @@
diff --git a/src/plugin_turtle.mjs b/src/plugin_turtle.mjs
index f29a750a..8805be84 100644
--- a/src/plugin_turtle.mjs
+++ b/src/plugin_turtle.mjs
@@ -1,646 +1,695 @@
-// @ts-nocheck
/**
* Turtle Graphics for Web browser (nadesiko3)
- * plugin_turtle.js
+ * plugin_turtle.mts
*/
-
-import turtleImage from './image_turtle64.mjs'
-import elephantImage from './image_turtle-elephant.mjs'
-import pandaImage from './image_turtle-panda.mjs'
-
-const PluginTurtle = {
- '初期化': {
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- /* istanbul ignore if */
- if (sys._turtle) { return }
- sys._turtle = {
- list: [],
- target: -1,
- ctx: null,
- canvas: null,
- canvas_r: { left: 0, top: 0, width: 640, height: 400 },
- clearAll: function () {
- const me = this
- for (let i = 0; i < me.list.length; i++) {
- const tt = me.list[i]
- tt.mlist = [] // ジョブをクリア
- document.body.removeChild(tt.canvas)
- }
- me.list = []
- if (me.canvas !== null) {
- me.ctx.clearRect(0, 0,
- me.canvas.width,
- me.canvas.height)
- }
-
- me.target = -1
- me.flagSetTimer = false
- },
- drawTurtle: function (id) {
- const tt = this.list[id]
- if (!tt) { return }
- const cr = this.canvas_r
- // カメの位置を移動
- tt.canvas.style.left = (cr.left + tt.x - tt.cx) + 'px'
- tt.canvas.style.top = (cr.top + tt.y - tt.cx) + 'px'
- if (!tt.f_update) { return }
- /* istanbul ignore if */
- if (!tt.flagLoaded) { return }
- tt.f_update = false
- tt.ctx.clearRect(0, 0,
- tt.canvas.width,
- tt.canvas.height)
- if (!tt.f_visible) { return }
- if (tt.dir !== 270) {
- const rad = (tt.dir + 90) * 0.017453292519943295
- tt.ctx.save()
- tt.ctx.translate(tt.cx, tt.cy)
- tt.ctx.rotate(rad)
- tt.ctx.translate(-tt.cx, -tt.cy)
- tt.ctx.drawImage(tt.img, 0, 0)
- tt.ctx.restore()
- } else { tt.ctx.drawImage(tt.img, 0, 0) }
- },
- getCur: function () {
- if (this.list.length === 0) { throw Error('最初に『カメ作成』命令を呼び出してください。') }
-
- return this.list[this.target]
- },
- flagSetTimer: false,
- setTimer: function () {
- if (this.flagSetTimer) { return }
- this.flagSetTimer = true
- console.log('[TURTLE] standby ...')
- setTimeout(() => {
- console.log('[TURTLE] Let\'s go!')
- sys._turtle.play()
- }, 1)
- },
- line: function (tt, x1, y1, x2, y2) {
- /* istanbul ignore else */
- if (tt) { if (!tt.flagDown) { return } }
-
- const ctx = this.ctx
- if (tt.flagBegeinPath) {
- ctx.lineTo(x2, y2)
- } else {
- ctx.beginPath()
- ctx.lineWidth = tt.lineWidth
- ctx.strokeStyle = tt.color
- ctx.moveTo(x1, y1)
- ctx.lineTo(x2, y2)
- ctx.stroke()
- }
- },
- doMacro: function (tt, wait) {
- const me = this
- if (!tt.flagLoaded && wait > 0) {
+import { turtleImage, elephantImage, pandaImage } from './plugin_turtle_images.mjs';
+class NakoTurtle {
+ constructor(sys, id) {
+ this.sys = sys;
+ this.id = id;
+ this.img = null;
+ this.canvas = null;
+ this.ctx = null;
+ this.dir = 270; // 上向き
+ this.cx = 32;
+ this.cy = 32;
+ this.x = 0;
+ this.y = 0;
+ this.color = 'black';
+ this.lineWidth = 4;
+ this.flagDown = true;
+ this.flagBegeinPath = false;
+ this.f_update = true;
+ this.flagLoaded = false;
+ this.f_visible = true;
+ this.mlist = [];
+ }
+ clear() {
+ this.mlist = []; // ジョブをクリア
+ document.body.removeChild(this.canvas);
+ }
+ loadImage(url, callback) {
+ const tt = this;
+ this.canvas = document.createElement('canvas');
+ this.ctx = tt.canvas.getContext('2d');
+ this.canvas.id = this.id;
+ this.img = document.createElement('img');
+ this.img.onload = () => {
+ tt.cx = tt.img.width / 2;
+ tt.cy = tt.img.height / 2;
+ tt.canvas.width = tt.img.width;
+ tt.canvas.height = tt.img.height;
+ tt.flagLoaded = true;
+ tt.f_update = true;
+ tt.canvas.style.position = 'absolute';
+ document.body.appendChild(tt.canvas);
+ // console.log('createTurtle::this.turtles=', this)
+ callback(tt);
+ };
+ this.img.onerror = () => {
+ console.log('カメの読み込みに失敗');
+ tt.flagLoaded = true;
+ tt.f_visible = false;
+ tt.f_update = true;
+ callback(tt);
+ };
+ this.img.src = url;
+ }
+}
+class NakoTurtleSystem {
+ static getInstance(sys) {
+ if (NakoTurtleSystem.instance === undefined) {
+ NakoTurtleSystem.instance = new NakoTurtleSystem(sys);
+ }
+ const i = NakoTurtleSystem.instance;
+ i.instanceCount += 1;
+ // console.log('@@instanceCount=', i.instanceCount)
+ return NakoTurtleSystem.instance;
+ }
+ constructor(sys) {
+ this.sys = sys;
+ this.turtles = []; // カメの一覧
+ this.target = -1;
+ this.ctx = null;
+ this.canvas = null;
+ this.canvas_r = { left: 0, top: 0, width: 640, height: 400 };
+ this.flagSetTimer = false;
+ this.instanceCount = 0;
+ this.timerId = null;
+ }
+ clearAll() {
+ // console.log('カメ全消去 turtles=', this.turtles)
+ for (let i = 0; i < this.turtles.length; i++) {
+ const tt = this.turtles[i];
+ tt.clear();
+ }
+ this.turtles = [];
+ if (this.canvas !== null) {
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+ this.target = -1;
+ this.flagSetTimer = false;
+ }
+ drawTurtle(id) {
+ const tt = this.turtles[id];
+ if (!tt) {
+ return;
+ }
+ const cr = this.canvas_r;
+ // カメの位置を移動
+ tt.canvas.style.left = (cr.left + tt.x - tt.cx) + 'px';
+ tt.canvas.style.top = (cr.top + tt.y - tt.cx) + 'px';
+ if (!tt.f_update) {
+ return;
+ }
+ /* istanbul ignore if */
+ if (!tt.flagLoaded) {
+ return;
+ }
+ tt.f_update = false;
+ tt.ctx.clearRect(0, 0, tt.canvas.width, tt.canvas.height);
+ if (!tt.f_visible) {
+ return;
+ }
+ if (tt.dir !== 270) {
+ const rad = (tt.dir + 90) * 0.017453292519943295;
+ tt.ctx.save();
+ tt.ctx.translate(tt.cx, tt.cy);
+ tt.ctx.rotate(rad);
+ tt.ctx.translate(-tt.cx, -tt.cy);
+ tt.ctx.drawImage(tt.img, 0, 0);
+ tt.ctx.restore();
+ }
+ else {
+ tt.ctx.drawImage(tt.img, 0, 0);
+ }
+ }
+ getCur() {
+ if (this.turtles.length === 0) {
+ throw Error('最初に『カメ作成』命令を呼び出してください。');
+ }
+ return this.turtles[this.target];
+ }
+ setTimer() {
+ if (this.flagSetTimer) {
+ return;
+ }
+ this.flagSetTimer = true;
+ console.log('[TURTLE] standby ...');
+ if (this.timerId) {
+ clearTimeout(this.timerId);
+ }
+ this.timerId = setTimeout(() => {
+ console.log('[TURTLE] Let\'s go!');
+ this.play();
+ }, 1);
+ }
+ line(tt, x1, y1, x2, y2) {
+ /* istanbul ignore else */
+ if (tt) {
+ if (!tt.flagDown) {
+ return;
+ }
+ }
+ const ctx = this.ctx;
+ if (tt.flagBegeinPath) {
+ ctx.lineTo(x2, y2);
+ }
+ else {
+ ctx.beginPath();
+ ctx.lineWidth = tt.lineWidth;
+ ctx.strokeStyle = tt.color;
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+ }
+ }
+ doMacro(tt, wait) {
+ const me = this;
+ if (!tt.flagLoaded && wait > 0) {
// console.log('[TURTLE] waiting ...')
- return true
- }
- const m = tt.mlist.shift()
- const cmd = (m !== undefined) ? m[0] : ''
- switch (cmd) {
+ return true;
+ }
+ const m = tt.mlist.shift();
+ const cmd = (m !== undefined) ? m[0] : '';
+ switch (cmd) {
case 'xy':
- // 起点を移動する
- tt.x = m[1]
- tt.y = m[2]
- break
+ // 起点を移動する
+ tt.x = m[1];
+ tt.y = m[2];
+ break;
case 'begin':
- // 描画を明示的に開始する
- this.ctx.beginPath()
- this.ctx.moveTo(tt.x, tt.y)
- tt.flagBegeinPath = true
- break
+ // 描画を明示的に開始する
+ this.ctx.beginPath();
+ this.ctx.moveTo(tt.x, tt.y);
+ tt.flagBegeinPath = true;
+ break;
case 'close':
- // パスを閉じる
- this.ctx.closePath()
- tt.flagBegeinPath = false
- break
+ // パスを閉じる
+ this.ctx.closePath();
+ tt.flagBegeinPath = false;
+ break;
case 'fill':
- if (tt.flagBegeinPath) {
- this.ctx.closePath()
- tt.flagBegeinPath = false
- }
- this.ctx.fill()
- break
+ if (tt.flagBegeinPath) {
+ this.ctx.closePath();
+ tt.flagBegeinPath = false;
+ }
+ this.ctx.fill();
+ break;
case 'stroke':
- if (tt.flagBegeinPath) {
- this.ctx.closePath()
- tt.flagBegeinPath = false
- }
- this.ctx.stroke()
- break
+ if (tt.flagBegeinPath) {
+ this.ctx.closePath();
+ tt.flagBegeinPath = false;
+ }
+ this.ctx.stroke();
+ break;
case 'text':
- this.ctx.fillText(m[1], tt.x, tt.y)
- break
+ this.ctx.fillText(m[1], tt.x, tt.y);
+ break;
case 'textset':
- this.ctx.font = m[1]
- break
+ this.ctx.font = m[1];
+ break;
case 'fillStyle':
- this.ctx.fillStyle = m[1]
- break
+ this.ctx.fillStyle = m[1];
+ break;
case 'mv': {
- // 線を引く
- me.line(tt, tt.x, tt.y, m[1], m[2])
- // カメの角度を変更
- const mvRad = Math.atan2(m[2] - tt.y, m[1] - tt.x)
- tt.dir = mvRad * 57.29577951308232
- tt.f_update = true
- // 実際に位置を移動
- tt.x = m[1]
- tt.y = m[2]
- break
+ // 線を引く
+ me.line(tt, tt.x, tt.y, m[1], m[2]);
+ // カメの角度を変更
+ const mvRad = Math.atan2(m[2] - tt.y, m[1] - tt.x);
+ tt.dir = mvRad * 57.29577951308232;
+ tt.f_update = true;
+ // 実際に位置を移動
+ tt.x = m[1];
+ tt.y = m[2];
+ break;
}
case 'fd': {
- const fdv = m[1] * m[2]
- const rad = tt.dir * 0.017453292519943295
- const x2 = tt.x + Math.cos(rad) * fdv
- const y2 = tt.y + Math.sin(rad) * fdv
- me.line(tt, tt.x, tt.y, x2, y2)
- tt.x = x2
- tt.y = y2
- break
+ const fdv = m[1] * m[2];
+ const rad = tt.dir * 0.017453292519943295;
+ const x2 = tt.x + Math.cos(rad) * fdv;
+ const y2 = tt.y + Math.sin(rad) * fdv;
+ me.line(tt, tt.x, tt.y, x2, y2);
+ tt.x = x2;
+ tt.y = y2;
+ break;
}
case 'angle': {
- const angle = m[1]
- tt.dir = ((angle - 90 + 360) % 360)
- tt.f_update = true
- break
+ const angle = m[1];
+ tt.dir = ((angle - 90 + 360) % 360);
+ tt.f_update = true;
+ break;
}
case 'rotr': {
- const rv = m[1]
- tt.dir = (tt.dir + rv) % 360
- tt.f_update = true
- break
+ const rv = m[1];
+ tt.dir = (tt.dir + rv) % 360;
+ tt.f_update = true;
+ break;
}
case 'rotl': {
- const lv = m[1]
- tt.dir = (tt.dir - lv + 360) % 360
- tt.f_update = true
- break
+ const lv = m[1];
+ tt.dir = (tt.dir - lv + 360) % 360;
+ tt.f_update = true;
+ break;
}
case 'color':
- tt.color = m[1]
- this.ctx.strokeStyle = tt.color
- break
+ tt.color = m[1];
+ this.ctx.strokeStyle = tt.color;
+ break;
case 'size':
- tt.lineWidth = m[1]
- this.ctx.lineWidth = tt.lineWidth
- break
+ tt.lineWidth = m[1];
+ this.ctx.lineWidth = tt.lineWidth;
+ break;
case 'penOn':
- tt.flagDown = m[1]
- break
+ tt.flagDown = m[1];
+ break;
case 'visible':
- tt.f_visible = m[1]
- tt.f_update = true
- break
+ tt.f_visible = m[1];
+ tt.f_update = true;
+ break;
case 'changeImage':
- tt.flagLoaded = false
- tt.img.src = m[1]
- break
- }
- if (tt.flagLoaded) { sys._turtle.drawTurtle(tt.id) }
- return (tt.mlist.length > 0)
- },
- doMacroAll: function (wait) {
- let hasNext = false
- for (let i = 0; i < sys._turtle.list.length; i++) {
- const tt = sys._turtle.list[i]
- if (this.doMacro(tt, wait)) { hasNext = true }
- }
- return hasNext
- },
- play: function () {
- const me = this
- const wait = sys.__v0['カメ速度']
- let hasNext = this.doMacroAll(wait)
- if (wait <= 0) {
- while (hasNext) { hasNext = this.doMacroAll(wait) }
- } else if (hasNext) {
- setTimeout(() => me.play(), wait)
- return
- }
- console.log('[TURTLE] finished.')
- me.flagSetTimer = false
- },
- setupCanvas: function (sys) {
- // 描画先をセットする
- let canvasId = sys.__v0['カメ描画先']
- if (typeof canvasId === 'string') {
- canvasId = document.getElementById(canvasId) || document.querySelector(canvasId)
- sys.__v0['カメ描画先'] = canvasId
- }
- console.log('カメ描画先=', canvasId)
- const cv = sys._turtle.canvas = canvasId
- if (!cv) {
- console.log('[ERROR] カメ描画先が見当たりません。' + canvasId)
- throw Error('カメ描画先が見当たりません。')
- }
- const ctx = sys._turtle.ctx = sys._turtle.canvas.getContext('2d')
- ctx.lineWidth = 4
- ctx.strokeStyle = 'black'
- ctx.lineCap = 'round'
- sys._turtle.resizeCanvas(sys)
- },
- resizeCanvas: function (sys) {
- const cv = sys._turtle.canvas
- const rect = cv.getBoundingClientRect()
- const rx = rect.left + window.pageXOffset
- const ry = rect.top + window.pageYOffset
- sys._turtle.canvas_r = {
+ tt.flagLoaded = false;
+ tt.img.src = m[1];
+ break;
+ }
+ if (tt.flagLoaded) {
+ this.drawTurtle(tt.id);
+ }
+ return (tt.mlist.length > 0);
+ }
+ doMacroAll(wait) {
+ let hasNext = false;
+ for (let i = 0; i < this.turtles.length; i++) {
+ const tt = this.turtles[i];
+ if (this.doMacro(tt, wait)) {
+ hasNext = true;
+ }
+ }
+ return hasNext;
+ }
+ play() {
+ const me = this;
+ const wait = this.sys.__getSysVar('カメ速度');
+ let hasNext = this.doMacroAll(wait);
+ if (wait <= 0) {
+ while (hasNext) {
+ hasNext = this.doMacroAll(wait);
+ }
+ }
+ else if (hasNext) {
+ if (this.timerId) {
+ clearTimeout(this.timerId);
+ }
+ this.timerId = setTimeout(() => me.play(), wait);
+ return;
+ }
+ console.log('[TURTLE] finished.');
+ me.flagSetTimer = false;
+ }
+ setupCanvas() {
+ // 描画先をセットする
+ let canvasId = this.sys.__getSysVar('カメ描画先');
+ if (typeof canvasId === 'string') {
+ canvasId = document.getElementById(canvasId) || document.querySelector(canvasId);
+ this.sys.__setSysVar('カメ描画先', canvasId);
+ }
+ console.log('カメ描画先=', canvasId);
+ const cv = this.canvas = canvasId;
+ if (!cv) {
+ console.log('[ERROR] カメ描画先が見当たりません。' + canvasId);
+ throw Error('カメ描画先が見当たりません。');
+ }
+ const ctx = this.ctx = cv.getContext('2d');
+ ctx.lineWidth = 4;
+ ctx.strokeStyle = 'black';
+ ctx.lineCap = 'round';
+ this.resizeCanvas();
+ }
+ resizeCanvas() {
+ const cv = this.canvas;
+ const rect = cv.getBoundingClientRect();
+ const rx = rect.left + window.scrollX;
+ const ry = rect.top + window.scrollY;
+ this.canvas_r = {
'left': rx,
'top': ry,
width: rect.width,
height: rect.height
- }
- },
- createTurtle: function (imageUrl, sys) {
- // キャンバス情報は毎回参照する (#734)
- sys._turtle.setupCanvas(sys)
- // const cv = sys._turtle.canvas
- // カメの情報を sys._turtle リストに追加
- const id = sys._turtle.list.length
- const tt = {
- id: id,
- img: null,
- canvas: null,
- ctx: null,
- dir: 270, // 上向き
- cx: 32,
- cy: 32,
- x: 0,
- y: 0,
- color: 'black',
- lineWidth: 4,
- flagDown: true,
- flagBegeinPath: false,
- f_update: true,
- flagLoaded: false,
- f_visible: true,
- mlist: []
- }
- sys._turtle.list.push(tt)
- sys._turtle.target = id
- // 画像を読み込む
- tt.img = document.createElement('img')
- tt.canvas = document.createElement('canvas')
- tt.ctx = tt.canvas.getContext('2d')
- tt.canvas.id = id
- tt.img.onload = () => {
- tt.cx = tt.img.width / 2
- tt.cy = tt.img.height / 2
- tt.canvas.width = tt.img.width
- tt.canvas.height = tt.img.height
- tt.flagLoaded = true
- tt.f_update = true
- sys._turtle.drawTurtle(tt.id)
- console.log('turtle.onload')
- }
- tt.img.onerror = () => {
- console.log('カメの読み込みに失敗')
- tt.flagLoaded = true
- tt.f_visible = false
- tt.f_update = true
- sys._turtle.drawTurtle(tt.id)
- }
- tt.img.src = imageUrl
- tt.canvas.style.position = 'absolute'
- document.body.appendChild(tt.canvas)
- // デフォルト位置の設定
- tt.x = sys._turtle.canvas_r.width / 2
- tt.y = sys._turtle.canvas_r.height / 2
- return id
- }
- }
- }
- },
-
- '!クリア': {
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- sys._turtle.clearAll()
- }
- },
-
- // @タートルグラフィックス・カメ描画
- 'カメ作成': { // @タートルグラフィックスを開始してカメのIDを返す // @かめさくせい
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const imageUrl = sys.__v0['カメ画像URL']
- return sys._turtle.createTurtle(imageUrl, sys)
+ };
}
- },
- 'ゾウ作成': { // @ゾウの画像でタートルグラフィックスを開始してIDを返す // @ぞうさくせい
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const imageUrl = elephantImage
- return sys._turtle.createTurtle(imageUrl, sys)
+ createTurtle(imageUrl) {
+ const self = this;
+ // キャンバス情報は毎回参照する (#734)
+ this.setupCanvas();
+ // カメの情報をリストに追加
+ const id = this.turtles.length;
+ const tt = new NakoTurtle(this.sys, id);
+ this.turtles.push(tt);
+ this.target = id;
+ // 画像を読み込む
+ tt.loadImage(imageUrl, (tt) => {
+ self.drawTurtle(tt.id);
+ console.log(`tutrle.onload(id=${tt.id})`);
+ });
+ // デフォルト位置(中央)の設定
+ tt.x = self.canvas_r.width / 2;
+ tt.y = self.canvas_r.height / 2;
+ return id;
}
- },
- 'パンダ作成': { // @パンダの画像でタートルグラフィックスを開始してIDを返す // @ぱんださくせい
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const imageUrl = pandaImage
- return sys._turtle.createTurtle(imageUrl, sys)
- }
- },
- 'カメ操作対象設定': { // @IDを指定して操作対象となるカメを変更する // @かめそうさたいしょうせってい
- type: 'func',
- josi: [['に', 'へ', 'の']],
- pure: true,
- fn: function (id, sys) {
- sys._turtle.target = id
+}
+const PluginTurtle = {
+ '初期化': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const turtleSystem = NakoTurtleSystem.getInstance(sys);
+ sys.tags._turtle = turtleSystem;
+ }
},
- return_none: true
- },
- 'カメ描画先': { type: 'var', value: 'turtle_cv' }, // @かめびょうがさき
- 'カメ画像URL': { type: 'var', value: turtleImage }, // @かめがぞうURL
- 'カメ画像変更': { // @カメの画像をURLに変更する // @かめがぞうへんこう
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (url, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['changeImage', url])
- sys._turtle.setTimer()
+ '!クリア': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ // console.log('tutle::!クリア')
+ sys.tags._turtle.clearAll();
+ }
},
- return_none: true
- },
- 'カメ速度': { type: 'const', value: 100 }, // @かめそくど
- 'カメ速度設定': { // @カメの動作速度vに設定(大きいほど遅い) // @かめそくどせってい
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (v, sys) {
- sys.__setSysVar('カメ速度', v)
- }
- },
- 'カメ移動': { // @カメの位置を[x,y]へ移動する // @かめいどう
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (xy, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['mv', xy[0], xy[1]])
- sys._turtle.setTimer()
+ // @タートルグラフィックス・カメ描画
+ 'カメ作成': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const imageUrl = sys.__getSysVar('カメ画像URL');
+ return sys.tags._turtle.createTurtle(imageUrl);
+ }
},
- return_none: true
- },
- 'カメ起点移動': { // @カメの描画起点位置を[x,y]へ移動する // @かめきてんいどう
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (xy, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['xy', xy[0], xy[1]])
- sys._turtle.setTimer()
+ 'ゾウ作成': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const imageUrl = elephantImage;
+ return sys.tags._turtle.createTurtle(imageUrl);
+ }
},
- return_none: true
- },
- 'カメ進': { // @カメの位置をVだけ進める // @かめすすむ
- type: 'func',
- josi: [['だけ']],
- pure: true,
- fn: function (v, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['fd', v, 1])
- sys._turtle.setTimer()
+ 'パンダ作成': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const imageUrl = pandaImage;
+ return sys.tags._turtle.createTurtle(imageUrl);
+ }
},
- return_none: true
- },
- 'カメ戻': { // @カメの位置をVだけ戻す // @かめもどる
- type: 'func',
- josi: [['だけ']],
- pure: true,
- fn: function (v, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['fd', v, -1])
- sys._turtle.setTimer()
+ 'カメ操作対象設定': {
+ type: 'func',
+ josi: [['に', 'へ', 'の']],
+ pure: true,
+ fn: function (id, sys) {
+ sys.tags._turtle.target = id;
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメ角度設定': { // @カメの向きをDEGに設定する // @かめかくどせってい
- type: 'func',
- josi: [['に', 'へ', 'の']],
- pure: true,
- fn: function (v, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['angle', parseFloat(v)])
- sys._turtle.setTimer()
+ 'カメ描画先': { type: 'var', value: '#turtle_cv' }, // @かめびょうがさき
+ 'カメ画像URL': { type: 'var', value: turtleImage }, // @かめがぞうURL
+ 'カメ画像変更': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (url, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['changeImage', url]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメ右回転': { // @カメの向きをDEGだけ右に向ける // @かめみぎかいてん
- type: 'func',
- josi: [['だけ']],
- pure: true,
- fn: function (v, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['rotr', v])
- sys._turtle.setTimer()
+ 'カメ速度': { type: 'const', value: 100 }, // @かめそくど
+ 'カメ速度設定': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (v, sys) {
+ sys.__setSysVar('カメ速度', v);
+ }
},
- return_none: true
- },
- 'カメ左回転': { // @カメの向きをDEGだけ左に向ける // @かめひだりかいてん
- type: 'func',
- josi: [['だけ']],
- pure: true,
- fn: function (v, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['rotl', v])
- sys._turtle.setTimer()
+ 'カメ移動': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (xy, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['mv', xy[0], xy[1]]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメペン色設定': { // @カメのペン描画色をCに設定する // @かめぺんいろせってい
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (c, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['color', c])
- sys._turtle.setTimer()
+ 'カメ起点移動': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (xy, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['xy', xy[0], xy[1]]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメペンサイズ設定': { // @カメペンのサイズをWに設定する // @かめぺんさいずせってい
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (w, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['size', w])
- sys._turtle.setTimer()
- }
- },
- 'カメペン設定': { // @カメペンを使うかどうかをV(オン/オフ)に設定する // @かめぺんせってい
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (w, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['penOn', w])
- sys._turtle.setTimer()
- }
- },
- 'カメパス開始': { // @カメで明示的にパスの描画を開始する // @かめぱすかいし
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['begin'])
- sys._turtle.setTimer()
- }
- },
- 'カメパス閉': { // @カメでパスを明示的に閉じる(省略可能) // @かめぱすとじる
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['close'])
- sys._turtle.setTimer()
- }
- },
- 'カメパス線引': { // @カメでパスを閉じて、カメペン色設定で指定した色で枠線を引く // @かめぱすせんひく
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['stroke'])
- sys._turtle.setTimer()
- }
- },
- 'カメパス塗': { // @カメでパスを閉じて、カメ塗り色設定で指定した色で塗りつぶす // @かめぱすぬる
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['fill'])
- sys._turtle.setTimer()
- }
- },
- 'カメ文字描画': { // @カメの位置に文字Sを描画 // @かめもじびょうが
- type: 'func',
- josi: [['を', 'と', 'の']],
- pure: true,
- fn: function (s, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['text', s])
- sys._turtle.setTimer()
- }
- },
- 'カメ文字設定': { // @カメ文字描画で描画するテキストサイズやフォント(48px serif)などを設定 // @かめもじせってい
- type: 'func',
- josi: [['に', 'へ', 'で']],
- pure: true,
- fn: function (s, sys) {
- s = '' + s // 文字列に
- if (s.match(/^\d+$/)) {
- s = s + 'px serif'
- } else if (s.match(/^\d+(px|em)$/)) {
- s = s + ' serif'
- }
- const tt = sys._turtle.getCur()
- tt.mlist.push(['textset', s])
- sys._turtle.setTimer()
- }
- },
- 'カメ塗色設定': { // @カメパスの塗り色をCに設定する // @かめぬりいろせってい
- type: 'func',
- josi: [['に', 'へ']],
- pure: true,
- fn: function (c, sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['fillStyle', c])
- sys._turtle.setTimer()
+ 'カメ進': {
+ type: 'func',
+ josi: [['だけ']],
+ pure: true,
+ fn: function (v, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['fd', v, 1]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
+ },
+ 'カメ戻': {
+ type: 'func',
+ josi: [['だけ']],
+ pure: true,
+ fn: function (v, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['fd', v, -1]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメ全消去': { // @表示しているカメと描画内容を全部消去する // @かめぜんしょうきょ
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- sys._turtle.clearAll()
+ 'カメ角度設定': {
+ type: 'func',
+ josi: [['に', 'へ', 'の']],
+ pure: true,
+ fn: function (v, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['angle', v]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメコマンド実行': { // @カメにコマンドSを実行する。コマンドは改行か「;」で区切る。コマンドと引数は「=」で区切り引数はかカンマで区切る // @かめこまんどじっこう
- type: 'func',
- josi: [['の', 'を']],
- pure: true,
- fn: function (cmd, sys) {
- const tt = sys._turtle.getCur()
- const a = cmd.split(/(\n|;)/)
- for (let i = 0; i < a.length; i++) {
- let c = a[i]
- c = c.replace(/^([a-zA-Z_]+)\s*(\d+)/, '$1,$2')
- c = c.replace(/^([a-zA-Z_]+)\s*=/, '$1,')
- const ca = c.split(/\s*,\s*/)
- tt.mlist.push(ca)
- }
- sys._turtle.setTimer()
+ 'カメ右回転': {
+ type: 'func',
+ josi: [['だけ']],
+ pure: true,
+ fn: function (v, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['rotr', v]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメ非表示': { // @カメの画像を非表示にする。描画に影響しない。 // @かめひひょうじ
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['visible', false])
- sys._turtle.setTimer()
+ 'カメ左回転': {
+ type: 'func',
+ josi: [['だけ']],
+ pure: true,
+ fn: function (v, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['rotl', v]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメ表示': { // @非表示にしたカメを表示する。 // @かめひょうじ
- type: 'func',
- josi: [],
- pure: true,
- fn: function (sys) {
- const tt = sys._turtle.getCur()
- tt.mlist.push(['visible', true])
- sys._turtle.setTimer()
+ 'カメペン色設定': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (c, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['color', c]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
},
- return_none: true
- },
- 'カメクリック時': { // @ 操作対象のカメをクリックした時のイベントを設定する // @かめくりっくしたとき
- type: 'func',
- josi: [['を']],
- pure: false,
- fn: function (func, sys) {
- func = sys.__findVar(func, null) // 文字列指定なら関数に変換
- const tid = sys._turtle.target
- const tt = sys._turtle.list[tid]
- tt.canvas.onclick = (e) => {
- sys.__v0['対象'] = e.target
- return func(e, sys)
- }
+ 'カメペンサイズ設定': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (w, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['size', w]);
+ sys.tags._turtle.setTimer();
+ }
},
- return_none: true
- }
-}
-
+ 'カメペン設定': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (w, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['penOn', w]);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメパス開始': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['begin']);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメパス閉': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['close']);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメパス線引': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['stroke']);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメパス塗': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['fill']);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメ文字描画': {
+ type: 'func',
+ josi: [['を', 'と', 'の']],
+ pure: true,
+ fn: function (s, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['text', s]);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメ文字設定': {
+ type: 'func',
+ josi: [['に', 'へ', 'で']],
+ pure: true,
+ fn: function (s, sys) {
+ s = '' + s; // 文字列に
+ if (s.match(/^\d+$/)) {
+ s = s + 'px serif';
+ }
+ else if (s.match(/^\d+(px|em)$/)) {
+ s = s + ' serif';
+ }
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['textset', s]);
+ sys.tags._turtle.setTimer();
+ }
+ },
+ 'カメ塗色設定': {
+ type: 'func',
+ josi: [['に', 'へ']],
+ pure: true,
+ fn: function (c, sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['fillStyle', c]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
+ },
+ 'カメ全消去': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ sys.tags._turtle.clearAll();
+ },
+ return_none: true
+ },
+ 'カメコマンド実行': {
+ type: 'func',
+ josi: [['の', 'を']],
+ pure: true,
+ fn: function (cmd, sys) {
+ const tt = sys.tags._turtle.getCur();
+ const a = cmd.split(/(\n|;)/);
+ for (let i = 0; i < a.length; i++) {
+ let c = a[i];
+ c = c.replace(/^([a-zA-Z_]+)\s*(\d+)/, '$1,$2');
+ c = c.replace(/^([a-zA-Z_]+)\s*=/, '$1,');
+ const ca = c.split(/\s*,\s*/);
+ tt.mlist.push(ca);
+ }
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
+ },
+ 'カメ非表示': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['visible', false]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
+ },
+ 'カメ表示': {
+ type: 'func',
+ josi: [],
+ pure: true,
+ fn: function (sys) {
+ const tt = sys.tags._turtle.getCur();
+ tt.mlist.push(['visible', true]);
+ sys.tags._turtle.setTimer();
+ },
+ return_none: true
+ },
+ 'カメクリック時': {
+ type: 'func',
+ josi: [['を']],
+ pure: false,
+ fn: function (func, sys) {
+ func = sys.__findVar(func, null); // 文字列指定なら関数に変換
+ const tid = sys.tags._turtle.target;
+ const tt = sys.tags._turtle.list[tid];
+ tt.canvas.onclick = (e) => {
+ sys.__setSysVar('対象', e.target);
+ return func(e, sys);
+ };
+ },
+ return_none: true
+ }
+};
// module.exports = PluginTurtle
-export default PluginTurtle
-
+export default PluginTurtle;
// scriptタグで取り込んだ時、自動で登録する
-/* istanbul ignore else */
-if (typeof (navigator) === 'object' && typeof (navigator.nako3)) { navigator.nako3.addPluginObject('PluginTurtle', PluginTurtle) }
+// @ts-ignore TS2339
+if (typeof (navigator) === 'object' && typeof (navigator.nako3)) {
+ navigator.nako3.addPluginObject('PluginTurtle', PluginTurtle);
+}
diff --git a/src/plugin_turtle_images.mts b/src/plugin_turtle_images.mts
new file mode 100644
index 00000000..bf25a0f4
--- /dev/null
+++ b/src/plugin_turtle_images.mts
@@ -0,0 +1,12 @@
+/**
+ * images
+ */
+export const turtleImage = 'data:image/png;base64,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'
+export const elephantImage = 'data:image/png;base64,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'
+export const pandaImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAq5pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIj4KICAgICAgICAgPHhtcDpDcmVhdGVEYXRlPjIwMTctMDUtMjVUMTM6Mzk6MTJaPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpDcmVhdG9yVG9vbD5BZG9iZSBGaXJld29ya3MgQ1MzPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6TW9kaWZ5RGF0ZT4yMDE3LTA1LTI1VDEzOjQwOjQ5WjwveG1wOk1vZGlmeURhdGU+CiAgICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgrVrWkKAAAPNElEQVRoBaVae3SU1bXf30wm7zTJJJm8eSqUUN4or9ikgF5qFUoLXdXlstiitl1WfBWVe+3FLrRW6+WPKtUWXJcrtghd1AbQatWwUORVpIAQ3klISELe78dkZk5/vzNzvjsJCSR2r3VyznfO2fv89j57n9dE5MuR07DdcMOoJSi/+uSTP9/x8EMPbVu5cuV/43sC29euXetQStl9WWcI9VZYcqJvRGFhYQTa2d9CcoRylgelazYOwsVBfJlpSV9X4nglKjp6UnlFlTzx+OPidrslOztb2traZP3LLz15oezSi2EyjCL+sLqhFjlmIJT68AxHAWuiiOukiDfTk7IU1tthWZb4QdEx0YFLlTVa1qybZgZGjBoVuX37n2Xe3Nm/Xf6976d5PJ4xDocjnhgsy+l1Op2tIG9HRwcxebu7eurPnD1bsXHjRoiXz5GqkVzr16/3P/roo80oG+KsUJFhExm1BTPSkm/PSHMrnTzuHubpqclqZG6WGn/jWIV+TP5t2972t7e3Q8+hkc/nUzU1NWrfvn2dAH4JMqhEZXpK0j+mTJr0MspZSCRiGRaZqZfM1OQHbfBpbp8pGwVcAJ+ekqiOHz9O1AGk3rDkQ9mk8PrwMts11dbWqj++9ZYxiJr0tbwqoB62EvQ9TRmpKb/L8qQELR8GnkpkpaeqsaNH6sGOHDmiAcCzFNNQKBAIKKYQDxUno04VFRU99634AWXXrl69elgKaPA5OTkx6Wnu97PSNXha3W8szzzNnai+NnGCBr9hw6saL4EYUMy/DIUZwN/T06N++ctnT0OJxKA5g+7Msu0eoQaT6ZUmNTU1Qfw9e52WIz8gyotG1vfxQaxC0lRfL109Xtm4cZPEx8cLAAuCFgFr6UShUETXs24oZHihiOVyuXwFBQWe7u7O2fv2fbYZ/DSYFtQHTEgw63xIERES+DuATIcRvWCJNEyhfhpQQkKiNLS0y/r1/yMZGRk2ePZpamqS5uZmDZ4KMVG54RBWLK50NFzvU089XYj8KfJj39DG7z8DtnngHlsdTuu2gB/gLQ2efH2InWNiY6WuvkHWrVsnWVlZNti3394q+TfdLAf/cVhKSkqktbVN4uLiJCkpKSTDNmIfmQN9hBS3YmJirCmTJ09/e9u2N/fs2dOKvldNgPb7jFT3Lxiw8H1vuL/3L4/IydK+f0v+PAVr265++fJlXY8BVKSlp9v+/vDDD3W/4cZGqL+3rbVVzZg2zcxCRLgG2u8zUpMKLIf1LEKPTtZ/hmwD0adpUdJ/LFqkLcs60oED+3U+bepk8WSmy7gbxsr0qVN03cKFC6WqqkrHhumvG67zJxQ7VnxCgvzkpz9dyu5wI59RgN5Avwdgx6tsBPUimXZdEf6HseiM0BMmUyYHwXEQgjp08JDuip1AXK5I8fV6pbGxQWbffJOur6q6HC5qyGVMncZz47hxeWDKJWMQQdDSPrjOGofDmgjrEzz2pcHJGeGSrs4u3WHs2LF2RwZpc3OT/v7n8RN2PQtllyr1d2amWc77NA/lg4YO5Obm4lgi45EqqAC18mVlJaQGetXPYDTSoK6jW/EnMjJKLpSWycwZ0yQTwUsieK4aT6/5T5kwIU8uXDgv9VhiOTMO1EdiNu6991594GNfBudwCDwQZflTUlIco7Mzskov1+gZoJSA3+v6Nqyfhk56Cb2WYA7O9Z+0YMGt9spCoGwbOXKkrHrkEcH5Rnp7OZlBwnqOtTk46ew7XDI80dFRkjd1mrv08ntageDx1pLlQxVIP4/CDJCmz5ihc6zV2vr8oBIkgjWAdQX+sM0AMXXDzLXwgC/AfUkroJKTk0fAHvNCgq47rzQeTi+6O/xR53SdcDJAjTKm7d8ETzGW3x8Qr9fbzg8NNiMtbU5UVHQcjOPHALqOA/cfnAwkgjBLYFRUcCbq6urs/mw3uy7L4Sko4cv9DeGx2tvb5KPiYp5OgwqUnD07r7a6ShK+8pVAPI4G7pRUSfNkSLI7VWLj4vVSaEDTVXq9XttdGhrqNZqior9KV1dwVaLf79q1U+5bsUK2bHnzy6G9BldHewdbY/lHR9TiO+6YUVFRLkePnRgwslKTEyQJytDv6Sq0RFxscBMrKTkts2fPEY8nXWiZWBwt3n//b3LnnYspX6phmGXLlks0gp58NMS/Saq+oYEi7kP6IxVIe+7558ePHjNGcARw0DVwkxJcKOSzfftk//7P5OPiPVLf1Eamq+ipx1ZJfn6+cC/gXZiK8OxDumHMaB3EXI36E5UJuYTtYv37DPJtlZeXs2n8zp07Y6nAqNS0tBQeC8aNG9fHPLfffrtWhsCYcI/Vp8tGWIDre119nZw/f17e2LRR7n/gAdutcrJz9Njd3V2Cs7wuh/+hkRgj4bNxvdlhu4krGhbUA5f1yV133TUfwNCuArBUAD5ubkasGxIdPnxY/eTHDyqcEHX/Cxcu2Ie3ObNuVlBW1wN4n1sab29FRUUKQOz2wQYENjYFTp8+3QnwKm/C+GBwPfbYY9/o7OzUjRigz/WJn2bQcMVY7tdV7d69S82bM8uu3759u63EyZMnNS4Et84bGxvVCy/8ym4/eOCArqfcgSgEnjcz//0rf0S+rsWLFvEooaN4fkNDA/mAqa8CAwnrX0cWw7Z71y71wQcf2F1waOMrg8Klxq47e/aMwvFDg583d47OOYOkcAUo0xiNTWx//vnn2P/EkiVLZmnwoSPPtEvl5WzXGujCMP8YBch2YP9+tXXr1j6gjThc0DVgDK7mzJ6ly99esliZ5xcDOFwR8Prr8ELx3Lp1B8E3C986TnFssHfOjDNnzujbCIAMPIcGwTVyKmEUqaysVFv/9CdtfQOOrBv/8AcN+huFBQqrhxozMleVll4cUCov8sCl3ti0qfeWeXPJ91zI6txobfC67uDBg/+kFACw32UGlDqEynDr8X1ox44dak9xscJOrf6CMga007Nr16pTp04inVJUGhcdde7cOYVHMXX33XeZfr5nnvkv9eTq1efBqzcv5H1WS3lnx47NIWzeIWC8bpdwJdi5rKxM7d27V7377m6A26ZefeUVDS4xLtKAHDCPxp55260L/a+/9pqamPfVRgD3IJHs85reiQ/gDrhk6dJ72YLx+qzPrBsu6XM+5PBayjKP10yGeOTAqiXvvvc3mTp5kvCNtL2tFa8PPhxnErGbx2kcX5wqkYKCwkBnV5fj5KnT3B1rQzKosCatyQsvvvg+prEbNS64EZdSLSA8R7WuC/FdPwsd6NgxnBezI3hdkFWrHtEyenHdbGtt1ptgJA6GnR3tUlNdKS0t+lbHOHB++ukn7Pu0ZhDh6dFWgKC0Er/bsOEtlEnXdCPq199FgmxD+xu0j1KtrS1q4YL52nVGI5j7v3h4UpLUyJwsntmZ+HC8OqQAM/u6G4HtXAcEbvqvtLS23I3jRAROe6q7p4c/QGir88g8evRoycvLE7zWaRejVckafhwIG+C6xYT4BJk6dap8+NHHuJ5GasuHy6LrdXV1WqNys2kZaWlu/DWe9WcHrIgfX7lyha5EwwdMNOsPVBQh3Yk04LUyNzsD991n5LvLluHQFownKqJ9HkyDEQGQCJBl5tiNZdFtt8rhI5/LqBE50t3VORg7eXgZDiA5UWyxLPVQdW3TFi0yxMVg9uHNpvBKTVUxH8ZD9UZBPfCl0nPS1u2XnEyPvLH5TZk/f4F9gBtsRsIVJHh+80iOmJOJEyfqEyv9HltQaMhrZvq1BHd3Uf7APdX1TW8ZgCZXHnfSp+gwD9sSZsHSqxRF0soMsuTkFCk58YXwpXfVqoflgQce1K5lhiVAQ3ggw4IdtDrBh88UDnIyc+ZMgW9La0sLDPT/fIZ/oDzC5fLixBnpD/iPX6lrmhK+oxFsID4+tgMDLQtNmYPTzUTiXbS1uVHcaakyAnfhop27ZMOGDfrMn4bYYHwQpOEheKz/8vLLv5Hi4o/xzHJR/47G39J433j99dfFA1k8dg+FLMiuqqkTl1M5Il1RzW0dna8Zy5OfZbqOhQeuE7DeRFiN8xquJD6DREsnu1O0UucvlurKJ554XL71rTvw7pOl3eTQocM8rhsWOz969KgcO3ZMVqxYoWeguanRNpLdqV+BRun19sjNc/J7y8tKXeUXz21s7wncH64AWXQs4FH3hzD9JigwYDAb2Th76OfFJLgVb11nz18wTXZ+41jeylxo78VbarxcPH9aWjuDb0XZiCUf7s9mhm2mfgWHwwl+r9Q1tKiXfvOS9cneT+SvRUV8z/m8vwJmFgTr8n4Ing0ldOD0k9nnUwcqAjMxKVm7Uzd2Wj5u8CGruamB7/vatfhGGgMlYmPjcVPrxtNkx3XBM3bisOReLLska9Y83ZOclBz189Wrfw8ADyI57SANIaIL6VnACvuwcqhD+HbBufgDBzeP/gprNh2cGKgZD7gkE6zBwAw+sVDJWAChYlTKxIpmGPiPXgm5cAC8Kiws8GIfiv6/zZsr0F0/ryMf8Fd0LgcR7V1dlXFxsU1Oh/VN4GYcEDxdiu0sX6WMCV60hSjYheD5Q8ilymppwQ8dqSlu/TQTch0NFAzMGXOUb4+BFUdiY6Ic2LkjmptbWne/+x7wSGkhftUvKyvzDxigRkBHZ9fBmLjYY9iSc1CXi7hwahP2VQZNmq5SyDRER8dIeWWVfPc7S/Hj99xA8Z69/hR3suJVEUpoYwQXO6wzmCJU6aWMbbre4WiH+xUdOXr8O5BZQvC4f9OYwXchM1C/nBZx1NY2vIP8HY8naYoEHIshcBEabsIowfMIXAfftBgFEgx3daOMzimINH78V/04edJoDv62YHlNN+nGq3gDdowKcJZiSyiBGl/ABXswTq/Dp0pqGlsqKWP58uVO3Lc1eH7bEvgxCDEmOLUGh2S43XnKaS2AMgtFBebAYGkoYzOiBJ6fmGulFIFerq6Vgq/f4v/Ryvsj39z8vyf//tHHL904Zoyjvb25FStZg0MiqiN9vitl/EVwcDKGIRab+gex3RBWMNracVDT2HgK7Uy/zc5OSFG9rumwYL4VkLm4sU5CfTrmnv+pgqNz8BKVn3+Ls7b2igD8GrQXnbt4EdmAZMYJbzRxEV6ny0OZgauYUGEGuUrwiMTEZF+kNQpn1UyxHKk4V0Wy85X6xvYf3HPP0Te2bDmDT1chpmoPCiHinFGWPcum4Xr5vwBI81/lIwKhoQAAAABJRU5ErkJggg=='
+
+
+
+
+
+