P5js
Børre Stenseth
Problemer >Kvadrater

Magiske kvadrater

Hva

Tema er å lese og forstå kode og gjøre endringer.

Vi skal se litt på problemer knyttet opp til å lage et enkelt spill. Brukeren skal reorganisere tall i en tabell slik at de danner et magisk kvadrat.

Med et magisk kvadrat forstår vi en to-dimensjonal tabell der hver rute inneholder et tall og summen av alle tallene i hver kolonne og hver rad er den samme. I eksempelet er summen av alle kolonner og summen av alle rader 15. Du finner masse materiale om magiske kvadrater på nettet.

Spillideen er å legge ut tall i en tabell som ikke er, men kan bli, et magisk kvadrat. Spilleren skal kunne bytte om på tall for at det skal bli "magisk".

Du kan se løsningen jeg har valgt å lage på en egen side:
test.html https://borres.hiof.no/pisk/P5js/p5jsskisser/kvadrater/test.html

Vurderinger

Jeg har truffet noen valg når dt gjelder selve problemet:

  • Jeg har valgt å bruke en 4x4 tabell. Dette er diskutabelt og avhenger selvsagt av den potesielle målgruppa. 3x3 kunne vært et alternativ for unge brukere.
  • Jeg har valgt å bruke tallene 1 t.o.m. 16, dvs så lave tall som mulig. Det gjør det greitt å summere i hodet.

Struktur

Jeg har forsøkt beskrive den planen jeg har hatt i hodet, og på papiret, når jeg laget denne koden. Jeg er fullstendig klar over at det er en ufullstendig forklaring og det er selvsagt også slik at jeg har prøvd og feilet underveis. Ikke desto mindre tror jeg at oppdelingen av problemløsningen kan være ganske fornuftig og være til hjelp hvis jeg, eller andre, skal gjøre noe med denne koden senere.

Det første jeg gjør er å lage kode som kan lage et magisk kvadrat i en 2-dimensjonal Array. Hvis jeg har løst dette har jeg en fasit, og jeg kan flytte litt på noen tall for å lage en oppgave.

Jeg begynner med å velge blandt noen kjente standardløsninger. Etterpå stokker jeg kolonner og rader. Dette endrer ikke summene. (Funksjonen tilfeldigTall() er definert i sketch.js)

_lagproblem.js
// vil lage en 2-dimensjonal array med et magisk kvadrat
function lagVerdier(){
    // skal lage og returnere denne som en 4x4 array
    var verdier; 
    // Setter opp verdier basert på kjente løsninger
    var select=tilfeldigTall(1,7);
    switch (select){
        case 1: verdier=[[4, 5,12,13],
                            [14,3,9, 8],
                            [15,10,2,7],
                            [1,16,11,6]];
        break;
        case 2:    verdier=[[1,8,9,16],
                            [15,3,14,2],
                            [12,13,4,5],
                            [6,10,7,11]];
        break;
        case 3:    verdier=[[4,5,10,15],
                            [8,12,11,3],
                            [9,16,7,2],
                            [13,1,6,14]];
        break;
        case 4:    verdier=[[4,8,9,13],
                            [14,3,7,10],
                            [15,11,2,6],
                            [1,12,16,5]];
        break;
        case 5:    verdier=[[1,8,10,15],
                            [16,13,3,2],
                            [11,4,14,5],
                            [6,9,7,12]];
        break;
        case 6:    verdier=[[1,8,14,11],
                            [16,2,3,13],
                            [10,15,5,4],
                            [7,9,12,6]];
        break;
        default:verdier=[[4,9,10,11],
                            [3,8,7,16],
                            [14,2,12,6],
                            [13,15,5,1]];
        }        
    // lager varianter ved å stokke rader og kolonner
    for(var i=0; i< 10; i++){
         var r1=tilfeldigTall(0,3);
         var r2=tilfeldigTall(0,3);
         for(var k=0; k< 4;k++){
            var tmp=verdier[r1][k];
            verdier[r1][k]=verdier[r2][k];
            verdier[r2][k]=tmp;
         }     
     }
    for(var i=0; i< 10; i++){
         var k1=tilfeldigTall(0,3);
         var k2=tilfeldigTall(0,3);
         for(var r=0; r< 4;r++){
            var tmp=verdier[r][k1];
            verdier[r][k1]=verdier[r][k2];
            verdier[r][k2]=tmp;
         }     
     }     
     return verdier;
}

