qr code

Enkla objekt och funktioner

Enkla objekt och funktioner

I Scratch kan du definiera ett eget block som har ett valfritt antal parametrar. Efter att du definierat blocket kan du använda det i den övriga koden och skicka in argument som anger vilka värden parametrarna ska ha.

block i Scratch
Blocket har en parameter sida som används i definitionen av blocket. Blocket anropas med argumentet 100.

I Processing motsvaras egna block av egna funktioner. En egenhändigt definierad funktion används för att strukturera koden. Om du upprepar samma kod på flera ställen i ett program blir programmet kortare och enklare om den kod som upprepas läggs in i en funktion. Även om kod inte upprepas kan det i vissa fall vara enklare att förstå koden om den strukturerats med hjälp av funktioner.

Tänk dig att du ska göra ett dataspel i Processing och att nästan all kod ligger i draw-funktionen. För att strukturera arbetet kan koden delas upp i de tre tillstånd spelet befinner sig i.

Ett dataspel

Visa startsida

Låt användaren spela

Visa resultat

Vad som ska hända i varje tillstånd kan beskrivas separat.

Visa startsida

Visa meny som låter spelaren välja svårighetsgrad.

Visa startknapp som sätter igång spelet

Den separata beskrivningen kan implementeras av en egenhändigt definierad funktion.

Innan vi kommer in på funktioner ska vi gå igenom hur man gör enkla objekt i Processing. Liksom funktioner används enkla objekt för att strukturera koden.

Enkla objekt

Ett objekt inom programmering har ett antal egenskaper och ett antal metoder. Ett objekt inom Javascript som vi stött på är en indexerad lista. Varje lista i Javascript har en egenskap length som håller reda på hur många element listan innehåller. En lista har också en metod push som lägger till ett element längst bak i listan. Man kommer åt ett objekts egenskaper och metoder med punknotation, dvs efter objektets namn skriver man en punkt följt av en egenskap eller en metod.

Följande kod:

let bilar = ["SAAB", "Volvo", "Toyota"];
	print("Antal bilar = " + bilar.length); //punknotation för att komma åt antalet
	bilar.push("BMW");  //punktnotation  för att lägga till ett element
	print("Antal bilar = " + bilar.length);

ger utskriften

Antalet bilar = 3
Antalet bilar = 4

Vi ska inte gå igenom hur man gör egna objekt som har metoder. Däremot ska vi gå igenom hur man gör egna objekt som har ett antal egenskaper.

En egenskap till ett objekt har ett namn och ett värde, precis som en variabel. Om du har ett antal variabler vars värden hänger ihop, kan du skapa ett objekt som har dessa värden som egenskaper. Några exempel på objekt med egenskaper är:

  • Objektet pos med egenskaperna x och y.
  • Objektet hastighet med egenskaperna x och y.
  • Objektet cirkel med egenskaperna x, y och radie.
  • Objektet rektangel med egenskaperna x, y, bredd och höjd.
  • Objektet cirkel med egenskaperna x, y, deltax, deltay, radie och färg, där deltax och deltay representerar steget som tas per tidsenhet, dvs hastigheten.

När du ska tilldela ett objekt ett antal egenskaper samlar du alla egenskaper mellan måsvingar. Egenskaperna separeras av komma och för varje egenskap skriver du namnet, kolon, därefter värdet.

function setup() {
	let person;
	person = {namn: "Eva", ålder: 36};
	print(person.namn + " är " + person.ålder + " år gammal.");
}

Objekt som ska kunna användas i både setup- och draw-funktionen deklareras globalt, dvs överst i programmet.

let pos, hastighet;

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(100);
	pos = {x: 200, y: 300}; 
	hastighet = {x: 5, y: 7};
}

function draw() {
	ellipse(pos.x, pos.y, 20, 20);
	pos.x += hastighet.x;
	pos.y += hastighet.y;
}

Om ett objekt har många egenskaper blir koden enklare att läsa om man använder radbrytning vid tilldelningen. Lägg märke till att det inte ska vara ett kommatecken efter sista egenskapen.

let pos, hastighet;

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(100);
	pos = {  // en egenskap per rad
		x: 200,
		y: 300
	};
	hastighet = {
		x: 5,
		y: 7
	};
}

Du kan i variabeldeklarationen ange att en variabel ska vara ett objekt genom att tilldela den "ett tomt objekt". Därefter kan du tilldela egenskaperna värden genom att använda punktnotation.

let pos = {}, hastighet = {}; //variablerna är objekt

function setup() {
	createCanvas(windowWidth, windowHeight);
	background(100);
	pos.x = 200; //punktnotation vid tilldelning
	pos.y = 100;
	hastighet.x = 5;
	hastighet.y = 7;
}

På detta vis kan vi göra en cirkel som har alla de egenskaper som behövs för en studsande boll. Bollens färg kan vi lagra med hjälp av funktionen color(<röd>, <grön>, <blå>) vilken ger oss en rgb-färg.

Exempel 1

Koden ritar upp en boll som rör sig med konstant hastighet. Hastigheten ges av hur mycket x- och y-koordinaten förändras per tidsenhet. Denna förändringen kallar vi deltax respektive deltay.

let  boll;

function setup() {
	createCanvas(windowWidth, windowHeight);
	boll = {
		x: width / 2,
		y: height / 2,
		deltax: 5,
		deltay: -4,
		radie: 15,
		färg: color(255, 150, 0)
	};
}

function draw() {
	background(255);
	fill(boll.färg);
	ellipse(boll.x, boll.y, 2 * boll.radie);
	boll.x += boll.deltax;
	boll.y += boll.deltay;
}

