Game Lab – Turtle (Spor A/B) · kom i gang & opgaver

Denne side guider eleverne til at oprette et nyt Game Lab-projekt, indsætte undervisningsskelettet (v4) og arbejde i ét samlet Svar-område nederst i filen: exerciseAnswer(). Ingen øvelsesvælger – Clear genindlæser altid elevens svar. Skelettet har to spor: Exercise (en turtle med spor og trin-kørsel) og Play (flere turtles uden spor). Nyhed: Vælg hvad der er “frem” for hver sprite (setForwardAxis()/setForwardOffset()).

1) Opret nyt Game Lab-projekt

  1. Gå til code.org og log ind.
  2. Åbn Create / Lav og vælg Game Lab.
  3. Skift til fanen Text (hvis du er i Blocks).
  4. Ryd al standardkode i editoren (marker alt og slet).

2) Tilføj “turtle” i Animation-fanen

Skelettet forventer en hvid figur kaldet "turtle".

  1. Klik fanen Animation (øverst).
  2. Tryk + og vælg/gem et billede – fx en hvid silhuet.
  3. Giv billedet præcis navnet: turtle.
  4. Gå tilbage til Code-fanen.

Hvis din sprite peger “op” eller “ned” som frem: Det håndteres i koden via setForwardAxis("up"|"down"|"left"|"right") eller setSpriteVisualOffset(±90, 180) for visuel finjustering.

3) Indsæt undervisningsskelettet (v4, med Svar-område)

Kopier hele blokken herunder og indsæt i din Game Lab-editor:

// === Game Lab Turtle – undervisningsskelet (Spor A/B) – v4  (Svar-område) ===
// Eleverne skriver KUN i exerciseAnswer() nederst. Clear/Start bygger køen fra det svar.
// Per-turtle "forward axis": vælg "frem" pr. sprite (setForwardAxis / setForwardOffset).
// Forudsætning: Animation-fanen har en HVID figur kaldet "turtle".

// • MODE vælges KUN herunder (ingen tast, ingen knap):
var MODE = "exercise";   // "exercise" eller "play"

// Skærmstørrelse (Game Lab default)
var SCREEN_W = 400, SCREEN_H = 400;

// --- Forward axis mapping ---
var AXIS_TO_OFFSET = { up:-90, right:0, down:90, left:180 };
var DEFAULT_FORWARD_AXIS = "down";
function axisToOffset(axis){
  if (AXIS_TO_OFFSET.hasOwnProperty(axis)) return AXIS_TO_OFFSET[axis];
  return AXIS_TO_OFFSET.up;
}

// -------------------------------------------------
// Turtle "klasse"
// -------------------------------------------------
var turtles = [];
function newTurtle(x, y, penColor, forwardAxis) {
  var t = {
    x: x, y: y, angle: 0,
    penDown: true, penColor: penColor || "black", penWidth: 2,
    sprite: createSprite(x, y),
    path: [],
    forwardOffset: axisToOffset(forwardAxis || DEFAULT_FORWARD_AXIS),
    spriteVisualOffset: 0
  };
  t.sprite.setAnimation("turtle");
  t.sprite.scale = 0.12;
  t.sprite.tint  = t.penColor;
  t.sprite.rotation = t.angle + t.forwardOffset + t.spriteVisualOffset;

  t.forward = function(dist){
    var oldX = this.x, oldY = this.y;
    var r = Math.PI * (this.angle + this.forwardOffset) / 180;
    this.x += dist * Math.cos(r);
    this.y += dist * Math.sin(r);
    if (this.penDown) this.path.push({x1:oldX,y1:oldY,x2:this.x,y2:this.y,color:this.penColor,width:this.penWidth});
    this.sprite.x = this.x; this.sprite.y = this.y;
    this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset;
  };
  t.turnRight = function(deg){ this.angle += deg; this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };
  t.turnLeft  = function(deg){ this.angle -= deg; this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };
  t.setHeading= function(deg){ this.angle  = deg; this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };
  t.penUp     = function(){ this.penDown = false; };
  t.penDownFn = function(){ this.penDown = true; };
  t.setPenColor = function(c){ this.penColor = c; this.sprite.tint = c; };
  t.setPenWidth = function(w){ this.penWidth = w; };
  t.goTo = function(nx, ny){ this.x=nx; this.y=ny; this.sprite.x=nx; this.sprite.y=ny; };
  t.home = function(){ this.goTo(SCREEN_W/2, SCREEN_H/2); this.setHeading(0); };
  t.clearPath = function(){ this.path = []; };

  t.setForwardAxis = function(axis){ this.forwardOffset = axisToOffset(axis); this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };
  t.setForwardOffset = function(deg){ this.forwardOffset = deg; this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };
  t.setSpriteVisualOffset = function(deg){ this.spriteVisualOffset = deg; this.sprite.rotation = this.angle + this.forwardOffset + this.spriteVisualOffset; };

  turtles.push(t);
  return t;
}

