minofoto and miscellaneous notes

個人的な備忘録ですが、たまに広く読んでもらいたい記事を書くこともあります。記事は随時修正したり追記したりすることがあります。

Processing3 用の BINGO プログラム

先日なぜか結婚パーティーとやらをやったのですが、まあこんな歳で結婚するからには、友達同士の縁ができるとしてもどちらかというと異業種交流会だよね、という話になり、異業種交流になるようなビンゴゲームを考えました。

参加者それぞれに番号を割り当て、割り当てられた番号が書かれたシールのシートを配ります。話をした人同士シールを交換して、白紙のビンゴカードに貼ってもらい、そのカードでビンゴゲームに参加してもらいました。いろんな人と交流を持ってもらえるきっかけを作るためです。

更に、グループで来ている友人と、ひとりで来てくれた友人が交流できるように、一人できている人には「レアカード」の番号を割り当てました。また、知り合いが1人しかいない人には「準レアカード」を割り当てました。レアカードの番号は、普通の人に比べて7倍出現しやすくしました。準レアカードの番号は、倍率3倍です。これで、レアカードを持っている人と話をするモチベーションが生まれるかと...

そして、倍率可変のビンゴをするために、オリジナルのビンゴプログラムを書きました。
素人でもちょっと凝ったグラフィックが作れるように、Java ベースの Processing という言語で作成しました。

f:id:Room-B:20170303232617j:plain
起動画面。ちょっと楽しい雰囲気にするため、半透明のカラーボールがランダムウォークします。

f:id:Room-B:20170303232637j:plain
番号が出ているところ。既に出た数字は上にリストされます。レアカードは赤で、準レアカードは黄色で表示されます。


プログラムは以下に示します。パラメーターとしてbiased1 にレアカードの番号を割り当て、biased2 に準レアカードを割り当てます。出現レートは bias_rate1, bias_rate2 で設定できます。また、人数に合わせて max_num を設定します。

//BINGO for processing
// 800x600 pixel にも対応する。

// parameters
int max_num = 75;        // max number of bingo

int decorate_num = 20;   // number of balls
int decorate_speed = 30; // speed of ball movements

int font_size = 0;       // display font size; if 0, size is set to defalt depending on the screen size;


// Rare characters  set empty ({}) for both biased1 and biased2 for normal BINGO
int bias_rate1 = 7;        // bias rate of most rare characters
int[] biased1 = {1,2,3};  // most rare characters
//int[] biased1 = {};  // most rare characters
int bias_rate2 = 3;        // bias rate of relatively rare characters
int[] biased2 = {4,5,6,7,8};  // relatively rate characters
//int[] biased2 = {};  /A/ relatively rate characters


// global variables
int mode = 0;    // mode 0: screen intialization; mode 1: BINGO started
int count = 0;   // counter to count until max_num
int current_num = -1;  // number to remember the last number
//boolean[] done = new boolean[max_num+1];

int transition = 0;
int num_display_max;

  boolean overBox;


// objects
class _Ball {
  int x, y;
  int radius;
  color c;
  int margin_x = 0;
  int margin_y = 0;
  int speed;
  
  _Ball() {
    init();
  }
  
  void init() {
    c = color(int(random(30, 145)), int(random(10, 105)), int(random(10, 105)), 105);
    x = int(random(width - margin_x*2) + margin_x);
    y = int(random(height - margin_y*2) + margin_y);
    speed = round(random(decorate_speed/1.2,decorate_speed*1.2));
    radius = round(width/25);
  }
  
  void update() {
    x += int(random(-speed, speed));
    y += int(random(-speed, speed));
    if (x > (width - margin_x)) {
      x -= width - margin_x*2;
    }
    if (y > height - margin_y) {
      y -= height - margin_y*2;
    }
    if (x < margin_x) {
      x += width - margin_x*2;
    }
    if (y < margin_y) {
      y += height - margin_y*2;
    }
  }

  void display() {
    noStroke();
    fill(c);
    ellipse(x,y,radius,radius);
  }
}

_Ball[] balls = new _Ball[decorate_num];


class _BingoTitle {
  String name;
  int size;
  int transition_density;
  
  _BingoTitle() {
    name = "BINGO";
    transition_density = 10;
  }
  
  void init() {
    if (font_size < 1) {
      size = round(width/5);
    } else {
      size = font_size;
    }
  }
  
  void display() {
    textSize(size);
    textAlign(CENTER,CENTER);
    fill(color(transition_density,transition_density,transition_density));
    text(name, 50,100,width-100, height-200);
    transition_density += 5;
    if (transition_density >= 255) {
      transition_density = 255;
      status.stat="press key to start";
    }
  }
}
_BingoTitle title = new _BingoTitle();


// 
class _MainNumber {
  int size;
  int transition_density;
  int N;
  
  _MainNumber() {
  }
  
  void init() {
    int i;
    transition_density = 12;
    
    if (font_size < 1) {
      size = round(width/5);
    } else {
      size = font_size;
    }

    //reset done counter
    //for (i=0; i<=max_num; i++) done[i] = false;
    ShownNumbers.init();
  }
  
  
  void update() {
    transition_density = 12;
    N = biased_random(max_num+1);
    while (ShownNumbers.isshown[N]) {
      N = biased_random(max_num+1);
    }
    //done[N] = true;
    ShownNumbers.add(N);
    display();
  }

