diff --git a/package-lock.json b/package-lock.json index 992ec6e4b5c..f66ef6f6020 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26917,7 +26917,7 @@ }, "node_modules/scratch-vm": { "version": "4.5.413", - "resolved": "git+ssh://git@github.com/smalruby/scratch-vm.git#47550973c90be3b7f504a7f6e43ffb56e030506c", + "resolved": "git+ssh://git@github.com/smalruby/scratch-vm.git#13c191625c66108379dce122918c290f9111ac20", "license": "BSD-3-Clause", "dependencies": { "@vernier/godirect": "^1.5.0", diff --git a/src/containers/ruby-tab/koshien-snippets.json b/src/containers/ruby-tab/koshien-snippets.json new file mode 100644 index 00000000000..fd9dbc5617c --- /dev/null +++ b/src/containers/ruby-tab/koshien-snippets.json @@ -0,0 +1,82 @@ +{ + "koshien.connect_game": { + "snippet": "koshien.connect_game(name: ${1:\"player1\"})", + "description": "プレイヤー名を (player1) にして、ゲームサーバへ接続する" + }, + "koshien.move_to": { + "snippet": "koshien.move_to(${1:0}, ${2:0})", + "description": "x座標 (0)、y座標 (0) に移動する" + }, + "koshien.get_map_area": { + "snippet": "koshien.get_map_area(${1:0}, ${2:0})", + "description": "x座標が (0) 、y座標が (0) 付近のマップ情報を取得する" + }, + "koshien.map": { + "snippet": "koshien.map(${1:0}, ${2:0})", + "description": "x座標が (0) 、y座標が (0) のマップ情報" + }, + "koshien.calc_route": { + "snippet": "koshien.calc_route(src: [${1:0}, ${2:0}], dst: [${3:0}, ${4:0}], except_cells: ${5:\"通らない座標\"}, result: ${6:\"最短経路\"})", + "description": "2点間の最短経路 始点 x座標 (0) y座標 (0) 終点 x座標 (0) y座標 (0) 通らない座標 リスト (通らない座標) をリスト (最短経路) に保存する" + }, + "koshien.set_dynamite": { + "snippet": "koshien.set_dynamite([${1:0},${2:0}])", + "description": "ダイナマイトをx座標 (0) y座標 (0) に置く" + }, + "koshien.set_bomb": { + "snippet": "koshien.set_bomb([${1:0},${2:0}])", + "description": "爆弾をx座標 (0) y座標 (0) に置く" + }, + "koshien.save_map_all": { + "snippet": "koshien.save_map_all(${1:\"map1\"})", + "description": "すべてのマップ情報を (map1) に保存する" + }, + "koshien.load_map": { + "snippet": "koshien.load_map(${1:\"map1\"}, ${2:0}, ${3:0})", + "description": "x座標が (0) 、y座標が (0) のマップ情報を (map1) から読み込む" + }, + "koshien.other_player_x": { + "snippet": "koshien.other_player_x", + "description": "対戦キャラクタの x座標" + }, + "koshien.other_player_y": { + "snippet": "koshien.other_player_y", + "description": "対戦キャラクタの y座標" + }, + "koshien.player_x": { + "snippet": "koshien.player_x", + "description": "プレイヤーの x座標" + }, + "koshien.player_y": { + "snippet": "koshien.player_y", + "description": "プレイヤーの y座標" + }, + "koshien.enemy_x": { + "snippet": "koshien.enemy_x", + "description": "妨害キャラクタの x座標" + }, + "koshien.enemy_y": { + "snippet": "koshien.enemy_y", + "description": "妨害キャラクタの y座標" + }, + "koshien.goal_x": { + "snippet": "koshien.goal_x", + "description": "ゴールの x座標" + }, + "koshien.goal_y": { + "snippet": "koshien.goal_y", + "description": "ゴールの y座標" + }, + "koshien.turn_over": { + "snippet": "koshien.turn_over", + "description": "ターンを終了する" + }, + "koshien.coordinate_of_x": { + "snippet": "koshien.coordinate_of_x(${1:'0:0'})", + "description": "(0:0) のx座標" + }, + "koshien.coordinate_of_y": { + "snippet": "koshien.coordinate_of_y(${1:'0:0'})", + "description": "(0:0) のy座標" + } +} diff --git a/src/containers/ruby-tab/snippets-completer.js b/src/containers/ruby-tab/snippets-completer.js index 4df893ea11e..dbf350e5b3d 100644 --- a/src/containers/ruby-tab/snippets-completer.js +++ b/src/containers/ruby-tab/snippets-completer.js @@ -19,6 +19,7 @@ import MicrobitSnippets from './microbit-snippets.json'; import MeshSnippets from './mesh-snippets.json'; import SmalrubotS1Snippets from './smalrubot-s1-snippets.json'; import MicrobitMoreSnippets from './microbit-more-snippets.json'; +import KoshienSnippets from './koshien-snippets.json'; class SnippetsCompleter extends BaseCompleter { #completions = []; @@ -45,7 +46,8 @@ class SnippetsCompleter extends BaseCompleter { MicrobitSnippets, MeshSnippets, SmalrubotS1Snippets, - MicrobitMoreSnippets + MicrobitMoreSnippets, + KoshienSnippets ]; snippetsList.forEach(snippets => { for (const [caption, item] of Object.entries(snippets)) { diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index 8b05eb7672a..032c6087e0a 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -403,4 +403,7 @@ const extensions = [ import microbitMore from './microbitMore/index.jsx'; extensions.push(microbitMore); +import koshien from './koshien/index.jsx'; +extensions.push(koshien); + export default extensions; diff --git a/src/lib/libraries/extensions/koshien/index.jsx b/src/lib/libraries/extensions/koshien/index.jsx new file mode 100644 index 00000000000..1222d9f8830 --- /dev/null +++ b/src/lib/libraries/extensions/koshien/index.jsx @@ -0,0 +1,43 @@ +let formatMessage = messageData => messageData.defaultMessage; + +/** + * Koshien extension + */ + +import koshienIconURL from './koshien.png'; +import koshienInsetIconURL from './koshien-small.png'; +import translations from './translations.json'; + +const entry = { + get name () { + return formatMessage({ + defaultMessage: 'Smalruby Koshien', + description: "Name for the 'koshien' extension", + id: 'koshien.entry.name' + }); + }, + extensionId: 'koshien', + iconURL: koshienIconURL, + insetIconURL: koshienInsetIconURL, + get description () { + return formatMessage({ + defaultMessage: 'Smalruby Koshien AI.', + description: "Description for the 'koshien' extension", + id: 'koshien.entry.description' + }); + }, + featured: true, + disabled: false, + bluetoothRequired: false, + internetConnectionRequired: false, + launchPeripheralConnectionFlow: false, + useAutoScan: false, + helpLink: 'https://smalruby-koshien.netlab.jp/', + setFormatMessage: formatter => { + formatMessage = formatter; + }, + translationMap: translations +}; + +export {entry}; // loadable-extension needs this line. +export default entry; diff --git a/src/lib/libraries/extensions/koshien/koshien-small.png b/src/lib/libraries/extensions/koshien/koshien-small.png new file mode 100644 index 00000000000..7264fa1f506 Binary files /dev/null and b/src/lib/libraries/extensions/koshien/koshien-small.png differ diff --git a/src/lib/libraries/extensions/koshien/koshien.png b/src/lib/libraries/extensions/koshien/koshien.png new file mode 100644 index 00000000000..8695f7c2f53 Binary files /dev/null and b/src/lib/libraries/extensions/koshien/koshien.png differ diff --git a/src/lib/libraries/extensions/koshien/translations.json b/src/lib/libraries/extensions/koshien/translations.json new file mode 100644 index 00000000000..a1536250796 --- /dev/null +++ b/src/lib/libraries/extensions/koshien/translations.json @@ -0,0 +1,14 @@ +{ + "en": { + "koshien.entry.name": "Smalruby Koshien", + "koshien.entry.description": "Smalruby Koshien AI." + }, + "ja": { + "koshien.entry.name": "スモウルビー甲子園", + "koshien.entry.description": "スモウルビー甲子園のAIを作成する。" + }, + "ja-Hira": { + "koshien.entry.name": "スモウルビー こうしえん", + "koshien.entry.description": "スモウルビー こうしえんのエー・アイをさくせいする。" + } +} diff --git a/src/lib/ruby-generator/index.js b/src/lib/ruby-generator/index.js index 8fd3689ad44..fb7e8a8ee90 100644 --- a/src/lib/ruby-generator/index.js +++ b/src/lib/ruby-generator/index.js @@ -30,6 +30,7 @@ import WeDo2Blocks from './wedo2.js'; import GdxForBlocks from './gdx_for.js'; import MeshBlocks from './mesh.js'; import SmalrubotS1Blocks from './smalrubot_s1.js'; +import KoshienBlocks from './koshien.js'; const SCALAR_TYPE = ''; const LIST_TYPE = 'list'; @@ -427,6 +428,29 @@ RubyGenerator.listName = function (id) { return this.variableName(id, LIST_TYPE); }; +RubyGenerator.variableNameByName = function (name, type = SCALAR_TYPE) { + let currVar; + let isStage; + const target = this.currentTarget; + if (target.runtime) { + const stage = target.runtime.getTargetForStage(); + currVar = stage.lookupVariableByNameAndType(name, type); + isStage = true; + } + if (!currVar) { + currVar = target.lookupVariableByNameAndType(name, type); + isStage = target.isStage; + } + if (currVar && currVar.type === type) { + return this.makeVariableName(isStage, currVar.name); + } + return null; +}; + +RubyGenerator.listNameByName = function (name) { + return this.variableNameByName(name, LIST_TYPE); +}; + RubyGenerator.getScripts = function () { return Generator.prototype.getScripts.call(this).sort((a, b) => { const aValue = (this.getBlock(a).opcode === 'procedures_definition' ? 1 : -1); @@ -462,5 +486,6 @@ WeDo2Blocks(RubyGenerator); GdxForBlocks(RubyGenerator); MeshBlocks(RubyGenerator); SmalrubotS1Blocks(RubyGenerator); +KoshienBlocks(RubyGenerator); export default RubyGenerator; diff --git a/src/lib/ruby-generator/koshien.js b/src/lib/ruby-generator/koshien.js new file mode 100644 index 00000000000..d37bb79f8be --- /dev/null +++ b/src/lib/ruby-generator/koshien.js @@ -0,0 +1,92 @@ +/** + * Define Ruby code generator for Microbit More Blocks + * @param {RubyGenerator} Generator The RubyGenerator + * @return {RubyGenerator} same as param. + */ +export default function (Generator) { + Generator.koshien_connectGame = function (block) { + const name = Generator.valueToCode(block, 'NAME', Generator.ORDER_NONE) || Generator.quote_('player1'); + return `koshien.connect_game(name: ${name})\n`; + }; + + Generator.koshien_getMapArea = function (block) { + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + return `koshien.get_map_area(${x}, ${y})\n`; + }; + + Generator.koshien_map = function (block) { + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + return [`koshien.map(${x}, ${y})`]; + }; + + Generator.koshien_moveTo = function (block) { + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + return `koshien.move_to(${x}, ${y})\n`; + }; + + Generator.koshien_calcRoute = function (block) { + const srcX = Generator.valueToCode(block, 'SRC_X', Generator.ORDER_NONE) || 0; + const srcY = Generator.valueToCode(block, 'SRC_Y', Generator.ORDER_NONE) || 0; + const dstX = Generator.valueToCode(block, 'DST_X', Generator.ORDER_NONE) || 0; + const dstY = Generator.valueToCode(block, 'DST_Y', Generator.ORDER_NONE) || 0; + const exceptCells = Generator.quote_( + Generator.getFieldValue(block, 'EXCEPT_CELLS', Generator.ORDER_NONE) || ' ' + ); + const result = Generator.quote_( + Generator.getFieldValue(block, 'RESULT', Generator.ORDER_NONE) || ' ' + ); + + // eslint-disable-next-line max-len + return `koshien.calc_route(src: [${srcX}, ${srcY}], dst: [${dstX}, ${dstY}], except_cells: ${exceptCells}, result: ${result})\n`; + }; + + Generator.koshien_setItem = function (block) { + const item = Generator.getFieldValue(block, 'ITEM') || null; + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + return `koshien.set_${item}(${x}, ${y})\n`; + }; + + Generator.koshien_loadMap = function (block) { + const location = Generator.valueToCode(block, 'LOCATION', Generator.ORDER_NONE) || Generator.quote_('map1'); + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + return [`koshien.load_map(${location}, ${x}, ${y})`]; + }; + + Generator.koshien_saveMapAll = function (block) { + const location = Generator.valueToCode(block, 'LOCATION', Generator.ORDER_NONE) || Generator.quote_('map1'); + return `koshien.save_map_all(${location})\n`; + }; + + Generator.koshien_locateObjects = function (block) { + const x = Generator.valueToCode(block, 'X', Generator.ORDER_NONE) || 0; + const y = Generator.valueToCode(block, 'Y', Generator.ORDER_NONE) || 0; + const sqSize = Generator.valueToCode(block, 'SQ_SIZE', Generator.ORDER_NONE) || 5; + const objects = Generator.valueToCode(block, 'OBJECTS', Generator.ORDER_NONE) || Generator.quote_('A B C D'); + const result = Generator.quote_( + Generator.getFieldValue(block, 'RESULT', Generator.ORDER_NONE) || ' ' + ); + + // eslint-disable-next-line max-len + return `koshien.locate_objects(sq_size: ${sqSize}, cent: [${x}, ${y}], objects: ${objects}, result: ${result})\n`; + }; + Generator.koshien_targetCoordinate = function (block) { + const target = Generator.getFieldValue(block, 'TARGET') || 'player'; + const coordinate = Generator.getFieldValue(block, 'COORDINATE') || 'x'; + return [`koshien.${target}_${coordinate}`]; + }; + Generator.koshien_turnOver = function () { + return `koshien.turn_over\n`; + }; + Generator.koshien_coordinateOf = function (block) { + const where = Generator.valueToCode(block, 'WHERE', Generator.ORDER_NONE) || Generator.quote_('0:0'); + const coordinate = Generator.getFieldValue(block, 'COORDINATE') || null; + return [`koshien.coordinate_of_${coordinate}(${where})`]; + }; + + return Generator; +} diff --git a/src/lib/ruby-to-blocks-converter/index.js b/src/lib/ruby-to-blocks-converter/index.js index 6082d5a98e8..ae45c71a2f8 100644 --- a/src/lib/ruby-to-blocks-converter/index.js +++ b/src/lib/ruby-to-blocks-converter/index.js @@ -33,6 +33,7 @@ import TranslateConverter from './translate'; import MakeyMakeyConverter from './makeymakey'; import VideoConverter from './video'; import Text2SpeechConverter from './text2speech'; +import KoshienConverter from './koshien'; const messages = defineMessages({ couldNotConvertPrimitive: { @@ -113,7 +114,8 @@ class RubyToBlocksConverter { Text2SpeechConverter, Wedo2Converter, MicrobitMoreConverter, - MeshConverter + MeshConverter, + KoshienConverter ].forEach(x => x.register(this)); } diff --git a/src/lib/ruby-to-blocks-converter/koshien.js b/src/lib/ruby-to-blocks-converter/koshien.js new file mode 100644 index 00000000000..eb8b8fd47ca --- /dev/null +++ b/src/lib/ruby-to-blocks-converter/koshien.js @@ -0,0 +1,269 @@ +const Koshien = 'koshien'; + +const KoshienConverter = { + register: function (converter) { + converter.registerCallMethod('self', Koshien, 0, params => { + const {node} = params; + + return converter.createRubyExpressionBlock(Koshien, node); + }); + + converter.registerCallMethod(Koshien, 'connect_game', 1, params => { + const {receiver, args} = params; + + const name = args[0].get('sym:name'); + if (!converter.isStringOrBlock(name)) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_connectGame', 'statement'); + converter.addTextInput(block, 'NAME', name, 'player1'); + return block; + }); + + const checkX = block => { + if (converter.isBlock(block)) return true; + return block.value >= 0 && block.value <= 14; + }; + const checkY = checkX; + + converter.registerCallMethod(Koshien, 'get_map_area', 2, params => { + const {receiver, args} = params; + + if (!converter.isNumberOrBlock(args[0])) return null; + if (!checkX(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + if (!checkY(args[1])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_getMapArea', 'statement'); + converter.addNumberInput(block, 'X', 'math_number', args[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[1], 0); + return block; + }); + + converter.registerCallMethod(Koshien, 'map', 2, params => { + const {receiver, args} = params; + + if (!converter.isNumberOrBlock(args[0])) return null; + if (!checkX(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + if (!checkY(args[1])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_map', 'value'); + converter.addNumberInput(block, 'X', 'math_number', args[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[1], 0); + + return block; + }); + + converter.registerCallMethod(Koshien, 'move_to', 2, params => { + const {receiver, args} = params; + + if (!converter.isNumberOrBlock(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_moveTo', 'statement'); + converter.addNumberInput(block, 'X', 'math_number', args[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[1], 0); + return block; + }); + + converter.registerCallMethod(Koshien, 'calc_route', 1, params => { + const {receiver, args} = params; + + const srcArray = args[0].get('sym:src'); + const dstArray = args[0].get('sym:dst'); + const exceptCells = args[0].get('sym:except_cells'); + const result = args[0].get('sym:result'); + if (!converter.isNumberOrBlock(srcArray.value[0])) return null; + if (!checkX(srcArray.value[0])) return null; + if (!converter.isNumberOrBlock(srcArray.value[1])) return null; + if (!checkY(srcArray.value[1])) return null; + if (!converter.isNumberOrBlock(dstArray.value[0])) return null; + if (!checkX(dstArray.value[0])) return null; + if (!converter.isNumberOrBlock(dstArray.value[1])) return null; + if (!checkY(dstArray.value[1])) return null; + if (!converter.isString(exceptCells)) return null; + if (!converter.isString(result)) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_calcRoute', 'statement'); + converter.addNumberInput(block, 'SRC_X', 'math_number', srcArray.value[0], 0); + converter.addNumberInput(block, 'SRC_Y', 'math_number', srcArray.value[1], 0); + converter.addNumberInput(block, 'DST_X', 'math_number', dstArray.value[0], 0); + converter.addNumberInput(block, 'DST_Y', 'math_number', dstArray.value[1], 0); + converter.addField(block, 'EXCEPT_CELLS', exceptCells); + converter.addField(block, 'RESULT', result); + return block; + }); + + converter.registerCallMethod(Koshien, 'set_dynamite', 2, params => { + const {receiver, args} = params; + + if (!converter.isNumberOrBlock(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_setItem', 'statement'); + converter.addField(block, 'ITEM', 'dynamite'); + converter.addNumberInput(block, 'X', 'math_number', args[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[1], 0); + return block; + }); + + converter.registerCallMethod(Koshien, 'set_bomb', 2, params => { + const {receiver, args} = params; + + if (!converter.isNumberOrBlock(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_setItem', 'statement'); + converter.addField(block, 'ITEM', 'bomb'); + converter.addNumberInput(block, 'X', 'math_number', args[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[1], 0); + return block; + }); + + converter.registerCallMethod(Koshien, 'save_map_all', 1, params => { + const {receiver, args} = params; + + if (!converter.isStringOrBlock(args[0])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_saveMapAll', 'statement'); + converter.addTextInput(block, 'LOCATION', args[0], 'map1'); + return block; + }); + + converter.registerCallMethod(Koshien, 'load_map', 3, params => { + const {receiver, args} = params; + + if (!converter.isStringOrBlock(args[0])) return null; + if (!converter.isNumberOrBlock(args[1])) return null; + if (!checkX(args[1])) return null; + if (!converter.isNumberOrBlock(args[2])) return null; + if (!checkY(args[2])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_loadMap', 'value'); + converter.addTextInput(block, 'LOCATION', args[0], 'map1'); + converter.addNumberInput(block, 'X', 'math_number', args[1], 0); + converter.addNumberInput(block, 'Y', 'math_number', args[2], 0); + + return block; + }); + + converter.registerCallMethod(Koshien, 'locate_objects', 1, params => { + const {receiver, args} = params; + + const sqSize = args[0].get('sym:sq_size'); + const centArray = args[0].get('sym:cent'); + const objects = args[0].get('sym:objects'); + const result = args[0].get('sym:result'); + + if (!converter.isNumberOrBlock(sqSize)) return null; + if (!converter.isNumberOrBlock(centArray.value[0])) return null; + if (!checkX(centArray.value[0])) return null; + if (!converter.isNumberOrBlock(centArray.value[1])) return null; + if (!checkY(centArray.value[1])) return null; + if (!converter.isStringOrBlock(objects)) return null; + if (!converter.isStringOrBlock(result)) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_locateObjects', 'statement'); + converter.addNumberInput(block, 'SQ_SIZE', 'math_number', sqSize, 0); + converter.addNumberInput(block, 'X', 'math_number', centArray.value[0], 0); + converter.addNumberInput(block, 'Y', 'math_number', centArray.value[1], 0); + converter.addTextInput(block, 'OBJECTS', objects, '0'); + converter.addField(block, 'RESULT', result); + return block; + }); + + converter.registerCallMethod(Koshien, 'other_player_x', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'other_player'); + converter.addField(block, 'COORDINATE', 'x'); + return block; + }); + + converter.registerCallMethod(Koshien, 'other_player_y', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'other_player'); + converter.addField(block, 'COORDINATE', 'y'); + return block; + }); + + converter.registerCallMethod(Koshien, 'enemy_x', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'enemy'); + converter.addField(block, 'COORDINATE', 'x'); + return block; + }); + + converter.registerCallMethod(Koshien, 'enemy_y', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'enemy'); + converter.addField(block, 'COORDINATE', 'y'); + return block; + }); + + converter.registerCallMethod(Koshien, 'goal_x', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'goal'); + converter.addField(block, 'COORDINATE', 'x'); + return block; + }); + + converter.registerCallMethod(Koshien, 'goal_y', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'goal'); + converter.addField(block, 'COORDINATE', 'y'); + return block; + }); + + converter.registerCallMethod(Koshien, 'player_x', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'player'); + converter.addField(block, 'COORDINATE', 'x'); + return block; + }); + + converter.registerCallMethod(Koshien, 'player_y', 0, params => { + const {receiver} = params; + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_targetCoordinate', 'value'); + converter.addField(block, 'TARGET', 'player'); + converter.addField(block, 'COORDINATE', 'y'); + return block; + }); + + converter.registerCallMethod(Koshien, 'turn_over', 0, params => { + const {receiver} = params; + return converter.changeRubyExpressionBlock(receiver, 'koshien_turnOver', 'statement'); + }); + + + converter.registerCallMethod(Koshien, 'coordinate_of_x', 1, params => { + const {receiver, args} = params; + + if (!converter.isStringOrBlock(args[0])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_coordinateOf', 'value'); + converter.addTextInput(block, 'WHERE', args[0], '0:0'); + converter.addField(block, 'COORDINATE', 'x'); + return block; + }); + + converter.registerCallMethod(Koshien, 'coordinate_of_y', 1, params => { + const {receiver, args} = params; + + if (!converter.isStringOrBlock(args[0])) return null; + + const block = converter.changeRubyExpressionBlock(receiver, 'koshien_coordinateOf', 'value'); + converter.addTextInput(block, 'WHERE', args[0], '0:0'); + converter.addField(block, 'COORDINATE', 'y'); + return block; + }); + } +}; + +export default KoshienConverter; diff --git a/test/integration/ruby-tab/extension_koshien.test.js b/test/integration/ruby-tab/extension_koshien.test.js new file mode 100644 index 00000000000..c4d118bb8fd --- /dev/null +++ b/test/integration/ruby-tab/extension_koshien.test.js @@ -0,0 +1,77 @@ +import dedent from 'dedent'; +import SeleniumHelper from '../../helpers/selenium-helper'; +import RubyHelper from '../../helpers/ruby-helper'; + +const seleniumHelper = new SeleniumHelper(); +const { + getDriver, + loadUri, + urlFor +} = seleniumHelper; + +const rubyHelper = new RubyHelper(seleniumHelper); +const { + expectInterconvertBetweenCodeAndRuby +} = rubyHelper; + +let driver; + +describe('Ruby Tab: Koshien extension blocks', () => { + beforeAll(() => { + driver = getDriver(); + }); + + afterAll(async () => { + await driver.quit(); + }); + + test('Ruby -> Code -> Ruby', async () => { + await loadUri(urlFor('/')); + + const code = dedent` + list("$通らない座標") + + list("$最短経路") + + list("$地形・アイテム") + + koshien.connect_game(name: "player1") + koshien.get_map_area(0, 1) + koshien.move_to(2, 3) + koshien.calc_route(src: [4, 5], dst: [6, 7], except_cells: "通らない座標", result: "最短経路") + koshien.set_dynamite(8, 9) + koshien.set_bomb(10, 11) + + koshien.map(12, 13) + + koshien.load_map("map1", 14, 0) + + koshien.save_map_all("map1") + koshien.locate_objects(sq_size: 3, cent: [1, 2], objects: "A B C D", result: "地形・アイテム") + + koshien.other_player_x + + koshien.turn_over + + koshien.coordinate_of_x("0:1") + + koshien.coordinate_of_y("2:3") + + koshien.player_y + + koshien.player_x + + koshien.goal_x + + koshien.goal_y + + koshien.enemy_x + + koshien.enemy_y + + koshien.other_player_y + + `; + await expectInterconvertBetweenCodeAndRuby(code); + }); +});