M9 | L11 – Joc de memorie III

Astăzi vom afla despre:

  • Intoarcem piesele

Aplicațiile pe care le vom folosi sunt:

Khan Academy

PROIECTE:

Joc de memorie

Exemplu: https://www.khanacademy.org/computer-programming/joc-memorie-03/6555802348240896

Intoarcem piesele

Bine, deci avem acum o grilă de dale pe care o putem afișa cu fața în sus sau cu fața în jos. Dar nu avem cum să jucăm jocul. Pentru a vă reaminti, iată cum funcționează jocul:

Când începe jocul, toate plăcile sunt întoarse cu fața în jos.

Jucătorul răstoarnă apoi peste două cărți, selectându-le făcând clic pe ele.

Dacă cele două plăci au aceeași imagine, ele rămân cu fața în sus. Dacă nu, ar trebui să fie răsturnate cu fața în jos după o scurtă întârziere.

Apasarea pieselor

În acest moment, avem un program care desenează o grilă de dale și apoi nu desenează nimic altceva. Înainte, vrem ca programul nostru să atragă diferite lucruri în timp – va începe să deseneze țiglele cu fața în jos, dar apoi va afișa plăcile făcute clic și, dacă totul merge bine pentru jucător (degetele încrucișate!), Va fi afișează un ecran de câștig.

Deci, să mutăm tot codul nostru de desen în funcția de procesare ProcessingJS. Computerul va continua să apeleze draw () în timp ce programul rulează, astfel încât plăcile vor continua să fie trase în funcție de faptul că sunt cu fața în sus sau cu fața în jos:

draw = function() {

    background(255, 255, 255);

    for (var i = 0; i < tiles.length; i++) {

        tiles[i].draw();

    }

};

Să luăm câteva dintre aceste plăci cu fața în sus acum! Pentru a răsturna o țiglă, jucătorul trebuie să facă clic pe ea. Pentru a răspunde la clic în programele ProcessingJS, putem defini o funcție mouseClicked, iar computerul va executa acel cod de fiecare dată când se face clic pe mouse.

mouseClicked = function() {

  // process click somehow

};

Când programul nostru vede că jucătorul a dat clic undeva, vrem să verificăm dacă au dat clic pe o țiglă, folosind mouseX și mouseY. Să începem prin adăugarea unei metode isUnderMouse la Tile care returnează true dacă un anumit x și y se află în zona unei țigle.

Cu modul în care am desenat plăcile, x și y ale plăcii corespund colțului din stânga sus al plăcii, așa că ar trebui să revenim adevărat doar dacă x-ul dat este între this.x și this.x + this.size , și dacă y dat este între this.y și this.y + this.size:

Tile.prototype.isUnderMouse = function(x, y) {

    return x >= this.x && x <= this.x + this.size  &&

        y >= this.y && y <= this.y + this.size;

};

Acum că avem această metodă, putem folosi o buclă for în mouseClicked pentru a verifica dacă fiecare țiglă se află sub mouseX și mouseY. Dacă da, setăm proprietatea isFaceUp a țiglei la adevărat:

mouseClicked = function() {

  for (var i = 0; i < tiles.length; i++) {

    if (tiles[i].isUnderMouse(mouseX, mouseY)) {

      tiles[i].isFaceUp = true;

    }

  }

};

Asta se intampla:

var Tile = function(x, y, face) {

    this.x = x;

    this.y = y;

    this.size = 50;

    this.face = face;

    this.isFaceUp = false;

};

Tile.prototype.draw = function() {

    fill(214, 247, 202);

    strokeWeight(2);

    rect(this.x, this.y, this.size, this.size, 10);

    if (this.isFaceUp) {

        image(this.face, this.x, this.y, this.size, this.size);

    } else {

        image(getImage(„avatars/leaf-green”), this.x, this.y, this.size, this.size);

    }

};

Tile.prototype.isUnderMouse = function(x, y) {

    return x >= this.x && x <= this.x + this.size  &&

        y >= this.y && y <= this.y + this.size;

};

// Global config

var NUM_COLS = 5;

var NUM_ROWS = 4;

// Declare an array of all possible faces

var faces = [

    getImage(„avatars/leafers-seed”),

    getImage(„avatars/leafers-seedling”),

    getImage(„avatars/leafers-sapling”),

    getImage(„avatars/leafers-tree”),

    getImage(„avatars/leafers-ultimate”),

    getImage(„avatars/marcimus”),

    getImage(„avatars/mr-pants”),

    getImage(„avatars/mr-pink”),

    getImage(„avatars/old-spice-man”),

    getImage(„avatars/robot_female_1”)

];

// Make an array which has 2 of each, then randomize it

var possibleFaces = faces.slice(0);

var selected = [];

for (var i = 0; i < 10; i++) {

    // Randomly pick one from the array of remaining faces

    var randomInd = floor(random(possibleFaces.length));

    var face = possibleFaces[randomInd];

    // Push 2 copies onto array

    selected.push(face);

    selected.push(face);

    // Remove from array

    possibleFaces.splice(randomInd, 1);

}

// Now shuffle the elements of that array

var shuffleArray = function(array) {

    var counter = array.length;

    // While there are elements in the array

    while (counter > 0) {

        // Pick a random index

        var index = Math.floor(Math.random() * counter);

        // Decrease counter by 1

        counter–;

        // And swap the last element with it

        var temp = array[counter];

        array[counter] = array[index];

        array[index] = temp;

    }

};

shuffleArray(selected);

// Create the tiles

var tiles = [];

for (var i = 0; i < NUM_COLS; i++) {

    for (var j = 0; j < NUM_ROWS; j++) {

        var tileX = i * 54 + 10;

        var tileY = j * 54 + 40;

        var tileFace = selected.pop();

        tiles.push(new Tile(tileX, tileY, tileFace));

    }

}

mouseClicked = function() {

    // check if mouse was inside a tile

    for (var i = 0; i < tiles.length; i++) {

        if (tiles[i].isUnderMouse(mouseX, mouseY)) {

            tiles[i].isFaceUp = true;

        }

    }

};

draw = function() {

    background(255, 255, 255);

    for (var i = 0; i < tiles.length; i++) {

        tiles[i].draw();

    }

};

Restrictionarea pieselor

Observi ceva? Am implementat un aspect al jocului, că jucătorul este capabil să răstoarne peste dale, dar ne lipsește o restricție importantă: nu ar trebui să poată răsfoi mai mult de două dale simultan.

Va trebui să urmărim cumva numărul de plăci răsturnate. O modalitate simplă ar fi o variabilă globală numFlipped pe care o incrementăm de fiecare dată când jucătorul întoarce o carte cu fața în sus. Întoarcem o țiglă numai dacă numFlipped este mai mic de 2 și nu este deja cu fața în sus:

var numFlipped = 0;

mouseClicked = function() {

    for (var i = 0; i < tiles.length; i++) {

        var tile = tiles[i];

        if (tiles.isUnderMouse(mouseX, mouseY)) {

            if (numFlipped < 2 && !tile.isFaceUp) {

              tile.isFaceUp = true;

              numFlipped++;

            }

        }

    }

};

Intarzierea pieselor

Bine, logica noastră cu două plăci este completă. Ce urmeaza? Să recapitulăm regulile jocului din nou:

Dacă cele două plăci au aceeași imagine, ele rămân cu fața în sus. În caz contrar, plăcile se întorc înapoi după o perioadă de timp.

Mai întâi vom implementa a doua parte, care întoarce automat dale înapoi, deoarece va fi greu să testăm prima parte dacă nu putem căuta cu ușurință noi potriviri.

Știm cum să întoarcem plăcile înapoi, setând isFaceUp la false, dar cum o putem face după o anumită perioadă de timp? Fiecare limbă și mediu are o abordare diferită pentru întârzierea executării codului și trebuie să ne dăm seama cum să o facem în ProcessingJS. Avem nevoie de un mod de a ține evidența timpului – dacă perioada de întârziere a trecut – și de un mod de a apela codul după ce a trecut perioada de timp. Iată ce aș sugera:

  • Creăm o variabilă globală numită delayStartFC, inițial nulă.
  • În funcția mouseClicked, imediat după ce am răsturnat o a doua țiglă, stocăm valoarea curentă a frameCount în delayStartFC. Această variabilă ne spune câte cadre au trecut de când programul a început să ruleze și este o modalitate de a spune timpul în programele noastre.
  • În funcția de extragere, verificăm dacă noua valoare a frameCount este semnificativ mai mare decât cea veche și, dacă da, întoarcem toate dale și setăm numFlipped la 0. De asemenea, resetăm delayStartFC la nul.

