Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,6 @@ const SettingsMenu = ({
const availableThemesLength = useMemo(() => Object.keys(availableThemesMap).length, [availableThemesMap]);

const handleChangeRubyVersion = useCallback(rubyVersion => {
if (rubyVersion === '2' && vm.extensionManager && vm.extensionManager.isExtensionLoaded('koshien')) {
// eslint-disable-next-line no-alert
alert(intl.formatMessage(rubyVersionMessages.koshienCannotChangeRubyVersion));
return;
}
// === Smalruby: Start of v1 switch prevention ===
// Prevent switching to v1 when v2 features (module/class) are in use
if (rubyVersion === '1' && vm.runtime) { // eslint-disable-line react/prop-types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -323,14 +323,6 @@ const MobileDrawerComponent = ({
onClose();
return;
}
// v1 へ切替時は v2 機能が使われていないか確認する。
// koshien 拡張のチェックは settings-menu 側でもやっているので、
// モバイルでもそちらのフローに合わせて同じ guard を入れる。
if (version === '2' && vm?.extensionManager?.isExtensionLoaded?.('koshien')) {
// eslint-disable-next-line no-alert
alert(intl.formatMessage(rubyVersionMessages.koshienCannotChangeRubyVersion));
return;
}
if (version === VERSION_1 && hasV2Features(vm)) {
// eslint-disable-next-line no-alert
alert(intl.formatMessage(rubyVersionMessages.cannotSwitchToV1));
Expand Down
18 changes: 2 additions & 16 deletions packages/scratch-gui/src/containers/extension-library.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,6 @@ const messages = defineMessages({
'Otherwise, if you want to use the new mesh extension, select Cancel.',
description: 'Warning message for legacy mesh extension deprecation',
id: 'gui.extensionLibrary.meshDeprecationWarning'
},
koshienOnlyAvailableForRubyV1: {
defaultMessage: 'The Koshien extension is only available for Ruby v1',
description: 'Error message when trying to use Koshien extension with non-Ruby v1',
id: 'gui.extensionLibrary.koshienOnlyAvailableForRubyV1'
}
});

Expand Down Expand Up @@ -81,13 +76,6 @@ class ExtensionLibrary extends React.PureComponent {
}
}

if (id === 'koshien' && !item.disabled && this.props.rubyVersion !== '1') {
// Koshien extension is only available for Ruby v1
// eslint-disable-next-line no-alert
alert(this.props.intl.formatMessage(messages.koshienOnlyAvailableForRubyV1));
return;
}

if (id && !item.disabled) {
if (this.props.vm.extensionManager.isExtensionLoaded(url)) {
this.props.onCategorySelected(id);
Expand Down Expand Up @@ -162,13 +150,11 @@ ExtensionLibrary.propTypes = {
onToggleShowAllExtensions: PropTypes.func,
showAllExtensions: PropTypes.bool,
visible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired,
rubyVersion: PropTypes.string
vm: PropTypes.instanceOf(VM).isRequired
};

const mapStateToProps = state => ({
showAllExtensions: state.scratchGui.extensionFilter.showAllExtensions,
rubyVersion: state.scratchGui.settings.rubyVersion
showAllExtensions: state.scratchGui.extensionFilter.showAllExtensions
});

const mapDispatchToProps = dispatch => ({
Expand Down
32 changes: 28 additions & 4 deletions packages/scratch-gui/src/lib/ruby-generator/koshien.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
/**
* Render a list-typed argument for a koshien block.
*
* Lists differ between Ruby versions:
* - v1: `list("$名前")` (Smalruby3::List wrapper)
* - v2: `$名前` (plain global array variable; `list()` syntax is rejected in v2)
* @param {RubyGenerator} Generator - the RubyGenerator.
* @param {?string} listName - the resolved list name (e.g. "$名前") or null.
* @returns {string} - the argument expression, or 'nil' when no list.
*/
const koshienListArg = (Generator, listName) => {
if (!listName) {
return 'nil';
}
return String(Generator.version) === '2' ? listName : `list(${Generator.quote_(listName)})`;
};

/**
* Define Ruby code generator for Microbit More Blocks
* @param {RubyGenerator} Generator The RubyGenerator
Expand All @@ -6,6 +23,13 @@
export default function (Generator) {
Generator.koshien_connectGame = function (block) {
const name = Generator.valueToCode(block, 'NAME', Generator.ORDER_NONE) || Generator.quote_('player1');
// v2: ゲーム接続をイベント hat として表現し、AI 本体を do...end (サブスタック) に包む。
// これによりクラス表現 (class < Smalruby3::Sprite) の中に配置できる。
// v1: 従来どおりフラットな文 (Sprite.new do...end の中で逐次実行)。
if (String(Generator.version) === '2') {
block.isStatement = true;
return `koshien.when_connect_game(name: ${name}) do\n`;
}
return `koshien.connect_game(name: ${name})\n`;
};

Expand All @@ -28,7 +52,7 @@ export default function (Generator) {
const resultListName = Generator.listNameByName(
Generator.getFieldValue(block, 'RESULT', Generator.ORDER_NONE)
);
const result = resultListName ? `list(${Generator.quote_(resultListName)})` : 'nil';
const result = koshienListArg(Generator, resultListName);

return `koshien.calc_route(result: ${result})\n`;
};
Expand All @@ -39,11 +63,11 @@ export default function (Generator) {
const exceptCellsListName = Generator.listNameByName(
Generator.getFieldValue(block, 'EXCEPT_CELLS', Generator.ORDER_NONE)
);
const exceptCells = exceptCellsListName ? `list(${Generator.quote_(exceptCellsListName)})` : 'nil';
const exceptCells = koshienListArg(Generator, exceptCellsListName);
const resultListName = Generator.listNameByName(
Generator.getFieldValue(block, 'RESULT', Generator.ORDER_NONE)
);
const result = resultListName ? `list(${Generator.quote_(resultListName)})` : 'nil';
const result = koshienListArg(Generator, resultListName);

return `koshien.calc_route(result: ${result}, src: ${src}, dst: ${dst}, except_cells: ${exceptCells})\n`;
};
Expand Down Expand Up @@ -74,7 +98,7 @@ export default function (Generator) {
const resultListName = Generator.listNameByName(
Generator.getFieldValue(block, 'RESULT', Generator.ORDER_NONE)
);
const result = resultListName ? `list(${Generator.quote_(resultListName)})` : 'nil';
const result = koshienListArg(Generator, resultListName);


return `koshien.locate_objects(result: ${result}, sq_size: ${sqSize}, cent: ${position}, objects: ${objects})\n`;
Expand Down
87 changes: 79 additions & 8 deletions packages/scratch-gui/src/lib/ruby-to-blocks-converter/koshien.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,69 @@
import {convertToListBlock} from './variable-hash-ops';
import {RubyToBlocksConverterError} from './errors';

const Koshien = 'koshien';

// convertToListBlock のみが参照する。version >= 2 でのみ呼ぶので実際には発火しない。
const LIST_ARG_MESSAGES = {
arraySyntaxNotAvailableInV1: {
id: 'gui.smalruby3.rubyToBlocksConverter.koshien.arraySyntaxNotAvailableInV1',
defaultMessage: 'Array syntax is only available in Ruby version 2.',
description: 'Error when array syntax is used for a koshien list argument in Ruby version 1',
},
};

// v2 ではゲーム接続をイベント hat (`koshien.when_connect_game(name:) do ... end`) で表現する。
// フラットな `koshien.connect_game(name:)` は v1 専用 (v2 ではクラス本体に置けないため)。
const CONNECT_GAME_MESSAGES = {
connectGameNotAvailableInV2: {
id: 'gui.smalruby3.rubyToBlocksConverter.koshien.connectGameNotAvailableInV2',
defaultMessage:
'koshien.connect_game is only available in Ruby version 1.\n' +
'Please use koshien.when_connect_game(name: ...) do ... end instead.',
description: 'Error when flat koshien.connect_game is used in Ruby version 2',
},
};

const KoshienConverter = {
register: function (converter) {
// リスト引数の解決:
// - v1: `list("$名前")` … data_listcontents ブロック
// - v2: `$名前` … data_variable ブロックを data_listcontents に変換する
// 返り値 {ok, name}: ok=false は不正な引数。name=null は nil (リスト未指定)。
const resolveListArg = block => {
if (converter.isNil(block)) {
return {ok: true, name: null};
}
if (converter.isListBlock(block)) {
return {ok: true, name: converter.lookupListFromListBlock(block)?.name || ' '};
}
if (String(converter.version) === '2' && converter.isVariableBlock(block)) {
const {block: listBlock, converted} = convertToListBlock(converter, LIST_ARG_MESSAGES, block);
if (converted && listBlock) {
return {ok: true, name: converter.lookupListFromListBlock(listBlock)?.name || ' '};
}
}
return {ok: false, name: null};
};

converter.registerOnSend('self', Koshien, 0, params => {
const {node} = params;

return converter.createRubyExpressionBlock(Koshien, node);
});

// v1: フラットな `koshien.connect_game(name:)` を statement として解析。
// v2: フラット形式はクラス本体に置けないためエラーにする (when_connect_game を使う)。
converter.registerOnSend(Koshien, 'connect_game', 1, params => {
const {receiver, args} = params;

if (String(converter.version) === '2') {
throw new RubyToBlocksConverterError(
converter._context.currentNode,
converter._translator(CONNECT_GAME_MESSAGES.connectGameNotAvailableInV2),
);
}

const name = args[0].get('sym:name');
if (!converter.isStringOrBlock(name)) return null;

Expand All @@ -19,6 +72,20 @@ const KoshienConverter = {
return block;
});

// v2: `koshien.when_connect_game(name:) do ... end` をイベント hat として解析。
// do...end の本体を hat のサブスタックに取り込む。
converter.registerOnSendWithBlock(Koshien, 'when_connect_game', 1, 0, params => {
const {receiver, args, rubyBlock} = params;

const name = args[0].get('sym:name');
if (!converter.isStringOrBlock(name)) return null;

const block = converter.changeRubyExpressionBlock(receiver, 'koshien_connectGame', 'hat');
converter.addTextInput(block, 'NAME', name, 'player1');
converter.setParent(rubyBlock, block);
return block;
});

const checkPosition = block => {
if (converter.isBlock(block)) return true;
if (!converter.isString(block)) return false;
Expand Down Expand Up @@ -67,25 +134,28 @@ const KoshienConverter = {
const result = args[0].get('sym:result');

if (!src && !dst && !exceptCells) {
if (!converter.isListBlock(result) && !converter.isNil(result)) return null;
const r = resolveListArg(result);
if (!r.ok) return null;

const block = converter.changeRubyExpressionBlock(receiver, 'koshien_calcGoalRoute', 'statement');
converter.addField(block, 'RESULT', converter.lookupListFromListBlock(result)?.name || ' ');
converter.addField(block, 'RESULT', r.name || ' ');
converter.removeBlock(result);
return block;
}

if (!checkPosition(src)) return null;
if (!checkPosition(dst)) return null;
if (!converter.isListBlock(exceptCells) && !converter.isNil(exceptCells)) return null;
if (!converter.isListBlock(result) && !converter.isNil(result)) return null;
const ec = resolveListArg(exceptCells);
if (!ec.ok) return null;
const r = resolveListArg(result);
if (!r.ok) return null;

const block = converter.changeRubyExpressionBlock(receiver, 'koshien_calcRoute', 'statement');
converter.addTextInput(block, 'SRC', src, '0:0');
converter.addTextInput(block, 'DST', dst, '0:0');
converter.addField(block, 'EXCEPT_CELLS', converter.lookupListFromListBlock(exceptCells)?.name || ' ');
converter.addField(block, 'EXCEPT_CELLS', ec.name || ' ');
converter.removeBlock(exceptCells);
converter.addField(block, 'RESULT', converter.lookupListFromListBlock(result)?.name || ' ');
converter.addField(block, 'RESULT', r.name || ' ');
converter.removeBlock(result);
return block;
});
Expand Down Expand Up @@ -142,13 +212,14 @@ const KoshienConverter = {
if (!converter.isNumberOrBlock(sqSize)) return null;
if (!checkPosition(cent)) return null;
if (!converter.isStringOrBlock(objects)) return null;
if (!converter.isListBlock(result) && !converter.isNil(result)) return null;
const r = resolveListArg(result);
if (!r.ok) return null;

const block = converter.changeRubyExpressionBlock(receiver, 'koshien_locateObjects', 'statement');
converter.addNumberInput(block, 'SQ_SIZE', 'math_number', sqSize, 0);
converter.addTextInput(block, 'POSITION', cent, '0:0');
converter.addTextInput(block, 'OBJECTS', objects, 'ABCD');
converter.addField(block, 'RESULT', converter.lookupListFromListBlock(result)?.name || ' ');
converter.addField(block, 'RESULT', r.name || ' ');
converter.removeBlock(result);
return block;
});
Expand Down
5 changes: 0 additions & 5 deletions packages/scratch-gui/src/lib/settings/ruby-version/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ const messages = defineMessages({
defaultMessage: 'Ruby',
description: 'Ruby version sub-menu'
},
koshienCannotChangeRubyVersion: {
id: 'gui.menuBar.koshienCannotChangeRubyVersion',
defaultMessage: 'The Ruby version cannot be changed when the Koshien extension is loaded.',
description: 'Alert message when trying to change Ruby version with Koshien extension'
},
cannotSwitchToV1: {
id: 'gui.menuBar.cannotSwitchToV1',
defaultMessage: 'Cannot switch to v1 because v2 features (module, class) are in use.' +
Expand Down
3 changes: 0 additions & 3 deletions packages/scratch-gui/src/locales/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,6 @@ export default {
'gui.extensionLibrary.showAllExtensions': 'Show all extensions',
'gui.extensionLibrary.meshDeprecationWarning':
'The legacy mesh extension can only be used until April 30. If you want to continue using the legacy mesh extension, select OK. Otherwise, if you want to use the new mesh extension, select Cancel.',
'gui.extensionLibrary.koshienOnlyAvailableForRubyV1': 'The Koshien extension is only available for Ruby v1',
'gui.smalruby3.rubyToBlocksConverter.couldNotConvertPrimitive': '"{ SOURCE }" could not be converted the block.',
'gui.smalruby3.rubyToBlocksConverter.wrongInstruction': '"{ SOURCE }" is the wrong instruction.',
'gui.smalruby3.rubyToBlocksConverter.wrongInstructionInClass':
Expand Down Expand Up @@ -327,8 +326,6 @@ export default {
'gui.menuBar.blockDisplay': 'Block Display...',
'gui.menuBar.tutorials': 'Tutorials',
'gui.menuBar.koshienEntryForm': 'Entry Form',
'gui.menuBar.koshienCannotChangeRubyVersion':
'The Ruby version cannot be changed when the Koshien extension is loaded.',
'gui.rubyVersion.v1': 'v1',
'gui.rubyVersion.v2': 'v2 (default)',

Expand Down
4 changes: 0 additions & 4 deletions packages/scratch-gui/src/locales/ja-Hira.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,6 @@ export default {
'gui.menuBar.saveAIAs': 'AIになまえをつけてほぞん...',
'gui.menuBar.testAI': 'AIをためす',
'gui.menuBar.koshienEntryForm': 'さんかもうしこみ',
'gui.menuBar.koshienCannotChangeRubyVersion':
'スモウルビーこうしえんかくちょうきのうがよみこまれているときは、Rubyのバージョンをへんこうできません。',
'gui.menuBar.aiSaving': 'ルビーをほぞんちゅう...',
'gui.menuBar.aiSaved': 'ルビーがほぞんされました。',
'gui.smalruby3.alerts.rubyVersionChangeFailed':
Expand Down Expand Up @@ -750,8 +748,6 @@ export default {
'gui.extensionLibrary.showAllExtensions': 'すべてのかくちょうきのうをひょうじ',
'gui.extensionLibrary.meshDeprecationWarning':
'じゅうらいのメッシュかくちょうきのうは4がつ30にちまでしかつかえません。このままじゅうらいのメッシュかくちょうきのうをりようするばあいはOKをせんたくします。そうではなく、あたらしいメッシュかくちょうきのうをつかうばあいはキャンセルをせんたくします。',
'gui.extensionLibrary.koshienOnlyAvailableForRubyV1':
'スモウルビーこうしえんかくちょうきのうは Ruby v1 でのみりようできます。',

// MicroBit More - Tilt gesture labels (override to match microbit extension)
'mbitMore.gesturesMenu.tiltUp': 'うえにかたむいた',
Expand Down
3 changes: 0 additions & 3 deletions packages/scratch-gui/src/locales/ja.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,6 @@ export default {
'gui.menuBar.saveAIAs': 'AIに名前をつけて保存...',
'gui.menuBar.testAI': 'AIを試す',
'gui.menuBar.koshienEntryForm': '参加申し込み',
'gui.menuBar.koshienCannotChangeRubyVersion':
'スモウルビー甲子園拡張機能が読み込まれているときは、Rubyのバージョンを変更できません。',
'gui.menuBar.aiSaving': 'ルビーを保存中...',
'gui.menuBar.aiSaved': 'ルビーが保存されました。',
'gui.smalruby3.alerts.rubyVersionChangeFailed':
Expand Down Expand Up @@ -721,7 +719,6 @@ export default {
'gui.extensionLibrary.showAllExtensions': 'すべての拡張機能を表示',
'gui.extensionLibrary.meshDeprecationWarning':
'従来のメッシュ拡張機能は4月30日までしか使えません。このまま従来のメッシュ拡張機能を利用する場合はOKを選択します。そうではなく、あたらしいメッシュ拡張機能を使う場合はキャンセルを選択します。',
'gui.extensionLibrary.koshienOnlyAvailableForRubyV1': 'スモウルビー甲子園拡張機能は Ruby v1 でのみ利用できます。',

// MicroBit More - Tilt gesture labels (override to match microbit extension)
'mbitMore.gesturesMenu.tiltUp': '上に傾いた',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ describe('MobileDrawer', () => {
}
});

test('switching to v2 with koshien loaded shows alert', () => {
test('switching to v2 with koshien loaded is allowed (koshien supports v2)', () => {
const alertSpy = jest.spyOn(window, 'alert').mockImplementation(() => {});
const vm = {
runtime: { targets: [] },
Expand All @@ -301,8 +301,8 @@ describe('MobileDrawer', () => {
fireEvent.click(getByTestId('mobile-drawer-toggle-settings'));
fireEvent.click(getByTestId('mobile-drawer-toggle-settings-ruby'));
fireEvent.click(getByTestId('mobile-drawer-ruby-version-2'));
expect(alertSpy).toHaveBeenCalledTimes(1);
expect(props.onChangeRubyVersion).not.toHaveBeenCalled();
expect(alertSpy).not.toHaveBeenCalled();
expect(props.onChangeRubyVersion).toHaveBeenCalledWith('2');
} finally {
alertSpy.mockRestore();
}
Expand Down
Loading
Loading