Lägg till if-satser i draw-funktionen som gör att bollen studsar i kanten, dvs ändra riktning i x-led om bollen är vid den vänstra eller högra kanten och ändra riktning i y-led om bollen är vid den övre eller nedre kanten.

I Exempel 1 hade vi kunnat använda sex variabler istället för ett objekt med sex egenskaper. Om vi behöver hantera flera olika saker som ska ritas upp, exempelvis flera bollar, blir koden dock mer strukturerad om vi använder objekt istället för väldigt många variabler. Om vi exempelvis hade haft bollar i listor, hade vi behövt flera listor, en för varje boll-egenskap som ska kunna variera. Genom att göra varje boll till ett objekt, räcker det med en enda lista av bollar.

Listor av objekt

Vi kan göra en lista med positioner där varje position är ett objekt med egenskaperna x och y.

Exempel 2

Följande kod ritar upp 50 cirklar.

let pos = [];

function setup() {
	let i;
	createCanvas(windowWidth, windowHeight);
	for (i = 0; i < 50; i += 1) {
		pos[i] = { // varje element i listan är ett objekt
			x: 100 + 20 * i,
			y: height / 2
		};
	}
}

function draw() {
	let i;
	background(100);
	for (i = 0; i < pos.length; i += 1) {
		ellipse(pos[i].x, pos[i].y, 20);
	}
	ellipse(mouseX, mouseY, 20);
}

Lägg märke till att punktnotation används på pos[i].

Ändra i koden för setup-funktionen så att positionen i både x-led och y-led blir ett slumptal.

Istället för att göra en boll kan vi göra 50 bollar som kastas ut från mitten av ritområdet.

Exempel 3

Följande kod ritar upp 50 bollar som rör sig från mitten av ritområdet. När en boll kommer till kanten börjar den om i mitten av ritområdet.

let  boll = [];

function setup() {
	let i;
	createCanvas(windowWidth, windowHeight);
	for (i = 0; i  < 50; i += 1) {
		boll[i] = {};
		boll[i].x = width / 2;
		boll[i].y = height / 2;
		boll[i].deltax = random(-10, 10);
		boll[i].deltay = random(-10, 10);
		boll[i].radie = random(2, 30);
		boll[i].färg = color(random(256), 150, 0);
	}
}

function draw() {
	let i;
	background(255);
	for (i = 0; i  < boll.length; i += 1) {
		fill(boll[i].färg);
		ellipse(boll[i].x, boll[i].y, 2 * boll[i].radie);
		boll[i].x += boll[i].deltax;
		boll[i].y += boll[i].deltay;
		if (boll[i].x > width  || boll[i].x  < 0 || 
			boll[i].y > height || boll[i].y  < 0) {
			boll[i].x = width / 2;
			boll[i].y = height / 2;
		}
	}
}

Ändra koden så att det istället är 500 bollar.

Kopiera objekt

Om du försöker göra en kopia av ett objekt kommer koden att bete sig på ett till synes märkligt sätt. Med följande kod får objekt1 två egenskaper med varsitt värde. Därefter kopieras dessa värden till objekt2.

function setup() {
	let objekt1, objekt2;
	objekt1 = {x: 100, y:50};
	objekt2 = objekt1;
	objekt1.x = 500;
	print(objekt2.x);
}

När objekt1.x får ett nytt värde, kommer även värdet på objekt2.x att förändras. Utskriften blir:

500

För att förstå detta beteende kan man tänka sig att ett objekt refererar till ett antal minnesceller där egenskaperna lagras.

objekt refererar till minnesceller

När objekt2 tilldelas objekt1, kommer objekt2 att referera till samma sjok minnesceller, det skapas inte en lagring i nya minnesceller.

När objekt1.x ändras, kommer värdet i en minnescell att ändras vilket även påverkar värdet på objekt2.x.

Om du vill göra en kopia så att kopians egenskaper lagras i nya minnesceller, kan du kopiera varje egenskap istället för att kopiera hela objektet, exempelvis med koden:

function setup() {
	let objekt1, objekt2;
	objekt1 = {x: 100, y:50};
	objekt2 = {};
	objekt2.x = objekt1.x;
	objekt2.y = objekt2.y;
	objekt1.x = 500;
	print(objekt2.x);
}

Eller med koden:

function setup() {
	let objekt1, objekt2;
	objekt1 = {x: 100, y:50};
	objekt2 = {x: objekt1.x, y: objekt1.y};
	objekt1.x = 500;
	print(objekt2.x);
}

Då blir utskriften

100

Funktioner

Det finns två sorters funktioner i programmering:

  • Funktioner som bara utför ett stycke kod.
  • Funktioner som utför ett stycke kod samt returnerar ett värde.

Några vanliga funktioner som returnerar ett värde är de fördefinierade matematiska funktionerna som finns i Processing. Dessa funktioner beskrivs under rubriken Math i referensbiblioteket till p5.js, dvs på sidan p5js.org/reference/. Exempelvis tar funktionen sqrt() emot ett argument som är ett tal och returnerar kvadratroten av talet. Det går exempelvis att skriva koden:

print("Roten ur 25 är " + sqrt(25) + ".");

Vid anropet sqrt(25) beräknas kvadratroten av 25.

När du ska definiera en egen funktion använder du följande struktur:

function funktionsNamn(<parameter1>, <parameter2>, ...) {
	//kod som ska utföras när funktionen anropas
}

Om du vill att din funktion ska returnera ett värde måste du sist i koden använda ordet return följt av det värde som ska returneras.