Este de fapt o soluție plăcută care nu necesită prea mult cod pentru a fi implementată. Ca optimizare a performanței, putem folosi funcțiile buclă și noLoop pentru a ne asigura că codul de extragere este apelat numai atunci când se întâmplă o întârziere. Iată totul:

var numFlipped = 0;

var delayStartFC = null;

mouseClicked = function() {

  for (var i = 0; i < tiles.length; i++) {

    var tile = tiles[i];

    if (tile.isUnderMouse(mouseX, mouseY)) {

      if (numFlipped < 2 && !tile.isFaceUp) {

        tile.isFaceUp = true;

        numFlipped++;

        if (numFlipped === 2) {

          delayStartFC = frameCount;

        }

        loop();

      }

    }

  }

};

draw = function() {

  if (delayStartFC &&

     (frameCount – delayStartFC) > 30) {

    for (var i = 0; i < tiles.length; i++) {

      tiles[i].isFaceUp = false;

    }

    numFlipped = 0;

    delayStartFC = null;

    noLoop();

  }

  background(255, 255, 255);

  for (var i = 0; i < tiles.length; i++) {

    tiles[i].draw();

  }

};

Codul:

var Tile = function(x, y, face) {

    this.x = x;

    this.y = y;

    this.size = 50;

    this.face = face;

    this.isFaceUp = false;

};

Tile.prototype.draw = function() {

    fill(214, 247, 202);

    strokeWeight(2);

    rect(this.x, this.y, this.size, this.size, 10);

    if (this.isFaceUp) {

        image(this.face, this.x, this.y, this.size, this.size);

    } else {

        image(getImage(„avatars/leaf-green”), this.x, this.y, this.size, this.size);

    }

};

Tile.prototype.isUnderMouse = function(x, y) {

    return x >= this.x && x <= this.x + this.size  &&

        y >= this.y && y <= this.y + this.size;

};

// Global config

var NUM_COLS = 5;

var NUM_ROWS = 4;

// Declare an array of all possible faces

var faces = [

    getImage(„avatars/leafers-seed”),

    getImage(„avatars/leafers-seedling”),

    getImage(„avatars/leafers-sapling”),

    getImage(„avatars/leafers-tree”),

    getImage(„avatars/leafers-ultimate”),

    getImage(„avatars/marcimus”),

    getImage(„avatars/mr-pants”),

    getImage(„avatars/mr-pink”),

    getImage(„avatars/old-spice-man”),

    getImage(„avatars/robot_female_1”)

];

// Make an array which has 2 of each, then randomize it

var possibleFaces = faces.slice(0);

var selected = [];

for (var i = 0; i < 10; i++) {

    // Randomly pick one from the array of remaining faces

    var randomInd = floor(random(possibleFaces.length));

    var face = possibleFaces[randomInd];

    // Push 2 copies onto array

    selected.push(face);

    selected.push(face);

    // Remove from array

    possibleFaces.splice(randomInd, 1);

}

// Now shuffle the elements of that array

var shuffleArray = function(array) {

    var counter = array.length;

    // While there are elements in the array

    while (counter > 0) {

        // Pick a random index

        var ind = Math.floor(Math.random() * counter);

        // Decrease counter by 1

        counter–;

        // And swap the last element with it

        var temp = array[counter];

        array[counter] = array[ind];

        array[ind] = temp;

    }

};

shuffleArray(selected);

// Create the tiles

var tiles = [];

for (var i = 0; i < NUM_COLS; i++) {

    for (var j = 0; j < NUM_ROWS; j++) {

        var tileX = i * 54 + 10;

        var tileY = j * 54 + 40;

        var tileFace = selected.pop();

        tiles.push(new Tile(tileX, tileY, tileFace));

    }

}

background(255, 255, 255);

var numFlipped = 0;

var delayStartFC = null;

mouseClicked = function() {

    for (var i = 0; i < tiles.length; i++) {

        var tile = tiles[i];

        if (tile.isUnderMouse(mouseX, mouseY)) {

            if (numFlipped < 2 && !tile.isFaceUp) {

                tile.isFaceUp = true;

                numFlipped++;

                if (numFlipped === 2) {

                    delayStartFC = frameCount;

                }

                loop();

            }

        }

    }

};