function drawPath(t){
  for (var i=0;i<t.path.length;i++){
    stroke(t.path[i].color); strokeWeight(t.path[i].width);
    line(t.path[i].x1, t.path[i].y1, t.path[i].x2, t.path[i].y2);
  }
}

// -------------------------------------------------
// UI – simple canvas-knapper
// -------------------------------------------------
var buttons = [];
function makeBtn(label,x,y,w,h,onClick){ buttons.push({label:label,x:x,y:y,w:w,h:h,onClick:onClick}); }
function drawBtn(b){ noStroke(); fill("#f4f4f4"); rect(b.x,b.y,b.w,b.h,6);
  stroke("#999"); noFill(); rect(b.x,b.y,b.w,b.h,6);
  noStroke(); fill("#111"); textSize(12); textAlign(LEFT, CENTER); text(b.label, b.x+8, b.y+b.h/2); }
function mouseIn(b){ return mouseX>b.x && mouseX<b.x+b.w && mouseY>b.y && mouseY<b.y+b.h; }
var mouseLatch=false;
function handleButtons(){
  var click=false;
  if (mouseWentDown()) mouseLatch=true;
  if (mouseLatch && !mouseDown()) { click=true; mouseLatch=false; }
  for (var i=0;i<buttons.length;i++){
    drawBtn(buttons[i]);
    if (click && mouseIn(buttons[i])) buttons[i].onClick();
  }
}

// -------------------------------------------------
// Exercise – kø, step & svarbygning
// -------------------------------------------------
var exT = null;
var queue = [];               // {op:"fd|rt|lt|col|wd|jump", val:number|string|{x,y}}
var running = false;
var opsPerFrame = 1;
var currentExercise = "";

function q(op,val){ queue.push({op:op,val:val}); }

function stepOnce(){
  if (queue.length===0){ running=false; return; }
  var cmd = queue.shift();
  if (cmd.op==="fd") exT.forward(cmd.val);
  else if (cmd.op==="rt") exT.turnRight(cmd.val);
  else if (cmd.op==="lt") exT.turnLeft(cmd.val);
  else if (cmd.op==="col") exT.setPenColor(cmd.val);
  else if (cmd.op==="wd")  exT.setPenWidth(cmd.val);
  else if (cmd.op==="jump"){
    var p=cmd.val, was=exT.penDown;
    exT.penUp(); exT.goTo(p.x,p.y); if (was) exT.penDownFn();
  }
}
function stepFrame(){ if (!running) return; for (var i=0;i<opsPerFrame;i++) stepOnce(); }

// Byg køen fra elevens svarområde
function loadExerciseByConfig(){
  queue=[]; exT.clearPath(); exT.home(); exT.penDownFn();
  exerciseAnswer(); // udfylder queue via q(...)
  currentExercise = "custom-answer";
}