Det går också bra att definiera en funktion som använder någon parameter alls. Vid anropet av en sådan funktion måste man skriva parenteser efter funktionsnamnet trots att inga argument ska skickas in till funktionen. Ett anrop av en sådan funktion kan se ut så här:

enFunktion();

Vi börjar med att göra en funktion som returnerar ett värde. Funktionen beräknar hypotenusan i en rätvinklig triangel. Funktionen har två parametrar för kateterna. Vi tänker oss att c är hypotenusan i en rätvinklig triangel med kateteterna a och b.

function setup() {
	let a, b , c;
	a = 3;
	b = 4;
	c = hypotenusa(a, b);
	print("Hypotenusan är " + c + " längdenheter.");
}

function hypotenusa(katet1, katet2) {
	let resultat;
	resultat= sqrt(katet1*katet1 + katet2*katet2);
	return resultat;
}

Koden i setup-funktionen kan skrivas helt utan variabler. Koden i definitionen av funktionen hypotenusa kan också skrivas utan variabler, efter ordet return kan det stå ett uttryck.

function setup() {
	print("Hypotenusan är " + hypotenusa(3, 4) + " längdenheter.");
}

function hypotenusa(katet1, katet2) {
	return sqrt(katet1 * katet1 + katet2 * katet2);
}

En funktion som inte returnerar någonting utan bara utför ett stycke kod kan exempelvis rita upp någonting.

Exempel 4

Funktionen smiley har tre parametrar och ritar upp en glad gubbe.

function setup() {
	createCanvas(windowWidth, windowHeight);
	angleMode(DEGREES);
	strokeWeight(3);
}

function draw() {
	background(100);
	smiley(mouseX, mouseY, 100);
}

function smiley(x, y, storlek) {
	fill(255, 255, 0);
	ellipse(x, y, storlek);
	fill(0);
	ellipse(x - 0.2 * storlek, y - 0.1 * storlek, 0.1 * storlek);
	ellipse(x + 0.2 * storlek, y - 0.1 * storlek, 0.1 * storlek);
	noFill();
	arc(x, y + 0.1 * storlek, 0.5 * storlek, 0.5 * storlek, 0, 180);
}

Testa att ändra det tredje argumentet i anropet till en annan storlek. Testa också att låta mouseX vara det tredje argumentet så att gubben blir större ju längre åt höger muspekaren är.

De två sista argumenten till kommandot arc anger startvinkel och slutvinkel för cirkelbågen. Ändra i koden så att det istället ritas ut en sur gubbe. Se till att det är lagom avstånd mellan munnen och ögonen.

Lägg märke till att det inte spelar någon roll let funktionen smiley() definieras. Definitionen ligger efter draw-funktionen där den anropas.

Strukturera ett spel

När du ska rita upp vad som ska hända i ett spel kan det som ska ritas upp bero på vilket tillstånd spelet befinner sig i. För att exemplifiera olika tillstånd tänker vi oss ett spel där användaren ska sikta och skjuta. Spelet har bara två tillstånd, tillståndet för att sikta och tillståndet för att skjuta.

Vi tänker oss att en boll ska skjutas iväg från någon startpunkt. Bollen ska ha en position och en hastighet. Vi börjar med att deklarera fyra variabler:

  1. tillstånd kan ha två värden 0 eller 1.
  2. startpunkt är ett objekt för startpunktens position.
  3. boll är ett objekt för en bollens position.
  4. hastighet är ett objekt för en bollens hastighet.

Sedan använder vi följande grundstruktur för koden:

let tillstånd, startpunkt, hastighet, boll;

function setup() {
	createCanvas(windowWidth, windowHeight);
	tillstånd = 0;
	startpunkt = {x: 0.2 * width, y: 0.9 * height};
}

function draw() {
	background(100);
	ritaStartpunkt();
	if (tillstånd == 0) {
		sikta();
	} else {
		skjut();
	}
}

function ritaStartpunkt() {
	// kod för att rita startpunkt
}

function sikta() {
	// kod för att sikta
}

function skjut() {
	//kod för att skjuta
}

Efter att vi bestämt oss för denna övergripande struktur kan vi hantera detaljerna. Det är tre funktioner som ska definieras: ritaStartpunkt(), sikta(), och skjut(). Bollen ska skjutas iväg när användaren klickar med musen. Funktionen mouseClicked() måste också hanteras.

Funktionen ritaStartpunkt() kan göras mer eller mindre avancerad. Man kan lägga in en bild eller med kod rita något komplext. Man kan också bara rita en vit cirkel.

function ritaStartpunkt() {
	fill(255);
	ellipse(startpunkt.x, startpunkt.y, 50);
}

Funktionen sikta() ska rita en linje från startpunkten till muspekaren. Den ska också ge objektet hastighet en riktning längs linjen. Vi multiplicerar hastigheten med någon faktor som är liten.

function sikta() {
	let faktor = 0.1;
	stroke(255);
	strokeWeight(3)
	line(startpunkt.x, startpunkt.y, mouseX, mouseY);
	hastighet = {
		x: faktor * (mouseX - startpunkt.x),
		y: faktor * (mouseY - startpunkt.y)
	}
}

När användaren klickar med musen ska bollen skjutas iväg, dvs tillståndet ska sättas till ett. Bollen ska få samma position som startpunkten. Detta ska bara hända om tillståndet är noll.

function mouseClicked() {
	if (tillstånd == 0) {
		tillstånd = 1;
		boll = {x: startpunkt.x, y: startpunkt.y};
	}
}

Funktionen skjut() ska ändra bollens position och rita upp bollen. Om bollen når kanten ska tillståndet sättas till noll.

