maze-blockly-wrapper
v0.7.27
Published
A Blockly-based maze game wrapper with editable maze generation and programming interface
Maintainers
Readme
@maze/blockly-wrapper
A React-based Blockly wrapper for creating interactive maze programming games. This package provides a complete maze game environment where users can program a spider to navigate through mazes using visual programming blocks.
Features
- Interactive Maze Game: Program a spider to navigate through mazes
- Visual Programming: Uses Google Blockly for drag-and-drop programming
- Editable Mazes: Create and customize mazes with an intuitive editor
- Real-time Execution: Watch your code execute step-by-step
- Responsive Design: Works on desktop and mobile devices
- TypeScript Support: Full TypeScript definitions included
Installation
npm install maze-blockly-wrapperRequired Dependencies
This package requires the following peer dependencies:
npm install react react-dom blockly⚠️ Important Setup
Before using any components, you must initialize Blockly to avoid the recentlyCreatedOwnerStacks error:
import React, { useEffect } from 'react';
import { MazeGame, initializeBlockly } from 'maze-blockly-wrapper';
function App() {
useEffect(() => {
// CRITICAL: Initialize Blockly before using any components
initializeBlockly();
}, []);
const handleRunFinish = (result) => {
console.log('Game finished!', result);
// result contains: steps, reachedFinish, maxMovesExceeded, finalPosition, path, executionTime
};
return (
<MazeGame
isEditable={true}
onRunFinish={handleRunFinish}
maxMoves={100}
/>
);
}Quick Start
import React, { useEffect } from 'react';
import { MazeGame, initializeBlockly } from 'maze-blockly-wrapper';
function App() {
useEffect(() => {
// Initialize Blockly first
initializeBlockly();
}, []);
return <MazeGame />;
}Components
MazeGame
The main component that combines the maze, programming interface, and game logic.
Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| isEditable | boolean | false | Enable maze editing mode |
| configuration | MazeConfig | undefined | Initial maze configuration |
| onChange | (config: MazeConfig) => void | undefined | Callback when maze configuration changes |
| onRunFinish | (result: RunResult) => void | undefined | Callback when game execution finishes |
| maxMoves | number | 50 | Maximum moves allowed |
| showControls | boolean | true | Show game control buttons |
| className | string | undefined | Additional CSS classes |
| initialXml | string | undefined | Initial Blockly XML content |
EditableMazeGrid
A standalone component for editing maze configurations.
import { EditableMazeGrid } from '@maze/blockly-wrapper';
<EditableMazeGrid
config={mazeConfig}
onConfigChange={handleConfigChange}
/>MazeBlocklyContainer
The Blockly programming interface component.
import { MazeBlocklyContainer } from '@maze/blockly-wrapper';
<MazeBlocklyContainer
config={blocklyConfig}
onCodeChange={handleCodeChange}
onExecuteCode={handleExecuteCode}
/>Types
MazeConfig
interface MazeConfig {
width: number;
height: number;
spiderStart: Position;
finishPosition: Position;
walls: Position[];
estimatedSteps?: number;
}RunResult
Each game component returns a result object via the onRunFinish callback when specific conditions are met.
MazeGame Result
When sent: Triggered when the spider reaches the finish line OR when maximum moves are exceeded.
interface RunResult {
steps: number; // Number of steps taken
reachedFinish: boolean; // True if spider reached the finish cell
maxMovesExceeded: boolean; // True if maxMoves limit was hit
finalPosition: Position; // Where the spider ended up
path: Position[]; // Array of positions visited (todo)
executionTime: number; // Time in ms (todo)
inventory: string[]; // Collected items (COIN, KEY, etc.)
blockList?: string; // XML string of the current block workspace
}FilmGame Result
When sent: Triggered when the user clicks the "Finish" button to check their movie against the target.
interface RunResult {
commands: number; // Total drawing commands used
reachedTarget: boolean; // True if accuracy > 80%
maxCommandsExceeded: boolean;
shapes: Shape[]; // Array of shapes drawn by student
executionTime: number; // Time in ms
accuracy: number; // 0-1 score representing similarity to target
tickCount: number; // Final tick count
comparisonResult: { // Detailed breakdown
shapeCountMatch: boolean;
shapeTypesMatch: boolean;
positionAccuracy: number;
sizeAccuracy: number;
colorAccuracy: number;
};
blockList?: string; // XML string of the current block workspace
}DrawingGame Result
When sent: Triggered automatically when code execution completes.
interface RunResult {
commands: number; // Total commands executed
reachedTarget: boolean; // True if game state marked as won (custom logic)
maxCommandsExceeded: boolean;
finalPosition: Position; // Final pen position
drawnPath: Position[]; // Array of points visited by pen
executionTime: number; // Time in ms
accuracy: number; // 0-1 score comparing drawn path to target path
blockList?: string; // XML string of the current block workspace
}MusicGame Result
When sent: Triggered automatically when the music finishes playing.
interface RunResult {
commands: number; // Total notes/pauses played
reachedTarget: boolean; // True if accuracy is 100%
maxCommandsExceeded: boolean;
notes: Note[]; // Array of notes played
executionTime: number; // Playback time in ms
accuracy: number; // 0-100 score comparing played notes to editor notes
comparisonResult: {
noteCountMatch: boolean;
melodyAccuracy: number; // Pitch/Duration match score
rhythmAccuracy: number; // Timing match score
};
blockList?: string; // XML string of the current block workspace
}Position
interface Position {
x: number;
y: number;
}Usage Examples
Basic Game
import { MazeGame } from '@maze/blockly-wrapper';
<MazeGame />Editable Maze with Configuration
import { MazeGame } from '@maze/blockly-wrapper';
const initialConfig = {
width: 12,
height: 10,
spiderStart: { x: 1, y: 1 },
finishPosition: { x: 10, y: 8 },
walls: [
{ x: 3, y: 2 },
{ x: 4, y: 3 },
{ x: 7, y: 5 }
]
};
<MazeGame
isEditable={true}
configuration={initialConfig}
onChange={(config) => console.log('Maze changed:', config)}
onRunFinish={(result) => console.log('Game result:', result)}
maxMoves={75}
/>Custom Blockly Configuration
import { MazeBlocklyContainer } from '@maze/blockly-wrapper';
const customConfig = {
allowedTypes: new Set(['maze_move_forward', 'maze_turn_left']),
limits: { maze_move_forward: 10, maze_turn_left: 5 },
toolbox: `<xml>...</xml>`,
initialXml: `<xml>...</xml>`
};
<MazeBlocklyContainer
config={customConfig}
onCodeChange={(code) => console.log('Code:', code)}
onExecuteCode={(commands) => console.log('Commands:', commands)}
/>Retrieving Results Instantaneously
All game components (MazeGame, FilmGame, DrawingGame, MusicGame) expose a getCurrentRunResult() method that can be accessed via a ref. This allows you to get the current state and blocks without waiting for the run to finish.
import React, { useRef } from 'react';
import { MazeGame, MazeGameBase } from '@maze/blockly-wrapper';
const App = () => {
const gameRef = useRef<MazeGameBase>(null);
const handleManualCheck = () => {
if (gameRef.current) {
const result = gameRef.current.getCurrentRunResult();
console.log('Current Blocks XML:', result.blockList);
console.log('Current Stats:', result);
}
};
return (
<>
<button onClick={handleManualCheck}>Check Progress</button>
<MazeGame
ref={gameRef}
initialXml={'<xml ...>...</xml>'}
/>
</>
);
};Development
Building the Package
# Build library for distribution
npm run build:lib
# Build types
npm run build:types
# Build both library and app
npm run buildDevelopment Mode
npm run devContributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
MIT License - see LICENSE file for details.
Troubleshooting
Error: "Cannot read properties of undefined (reading 'recentlyCreatedOwnerStacks')"
This error occurs when Blockly is not properly initialized. Make sure to:
- Call
initializeBlockly()before using any components - Install Blockly as a peer dependency:
npm install blockly - Import Blockly in your project if using custom configurations:
import 'blockly/core';
import 'blockly/blocks';
import 'blockly/javascript';Multiple Blockly Instances
If you're using multiple Blockly instances in your app:
- Initialize Blockly only once at the app level
- Properly dispose of workspaces when components unmount
- Avoid reinitializing Blockly unnecessarily
Build Configuration
For bundlers like Webpack or Vite, ensure Blockly is properly externalized:
// webpack.config.js
module.exports = {
externals: {
'blockly': 'Blockly'
}
};// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
external: ['blockly']
}
}
});API Reference
Blockly Setup Utilities
initializeBlockly()- Initialize Blockly for package usageisBlocklyReady()- Check if Blockly is readygetBlockly()- Get the initialized Blockly instancegetJavaScriptGenerator()- Get the JavaScript generatorresetBlocklyInitialization()- Reset initialization state (for testing)
Support
For issues and questions, please use the GitLab issue tracker at: https://gitlab.com/smartbooksdev/blocklysbwrapper