// -------------------------------------------------
// Play – flere turtles uden spor (demo)
// -------------------------------------------------
var playTs = [];
function resetPlay(count){
  turtles = [];
  playTs = [];
  for (var i=0;i<count;i++){
    var axis = DEFAULT_FORWARD_AXIS;
    var t = newTurtle(randomNumber(40,360), randomNumber(40,360), randColor(), axis);
    t.penUp(); t.setHeading(randomNumber(0,359));
    t.speed = randomNumber(1,3);
    playTs.push(t);
  }
}
function updatePlay(){
  for (var i=0;i<playTs.length;i++){
    var t = playTs[i];
    t.forward(t.speed);
    if (t.x < 10 || t.x > SCREEN_W-10) { t.turnRight(180 - randomNumber(0,20)); clampInside(t); }
    if (t.y < 10 || t.y > SCREEN_H-10) { t.turnRight(180 - randomNumber(0,20)); clampInside(t); }
  }
}
function setPlayForwardAxis(axis){ for (var i=0;i<playTs.length;i++) playTs[i].setForwardAxis(axis); }
function setPlaySpriteOffset(deg){ for (var i=0;i<playTs.length;i++) playTs[i].setSpriteVisualOffset(deg); }
function clampInside(t){ if (t.x<10) t.goTo(10,t.y); if (t.x>SCREEN_W-10) t.goTo(SCREEN_W-10,t.y); if (t.y<10) t.goTo(t.x,10); if (t.y>SCREEN_H-10) t.goTo(t.x,SCREEN_H-10); }
function randColor(){ var cs=["#e53935","#1e88e5","#43a047","#fb8c00","#8e24aa","#00897b"]; return cs[randomNumber(0,cs.length-1)]; }

// -------------------------------------------------
// Setup + UI
// -------------------------------------------------
function buildButtons(){
  buttons = [];
  if (MODE==="exercise"){
    makeBtn(running?"Pause":"Run", 8,8,68,24, function(){ running=!running; });
    makeBtn("Step", 84,8,56,24, function(){ stepOnce(); });
    makeBtn("Clear", 148,8,64,24, function(){ exT.clearPath(); exT.home(); queue=[]; running=false; loadExerciseByConfig(); });
    makeBtn("Speed -", 8,40,68,24, function(){ opsPerFrame = Math.max(1, opsPerFrame-1); });
    makeBtn("Speed +", 84,40,68,24, function(){ opsPerFrame = Math.min(20, opsPerFrame+1); });
  } else {
    makeBtn("Reset Play", 8,8,100,24, function(){ resetPlay(8); });
    makeBtn("Axis: up",   8,40,76,22, function(){ setPlayForwardAxis("up"); });
    makeBtn("right",      88,40,60,22, function(){ setPlayForwardAxis("right"); });
    makeBtn("down",      152,40,60,22, function(){ setPlayForwardAxis("down"); });
    makeBtn("left",      216,40,60,22, function(){ setPlayForwardAxis("left"); });
    makeBtn("img +90°",   8,68,76,22, function(){ setPlaySpriteOffset(90); });
    makeBtn("img 0°",     88,68,60,22, function(){ setPlaySpriteOffset(0); });
    makeBtn("img -90°",  152,68,60,22, function(){ setPlaySpriteOffset(-90); });
    makeBtn("img 180°",  216,68,60,22, function(){ setPlaySpriteOffset(180); });
  }
}

function handleKeys(){
  if (MODE==="exercise"){
    if (keyWentDown(" ")) running=!running;
    if (keyWentDown("s")) stepOnce();
    if (keyWentDown("c")) { exT.clearPath(); exT.home(); queue=[]; running=false; loadExerciseByConfig(); }
    if (keyWentDown("z")) opsPerFrame = Math.max(1, opsPerFrame-1);
    if (keyWentDown("x")) opsPerFrame = Math.min(20, opsPerFrame+1);
  } else {
    if (keyWentDown("r")) resetPlay(8);
  }
}

function setupExercise(){
  turtles = [];
  exT = newTurtle(SCREEN_W/2, SCREEN_H/2, "#111", DEFAULT_FORWARD_AXIS);
  exT.setPenWidth(2);
  exT.clearPath();
  loadExerciseByConfig(); // bygger fra elevens svarområde
}

// Init
if (MODE==="exercise") { setupExercise(); } else { resetPlay(8); }
buildButtons();

// ===============================================
// ELEVENS SVAR-OMRÅDE — skriv KUN herunder
// Brug q("fd"/"rt"/"lt"/"col"/"wd"/"jump", værdi)
// Eksempler:
//   q("fd", 60); q("rt", 90);
//   q("col", "red"); q("wd", 3);
//   q("jump", {x:200,y:240});
// ===============================================
function exerciseAnswer(){
  // Eksempel: A1 — kvadrat 60 px (udskift med din opgave)
  q("wd", 3); // pen WiDth 
  q("col", "red");  // COLor 
  
  q("fd", 60);   // ForwarD 
  q("rt", 90);   // Right Turn, 90 deg 
  q("fd", 60); 
  q("rt", 90);
  q("fd", 60); 
  q("rt", 90);
  q("fd", 60); 

  q("jump", {x:150,y:200})  // JUMP, hop, uden at tegne. 
}