function skjut() {
	boll.x += hastighet.x;
	boll.y += hastighet.y;
	noStroke();
	fill(255, 0, 0);
	ellipse(boll.x, boll.y, 20);
	if (boll.x > width || boll.y > height || boll.x < 0 || boll.<  0) {
		tillstånd = 0;
	}
}

Genom att på detta vis arbeta med övergripande struktur och fylla i detaljerna i efterhand kan man skapa komplexa program.

Booleska värden

Vi har hittills bara hanterat variabler som har varit antingen tal eller textsträngar. En variabel kan också vara en så kallad boolesk variabel som bara antar värdena true eller false. En boolesk variabel kan användas för tillstånd som är antingen "av" eller "på".

Exempel 5

Med denna kod kan användaren dra i en cirkel. Variabeln dra används för att avgöra om cirkeln dras eller inte. När användaren sätter ner musknappen med muspekaren på cirkeln sätts dra på. När användaren släpper musknappen stängs dra av.

let dra, x, y, radie;

function setup() {
	createCanvas(windowWidth, windowHeight);
	dra = false;
	x = width / 2;
	y = height / 2;
	radie = 30;
}

function draw() {
	background(100);
	if (dra) {
		x = mouseX;
		y = mouseY;
	}
	ellipse(x, y, 2 * radie);
}

function mousePressed() {
	if (dist(mouseX, mouseY, x, y) < radie) {
		dra = true;
	}
}

function mouseReleased() {
	dra = false;
}

Testkör koden och testa att dra cirkeln till en annan position.

En funktion kan returnera ett booleskt värde som kan användas som villkor i en villkorssats. Om vi exempelvis vill avgöra huruvida muspekaren befinner sig på en rektangel kan vi skriva följande if-sats.

if ( mouseX > x && mouseY > y && mouseX < x + bredd && mouseY < y + höjd }
	// kod som ska utföras när muspekaren är på rektangeln
}

Detta villkor kan läggas in som kod till en funktion som returnerar ett booleskt värde. De egenskaper en rektangel har (x, y, bredd, höjd) kan vara egenskaper till ett rektangel-objekt. Funktionen som kollar om muspekaren är på rektangeln kan ha en parameter som är ett rektangel-objekt

Exempel 6

En funktion har ersatt ett villkor i en if-sats.

function setup() {
	createCanvas(windowWidth, windowHeight);
}

function draw() {
	let rekt;
	background(100);
	rekt = {
		x: 100,
		y: 100,
		bredd: 200,
		höjd: 50
	}
	if (påRektangel(rekt)) {
		fill(100, 100, 255);
	} else {
		fill(255);
	}
	rect(rekt.x, rekt.y, rekt.bredd, rekt.höjd);
}

function påRektangel(r) {
	return mouseX>r.x && mouseY>r.y && mouseX<r.x+r.bredd && mouseY<r.y+r.höjd;
}

Testkör koden och dra med musen över rektangeln.

Nu kan vi använda en snarlik kod för en lista av slumpvis placerade rektanglar. Vi deklarerar en lista rekt globalt och skapar listan i en funktion. Funktionen returnerar en lista som tilldelas rekt. För att ge varje rektangel blå färg när man håller musen på rektangeln använder vi en loop. Vi återanvänder koden för påRektangel(r).

Exempel 7

Kod för flera 20 slumpvis placerade rektanglar.

let rekt = [];

function setup() {
	createCanvas(windowWidth, windowHeight);
	rekt = görRektanglar();
}

function draw() {
	let i;
	background(100);
	for (i = 0; i < rekt.length; i += 1) {
		if (påRektangel(rekt[i])) {
			fill(100, 100, 255);
		} else {
			fill(255);
		}
		rect(rekt[i].x, rekt[i].y, rekt[i].bredd, rekt[i].höjd);
	}
}

function görRektanglar() {
	let resultat = [], i;
	for (i = 0; i < 20; i += 1) {
		resultat[i] = {
			x: random(width),
			y: random(height),
			bredd: 70,
			höjd: 30
		}
	}
	return resultat;
}

function påRektangel(r) {
	return mouseX > r.x && mouseY > r.y &&
		mouseX < r.x + r.bredd && mouseY < r.y + r.höjd;
}

Se till att det skapas nya rektanglar när användaren klickar med musen, dvs använd funktionen görRektanglar() i funktionen mouseClicked().

Avslutningsvis ska vi visa tre längre kodexempel där rektanglar används för att låta användaren göra val.

Menyer

Menyer används för att låta användaren göra val genom att klicka. En meny kan exempelvis låta användaren välja pennans utseende i ett ritprogram. Vi ska göra ett sådant ritprogram där användaren kan välja mellan att rita med en penna som är en ellips (som inte är en cirkel), en cirkel eller en kvadrat. Varje alternativ ska visas i en rektangel med en text.

Vi lagrar de tre alternativen i en lista meny. Varje element i listan ska ha en rektangels egenskaper samt ett namn. Menyn ska ligga i den vänstraste femtedelen av ritområdet. För att göra alla positioneringar enkla använder vi rectMode(CENTER) vilken låter en rektangels position le mitten av rektangeln. För de texter som ska visas använder vi textAlign(CENTER, CENTER) vilken centrerar texten både vertikalt och horisontellt.

Förutom variabeln meny behöver vi också en variabel val vilken håller reda på vilken penna användaren valt.

Grundstrukturen kan se ut så här:

let val, meny = [];