draw = function() {

    if (delayStartFC && (frameCount – delayStartFC) > 30) {

        for (var i = 0; i < tiles.length; i++) {

            tiles[i].isFaceUp = false;

        }

        numFlipped = 0;

        delayStartFC = null;

        noLoop();

    }

    background(255, 255, 255);

    for (var i = 0; i < tiles.length; i++) {

        tiles[i].draw();

    }

};

noLoop();

Verificarea pieseleor

Dacă ați reușit să potriviți orice țiglă de mai sus, probabil că ați fost trist când au răsturnat, pentru că, hei, ați făcut un meci! Deci, acum este timpul să implementăm această regulă a jocului:

Dacă două plăci se potrivesc, atunci acestea ar trebui să rămână cu fața în sus.

Asta înseamnă că ar trebui să verificăm dacă există dale potrivite ori de câte ori sunt răsturnate 2 și înainte de a stabili întârzierea. În pseudo-cod, acesta ar fi:

Avem deja o verificare dacă există două plăci întoarse (numFlipped === 2), deci cum putem verifica dacă plăcile au aceeași față? În primul rând, avem nevoie de o modalitate de a accesa cele două flip peste dale. Cum le găsim?

Am putea itera prin matricea noastră de fiecare dată, să găsim toate plăcile cu isFaceUp setat la adevărat și apoi să le stocăm într-o matrice.

Iată o comandă rapidă: haideți să păstrăm întotdeauna plăcile noastre răsturnate într-o matrice, pentru acces ușor. În acest fel, nu trebuie să parcurgem întreaga noastră gamă de țiglă de fiecare dată când jucătorul rotește o țiglă.

Ca prim pas, putem înlocui numFlipped cu o matrice și apoi să folosim flippedTiles.length oriunde am folosit anterior numFlipped. Funcția noastră mouseClicked arată astfel:

var flippedTiles = [];

var delayStartFC = null;

mouseClicked = function() {

  for (var i = 0; i < tiles.length; i++) {

    var tile = tiles[i];

    if (tile.isUnderMouse(mouseX, mouseY)) {

      if (flippedTiles.length < 2 && !tile.isFaceUp) {

        tile.isFaceUp = true;

        flippedTiles.push(tile);

        if (flippedTiles.length === 2) {

          delayStartFC = frameCount;

          loop();

        }

      }

    }

  }

};

Acum, trebuie să ne dăm seama dacă cele două dale din matricea flippedTiles au într-adevăr aceeași față. Ei bine, care este proprietatea feței? Este un obiect – și, de fapt, fața plăcilor potrivite ar trebui să fie exact același obiect, ca în, variabila indică în același loc în memoria computerului pentru ambele. Asta pentru că am creat fiecare obiect de imagine o singură dată (cum ar fi cu getImage („avatare / vechi-spice-man”)) și apoi am împins același obiect de imagine de două ori pe tabloul de fețe:

var face = possibleFaces[randomInd];

selected.push(face);

selected.push(face);

În JavaScript cel puțin, operatorul de egalitate va reveni adevărat dacă este utilizat pe două variabile care indică obiecte și ambele variabile se referă la același obiect din memorie. Asta înseamnă că verificarea noastră poate fi simplă – folosiți doar operatorul de egalitate de pe proprietatea feței fiecărei țigle:

if (flippedTiles[0].face === flippedTiles[1].face) {

  …

}

Acum, când știm că se potrivesc plăcile, trebuie să le menținem. În prezent, toți ar fi răsturnați după o întârziere. Nu am putea configura animația în acest caz, dar amintiți-vă, va exista o animație în ture ulterioare – deci nu ne putem baza pe asta.

În schimb, avem nevoie de un mod de a cunoaște „hei, când le întoarcem pe toate, nu ar trebui să le întoarcem pe acestea”. Sună ca o utilizare bună pentru o proprietate booleană! Să adăugăm o proprietate isMatch la constructorul Tile și apoi setăm isMatch la true numai în interiorul blocului dacă:

if (flippedTiles[0].face === flippedTiles[1].face) {

  flippedTiles[0].isMatch = true;

  flippedTiles[1].isMatch = true;

}

Acum putem folosi acea proprietate pentru a decide dacă întoarcem dale după întârziere.

for (var i = 0; i < tiles.length; i++) {

  var tile = tiles[i];

  if (!tile.isMatch) {

    tile.isFaceUp = false;

  }

}

Final: https://www.khanacademy.org/computer-programming/joc-memorie-03/6555802348240896