  void display() {
    textSize(size);
    textAlign(CENTER,CENTER);  
    fill(color(transition_density,transition_density,transition_density));    

    text(str(N), 50,100, width-100, height-200);
 
    transition_density += 12;
    if (transition_density >= 255) {
      transition_density = 255;
    }
  }
  
} 
_MainNumber MainNumber = new _MainNumber();


// 画面の上
class _ShownNumbers {
  boolean[] isshown = new boolean[max_num+1];
  int size;
  int margin_x = 10;
  
  _ShownNumbers() {
  }
  
  void init() {
    int i;
    
    for (i=0; i<=max_num; i++) isshown[i] = false;
    
    num_display_max = 20;
    if (font_size < 1) {
      size = round(width/30);
    } else {
      size = 40;  //default value;
    }

  }
  
  void add(int num) {
    isshown[num] = true;
  }
  
  void display() {
    int i,j;
    int numbox_size_x, numbox_size_y;
    int offset_x, offset_y;
  
    textSize(size);
    textAlign(CENTER,CENTER);
    numbox_size_x = floor((width - margin_x*2)/num_display_max);
    numbox_size_y = floor(height/10);
    for(i=1; i<=max_num; i++) {
      fill(#101010);
      offset_x = (i-1 - round((i-1)/num_display_max)*num_display_max)*numbox_size_x + margin_x;
      offset_y = int((i-1)/num_display_max)*numbox_size_y;
      text(str(i), offset_x, offset_y, numbox_size_x, numbox_size_y);

      if (isshown[i] && (i != MainNumber.N)) {  //show color for biased numbers
        fill(#B0A0D0);
        for (j=0; j<biased2.length; j++) {
          if (i == biased2[j]) {
            fill(#E8E8A0);
          }
        }
        for (j=0; j<biased1.length; j++) {
          if (i == biased1[j]) {
            fill(#F88090);
          }
        }
        text(str(i), offset_x, offset_y, numbox_size_x,numbox_size_y);
      }
    }
  }

}
_ShownNumbers ShownNumbers = new _ShownNumbers();

class _Status {
  String stat = "";
  int size;
  
  _Status() {
  }
  
  void display() {
    size = round(width/50);
    textSize(size);

    textAlign(CENTER,CENTER);
    fill(64,64,64);
    text(stat, width*2/3,height*4/5, width/3,height/5);
  }
}
_Status status = new _Status();



class _Config {
  int ButtonX0, ButtonX1, ButtonY0, ButtonY1;
  color col;
  
  _Config() {
  }
  
  void init() {
    ButtonX0 = 10;
    ButtonX1 = 30;
    ButtonY0 = 500;
    ButtonY1 = 530;
    col = color(255,255,255,128);
  }
  
  void pressed() {
    col = color(255,255,0,128);
    fill(col);
    rect(ButtonX0, ButtonY0, ButtonX1-ButtonX0, ButtonY1-ButtonY0);
  }
  
  void display() {
    fill(col);
    rect(ButtonX0, ButtonY0, ButtonX1-ButtonX0, ButtonY1-ButtonY0);
  }
}
_Config config = new _Config();





int biased_random(int max){
  int rand_number;
  int bias1_threshold = biased1.length*bias_rate1;
  int bias2_threshold = biased2.length*bias_rate2;

  rand_number = int(random(1, max+floor(bias1_threshold + bias2_threshold)));
  if (rand_number >= max + bias1_threshold) {
    rand_number = biased2[int((rand_number-max-bias1_threshold) / bias_rate2)];
  } else if (rand_number >= max) {
    rand_number = biased1[int((rand_number-max) / bias_rate1)];
  }
  return(rand_number);
}

void update_balls() {
  int i;

  fill(#000000);
  rect(0, 0, width, height);
  for(i=0; i<decorate_num; i++) {
    balls[i].update();
    balls[i].display();
  }
}


void keyReleased() {  
  //if (transition >= 255) {
    mode=1;
    if (count<max_num) {
      status.stat="";      
      update_balls();

      MainNumber.update();
      ShownNumbers.display();
      count++;
    } else {
      mode=2;
      status.stat="finished";
    }
  //} else {
  //  textSize(40);
  //  textAlign(CENTER,CENTER);
  //  text("wait", width/2,height-150,500,100);
  //}
}

void mousePressed() {
  if(overBox) { 
    config.pressed();
  } else {
  }
}

void draw() {
  update_balls();
  //config.display();
  if (mouseX > config.ButtonX0 && mouseX < config.ButtonX1 && 
      mouseY > config.ButtonY0 && mouseY < config.ButtonY1) {
    overBox = true;  
  }
    
  switch (mode) {
    case 0:
      title.display();
      break;
    case 1:
      MainNumber.display();
      ShownNumbers.display();
      break;
    case 2:
      MainNumber.display();
      ShownNumbers.display();
      //status.display();
      break;    
  }
  status.display();
}

void setup() {
  int i;
  
  // setting up the screen;
  fullScreen();
  //size(800, 600);
  frameRate(10);  // speed of the balls can be regulated by this parameter.
  background(#000000);
  
  count=0;
  
  //create and initialize balls
  for (i=0;i<decorate_num;i++) {
    balls[i] = new _Ball();
  }

  title.init();
  MainNumber.init();
  //ShownNumbers.init();
  config.init();
}

マウスで操作できるコントロールパネルも実装しようとしかけて挫折した痕跡がコードに残っていますが、気にしないでください(笑)。

感想を聞いていませんが、皆さん楽しんでくれたと信じています。
いろいろとありがとう!