function setup() {
	let i;
	createCanvas(windowWidth, windowHeight);
	background(100);
	val = 0;
	textAlign(CENTER, CENTER);
	rectMode(CENTER);
	for (i = 0; i < 3; i += 1) {
		meny[i] = {
			x: 0.1 * width,
			y: (i + 1) * height / 4,
			bredd: 0.15 * width,
			höjd: height * 0.1
		}
	}
	meny[0].namn = "Ellips";
	meny[1].namn = "Cirkel";
	meny[2].namn = "Kvadrat";
}

function draw() {
	ritaMeny();
	fill(0)
	ritaMedPennan();
}

function ritaMedPennan() {
	// kod för att låta användaren rita
}

function ritaMeny() {
	// kod för att rita upp menyn
}

function påRektangel(r) {
	// kod som kollar om muspekaren är på en rektangel
}

function mouseClicked() {
	// kod för att hantera menyval
}

Användaren ska bara kunna rita om muspekaren inte är i den vänstraste femtedelen av ritområdet. Koden för att låta användaren rita kan se ut så här:

function ritaMedPennan() {
	if (mouseX > 0.2 * width && mouseIsPressed) {
		if (val == 0) {
			ellipse(mouseX, mouseY, 10, 30);
		} else if (val == 1) {
			ellipse(mouseX, mouseY, 20, 20);
		} else {
			rect(mouseX, mouseY, 20, 20);
		}
	}
}

Menyn ska ritas ut så att det valda alternativet ritas i en annan färg. Vi börjar med att rita över hela området till vänster med en mörkgrå färg. Koden kan se ut så här:

function ritaMeny() {
	let i;
	fill(50);
	rect(0.1 * width, 0.5 * height, 0.2 * width, height);
	for (i = 0; i < meny.length; i += 1) {
		if (i == val) {
			fill(200, 200, 255);
		} else {
			fill(255);
		}
		rect(meny[i].x, meny[i].y, meny[i].bredd, meny[i].höjd, 10);
		fill(0);
		text(meny[i].namn, meny[i].x, meny[i].y);
	}
}

Eftersom rektangelns position nu är i mitten av rektangeln måste den funktion påRektangel(r) som vi tidigare använt modifieras.

function påRektangel(r) {
	return mouseX > r.x - r.bredd / 2 && mouseY > r.y - r.höjd / 2 &&
		mouseX < r.x + r.bredd / 2 && mouseY < r.y + r.höjd / 2;
}

Slutligen gör vi koden som hanterar användarens val.

function mouseClicked() {
	let i;
	for (i = 0; i < meny.length; i += 1) {
		if (påRektangel(meny[i])) {
			val = i;
		}
	}
}

Slumpade flervalsfrågor

Att låta användaren göra val genom att klicka på rektangelformade menyer kan användas till att ställa flervalsfrågor. Vi kan göra ett enkelt program för att öva på multiplikationstabellen. Användaren får välja mellan tre olika svarsalternativ. Efter att användaren svarat ska det visa någon återkoppling som visar om svaret le rätt eller fel, därefter ska användaren kunna fortsätta öva.

Programmet kommer hela tiden att befinna sig i ett av två tillstånd, antingen ska det visas en fråga med tre svarsalternativ eller så ska det visas någon återkoppling.

Svarsalternativen lagras i en lista rekt av objekt som förutom en rektangels egenskaper också innehåller ett tal.

Vi behöver variabler för de två tal som ska multipliceras, en variabel för rätt index och en variabel för det index som användaren valt. En grundstruktur kan se ut så här:

let tillstånd, rekt = [], valtIndex, rättIndex, tal1, tal2;

function setup() {
	let i;
	createCanvas(windowWidth, windowHeight);
	background(100);
	tillstånd = 0;
	textAlign(CENTER, CENTER);
	rectMode(CENTER);
	textSize(32);
	for (i = 0; i < 3; i += 1) {
		rekt[i] = {
			x: (i + 1) * width / 4,
			y: 0.6 * height,
			bredd: 0.15 * width,
			höjd: height * 0.1
		}
	}
	initieraVärden();
}

function draw() {
	if (tillstånd == 0) {
		visaAlternativ()
	} else {
		visaÅterkoppling()
	}
}

function initieraVärden() {
	// kod som generar de värden som ska användas
}

function visaAlternativ() {
	// kod som visar fråga och svarsalternativ
}

function visaÅterkoppling() {
	// kod som visar rätt eller fel
}

function påRektangel(r) {
	return mouseX > r.x - r.bredd / 2 && mouseY > r.y - r.höjd / 2 &&
		mouseX < r.x + r.bredd / 2 && mouseY < r.y + r.höjd / 2;
}

function mouseClicked() {
	// kod för att hantera musklickningar
}

Vi kan återanvända koden för funktionen påRektangel(r) som används för rektanglar med centrerad position.

Vid initieringen av värden ska tal1, tal2 och rättIndex slumptalsgenereras. Eftersom vi bara vill ha heltal använder vi funktionen floor() på det slumptalsgenererade flyttalet. För att slumptalsgenera ett heltal mellan 1 och 10 används kommandot:

tal1 = floor(random(1, 11));

Varje listelement i listan rekt ska tilldelas ett heltal som antingen är ett slumptal mellan 1 och 100 eller som är produkten av tal1 och tal2 beroende på om listelementets index är lika med rättIndex eller inte.

Vi kan använda följande kod för detta:

for (i = 0; i < rekt.length; i += 1) {
	if (i == rättIndex) {
		rekt[i].tal = tal1 * tal2;
	} else {
		rekt[i].tal = floor(random(1, 101));
	}
}

Problemet med koden ovan är att det finns en viss sannolikhet att samma tal kommer med flera gånger. För att programmet alltid ska fungera måste de tre talen garanterat vara olika. Om något av de tre talen är lika med något annat av de tre talen vill vi göra om hela slumptalsgenereringen. Vi använder en do-while-sats för att slumptalsgenerera tal tills de garanterat är olika. Hela for-satsen kan läggas inuti en do-while-sats.

do {
	for (i = 0; i < rekt.length; i += 1) {
		if (i == rättIndex) {
			rekt[i].tal = tal1 * tal2;
		} else {
			rekt[i].tal = floor(random(1, 101));
		}
	}
} while (rekt[0].tal == rekt[1].tal || rekt[0].tal == rekt[2].tal || 
		 rekt[1].tal == rekt[2].tal);

Koden för funktionen initieraVärden() är sammanfattningsvis:

function initieraVärden() {
	let i;
	tal1 = floor(random(1, 11));
	tal2 = floor(random(1, 11));
	rättIndex = floor(random(3));
	do {
		for (i = 0; i < rekt.length; i += 1) {
			if (i == rättIndex) {
				rekt[i].tal = tal1 * tal2;
			} else {
				rekt[i].tal = floor(random(1, 101));
			}
		}
	} while (rekt[0].tal == rekt[1].tal || rekt[0].tal == rekt[2].tal || 
		     rekt[1].tal == rekt[2].tal);
}

I koden för visaAlternativ() ska frågan ställas sedan ska de tre svarsalternativen visas. Om muspekaren befinner sig på en rektangel ska denna visas i en annan färg.

function visaAlternativ() {
	let i;
	background(100);
	text("Vad är " + tal1 + "*" + tal2 + "?", 0.5 * width, 0.4 * height);
	for (i = 0; i < rekt.length; i += 1) {
		if (påRektangel(rekt[i])) {
			fill(200, 200, 255);
		} else {
			fill(255);
		}
		rect(rekt[i].x, rekt[i].y, rekt[i].bredd, rekt[i].höjd, 10);
		fill(0);
		text(rekt[i].tal, rekt[i].x, rekt[i].y);
	}
}

Funktionen visaÅterkoppling() anropas efter det att användaren valt ett svarsalternativ. Vi kan förutsätta att variabeln valtIndex har rätt värde.

function visaÅterkoppling() {
	background(100);
	if (valtIndex == rättIndex) {
		text("Rätt!", 0.5 * width, 0.4 * height);
	} else {
		text("Fel!", 0.5 * width, 0.4 * height);
	}
	text("Klicka för att fortsätta!", 0.5 * width, 0.6 * height);
}

Avslutningsvis ska vi hantera användarens musklickningar.

function mouseClicked() {
	let i;
	if (tillstånd == 0) {
		for (i = 0; i < rekt.length; i += 1) {
			if (påRektangel(rekt[i])) {
				valtIndex = i;
				tillstånd = 1;
			}
		}
	} else {
		tillstånd = 0;
		initieraVärden();
	}
}

Öva geografi

Det går att göra flervalsövningar där ett objekt ska paras ihop med ett annat objekt. Det kan handla om glosor, vad heter djurens barn, namngeografi eller andra par av ord. Det kan också vara ett objekt som är en bild och ett objekt som är ett ord, exempelvis en bild på en vinkel som ska paras ihop med något av orden "spetsig", "rät" eller "trubbig".

Vi ska göra ett program där användaren ska para ihop huvudstad och land. För detta behöver vi en lista land som innehåller objekt med varje lands namn och dess huvudstad. För enkelhetens skull visar vi lika många alternativ som det finns element i listan land. Vi lagrar rektangeldata i listan rekt.

Vi lägger in fem länder i listan och utgår ifrån följande grundstruktur:

let land = [], rekt = [], rättIndex, valtIndex, tillstånd;

function setup() {
	let i
	createCanvas(windowWidth, windowHeight);
	textAlign(CENTER, CENTER);
	rectMode(CENTER);
	textSize(24);
	land[0] = {namn: "Sverige", stad: "Stockholm"};
	land[1] = {namn: "Danmark", stad: "Köpenhamn"};
	land[2] = {namn: "Norge", stad: "Oslo"};
	land[3] = {namn: "Finland", stad: "Helsingfors"};
	land[4] = {namn: "Island", stad: "Reykavik"};
	for (i = 0; i < land.length; i += 1) {
		rekt[i] = {
			x: (i+1) * width / (land.length + 1),
			y: 0.6 * height,
			bredd: width / (land.length + 1) - 50,
			höjd: height * 0.1
		};
	}
	rättIndex = floor(random(land.length));
	tillstånd = 0;
}

function draw() {
	background(100);
	if (tillstånd == 0) {
		visaFråga();
	} else {
		visaResultat();
	}
}

function påRektangel(r) {
	return mouseX > r.x - r.bredd / 2 && mouseY > r.y - r.höjd / 2 &&
		mouseX < r.x + r.bredd / 2 && mouseY < r.y + r.höjd / 2;
}

function visaFråga() {
	// kod som visar frågan och alternativen
}

function visaResultat() {
	// kod som visar rätt eller fel
}

function mouseClicked() {
	// kod för att hantera musklickningar
}

Det enda som ska slumptalsgenereras är värdet på rättIndex. Detta görs i setup-funktionen och om användaren vill fortsätta öva efter att ha sett resultatet, dvs om användaren klickar när tillståndet är 1. Om användaren klickar när tillståndet är 0, ska eventuellt valtIndex få ett värde och tillståndet ska sättas till 1. Koden för musklickningar blir:

function mouseClicked() {
	let i;
	if (tillstånd == 0) {
		for (i = 0; i < rekt.length; i += 1) {
			if (påRektangel(rekt[i])) {
				valtIndex = i;
				tillstånd = 1;
			}
		}
	} else {
		tillstånd = 0;
		rättIndex = floor(random(land.length));
	}
}

Koden för att visa frågan och svarsalternativen är snarlik koden för att fråga om multiplikation.

function visaFråga() {
	let i;
	text("Vad heter huvudstaden i " + land[rättIndex].namn + "?", 0.5 * width, 0.3 * height);
	for (i = 0; i < rekt.length; i += 1) {
		if (påRektangel(rekt[i])) {
			fill(100, 100, 255);
		} else {
			fill(255);
		}
		rect(rekt[i].x, rekt[i].y, rekt[i].bredd, rekt[i].höjd, 10);
		fill(0);
		text(land[i].stad, rekt[i].x, rekt[i].y);
	}
}

Koden för att visa resultat är snarlik koden för återkoppling om multiplikation.

function visaResultat() {
	if (valtIndex == rättIndex) {
		text("Rätt!", width * 0.5, height * 0.4);
	} else {
		text("Fel!", width * 0.5, height * 0.4);
	}
	text(land[rättIndex].stad + " är huvudstad i " + land[rättIndex].namn + ".", width * 0.5, height * 0.5);
	text("Klicka för att fortsätta!", width * 0.5, height * 0.6);
}

Den här koden fungerar men är inte särskilt generell. Tänk om vi hade velat öva på 50 länder, eller 500 glosor, då hade det inte fått plats att visa ett svarsalternativ per element i listan. Hur koden för en sådan övning skulle se ut ger vi tips om i en av uppgifterna.

Programmeringsuppgifter

Uppgift 1

Skriv ut i konsol

Deklarera tre objekt i setup-funktionen med följande egenskaper.

  1. En bil som har egenskaperna märke och årsmodell.
  2. En elev som har egenskaperna namn, årskurs och skola.
  3. Ett land som har ett namn och en huvudstad.

Ge objektens egenskaper valfria värden. Skriv sedan ut kort information om varje objekt i konsolen.

Uppgift 2

Snöfall

Gör 50 vita cirklar som rör sig nedåt. Se till att det inte ritas ut någon kantfärg. När en snöflinga når den nedre kanten ska den få en ny slumpmässig x-position och en y-position som är vid den övre kanten.

Uppgift 3

Resa genom rymden

Gör en lista av 50 stjärnor vilka ritas som cirklar. Ge stjärnorna en slumpmässig position och radie i en for-sats. Efter att stjärnorna fått en position ska de få riktningar i en ny for-sats. Riktningen ska inte vara slumpmässig utan peka ut ifrån mitten av ritområdet.

riktning

Riktningar kan exempelvis skrivas med koden

for (i = 0; i < 50; i += 1) {
	stjärna[i].deltax = (stjärna[i].x-width/2)*0.05;
	stjärna[i].deltay = (stjärna[i].y-height/2)*0.05;
}

där 0.05 är en faktor som gör vektorn kortare.

Se till att alla stjärnor rör sig i draw-funktionen.

När en stjärna når en kant ska den få en ny position och en ny radie. Därefter ska den få en ny riktning.

Uppgift 4

Studsande smiley

Gör ett program för en studsande boll. När allting fungerar ska du rita upp bollen med en funktion som du definierar själv istället för att bara rita en ellips. Du kan använda koden för att rita en smiley eller hitta på någonting själv.

Uppgift 5

Enkel "Sikta och skjut"

Utgå ifrån beskrivningen under rubriken Strukturera ett spel. Gör ett enkelt program för att "Sikta och skjuta".

Uppgift 6

Sikta och skjut med gravitation

Utgå ifrån ditt program för att sikta och skjuta men se till att bollen faller neråt så att den rör sig längs en parabel.

Uppgift 7

Sikta och skjut med poängräkning

Utgå ifrån ett program för att sikta och skjuta men gör det till ett spel med poängräkning. Det kommer att behövas ett tredje tillstånd för att visa resultat.

function draw() {
	background(100);
	ritaStartpunkt();
	if (tillstånd == 0) {
		sikta();
	} else if (tillstånd == 1) {
		skjut();
	} else {
		visaResultat();
	}
}

Lägg in ett målobjekt som spelaren ska träffa. Om du använder rörelse utan gravitation kan målobjektet röra sig. Om du använder rörelse med gravitation kan målobjektet vara stilla på en slumpmässig position. Det går också att använda flera målobjekt i en lista.

Varje gång bollen träffar målobjektet ska spelaren få poäng och tillståndet ska ändras till noll. Använd funktionen dist(<x1>, <y1>, <x2>, <y2>) för att upptäcka kollisioner.

Spelet kan avslutas efter en viss tid. Om spelaren bara ska få spela en gång kan du använda frameCount och noLoop() för att avsluta draw-funktionen efter att du visat spelarens resultat. Om du istället vill att spelaren ska få testa att spela igen efter att du visat resultatet, kan du göra en egen variabel timer som räknas upp i draw-funktionen. Variabeln timer sätts till noll när spelet startar och om användaren klickar med musknappen när tillståndet är 2.

Uppgift 8

Ritprogram med meny

Utgå ifrån beskrivningen under rubriken Menyer. Gör ett ritprogram med meny.

Ändra uppritningen av menyn så att det inte visas någon text, istället ska respektive form ritas upp inuti respektive rektangel.