Jeg trenger å kunne beskrive en rute, med verdi, posisjon, og fasitverdi. En rute må være klikkbar og må kunne endre farge. Jeg definerer et rute-objekt. Etter en del iterasjoner og testing har jeg endt opp med dette:

_rute.js
// obbjekt definisjon av en rute
function rute(rad,kol,left,top,verdi){
    this.rad=rad;
    this.kol=kol;
    this.top=top;
    this.left=left;
    this.verdi=verdi;
    this.rettVerdi=verdi;
    this.markert=false;
    
    this.getRad=function(){
        return this.rad;
    }
    this.getKolonne=function(){
        return this.kol;
    }
    this.markerValgt=function(){
        this.markert=true;
    }
    this.markerIkkeValgt=function(){
        this.markert=false;
    }
    
    this.setVerdi=function(v){
        this.verdi=v;
    }
    
    this.getVerdi=function(){
        return this.verdi;
    }
    
    this.setTargetVerdi=function(v){
        this.rettVerdi=v;
    }
    
    this.getTargetVerdi=function(){
        return this.rettVerdi;
    }
    
    this.valgt=function(){
        if(this.verdi==this.rettVerdi)
            return false;
        return    (mouseX > this.left) && 
                    (mouseX < this.left+ruteKant) && 
                    (mouseY > this.top) && 
                    (mouseY < this.top+ruteKant);
    }
    // brukes for å tegne ikkefargede kolonne og radsummer
    this.tegnTall=function(){
        push();
            translate(this.left,this.top);
            textFont('Arial',fontBig);
            textAlign(CENTER,CENTER);
            text(""+this.verdi, ruteKant/2,ruteKant/2)
        pop();        
    }    
    
    this.tegn=function(){
        push();
            translate(this.left,this.top);
            if(this.markert)
                fill(markertFarge);
            else if(this.verdi==this.rettVerdi)        
                fill(rettFarge);
            else
                fill(galFarge);
            stroke(0);
            rect(0,0,ruteKant,ruteKant);
            fill(0);
            textFont('Arial',fontBig);
            textAlign(CENTER,CENTER);
            text(""+this.verdi, ruteKant/2,ruteKant/2)
        pop();        
    }
}

Da har vi altså laget en oppgave generator og et ruteobjekt. Da må vi kople de to. Det er her vi manipulerer et magisk kvadrat slik at det blir en oppgave å rekonstruere det. Jeg administrerer ruter og oppgaver med denne koden:

_kvadrater.js
// administrasjon av verdier og ruter
// bytter om innholdet i to ruter
function byttInnhold(r1,k1,r2,k2){
    var tmp=rutene[r2][k2].getVerdi();
    rutene[r2][k2].setVerdi(rutene[r1][k1].getVerdi());
    rutene[r1][k1].setVerdi(tmp);
}
// setter opp ruteverdier basert på noen
// kjente løsninger
function setOppRutene(){
    // lag et magisk kvadrat, 4x4 array
    var verdier=lagVerdier();
    //oppretter ruter basert på de verdiene vi har funnet        
    rutene = new Array(4);
    for (var i = 0; i < 4; i++) {
        rutene[i] = new Array(4);
    }
    for(var r =0;r<4;r++)
    for(var k =0;k<4;k++)            
        rutene[r][k]=new rute(r,k, ruteKant*(k+1),ruteKant*(r+1),verdier[r][k]);
    // setter opp kolonne med radsummer
    radSumRuter= new Array(4);
    for(var r=0;r < 4; r++)
        radSumRuter[r]=new rute(r,0, ruteKant*(6),ruteKant*(r+1),0);
    // setter opp rad med kolonnesummer
    kolSumRuter= new Array(4);
    for(var k=0;k < 4; k++)
        kolSumRuter[k]=new rute(0,k, ruteKant*(k+1),ruteKant*(6),0);
    // beregn summer
    oppdaterSummer();        
    // stokk ruter, dvs. 
    // flytt om intil vi er sikre på at vi ikke legger ut
    // et ferdig løst problem
    while(recalculateSquares())
    {
      var swaps=tilfeldigTall(4,10);
      for(var ix=0; ix<swaps; ix++)
      {
         byttInnhold(
                tilfeldigTall(0,3),tilfeldigTall(0,3),
                tilfeldigTall(0,3),tilfeldigTall(0,3));
      }
  }      
}
function oppdaterSummer(){
    for(var r=0; r < 4; r++){
        radSumRuter[r].setVerdi(rutene[r][0].getVerdi()+
                                rutene[r][1].getVerdi()+
                                        rutene[r][2].getVerdi()+
                                        rutene[r][3].getVerdi());
    }
    for(var k=0; k < 4; k++){
        kolSumRuter[k].setVerdi(rutene[0][k].getVerdi()+
                                rutene[1][k].getVerdi()+
                                        rutene[2][k].getVerdi()+
                                        rutene[3][k].getVerdi());
    }
}
function recalculateSquares()
{
  //returnerer true hvis alle summene er 34
  oppdaterSummer()
  for(var i=0; i< 4; i++){
      if(radSumRuter[i].getVerdi()!=34)
          return false
      if(kolSumRuter[i].getVerdi()!=34)
          return false
  }
  return true;
}
function erViFerdig(){
    return recalculateSquares();
}