// -------------------------------------------------
// DRAW LOOP
// -------------------------------------------------
function draw(){
  background("white");
  handleButtons();
  handleKeys();
  noStroke(); fill("#555"); textSize(12); textAlign(LEFT,TOP);
  var status = (MODE==="exercise"
    ? (running?"RUNNING":"PAUSED")+" · speed="+opsPerFrame+" ops/frame · ex=answer"
    : "PLAY · "+playTs.length+" turtles · no trails");
  text(status, 8, (MODE==="exercise"?72:64));

  if (MODE==="exercise"){
    stepFrame();
    drawPath(exT);
    drawSprites();
  } else {
    updatePlay();
    drawSprites();
  }
}

4) Vælg MODE & Svar-område

var MODE = "exercise"; 
    // "exercise" eller "play"

Opgave - Kvadrat (side = 60 px)

Målet er et kvadrat. Du må gerne starte hvor som helst – formen afgør.

// Hint (turtle): brug et loop
q("wd", 3); q("col", "red");
for (var i=0;i<4;i++){ q("fd", 60); q("rt", 90); }

Opgave - Lige-sidet trekant (samme vinkeldrej: 120°)

Drej den samme vinkel hver gang (120°) og brug samme sidelængde.

// Hint (turtle): 3 gentagelser, 120° højredrej
q("wd", 3); q("col", "seagreen");
for (var i=0;i<3;i++){ q("fd", 70); q("rt", 120); }

Opgave - Retvinklet trekant (start ved en spids)

Start i en spids (ikke det retvinklede hjørne). Brug to rette ben, den tredje side lukker trekanten.

// Hint (turtle):
// 1) Gå langs første katete a
// 2) Drej 90° (rt 90) og gå katete b
// 3) Drej mod start og luk (hypotenuse)
q("wd",3); q("col","#1e88e5");
q("fd", 90);      // a
q("rt", 90); q("fd", 50); // b
// Luk: drej mod start (beregn evt. vinkel selv eller prøv dig frem)
q("rt", 143); q("fd", 103);

Opgave - Retvinklet trekant (start ved det retvinklede hjørne)

Start i det retvinklede hjørne. Gå først langs én katete, drej 90°, gå anden katete, og luk med hypotenusen.

// Hint (turtle): to 90°-ben, luk med hypotenuse
q("wd",3); q("col","#8e24aa");
q("fd", 90);           // katete 1
q("rt", 90); q("fd", 55); // katete 2
// Luk (hypotenuse)
q("rt", 141); q("fd", 106);

Opgave - Stjerne (5 takker)

Klassisk 5-takket stjerne tegnes ved at dreje 144° samme vej for hver tak.

// Hint (turtle): 5 gentagelser, 144° højredrej
q("wd",3); q("col","#fb8c00");
var s = 70;
for (var i=0;i<5;i++){ q("fd", s); q("rt", 144); }

Opgave - Juletræ (3 trekanter + stamme)

Byg træet af tre ligesidede trekanter (øverst lille, nederst størst) og afslut med en stamme.

// Hint (turtle): hjælpere + jump mellem lag
    q("wd",3); q("col","#2e7d32");
    function tri(side){ for (var i=0;i<3;i++){ q("fd", side); q("rt", 120); } }

    // Øverste lag
    q("jump",{x:200,y:120}); tri(40);
    // Midterste lag
    q("jump",{x:200,y:160}); tri(60);
    // Nederste lag
    q("jump",{x:200,y:200}); tri(90);

    // Stamme (lille rektangel)
    q("col","#6d4c41"); q("jump",{x:200,y:245});
    for (var i=0;i<2;i++){ q("fd", 20); q("rt", 90); q("fd", 10); q("rt", 90); }

Tip: Justér jump-punkter, så lagene overlapper pænt.

Opgave - Fri tegning

Tegn en frit valgt figur.

Tips & fejlfinding

Tip: Gem ofte. Brug Remix til nye versioner, så du bevarer en fungerende baseline.