Skip to content

Instantly share code, notes, and snippets.

@netomx
Created June 9, 2026 07:04
Show Gist options
  • Select an option

  • Save netomx/560542b6f500d774f1d1f258c507dce6 to your computer and use it in GitHub Desktop.

Select an option

Save netomx/560542b6f500d774f1d1f258c507dce6 to your computer and use it in GitHub Desktop.
LOGO clone, made with Qwen3.6
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LOGO - Educational Programming Language</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=VT323&family=Press+Start+2P&display=swap');
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: #1a1a2e;
color: #e0e0e0;
font-family: 'VT323', monospace;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
overflow-x: hidden;
}
header {
text-align: center;
padding: 20px;
width: 100%;
background: linear-gradient(180deg, #16213e, #1a1a2e);
border-bottom: 3px solid #0f3460;
}
header h1 {
font-family: 'Press Start 2P', cursive;
font-size: 2.2em;
color: #e94560;
text-shadow: 0 0 20px rgba(233,69,96,0.5), 0 0 40px rgba(233,69,96,0.2);
letter-spacing: 8px;
}
header p {
font-size: 1.3em;
color: #7f8fa6;
margin-top: 8px;
letter-spacing: 2px;
}
.main-container {
display: flex;
gap: 20px;
padding: 20px;
max-width: 1200px;
width: 100%;
flex-wrap: wrap;
justify-content: center;
}
.canvas-panel {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 30px rgba(15,52,96,0.5);
}
.canvas-panel h2 {
color: #e94560;
font-size: 1.4em;
margin-bottom: 10px;
text-align: center;
letter-spacing: 3px;
}
canvas {
background: #0a0a1a;
border: 1px solid #0f3460;
border-radius: 4px;
display: block;
cursor: crosshair;
}
.right-panel {
display: flex;
flex-direction: column;
gap: 15px;
min-width: 340px;
max-width: 420px;
flex: 1;
}
.command-input-panel {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 20px rgba(15,52,96,0.4);
}
.command-input-panel h2 {
color: #e94560;
font-size: 1.4em;
margin-bottom: 10px;
text-align: center;
letter-spacing: 3px;
}
.command-row {
display: flex;
gap: 8px;
}
.logo-prompt {
color: #53d769;
font-size: 1.5em;
user-select: none;
}
#commandInput {
flex: 1;
background: #0a0a1a;
border: 1px solid #0f3460;
color: #53d769;
font-family: 'VT323', monospace;
font-size: 1.4em;
padding: 8px 12px;
border-radius: 4px;
outline: none;
caret-color: #53d769;
}
#commandInput:focus {
border-color: #e94560;
box-shadow: 0 0 10px rgba(233,69,96,0.3);
}
#commandInput::placeholder {
color: #2a2a4a;
}
.button-row {
display: flex;
gap: 8px;
margin-top: 10px;
flex-wrap: wrap;
}
.btn {
background: #0f3460;
color: #e0e0e0;
border: 1px solid #1a4a8a;
font-family: 'VT323', monospace;
font-size: 1.1em;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
letter-spacing: 1px;
}
.btn:hover {
background: #1a4a8a;
border-color: #e94560;
color: #fff;
box-shadow: 0 0 10px rgba(233,69,96,0.3);
}
.btn:active {
transform: scale(0.97);
}
.btn.danger {
background: #5c1a1a;
border-color: #e94560;
}
.btn.danger:hover {
background: #8a2a2a;
}
.btn.success {
background: #1a4a2a;
border-color: #53d769;
}
.btn.success:hover {
background: #2a6a3a;
}
.output-panel {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 20px rgba(15,52,96,0.4);
flex: 1;
}
.output-panel h2 {
color: #e94560;
font-size: 1.4em;
margin-bottom: 10px;
text-align: center;
letter-spacing: 3px;
}
#output {
background: #0a0a1a;
border: 1px solid #0f3460;
border-radius: 4px;
padding: 12px;
height: 200px;
overflow-y: auto;
font-size: 1.2em;
line-height: 1.6;
}
#output::-webkit-scrollbar {
width: 8px;
}
#output::-webkit-scrollbar-track {
background: #0a0a1a;
}
#output::-webkit-scrollbar-thumb {
background: #0f3460;
border-radius: 4px;
}
.output-line {
margin-bottom: 2px;
}
.output-line.cmd {
color: #53d769;
}
.output-line.result {
color: #ffd700;
}
.output-line.error {
color: #e94560;
}
.output-line.info {
color: #7f8fa6;
}
.commands-list {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 20px rgba(15,52,96,0.4);
}
.commands-list h2 {
color: #e94560;
font-size: 1.4em;
margin-bottom: 10px;
text-align: center;
letter-spacing: 3px;
}
.commands-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
}
.cmd-item {
background: #0a0a1a;
border: 1px solid #0f3460;
border-radius: 4px;
padding: 6px 10px;
font-size: 1em;
cursor: pointer;
transition: all 0.2s;
}
.cmd-item:hover {
border-color: #53d769;
color: #53d769;
background: #0a1a2a;
}
.cmd-item .cmd-name {
color: #ffd700;
font-weight: bold;
}
.cmd-item .cmd-desc {
color: #7f8fa6;
font-size: 0.85em;
}
.status-bar {
display: flex;
justify-content: space-between;
padding: 6px 12px;
background: #0a0a1a;
border: 1px solid #0f3460;
border-radius: 4px;
margin-top: 8px;
font-size: 1em;
color: #7f8fa6;
}
.status-bar span {
display: flex;
align-items: center;
gap: 5px;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-indicator.pen-down { background: #53d769; }
.status-indicator.pen-up { background: #e94560; }
.examples-panel {
background: #16213e;
border: 2px solid #0f3460;
border-radius: 8px;
padding: 15px;
box-shadow: 0 0 20px rgba(15,52,96,0.4);
}
.examples-panel h2 {
color: #e94560;
font-size: 1.4em;
margin-bottom: 10px;
text-align: center;
letter-spacing: 3px;
}
.example-btn {
display: block;
width: 100%;
text-align: left;
background: #0a0a1a;
border: 1px solid #0f3460;
color: #e0e0e0;
font-family: 'VT323', monospace;
font-size: 1.1em;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 6px;
cursor: pointer;
transition: all 0.2s;
}
.example-btn:hover {
border-color: #ffd700;
color: #ffd700;
background: #0a1a2a;
}
.example-btn .example-title {
color: #ffd700;
}
.example-btn .example-desc {
color: #7f8fa6;
font-size: 0.85em;
}
@media (max-width: 800px) {
.main-container {
flex-direction: column;
align-items: center;
}
.right-panel {
min-width: unset;
width: 100%;
max-width: 600px;
}
header h1 {
font-size: 1.4em;
}
}
.turtle-trail {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.speed-control {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.speed-control label {
color: #7f8fa6;
font-size: 1em;
}
#speedSlider {
flex: 1;
-webkit-appearance: none;
background: #0a0a1a;
height: 6px;
border-radius: 3px;
outline: none;
border: 1px solid #0f3460;
}
#speedSlider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #e94560;
cursor: pointer;
}
#speedValue {
color: #53d769;
font-size: 1em;
min-width: 30px;
text-align: right;
}
footer {
padding: 15px;
text-align: center;
color: #2a2a4a;
font-size: 1em;
border-top: 1px solid #0f3460;
width: 100%;
margin-top: 20px;
}
</style>
</head>
<body>
<header>
<h1>◆ LOGO ◆</h1>
<p>Educational Programming Language — Designed in 1967 by Wally Feurzeig & Seymour Papert</p>
</header>
<div class="main-container">
<div class="canvas-panel">
<h2>⬡ TURTLE GRAPHICS ⬡</h2>
<canvas id="canvas" width="560" height="420"></canvas>
<div class="status-bar">
<span>X: <span id="statusX">0</span></span>
<span>Y: <span id="statusY">0</span></span>
<span>Angle: <span id="statusAngle">0</span>°</span>
<span>Pen: <span class="status-indicator pen-down" id="penIndicator"></span> <span id="penStatus">DOWN</span></span>
<span>Color: <span id="statusColor" style="color:#ffd700"></span></span>
</div>
<div class="speed-control">
<label>Speed:</label>
<input type="range" id="speedSlider" min="1" max="100" value="50">
<span id="speedValue">50</span>
</div>
</div>
<div class="right-panel">
<div class="command-input-panel">
<h2>⌨ COMMAND INPUT ⌨</h2>
<div class="command-row">
<span class="logo-prompt">logo&gt;</span>
<input type="text" id="commandInput" placeholder="Type a command..." autocomplete="off">
</div>
<div class="button-row">
<button class="btn success" onclick="executeCommand()">RUN</button>
<button class="btn" onclick="executeLine()">ENTER</button>
<button class="btn" onclick="undoCommand()">↶ UNDO</button>
<button class="btn danger" onclick="clearAll()">✕ CLEAR</button>
</div>
</div>
<div class="output-panel">
<h2>📟 OUTPUT 📟</h2>
<div id="output"></div>
</div>
<div class="commands-list">
<h2>📖 COMMANDS 📖</h2>
<div class="commands-grid">
<div class="cmd-item" onclick="insertCmd('FD <n>')"><span class="cmd-name">FD</span> <span class="cmd-desc">Move forward</span></div>
<div class="cmd-item" onclick="insertCmd('BK <n>')"><span class="cmd-name">BK</span> <span class="cmd-desc">Move backward</span></div>
<div class="cmd-item" onclick="insertCmd('LT <n>')"><span class="cmd-name">LT</span> <span class="cmd-desc">Turn left</span></div>
<div class="cmd-item" onclick="insertCmd('RT <n>')"><span class="cmd-name">RT</span> <span class="cmd-desc">Turn right</span></div>
<div class="cmd-item" onclick="insertCmd('HOME')"><span class="cmd-name">HOME</span> <span class="cmd-desc">Go home</span></div>
<div class="cmd-item" onclick="insertCmd('CS')"><span class="cmd-name">CS</span> <span class="cmd-desc">Clear screen</span></div>
<div class="cmd-item" onclick="insertCmd('PU')"><span class="cmd-name">PU</span> <span class="cmd-desc">Pen up</span></div>
<div class="cmd-item" onclick="insertCmd('PD')"><span class="cmd-name">PD</span> <span class="cmd-desc">Pen down</span></div>
<div class="cmd-item" onclick="insertCmd('SETCOLOR <c>')"><span class="cmd-name">SETCOLOR</span> <span class="cmd-desc">Set color</span></div>
<div class="cmd-item" onclick="insertCmd('SETPC')"><span class="cmd-name">SETPC</span> <span class="cmd-desc">Random color</span></div>
<div class="cmd-item" onclick="insertCmd('SETX <n>')"><span class="cmd-name">SETX</span> <span class="cmd-desc">Set X position</span></div>
<div class="cmd-item" onclick="insertCmd('SETY <n>')"><span class="cmd-name">SETY</span> <span class="cmd-desc">Set Y position</span></div>
<div class="cmd-item" onclick="insertCmd('SETHEADING <n>')"><span class="cmd-name">SETHEADING</span> <span class="cmd-desc">Set heading</span></div>
<div class="cmd-item" onclick="insertCmd('SHOWTURTLE')"><span class="cmd-name">SHOWTURTLE</span> <span class="cmd-desc">Show turtle</span></div>
<div class="cmd-item" onclick="insertCmd('HIDETURTLE')"><span class="cmd-name">HIDETURTLE</span> <span class="cmd-desc">Hide turtle</span></div>
<div class="cmd-item" onclick="insertCmd('REPEAT <n> [<cmds>]')"><span class="cmd-name">REPEAT</span> <span class="cmd-desc">Repeat commands</span></div>
</div>
</div>
<div class="examples-panel">
<h2>✨ EXAMPLES ✨</h2>
<button class="example-btn" onclick="loadExample('square')">
<span class="example-title">▢ Square</span><br>
<span class="example-desc">REPEAT 4 [FD 100 RT 90]</span>
</button>
<button class="example-btn" onclick="loadExample('hexagon')">
<span class="example-title">⬡ Hexagon</span><br>
<span class="example-desc">REPEAT 6 [FD 100 RT 60]</span>
</button>
<button class="example-btn" onclick="loadExample('star')">
<span class="example-title">★ Star</span><br>
<span class="example-desc">REPEAT 5 [FD 150 RT 144]</span>
</button>
<button class="example-btn" onclick="loadExample('spiral')">
<span class="example-title">🌀 Spiral</span><br>
<span class="example-desc">REPEAT 100 [FD 5 RT 1]</span>
</button>
<button class="example-btn" onclick="loadExample('flower')">
<span class="example-title">🌸 Flower</span><br>
<span class="example-desc">REPEAT 36 [FD 80 RT 100 FD 80 BK 80 RT 10]</span>
</button>
<button class="example-btn" onclick="loadExample('sunburst')">
<span class="example-title">☀ Sunburst</span><br>
<span class="example-desc">REPEAT 36 [SETCOLOR RANDOM FD 120 RT 10]</span>
</button>
<button class="example-btn" onclick="loadExample('tree')">
<span class="example-title">🌲 Recursive Tree</span><br>
<span class="example-desc">Uses DEFINE to create a fractal tree</span>
</button>
<button class="example-btn" onclick="loadExample('mandala')">
<span class="example-title">🔮 Mandala</span><br>
<span class="example-desc">REPEAT 12 [REPEAT 36 [FD 3 RT 10] RT 30]</span>
</button>
</div>
</div>
</div>
<footer>
LOGO — A programming language designed for learning — MIT Media Lab — 1967
</footer>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const output = document.getElementById('output');
const commandInput = document.getElementById('commandInput');
// Turtle state
let turtle = {
x: 280,
y: 210,
angle: -90, // pointing up
penDown: true,
color: '#ffd700',
visible: true,
size: 10
};
// Drawing state
let trail = []; // array of {x1,y1,x2,y2,color}
let undoStack = [];
// Color palette
const COLORS = {
'RED': '#e94560',
'GREEN': '#53d769',
'BLUE': '#4a90d9',
'YELLOW': '#ffd700',
'CYAN': '#00bcd4',
'MAGENTA': '#e040fb',
'WHITE': '#ffffff',
'ORANGE': '#ff9800',
'PURPLE': '#9c27b0',
'PINK': '#f48fb1',
'LIME': '#00e676',
'TEAL': '#009688',
'NAVY': '#1a237e',
'BROWN': '#795548',
'GRAY': '#9e9e9e',
'BLACK': '#1a1a2e',
'CORAL': '#ff6f61',
'GOLD': '#ffd700'
};
const COLOR_NAMES = Object.keys(COLORS);
// Speed control
const speedSlider = document.getElementById('speedSlider');
const speedValue = document.getElementById('speedValue');
speedSlider.addEventListener('input', () => {
speedValue.textContent = speedSlider.value;
});
function getDelay() {
const v = parseInt(speedSlider.value);
return Math.max(1, 200 - v * 1.9);
}
function addOutput(text, className = 'cmd') {
const line = document.createElement('div');
line.className = `output-line ${className}`;
line.textContent = text;
output.appendChild(line);
output.scrollTop = output.scrollHeight;
}
function insertCmd(cmd) {
commandInput.value = cmd;
commandInput.focus();
}
function updateStatus() {
document.getElementById('statusX').textContent = Math.round(turtle.x);
document.getElementById('statusY').textContent = Math.round(turtle.y);
let deg = ((turtle.angle % 360) + 360) % 360;
document.getElementById('statusAngle').textContent = Math.round(deg);
document.getElementById('penStatus').textContent = turtle.penDown ? 'DOWN' : 'UP';
const pi = document.getElementById('penIndicator');
pi.className = `status-indicator ${turtle.penDown ? 'pen-down' : 'pen-up'}`;
document.getElementById('statusColor').textContent = '◆';
document.getElementById('statusColor').style.color = turtle.color;
}
function drawTurtle() {
if (!turtle.visible) return;
const rad = (turtle.angle * Math.PI) / 180;
const size = turtle.size;
ctx.save();
ctx.translate(turtle.x, turtle.y);
ctx.rotate(rad);
// Turtle body (triangle)
ctx.beginPath();
ctx.moveTo(size, 0);
ctx.lineTo(-size * 0.6, -size * 0.7);
ctx.lineTo(-size * 0.3, 0);
ctx.lineTo(-size * 0.6, size * 0.7);
ctx.closePath();
ctx.fillStyle = turtle.color;
ctx.fill();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.stroke();
// Eye
ctx.beginPath();
ctx.arc(size * 0.2, -size * 0.15, 1.5, 0, Math.PI * 2);
ctx.fillStyle = '#000';
ctx.fill();
ctx.restore();
}
function drawTrail() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid
ctx.strokeStyle = '#0f1a2e';
ctx.lineWidth = 0.5;
for (let x = 0; x < canvas.width; x += 20) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += 20) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// Draw center
ctx.strokeStyle = '#1a2a4a';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
// Draw trails
for (const t of trail) {
ctx.beginPath();
ctx.strokeStyle = t.color || turtle.color;
ctx.lineWidth = 2;
ctx.moveTo(t.x1, t.y1);
ctx.lineTo(t.x2, t.y2);
ctx.stroke();
}
drawTurtle();
}
function drawTrailAnimated(x1, y1, x2, y2, color) {
return new Promise(resolve => {
const dx = x2 - x1;
const dy = y2 - y1;
const dist = Math.sqrt(dx*dx + dy*dy);
const steps = Math.max(1, Math.round(dist / 2));
let step = 0;
function animate() {
const progress = Math.min(step / steps, 1);
const cx = x1 + dx * progress;
const cy = y1 + dy * progress;
ctx.beginPath();
ctx.strokeStyle = color || turtle.color;
ctx.lineWidth = 2;
ctx.moveTo(x1, y1);
ctx.lineTo(cx, cy);
ctx.stroke();
turtle.x = cx;
turtle.y = cy;
step++;
if (progress < 1) {
setTimeout(animate, getDelay() / (dist > 50 ? 3 : 1));
} else {
trail.push({x1, y1, x2: x2, y2: y2, color: color || turtle.color});
undoStack.push({type: 'trail', x1, y1, x2, y2, color: color || turtle.color});
drawTurtle();
updateStatus();
resolve();
}
}
animate();
});
}
async function executeCommand() {
const cmd = commandInput.value.trim();
if (!cmd) return;
addOutput(`logo> ${cmd}`, 'cmd');
commandInput.value = '';
try {
await parseAndExecute(cmd);
} catch (e) {
addOutput(`** ${e.message}`, 'error');
}
}
function executeLine() {
executeCommand();
}
function undoCommand() {
if (undoStack.length === 0) {
addOutput('(nothing to undo)', 'info');
return;
}
const action = undoStack.pop();
// Remove the last trail entry
trail.pop();
drawTrail();
updateStatus();
addOutput('↶ undone', 'info');
}
function clearAll() {
trail = [];
undoStack = [];
turtle.x = 280;
turtle.y = 210;
turtle.angle = -90;
turtle.penDown = true;
turtle.color = '#ffd700';
turtle.visible = true;
drawTrail();
updateStatus();
addOutput('Screen cleared.', 'info');
}
function parseValue(val) {
val = val.trim();
if (val === 'RANDOM') {
return Math.random() * 200;
}
const num = parseFloat(val);
if (isNaN(num)) {
throw new Error(`Unknown value: ${val}`);
}
return num;
}
function parseColor(val) {
val = val.trim().toUpperCase();
if (val === 'RANDOM') {
return COLOR_NAMES[Math.floor(Math.random() * COLOR_NAMES.length)];
}
if (COLORS[val]) {
return COLORS[val];
}
// Try hex
if (val.startsWith('#') && (val.length === 4 || val.length === 7)) {
return val;
}
throw new Error(`Unknown color: ${val}`);
}
function parseCommand(cmd) {
const upper = cmd.toUpperCase().trim();
// REPEAT n [commands]
const repeatMatch = upper.match(/^REPEAT\s+(\d+)\s+\[(.+)\]$/s);
if (repeatMatch) {
return { type: 'repeat', count: parseInt(repeatMatch[1]), commands: repeatMatch[2] };
}
// DEFINE name [params] [commands]
const defineMatch = upper.match(/^DEFINE\s+(\w+)\s*\[([^\]]+)\]$/s);
if (defineMatch) {
return { type: 'define', name: defineMatch[1], commands: defineMatch[2] };
}
// Simple command
const parts = upper.split(/\s+/);
const base = parts[0];
switch(base) {
case 'FD':
case 'FORWARD':
return { type: 'forward', distance: parseValue(parts[1]) };
case 'BK':
case 'BACK':
return { type: 'backward', distance: parseValue(parts[1]) };
case 'LT':
case 'LEFT':
return { type: 'left', degrees: parseValue(parts[1]) };
case 'RT':
case 'RIGHT':
return { type: 'right', degrees: parseValue(parts[1]) };
case 'HOME':
return { type: 'home' };
case 'CS':
case 'CLEARSCREEN':
case 'CLEARSCREEN':
return { type: 'clearscreen' };
case 'PU':
case 'PENUP':
return { type: 'penup' };
case 'PD':
case 'PENDOWN':
return { type: 'pendown' };
case 'SETCOLOR':
case 'SETPENCOLOR':
return { type: 'setcolor', color: parseColor(parts[1]) };
case 'SETPC':
return { type: 'setcolor', color: parseColor('RANDOM') };
case 'SETX':
return { type: 'setx', x: parseValue(parts[1]) };
case 'SETY':
return { type: 'sety', y: parseValue(parts[1]) };
case 'SETHEADING':
case 'SETHEADING':
return { type: 'setheading', angle: parseValue(parts[1]) };
case 'SHOWTURTLE':
case 'SHOWT':
return { type: 'showturtle' };
case 'HIDETURTLE':
case 'HIDET':
return { type: 'hideturtle' };
case 'SETPENSIZE':
return { type: 'setpensize', size: parseValue(parts[1]) };
case 'WAIT':
return { type: 'wait', ms: parseValue(parts[1]) };
case 'PRINT':
case 'PR':
return { type: 'print', text: parts.slice(1).join(' ') };
case 'DRAW':
return { type: 'draw', name: parts[1] };
default:
throw new Error(`Unknown command: ${base}`);
}
}
async function executeCommandObj(cmdObj) {
switch(cmdObj.type) {
case 'forward': {
const rad = (turtle.angle * Math.PI) / 180;
const nx = turtle.x + cmdObj.distance * Math.cos(rad);
const ny = turtle.y + cmdObj.distance * Math.sin(rad);
if (turtle.penDown) {
await drawTrailAnimated(turtle.x, turtle.y, nx, ny, turtle.color);
} else {
turtle.x = nx;
turtle.y = ny;
updateStatus();
}
break;
}
case 'backward': {
const rad = (turtle.angle * Math.PI) / 180;
const nx = turtle.x - cmdObj.distance * Math.cos(rad);
const ny = turtle.y - cmdObj.distance * Math.sin(rad);
if (turtle.penDown) {
await drawTrailAnimated(turtle.x, turtle.y, nx, ny, turtle.color);
} else {
turtle.x = nx;
turtle.y = ny;
updateStatus();
}
break;
}
case 'left': {
turtle.angle -= cmdObj.degrees;
updateStatus();
break;
}
case 'right': {
turtle.angle += cmdObj.degrees;
updateStatus();
break;
}
case 'home': {
if (turtle.penDown) {
await drawTrailAnimated(turtle.x, turtle.y, 280, 210, turtle.color);
} else {
turtle.x = 280;
turtle.y = 210;
updateStatus();
}
turtle.angle = -90;
updateStatus();
break;
}
case 'clearscreen': {
clearAll();
break;
}
case 'penup': {
turtle.penDown = false;
updateStatus();
break;
}
case 'pendown': {
turtle.penDown = true;
updateStatus();
break;
}
case 'setcolor': {
turtle.color = cmdObj.color;
updateStatus();
break;
}
case 'setx': {
const oldX = turtle.x;
turtle.x = cmdObj.x;
if (turtle.penDown) {
await drawTrailAnimated(oldX, turtle.y, turtle.x, turtle.y, turtle.color);
}
updateStatus();
break;
}
case 'sety': {
const oldY = turtle.y;
turtle.y = cmdObj.y;
if (turtle.penDown) {
await drawTrailAnimated(turtle.x, oldY, turtle.x, turtle.y, turtle.color);
}
updateStatus();
break;
}
case 'setheading': {
turtle.angle = cmdObj.angle;
updateStatus();
break;
}
case 'showturtle': {
turtle.visible = true;
drawTurtle();
break;
}
case 'hideturtle': {
turtle.visible = false;
drawTurtle();
break;
}
case 'setpensize': {
turtle.size = Math.max(2, Math.min(30, cmdObj.size));
drawTurtle();
break;
}
case 'wait': {
await new Promise(r => setTimeout(r, cmdObj.ms));
break;
}
case 'print': {
addOutput(cmdObj.text, 'result');
break;
}
case 'draw': {
await executeDrawCommand(cmdObj.name);
break;
}
case 'repeat': {
for (let i = 0; i < cmdObj.count; i++) {
await parseAndExecute(cmdObj.commands);
}
break;
}
case 'define': {
userCommands[cmdObj.name] = cmdObj.commands;
addOutput(`Defined: ${cmdObj.name}`, 'info');
break;
}
default:
throw new Error(`Unknown command type: ${cmdObj.type}`);
}
}
// User-defined commands storage
let userCommands = {};
async function executeDrawCommand(name) {
name = name.toUpperCase();
if (userCommands[name]) {
await parseAndExecute(userCommands[name]);
} else {
throw new Error(`Undefined command: ${name}`);
}
}
async function parseAndExecute(cmd) {
const cmdObj = parseCommand(cmd);
await executeCommandObj(cmdObj);
}
// Load example
async function loadExample(name) {
clearAll();
const examples = {
'square': 'REPEAT 4 [FD 100 RT 90]',
'hexagon': 'REPEAT 6 [FD 100 RT 60]',
'star': 'REPEAT 5 [FD 150 RT 144]',
'spiral': 'REPEAT 100 [FD 5 RT 1]',
'flower': 'REPEAT 36 [FD 80 RT 100 FD 80 BK 80 RT 10]',
'sunburst': 'REPEAT 36 [SETCOLOR RANDOM FD 120 RT 10]',
'tree': 'DEFINE TREE [REPEAT 2 [FD 30 LT 20 TREE FD 30 RT 40 TREE FD 30 RT 20]] CS SETCOLOR GREEN TREE',
'mandala': 'REPEAT 12 [REPEAT 36 [FD 3 RT 10] RT 30]'
};
const cmd = examples[name];
if (cmd) {
addOutput(`Loading: ${name}...`, 'info');
await parseAndExecute(cmd);
addOutput(`Done: ${name}`, 'info');
}
}
// Keyboard handling
commandInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
executeCommand();
}
});
// Focus input on click
canvas.addEventListener('click', () => {
commandInput.focus();
});
// Initial draw
drawTrail();
updateStatus();
addOutput('LOGO Ready! Type a command or click an example.', 'info');
addOutput('Type DEFINE name [commands] to create custom commands.', 'info');
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment