【Processing】100行でオセロを作ってみました

自分は大学3年の時、今からちょうど4年前くらいにプログラムを始めたわけですが、そのとき一番最初に自分に課した課題がProcessingでオセロを作ることでした。
その当時はif文もfor文も本を参考にしながら、しどろもどろなプログラムを組んでいたわけですが、あれから4年経ち、とうとう大学院を卒業した今、どれだけの実力がついたのか、オセロを作ってみました。

コード

final int BSIZE = 100;
int[][] field;
boolean bBlacksTurn;
void setup(){
  size(8*BSIZE,8*BSIZE);
  bBlacksTurn = true;
  field = new int[8][8];
  for(int i=0; i<8; ++i){
    for(int j=0; j<8; ++j){
      if((i==3||i==4)&&(j==3||j==4)){
        field[i][j] = ((i+j)%2==0)?1:-1; // initial stones;
      }else{
        field[i][j] = 0;
      }
    }
  }
}
void draw(){
  //draw field
  background(0,160,0);
  stroke(0);
  for(int i=1; i<8; ++i){
    line(i*BSIZE,0,i*BSIZE,height);
    line(0, i*BSIZE, width, i*BSIZE);
  }
  noStroke();
  fill(0);
  ellipse(BSIZE*2,BSIZE*2,10,10);
  ellipse(BSIZE*6,BSIZE*2,10,10);
  ellipse(BSIZE*2,BSIZE*6,10,10);
  ellipse(BSIZE*6,BSIZE*6,10,10);
  // draw stones
  noStroke();
  for(int i=0; i<8; ++i){
    for(int j=0; j<8; ++j){
      if(field[i][j]==1){
        fill(0);
        ellipse((i*2+1)*BSIZE/2,(j*2+1)*BSIZE/2, BSIZE*0.8, BSIZE*0.8);
      }else if(field[i][j]==-1){
        fill(255);
        ellipse((i*2+1)*BSIZE/2,(j*2+1)*BSIZE/2, BSIZE*0.8, BSIZE*0.8);
      }
    }
  }
}
void mouseReleased(){
  int x = mouseX/BSIZE;
  int y = mouseY/BSIZE;
  boolean puttable = false;
  if(field[x][y]==0){
    puttable = checkDirection(x,y,-1,-1) | puttable;
    puttable = checkDirection(x,y,-1,0) | puttable;
    puttable = checkDirection(x,y,-1,1) | puttable;
    puttable = checkDirection(x,y,0,-1) | puttable;
    puttable = checkDirection(x,y,0,1) | puttable;
    puttable = checkDirection(x,y,1,-1) | puttable;
    puttable = checkDirection(x,y,1,0) | puttable;
    puttable = checkDirection(x,y,1,1) | puttable;
    if(puttable){
      field[x][y] = currentStone();
      bBlacksTurn = !bBlacksTurn;
    }
  }
}
boolean checkDirection(int x, int y, int directionX, int directionY){
  if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY] != currentStone()){
    return checkStones(x, y, directionX, directionY);
  }
  return false;
}
boolean checkStones(int x, int y, int directionX, int directionY){
  if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY]==currentStone()){ // find
    return true;
  }else if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY]==0){ // not find
    return false;
  }else if(checkBound(x+directionX, y+directionY) && checkStones(x+directionX, y+directionY, directionX, directionY)){
    field[x+directionX][y+directionY] = currentStone(); // reverse
    return true;
  }else{
    return false;
  }
}
boolean checkBound(int x, int y){
  return x>=0 && x<8 && y>=0 && y<8;
}
int currentStone(){
  return (bBlacksTurn)?1:-1;
}

実行したら以下のような画面になります。

通常のオセロ実行画面

当時は途中で挫折して、完成までは至りませんでしたが、今回は30分くらいで作ることができました。ゴリ押しでひっくり返す判定をしていた部分も再帰を使って美しく書くこともできました。

応用① N色オセロ

さらに改造して、色数も盤面のサイズも自由に設定できるようにしてみます。

final int BSIZE = 50;
final int COLS = 16, ROWS = 16;
final int PLAYER_NUM = 8;
int[][] field;
int currentColor;
void setup(){
  size(COLS*BSIZE,ROWS*BSIZE);
  colorMode(HSB);
  currentColor = 1;
  field = new int[COLS][ROWS];
  for(int i=0; i<COLS; ++i){
    for(int j=0; j<ROWS; ++j){
      field[i][j] = 0;
    }
  }
  for(int i=0; i<PLAYER_NUM; ++i){
    for(int j=0; j<PLAYER_NUM; ++j){
      field[floor(COLS/2)+i-floor(PLAYER_NUM/2)][floor(ROWS/2)+j-floor(PLAYER_NUM/2)] = ((i+j)%PLAYER_NUM)+1;
    }
  }
}
void draw(){
  //draw field
  background(0);
  stroke(64);
  for(int i=1; i<COLS; ++i) line(i*BSIZE,0,i*BSIZE,height);
  for(int i=1; i<ROWS; ++i) line(0, i*BSIZE, width, i*BSIZE);
  // draw stones
  noStroke();
  for(int i=0; i<COLS; ++i){
    for(int j=0; j<ROWS; ++j){
      for(int k=1; k<=PLAYER_NUM; ++k){
        if(field[i][j]==k){
          fill(255*(k-1)/PLAYER_NUM,255,255);
          ellipse((i*2+1)*BSIZE/2,(j*2+1)*BSIZE/2, BSIZE*0.8, BSIZE*0.8);
        }
      }
    }
  }
}
void mouseReleased(){
  int x = mouseX/BSIZE;
  int y = mouseY/BSIZE;
  boolean puttable = false;
  if(field[x][y]==0){
    puttable = checkDirection(x,y,-1,-1) | puttable;
    puttable = checkDirection(x,y,-1,0) | puttable;
    puttable = checkDirection(x,y,-1,1) | puttable;
    puttable = checkDirection(x,y,0,-1) | puttable;
    puttable = checkDirection(x,y,0,1) | puttable;
    puttable = checkDirection(x,y,1,-1) | puttable;
    puttable = checkDirection(x,y,1,0) | puttable;
    puttable = checkDirection(x,y,1,1) | puttable;
    if(puttable){
      field[x][y] =currentColor;
      currentColor = (currentColor==PLAYER_NUM)?1:currentColor+1;
    }
  }
}
boolean checkDirection(int x, int y, int directionX, int directionY){
  if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY] != currentColor&&field[x+directionX][y+directionY] != 0){
    return checkStones(x, y, directionX, directionY);
  }
  return false;
}
boolean checkStones(int x, int y, int directionX, int directionY){
  if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY]==currentColor){ // find
    return true;
  }else if(checkBound(x+directionX, y+directionY) && field[x+directionX][y+directionY]==0){ // not find
    return false;
  }else if(checkBound(x+directionX, y+directionY) && checkStones(x+directionX, y+directionY, directionX, directionY)){
    field[x+directionX][y+directionY] = currentColor; // reverse
    return true;
  }else{
    return false;
  }
}
boolean checkBound(int x, int y){
  return x>=0 && x<COLS && y>=0 && y<ROWS;
}


実行すると以下のようになります。

N色オセロ実行画面

応用② 3Dオセロ

3Dオセロも作ってみた。操作感が超悪いですが…

final int BSIZE = 40;
final int COLS = 8, ROWS = 8, DEPTH = 8;
final int PLAYER_NUM = 2;
int[][][] field;
int currentColor;
int x=0, y=0, z=0;
void setup() {
  size(COLS*BSIZE*4, ROWS*BSIZE*4, P3D);
  colorMode(HSB);
  frameRate(10);
  currentColor = 1;
  field = new int[COLS][ROWS][DEPTH];
  for (int i=0; i<COLS; ++i) {
    for (int j=0; j<ROWS; ++j) {
      for (int k=0; k<DEPTH; ++k) {
        field[i][j][k] = 0;
      }
    }
  }
  for (int i=0; i<PLAYER_NUM; ++i) {
    for (int j=0; j<PLAYER_NUM; ++j) {
      for (int k=0; k<PLAYER_NUM; ++k)
        field[floor(COLS/2)+i-floor(PLAYER_NUM/2)][floor(ROWS/2)+j-floor(PLAYER_NUM/2)][floor(DEPTH/2)+k-floor(PLAYER_NUM/2)] = ((i+j+k)%PLAYER_NUM)+1;
    }
  }
}
void draw() {
  //  directionalLight(204, 204, 204, -3, -5, -1);
  lights();
  pushMatrix();
  translate(COLS*BSIZE/2*4, ROWS*BSIZE/2*4, DEPTH*BSIZE/2*2);
  rotateX(radians(-20.0));
  rotateY(frameCount / 50.0);
  translate(-COLS*BSIZE/2, -ROWS*BSIZE/2, -DEPTH*BSIZE/2);
  //draw field
  background(0);
  stroke(255, 64);
  for (int k=0; k<=DEPTH; ++k) {
    for (int i=0; i<=COLS; ++i) {
      line(i*BSIZE, 0, k*BSIZE, i*BSIZE, ROWS*BSIZE, k*BSIZE);
    }
    for (int j=0; j<=ROWS; ++j) {
      line(0, j*BSIZE, k*BSIZE, COLS*BSIZE, j*BSIZE, k*BSIZE);
    }
  }
  for (int i=0; i<=COLS; ++i) {
    for (int j=0; j<=ROWS; ++j) {
      line(i*BSIZE, j*BSIZE, 0, i*BSIZE, j*BSIZE, DEPTH*BSIZE);
    }
  }
  // draw stones
  noStroke();
  for (int i=0; i<COLS; ++i) {
    for (int j=0; j<ROWS; ++j) {
      for (int k=0; k<DEPTH; ++k) {
        for (int l=1; l<=PLAYER_NUM; ++l) {
          if (field[i][j][k]==l) {
            fill(255*(l-1)/PLAYER_NUM, 255, 255);
            pushMatrix();
            translate((i*2+1)*BSIZE/2, (j*2+1)*BSIZE/2, (k*2+1)*BSIZE/2);
            sphere(BSIZE*0.3);
            popMatrix();
          }
        }
      }
    }
  }
  pushMatrix();
  translate((x*2+1)*BSIZE/2, (y*2+1)*BSIZE/2, (z*2+1)*BSIZE/2);
  stroke(255*(currentColor-1)/PLAYER_NUM, 255, 255);
  noFill();
  box(BSIZE);
  popMatrix();
  popMatrix();
}
void keyReleased() {
  if (key==' ') {
    boolean puttable = false;
    if (field[x][y][z]==0) {
      for (int i=-1; i<=1; ++i) {
        for (int j=-1; j<=1; ++j) {
          for (int k=-1; k<=1; ++k) {
            if (!(i==0 && j==0 && k==0)) puttable = checkDirection(x, y, z, i, j, k) | puttable;
          }
        }
      }
      if (puttable) {
        field[x][y][z] =currentColor;
        currentColor = (currentColor==PLAYER_NUM)?1:currentColor+1;
      }
    }
  }
  else if (key=='x') y = ( y+1==ROWS ) ? 0: y+1;
  else if (key=='e') y = ( y-1<0 ) ? ROWS-1: y-1;
  else if (key=='a') x = ( x+1==ROWS ) ? 0: x+1;
  else if (key=='d') x = ( x-1<0 ) ? ROWS-1: x-1;
  else if (key=='w') z = ( z+1==ROWS ) ? 0: z+1;
  else if (key=='z') z = ( z-1<0 ) ? ROWS-1: z-1;
}
boolean checkDirection(int x, int y, int z, int directionX, int directionY, int directionZ) {
  if (checkBound(x+directionX, y+directionY, z+directionZ) && field[x+directionX][y+directionY][z+directionZ] != currentColor&&field[x+directionX][y+directionY][z+directionZ] != 0) {
    return checkStones(x, y, z, directionX, directionY, directionZ);
  }
  return false;
}
boolean checkStones(int x, int y, int z, int directionX, int directionY, int directionZ) {
  if (checkBound(x+directionX, y+directionY, z+directionZ) && field[x+directionX][y+directionY][z+directionZ]==currentColor) { // find
    return true;
  }
  else if (checkBound(x+directionX, y+directionY, z+directionZ) && field[x+directionX][y+directionY][z+directionZ]==0) { // not find
    return false;
  }
  else if (checkBound(x+directionX, y+directionY, z+directionZ) && checkStones(x+directionX, y+directionY, z+directionZ, directionX, directionY, directionZ)) {
    field[x+directionX][y+directionY][z+directionZ] = currentColor; // reverse
    return true;
  }
  else {
    return false;
  }
}
boolean checkBound(int x, int y, int z) {
  return x>=0 && x<COLS && y>=0 && y<ROWS && z>=0 && z<DEPTH;
}
3Dオセロ実行画面

まとめ

プログラミングを初めて学んだころの課題を再チャレンジすると、成長を実感できます。

皆さんも、数年おきに過去にチャレンジした課題を今ならどう作るかチャレンジしてみてはいかがでしょうか?