神経衰弱

Processingで神経衰弱をつくります。

課題

ステップ1

5枚のイメージを以下のように整列して描画してください。一つの画像は150x150のサイズです。
MemoryCard1

ステップ2

クリックしたら、その箇所の画像を開いてください。画像が開いているか否か保持する配列openedを以下のように定義してください。

boolean[] opened = {
  false,false,false,false,false,
  false,false,false,false,false};

MemoryCard2

ステップ3

画像をランダムな順序に配置します。

int[] numbers = {0,0,1,1,2,2,3,3,4,4};

という配列をグローバル変数として用意します。番号がカードの種表しているものとします。0~4までのカードが2枚づつある状態を表しています。setup関数のなかでこの配列をランダムに入れ替えましょう。「2つのカードをランダムに選んで、それらを入れ替える」、そんな作業を何度も繰り返してシャッフルしてみましょう。

  for(int i = 0 ; i < 100 ; i++){
    int a = (int)random(9);
    int b = (int)random(9);
    int t = numbers[a];
    numbers[a] = numbers[b];
    numbers[b] = t;
  }

ステップ4

2枚のカードがそろった時は開いたまま、2枚のカードが異なるときはまた裏返し、という処理を実装することでゲームを完成させましょう。何番目のカードがクリックされたか保持する変数を2つ導入します。

int card0 = -1;
int card1 = -1;

card0は1枚目のカードの番号、card1は2枚目のカードの番号です。この値が-1だと選ばれていないという意味とします。

mousePressed関数で、1枚目なのか(card0が-1)、2枚目なのか(card0が-1でない)を判断することができます。1枚目なら単にその番号を変数card0に保存します。2枚目ならカード番号が一致しているか否か比較します。一致していたらopened配列を更新します。

mouseReleased関数でもし両方とも-1でなければ(すなわち2枚カードがめくられたら)それらを元に戻すようにしましょう。

解答例

ステップ1

PImage[] cards = new PImage[5];

void setup(){
  size(750, 300);
  for(int i = 0 ; i < 5 ; i++){
    cards[i] = loadImage("image"+i+".png");
  }
}

void draw(){
  for(int i = 0 ; i < 10 ; i++){
    int x = (i % 5)*150;
    int y = (int)(i/5)*150;    
    image(cards[i%5], x, y, 150, 150);
  }
}

draw関数で10枚分の画像を表示するため、10回繰り返しています。その際、

  • x = 5で割った余りに150を掛ける
  • y = 5で割った商(整数)に150を掛ける
    として座標を求めていることに注目してください。for文は10回繰り返していますが、描画する画像は5種類です。よって、image関数ではcards[i%5]として、5で割った余り(余りは0~4の範囲になる)を添え字番号として使用しています。

ステップ2

PImage[] cards = new PImage[5];
boolean[] opened = {
  false,false,false,false,false,
  false,false,false,false,false};
  
void setup(){
  size(750, 300);
  for(int i = 0 ; i < 5 ; i++){
    cards[i] = loadImage("image"+i+".png");
  }
}

void draw(){
  for(int i = 0 ; i < 10 ; i++){
    int x = (i % 5)*150;
    int y = (int)(i/5)*150;
    rect(x, y, 150, 150);
    if (opened[i]) {
      image(cards[i%5], x, y, 150, 150);
    }
  }
}

void mousePressed(){
  int mx = (int)(mouseX / 150);
  int my = (int)(mouseY / 150);
  int n = my * 5 + mx;
  opened[n] = true;
}

マウスがクリックされるとmousePressed関数が呼び出されます。mouseXとmouseYそれぞれを150で割った商を求めることで、どのマスがクリックされたか判定しています。その番号を変数nで計算し、その場所のopened配列をtrueに変更しています。
draw関数では、opened[i]がtrueの場合のみ画像を描画するようにしています。

ステップ3

int[] numbers = {0,0,1,1,2,2,3,3,4,4};
PImage[] cards = new PImage[5];
boolean[] opened = {
  false,false,false,false,false,
  false,false,false,false,false};
  
void setup(){
  size(750, 300);
  for(int i = 0 ; i < 5 ; i++){
    cards[i] = loadImage("image"+i+".png");
  }

  for(int i = 0 ; i < 100 ; i++){
    int a = (int)random(9);
    int b = (int)random(9);
    int t = numbers[a];
    numbers[a] = numbers[b];
    numbers[b] = t;
  }
}

void draw(){
  for(int i = 0 ; i < 10 ; i++){
    int x = (i % 5)*150;
    int y = (int)(i/5)*150;
    rect(x, y, 150, 150);
    if (opened[i]) {
      int c = numbers[i];
      image(cards[c], x, y, 150, 150);
    }
  }
}

void mousePressed(){
  int mx = (int)(mouseX / 150);
  int my = (int)(mouseY / 150);
  int n = my * 5 + mx;
  opened[n] = true;
}

numbersという配列がポイントです。setup関数の中でシャッフルして、表示するときにi番目の場所にあるカードの番号cを取得し、そのカードを表示しています。

ステップ4

int[] numbers = {0,0,1,1,2,2,3,3,4,4};
PImage[] cards = new PImage[5];
boolean[] opened = {
  false,false,false,false,false,
  false,false,false,false,false};
int card0 = -1;
int card1 = -1;

void setup(){
  size(750, 300);

  for(int i = 0 ; i < 5 ; i++){
    cards[i] = loadImage("image"+i+".png");
  }
  
  for(int i = 0 ; i < 100 ; i++){
    int a = (int)random(9);
    int b = (int)random(9);
    int t = numbers[a];
    numbers[a] = numbers[b];
    numbers[b] = t;
  }
}

void draw(){
  for(int i = 0 ; i < 10 ; i++){
    int x = (i % 5)*150;
    int y = (int)(i/5)*150;    
    rect(x, y, 150, 150);
    if (opened[i] || i == card0 || i == card1){
      int c = numbers[i];
      image(cards[c], x, y, 150, 150);
    }
  }
}

void mousePressed(){
  int mx = (int)(mouseX / 150);
  int my = (int)(mouseY / 150);
  int n = my * 5 + mx;
  
  if (n == card0 || opened[n]){
    return;
  }
  
  if (card0 == -1){
    card0 = n;
  }else{
    card1 = n;
    if(numbers[card0] == numbers[card1]){
      opened[card0] = true;
      opened[card1] = true;
    }
  }
}

void mouseReleased(){
  if(card0 != -1 && card1 != -1){
    card0 = -1;
    card1 = -1;
  }
}

draw関数では、(opened[i] || i == card0 || i == card1) のように、openedでカードが表になった状態か、それとも現在の番号iがcard0もしくはcard1に等しいかという条件でカードを表示しています。

mousePressedにある n == card0 || opened[n] は1枚目のカードがさらにクリックされた場合や、既に表になっているカードがクリックされた場合を除外するためのものです。

コードをシンプルにするために、2枚のカードが違った時にmouseReleasedを使って元に戻しています。そのため、カードが違った時はすぐに裏にもどってしまいます。カウンタを使ってしばらくしてから裏に戻すような処理を追加してもよいでしょう、。