Hovedskriptet som drar det hele i gang er slik:

_sketch.js
/*
4X4 magisk kvadrat
*/
var ruteKant=40;
var fontBig=25;
var rettFarge,galFarge,markertFarge;
var rutene;            //4x4 brettet
var radSumRuter;    // kolonne til høyre
var kolSumRuter;    // rad under
var valg1;            // har klikket på en rute
var melding;        // når problemet er løst
function setup(){
    var canvas = createCanvas(ruteKant*7.5,ruteKant*7.5);
    canvas.parent('canvasHere');
    rettFarge=color(255,255,255);
    galFarge=color(200,200,255);
    markertFarge=color(255,200,200);
    valg1=null;
    melding="";
    // første oppgave
    setOppRutene();
}
function draw(){
    background(230);
    visMelding();    
    for(var r =0;r<4;r++)
    for(var k =0;k<4;k++){
        rutene[r][k].tegn();
    }
    for(var i =0;i<4;i++){
        radSumRuter[i].tegnTall();
        kolSumRuter[i].tegnTall();
    }
    stroke(0);
    line(5.5*ruteKant,ruteKant,5.5*ruteKant,ruteKant+6*ruteKant);
    line(ruteKant,5.5*ruteKant,7*ruteKant,5.5*ruteKant)
 }
 
 function visMelding(){
     push(),
        fill(255,0,0);
        textAlign(CENTER,CENTER);
        textFont('Arial',fontBig-3);
        text(melding,width/2,ruteKant/2);
    pop();
}
// trenger å lage noen tilfeldige tall
function tilfeldigTall(min,max){
    return Math.floor(Math.random()*(max-min+1)+min);
}
// kalles fra button under skissa
// ny oppgave
function nyttBrett(){
    melding="";
    setOppRutene();
}
  
function mousePressed()
{
    for(var r=0; r<4; r++)
    for(var k=0; k<4; k++){
        if(rutene[r][k].valgt()){
            if(valg1!=null){
                byttInnhold(r,k,valg1.getRad(),valg1.getKolonne());
                valg1.markerIkkeValgt();
                valg1=null;
                oppdaterSummer();
                if(erViFerdig()){
                    melding="Gratulerer, problemet løst !";
                }
            }
            else{
                valg1=rutene[r][k];
                valg1.markerValgt();
                return;
            }
        }
    }
}

Selve websiden er ganske grei

_test.html

Oppgaverforslag

Endre dimensjoner

Skriv om løsningen slik at vi lager 3x3 ruter.

Alternativt innhold

Egentlig er jo dette en form for puslespill. Kan vi fylle rutene med noe annet og lage andre problemer som skal kunne løses med klikk-klikk? Bildedeler, bilder, andre tall, bokstaver, ord ?

Problemer >Kvadrater