A Blockly-based maze game wrapper with editable maze generation and programming interface
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.
- 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
``bash`
npm install maze-blockly-wrapper
This package requires the following peer dependencies:
`bash`
npm install react react-dom blockly
Before using any components, you must initialize Blockly to avoid the recentlyCreatedOwnerStacks error:
`tsx
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 (
onRunFinish={handleRunFinish}
maxMoves={100}
/>
);
}
`
`tsx
import React, { useEffect } from 'react';
import { MazeGame, initializeBlockly } from 'maze-blockly-wrapper';
function App() {
useEffect(() => {
// Initialize Blockly first
initializeBlockly();
}, []);
return
}
`
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 |
A standalone component for editing maze configurations.
`tsx
import { EditableMazeGrid } from '@maze/blockly-wrapper';
onConfigChange={handleConfigChange}
/>
`
The Blockly programming interface component.
`tsx
import { MazeBlocklyContainer } from '@maze/blockly-wrapper';
onCodeChange={handleCodeChange}
onExecuteCode={handleExecuteCode}
/>
`
`typescript`
interface MazeConfig {
width: number;
height: number;
spiderStart: Position;
finishPosition: Position;
walls: Position[];
estimatedSteps?: number;
}
callback when specific conditions are met.#### MazeGame Result
When sent: Triggered when the spider reaches the finish line OR when maximum moves are exceeded.
`typescript
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.
`typescript
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.
`typescript
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.
`typescript
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
}
`$3
`typescript
interface Position {
x: number;
y: number;
}
`Usage Examples
$3
`tsx
import { MazeGame } from '@maze/blockly-wrapper';
`$3
`tsx
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 }
]
};
isEditable={true}
configuration={initialConfig}
onChange={(config) => console.log('Maze changed:', config)}
onRunFinish={(result) => console.log('Game result:', result)}
maxMoves={75}
/>
`$3
`tsx
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:
,
initialXml:
}; config={customConfig}
onCodeChange={(code) => console.log('Code:', code)}
onExecuteCode={(commands) => console.log('Commands:', commands)}
/>
`$3
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.`tsx
import React, { useRef } from 'react';
import { MazeGame, MazeGameBase } from '@maze/blockly-wrapper';const App = () => {
const gameRef = useRef(null);
const handleManualCheck = () => {
if (gameRef.current) {
const result = gameRef.current.getCurrentRunResult();
console.log('Current Blocks XML:', result.blockList);
console.log('Current Stats:', result);
}
};
return (
<>
ref={gameRef}
initialXml={'... '}
/>
>
);
};
`Development
$3
`bash
Build library for distribution
npm run build:libBuild types
npm run build:typesBuild both library and app
npm run build
`$3
`bash
npm run dev
`Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
License
MIT License - see LICENSE file for details.
Troubleshooting
$3
This error occurs when Blockly is not properly initialized. Make sure to:
1. Call
initializeBlockly() before using any components
2. Install Blockly as a peer dependency: npm install blockly
3. Import Blockly in your project if using custom configurations:`typescript
import 'blockly/core';
import 'blockly/blocks';
import 'blockly/javascript';
`$3
If you're using multiple Blockly instances in your app:
1. Initialize Blockly only once at the app level
2. Properly dispose of workspaces when components unmount
3. Avoid reinitializing Blockly unnecessarily
$3
For bundlers like Webpack or Vite, ensure Blockly is properly externalized:
`javascript
// webpack.config.js
module.exports = {
externals: {
'blockly': 'Blockly'
}
};
``typescript
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
external: ['blockly']
}
}
});
`API Reference
$3
-
initializeBlockly() - Initialize Blockly for package usage
- isBlocklyReady() - Check if Blockly is ready
- getBlockly() - Get the initialized Blockly instance
- getJavaScriptGenerator() - Get the JavaScript generator
- resetBlocklyInitialization()` - Reset initialization state (for testing)For issues and questions, please use the GitLab issue tracker at:
https://gitlab.com/smartbooksdev/blocklysbwrapper