Lägg till ett val till, exempelvis formen "punkt". Ändra rektanglarnas positioner så att de är jämnt fördelade i vertikal led. Ändra uppritningen så att användaren också kan rita punkter.

Uppgift 9

Avancerat ritprogram

Gör ett ritprogram där användaren kan göra flera val både genom att använda en meny och med tangentklickningar.

Låt något menyval vara att det ritas någon effekt, exempelvis ett antal slumpmässiga sträckor som utgår ifrån muspekaren.

Se till att användaren kan ta bort allt som ritats upp genom att klicka på någon tangent.

Låt användaren välja mellan några färger, antingen med menyval eller tangenttryckningar.

Det går också att låta användaren ändra storlek på pennan med pil upp eller pil ned, eller att låta användaren välja hur genomskinlig pennan ska vara med pil höger eller vänster.

Om du använder tangenttryckningar kan du visa en instruktion i början av programmet som berättar vilken tangent som gör var. Låt sedan själva ritandet börja när användaren klickar med musen eller på någon tangent.

Om du har många olika menyval kan det vara enklare att använda absoluta positioner än att använda width och height. Se då till att ritområdet alltid har samma bredd och höjd genom att exempelvis använda:

createCanvas(800, 450);

Uppgift 10

Öva multiplikationstabell

Utgå ifrån beskrivningen under rubriken Slumpade flervalsfrågor. Gör ett övningsprogram för multiplikation. Ändra koden som visar återkoppling så att användaren får se vad rätt sle är om användaren svarat fel.

Uppgift 11

Valfri matematikövning

Gör ett program som låter användaren öva på multiplikation, division, ekvationslösning eller annan matematikuppgift som lätt kan slumptalsgenereras. För att undvika avrundningsfel är det enklast om bara heltal används.

För att öva på division kan du utgå ifrån programmet för att öva multiplikationstabell men ändra på hur frågan ställs och hur svarsalternativen visas.

Det är förhållandevis enkelt att slumptalsgenerera en övning om enkel ekvationslösning med ekvationer av typen \( ax+b = c\). Börja med att slumptalgenerera lösningen som ett heltal, dvs talet \(x\). Slumptalsgenerera sedan ett heltal \(a\) och se till att \(a\) inte är lika med noll genom att använda en do-while-sats. Slumptalsgenerera sedan ett heltal \(b\) och beräkna slutligen talet \(c\). När du ställer frågan bör du skriva olika textsträngar beroende på om \(b\) är positivt eller negativt. Det bör se ut som:

Lös ekvationen 2x+5 = 11 eller Lös ekvationen 2x-5 = 1

Räkna antalet besvarade frågor och antalet rätta svar. Avsluta programmet när ett visst antal frågor besvarats eller efter det att användaren svarat rätt ett visst antal gånger. Visa något slutmeddelande om hur övningen gick. Ge programmet det utseende du tycker blir bra, exempelvis kan olika färger användas för rätt eller fel svar. Man kan också använda bilder för att visualisera rätt eller fel på olika sätt.

Uppgift 12

Öva namngeografi

Utgå ifrån beskrivningen under rubriken Öva geografi. Gör ett övningsprogram för namngeografi. Lägg in ett eller två länder till i listan. Koden ska fortfarande fungera men om fönstret är litet får inte huvudstäderna plats i rektanglarna.

Uppgift 13

Många länder

Utgå ifrån koden för att öva namngeografi. Nu ska koden göras om så att många länder kan användas men bara tre svarsalternativ ska visas. Deklarera en lista till överst i programmet kallad lång. Byt ut koden där länderna läggs in i listan land så att länderna istället läggs in i listan lång.

lång[0] = {namn: "Sverige", stad: "Stockholm"};
lång[1] = {namn: "Danmark", stad: "Köpenhamn"};
lång[2] = {namn: "Norge", stad: "Oslo"};
lång[3] = {namn: "Finland", stad: "Helsingfors"};
lång[4] = {namn: "Island", stad: "Reykavik"};

Lägg till några länder till i listan lång så att den blir just lång.

Nu ska du välja ut tre slumpmässiga länder från listan lång och lägga in i listan land. Gör en funktion med följande kod:

function väljTre() {
	let ett, två, tre;
	do {
		ett = floor(random(lång.length));
		två = floor(random(lång.length));
		tre = floor(random(lång.length));
	} while (ett == två || ett == tre || två == tre);
	land[0] = {namn: lång[ett].namn, stad: lång[ett].stad};
	land[1] = {namn: lång[två].namn, stad: lång[två].stad};
	land[2] = {namn: lång[tre].namn, stad: lång[tre].stad};
}

Funktionen väljTre() måste anropas efter att listan lång fyllts på i setup-funktionen. Den måste också anropas när användaren klickar med musen då tillståndet är 0, innan rättIndex slumptalsgenereras.

Testkör din kod för att se om du fått det att fungera.

Uppgift 14

Valfri flervalsövning som utgår ifrån listor

Gör ett program som till strukturen liknar programmet för många länder men där du också håller reda på hur många gånger användaren besvarat en fråga och hur många sle som varit rätt.

Istället för att para ihop ord kan du para ihop bilder och ord. Använd bildfiler som du har på din dator, eller producera bildfiler på valfritt sätt.

Avsluta programmet när ett visst antal frågor besvarats eller efter det att användaren svarat rätt ett visst antal gånger. Visa något slutmeddelande om hur övningen gick. Ge programmet det utseende du tycker blir bra, exempelvis kan olika färger användas för rätt eller fel svar. Man kan också använda bilder för att visualisera rätt eller fel på olika sätt.