diff --git a/.gitignore b/.gitignore index f650315..37c2b6f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ yarn-error.log* # typescript *.tsbuildinfo -next-env.d.ts \ No newline at end of file +next-env.d.ts diff --git a/README.md b/README.md index 7b7addb..d993db5 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ An integrated help system with multiple support options: ### Installation -```bash +\`\`\`bash # Clone the repository git clone https://github.com/your-username/ocean-cleanup-blocks.git @@ -106,16 +106,16 @@ npm install # Start development server npm run dev -``` +\`\`\` Open [http://localhost:3000](http://localhost:3000) in your browser. ### Building for Production -```bash +\`\`\`bash npm run build npm start -``` +\`\`\` ## Usage Guide @@ -150,7 +150,7 @@ npm start ## Project Structure -``` +\`\`\` ├── app/ │ ├── page.tsx # Main application component │ ├── layout.tsx # Root layout @@ -160,7 +160,7 @@ npm start ├── lib/ │ └── utils.ts # Utility functions └── public/ # Static assets -``` +\`\`\` ## Contributing diff --git a/app/page.tsx b/app/page.tsx index 75f65eb..dd7024d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -163,7 +163,7 @@ interface AIAssistantState { isVisible: boolean isMinimized: boolean isMaximized: boolean - surveyStep: "main" | "strategy" | "predict" | "fix" | "compare" | "feel" | "partner" + surveyStep: "main" | "strategy" | "predict" | "fix" | "compare" | "feel" | "partner" | "strategy-examples" } interface CategoryState { @@ -831,6 +831,10 @@ function BlocklyEditor() { const [trashItems, setTrashItems] = useState([]) const [gameState, setGameState] = useState({ trashCollected: 0, + isGameOver: false, + trashItems: [], + }) + const [codeView, setCodeView] = useState<"blocks" | "python">("blocks") gameLost: false, isGameOver: false, isSpawningTrash: false, @@ -1633,6 +1637,275 @@ function BlocklyEditor() { }) } + const getPythonCode = useCallback(() => { + if (!workspace) return "# No code yet" + + // Recursively generate code for a statement input (DO, ELSE, etc.) + const generateStatements = (block: any, inputName: string, indent: string): string => { + const child = block.getInputTargetBlock(inputName) + if (!child) return `${indent}pass\n` + return generateSequence(child, indent) + } + + // Walk a chain of next-connected blocks + const generateSequence = (block: any, indent: string): string => { + if (!block) return "" + return generatePythonFromBlock(block, indent) + generateSequence(block.getNextBlock(), indent) + } + + // Custom Python code generator - traverse blocks and generate Python syntax + const generatePythonFromBlock = (block: any, indent: string = ""): string => { + if (!block) return "" + + const type = block.type + let code = "" + + switch (type) { + case "when_started": + code = "# When Started\ndef main():\n" + code += generateStatements(block, "DO", " ") + code += "\nmain()" + break + + case "drive_simple": { + const dir = block.getFieldValue("DIRECTION") || "forward" + code = `${indent}drivetrain.drive(${dir.toUpperCase()})\n` + break + } + + case "drive_distance": { + const direction = block.getFieldValue("DIRECTION") || "forward" + const distance = block.getFieldValue("DISTANCE") || "200" + const unit = block.getFieldValue("UNIT") || "mm" + const unitPython = unit === "inches" ? "INCHES" : "MM" + code = `${indent}drivetrain.drive_for(${direction.toUpperCase()}, ${distance}, ${unitPython})\n` + break + } + + case "turn_simple": { + const dir = block.getFieldValue("DIRECTION") || "right" + code = `${indent}drivetrain.turn(${dir.toUpperCase()})\n` + break + } + + case "turn_degrees": { + const turnDir = block.getFieldValue("DIRECTION") || "right" + const degrees = block.getFieldValue("DEGREES") || "90" + code = `${indent}drivetrain.turn_for(${turnDir.toUpperCase()}, ${degrees}, DEGREES)\n` + break + } + + case "turn_to_heading": { + const heading = block.getFieldValue("HEADING") || "0" + code = `${indent}drivetrain.turn_to_heading(${heading}, DEGREES)\n` + break + } + + case "turn_to_rotation": { + const rotation = block.getFieldValue("ROTATION") || "0" + code = `${indent}drivetrain.turn_to_rotation(${rotation}, DEGREES)\n` + break + } + + case "set_drive_velocity": { + const velocity = block.getFieldValue("VELOCITY") || "50" + code = `${indent}drivetrain.set_drive_velocity(${velocity}, PERCENT)\n` + break + } + + case "set_turn_velocity": { + const turnVel = block.getFieldValue("VELOCITY") || "50" + code = `${indent}drivetrain.set_turn_velocity(${turnVel}, PERCENT)\n` + break + } + + case "set_drive_heading": { + const heading = block.getFieldValue("HEADING") || "0" + code = `${indent}drivetrain.set_heading(${heading}, DEGREES)\n` + break + } + + case "set_drive_rotation": { + const rotation = block.getFieldValue("ROTATION") || "0" + code = `${indent}drivetrain.set_rotation(${rotation}, DEGREES)\n` + break + } + + case "set_drive_timeout": { + const timeout = block.getFieldValue("TIMEOUT") || "1" + code = `${indent}drivetrain.set_timeout(${timeout}, SECONDS)\n` + break + } + + case "stop_driving": + code = `${indent}drivetrain.stop()\n` + break + + case "forever": + case "forever_loop": { + code = `${indent}while True:\n` + code += generateStatements(block, "DO", indent + " ") + break + } + + case "repeat": + case "repeat_times": { + const times = block.getFieldValue("TIMES") || "10" + code = `${indent}for i in range(${times}):\n` + code += generateStatements(block, "DO", indent + " ") + break + } + + case "repeat_until": { + const cond = block.getInputTargetBlock("CONDITION") + const condStr = cond ? `not ${cond.type}()` : "not condition" + code = `${indent}while ${condStr}:\n` + code += generateStatements(block, "DO", indent + " ") + break + } + + case "while_loop": { + code = `${indent}while condition:\n` + code += generateStatements(block, "DO", indent + " ") + break + } + + case "wait_seconds": { + const waitTime = block.getFieldValue("SECONDS") || "1" + code = `${indent}wait(${waitTime}, SECONDS)\n` + break + } + + case "wait_until": + code = `${indent}wait_until(condition)\n` + break + + case "if_then": { + const ifCond = block.getInputTargetBlock("CONDITION") + const ifCondStr = ifCond ? generatePythonFromBlock(ifCond, "").trim() : "condition" + code = `${indent}if ${ifCondStr}:\n` + code += generateStatements(block, "DO", indent + " ") + break + } + + case "if_then_else": { + const ifelseCond = block.getInputTargetBlock("CONDITION") + const ifelseCondStr = ifelseCond ? generatePythonFromBlock(ifelseCond, "").trim() : "condition" + code = `${indent}if ${ifelseCondStr}:\n` + code += generateStatements(block, "DO", indent + " ") + code += `${indent}else:\n` + code += generateStatements(block, "ELSE", indent + " ") + break + } + + case "break_block": + code = `${indent}break\n` + break + + case "stop_project": + code = `${indent}stop()\n` + break + + case "comment_block": { + const comment = block.getFieldValue("COMMENT") || "" + code = `${indent}# ${comment}\n` + break + } + + case "energize_magnet": { + const action = block.getFieldValue("ACTION") || "pick up" + code = action === "pick up" + ? `${indent}electromagnet.pickup()\n` + : `${indent}electromagnet.drop()\n` + break + } + + case "move_pen": { + const penAction = block.getFieldValue("POSITION") || "down" + code = `${indent}pen.move(${penAction.toUpperCase()})\n` + break + } + + case "set_pen_width": { + const width = block.getFieldValue("WIDTH") || "1" + code = `${indent}pen.set_pen_width(${width})\n` + break + } + + case "set_pen_color": { + const color = block.getFieldValue("COLOR") || "red" + code = `${indent}pen.set_pen_color("${color}")\n` + break + } + + case "print_text": { + const text = block.getFieldValue("TEXT") || "" + code = `${indent}brain.print("${text}")\n` + break + } + + case "clear_all_rows": + code = `${indent}brain.clear()\n` + break + + case "set_cursor_next_row": + code = `${indent}brain.next_row()\n` + break + + // Sensing blocks + case "bumper_pressed": + code = `bumper.pressed()` + break + + case "eye_is_near": { + const nearObj = block.getFieldValue("OBJECT") || "any" + code = `eye.is_near_object()` + break + } + + case "eye_detects_color": { + const detColor = block.getFieldValue("COLOR") || "red" + code = `eye.detect("${detColor}")` + break + } + + // Operators + case "math_arithmetic": { + const op = block.getFieldValue("OP") || "ADD" + const opMap: { [key: string]: string } = { ADD: "+", MINUS: "-", MULTIPLY: "*", DIVIDE: "/" } + const a = block.getFieldValue("A") || "0" + const b = block.getFieldValue("B") || "0" + code = `(${a} ${opMap[op] || "+"} ${b})` + break + } + + case "random_int": { + const from = block.getFieldValue("FROM") || "1" + const to = block.getFieldValue("TO") || "10" + code = `random.randint(${from}, ${to})` + break + } + + default: + code = `${indent}# ${type}()\n` + } + + return code + } + + // Get top-level blocks and generate code + const topBlocks = workspace.getTopBlocks(true) + if (topBlocks.length === 0) return "# No code yet\n# Add blocks to see Python code" + + let pythonCode = "# VEXcode VR Python\nfrom vexcode import *\nimport random\n\n" + + for (const block of topBlocks) { + pythonCode += generatePythonFromBlock(block) + } + + return pythonCode + }, [workspace]) + const handleRun = async () => { if (!workspace || !window.Blockly || isRunning) return @@ -2588,6 +2861,12 @@ function BlocklyEditor() {
VEXcode Project + Not Saving
@@ -2758,7 +3037,20 @@ function BlocklyEditor() {

Loading Blockly...

)} -
+ {/* Always keep blocklyDiv mounted, just hide with CSS */} +
+ {/* Python code view */} + {codeView === "python" && ( +
+
+                {getPythonCode()}
+              
+
+ )}
@@ -2884,6 +3176,74 @@ function BlocklyEditor() { )} + + {aiStep === "strategy-examples" && ( +
+
+
+
+

Strategies to Collect More Trash

+

Try these approaches and compare the results

+
+ +
+
+
+ {/* Approach 1 */} +
+

Approach 1: Increase Velocity

+ + {/* Movement visualization */} + + + + + + Straight line forward + + +
+ when started
+ set drive velocity to 100
+ drive forward 500 mm +
+

Result: Fast collection in one line. Good for quick focused movement.

+
+ + {/* Approach 2 */} +
+

Approach 2: Continuous Patrol Loop

+ + {/* Movement visualization */} + + + + + Square patrol pattern + + +
+ when started
+ forever
+   drive forward 300 mm
+   turn right 90 degrees +
+

Result: Covers large area continuously. Maximum trash collection.

+
+
+ +
+

Challenge: Try both approaches and see which collects more trash!

+
+
+
+
+ )} )} @@ -3014,7 +3374,10 @@ function BlocklyEditor() {

What strategy would you like help with?

-
+ ) : aiStep === "strategy-examples" ? ( +
+ +

Two approaches to move efficiently:

+ + {/* Approach 1 */} +
+

Approach 1: Increase Velocity

+

Set drive velocity to 100 at the start, then drive forward.

+
+
when started
+
set drive_velocity to 100
+
drive forward 500 mm
+
+

Why: Higher velocity = faster movement. This approach is simple and direct.

+
+ + {/* Approach 2 */} +
+

Approach 2: Add Loop for Continuous Movement

+

Use a forever loop to keep collecting trash continuously without stopping.

+
+
when started
+
forever
+
drive forward 300 mm
+
turn right 90 degrees
+
+

Why: Loops allow the robot to patrol continuously, covering more area and collecting more trash automatically.

+
+ + {/* Comparison */} +
+

Comparison:

+
+
Approach 1: Best for collecting one area quickly. Limited trash collection.
+
Approach 2: Best for collecting more trash over time. Continuously patrols the area.
+
Try both approaches and see which gets you more trash!
+
+
+
) : aiStep === "predict" ? (