Rubyでマインスイーパー

#はじめに
Swiftと同時にRubyを学び始めてはや２週間
何か作ってみたいと思い浮かんだのがマインスイーパーであった
各関数の解説も書いてみたいと思う。

##環境
OS X 10.9.5
ruby 2.0.0p481
ターミナル上で動作
Winの動作は確認していません

#ソース

https://github.com/konpeto000/MineSweeper-Ruby-

#イメージ
![minesweeper.gif](https://qiita-image-store.s3.amazonaws.com/0/57208/e5f88063-7f83-54dd-e09c-eafbf1979632.gif)

#解説
*def initialize*

      @board = Array.new(SIZE+1){Array.new(SIZE+1,0)}
      @board_flag = Array.new(SIZE+1){Array.new(SIZE+1,0)}

地雷とその個数を示す数字を表示する@boardと
■で覆い隠すようにする@board_flagを作成する
それぞれ0を初期値とするSIZE分の長さを持つ２次元配列となる
@board_flag = 0 そのマスは開けられていない
@board_flag = 1 そのマスは開けられた

*def showBoard*

    print "  "
    for i in 1..SIZE - 1
       print " #{i}"
    end
    print ("\n")

    for i in 1..SIZE - 1
      print "#{i} "
      for j in 1..SIZE - 1

        if @board_flag[j][i] == 1
          case @board[j][i]
          when 0
            print(" □")
          when -1
           print(" *")
          when 1..8
            print(" #{@board[j][i]}")
          end
        else
          print(" ■")
        end

      end
      print ("\n")
    end

番兵を除くすべてのボードを探索する
@board_flagが1のとき（開けられている）その位置の@boardがどんな状態であるか表示する
0  :開いている
-1 :地雷である
1~8:そのマスの周りの地雷の数

*checkAround*

    for i in 1..SIZE - 1
      for j in 1..SIZE - 1
        count = 0
        if @board[j][i] == -1
          next
        else
          for k in -1..1
            for l in -1..1
              if @board[l+j][k+i] == -1
                count += 1
              end

            end
          end
          
          @board[j][i] = count

        end
        
      end
    end

@boardを全探索をしマスの周囲に-1(地雷)がどれだけ存在しているかを調べる関数
また自身が地雷のときは次のマスに移動する

*putMine(x,y)*

    @board.each{|b_num|
      b_num.each{|b_n|
        if b_n == -1
          return
        end
      }
    }

    n = 0
    while(n < MINE)
      i = rand(SIZE-1) + 1
      j = rand(SIZE-1) + 1
      flag = 0
      for k in -1..1
        for l in -1..1
          if (i == x+k) && (j == y+l)
            flag = 1
          end
        end
      end
      if flag == 1
        redo
      end

      if @board[j][i] == -1
        redo
      end

      @board[j][i] = -1
      n += 1

    end

    checkAround

引数x,yはユーザが指定した(xy)座標である
なぜか全探索をするときここだけeachメソッドを使っている、自分が書いたにもかかわらず謎である
これは地雷がおかれていたらこの関数処理をしないという理由で書かれたが今となって思えばinitializeにこの関数とcheckAround関数ぶち込んでおけば必要ないのではと思っている。

このボード全体にランダムに地雷を指定された個数だけ設置するというのだが
1:地雷をおくときに既に地雷があったら避ける
2:最初にあけた場所の周りに地雷がないようにする
の1,2を満たすように書いている

*openBoard(x,y)*

    if x < 1 || x > SIZE - 1 || y < 1 || y > SIZE - 1
      return
    end

    putMine(x,y)
    if @board[x][y] == 0

      for i in -1..1
        for j in -1..1

          if @board_flag[j+x][i+y] == 1
            next
          end

          case @board[j+x][i+y]
          when 0
            @board_flag[j+x][i+y] = 1
            openBoard(j+x,i+y)
          when 1..8
            @board_flag[j+x][i+y] = 1
          end
          
        end
      end

    else
      @board_flag[x][y] = 1
      return
    end

    return

引数x,yはユーザが指定した(xy)座標である
ここではユーザが指定した場所を実際に開けるところである
再帰関数として利用

実際に開けた場所が0(空白)である場合その周りの状態も調べ0(空白)であった場合その場所を引数として再帰する、しかしそうでない場合1~8(周りに地雷があるマス)そこを開け再帰をやめる
こうすることによって隣接する0(空白)はすべて開けられることとなる
また0(空白)意外の場合はその場所をあけreturnする

*choseNumber*

    while 1
      p "Choose number (xy)[1 to 9]"
      # number check
      num = gets.chop
      if num[1] == nil
        p "Entry (xy)"
        redo
      end
      nx = num[0].to_i
      ny = num[1].to_i
      
      if !(nx > 0 && ny > 0 && nx < SIZE && ny < SIZE)
        redo
      end
      
      self.openBoard(nx,ny)
      if self.gameOver(nx,ny)
        return true
      end

      break
      
    end
    
    return false

ユーザが実際に数値(xy)を入力する関数
numにユーザが入力した値が代入されそのリストの一番目と二番目の値をnx,nyに代入しIntengerに変換
その値がゲームのマスより小さかったり大きかったり変な値であったら再入力させる
そのx,y情報を引数としopenBoard関数を呼ぶ
またgameOver関数もよび出す
returnはtrueとfalseを返すが
trueであればゲーム終了
falseであればゲーム続行である

*gameOver(x,y)*

    count = 0

    for i in 1..SIZE - 1
      for j in 1..SIZE - 1
        if @board_flag[j][i] == 0
          count += 1
        end
      end
    end

    if count == MINE
      self.showBoard
      p "Clear!"
      return true
    end
    if @board[x][y] == -1
      @board_flag[x][y] = 1
      self.showBoard
      p "Failed.."
      return true
    end

     return false

引数x,yはユーザが指定した(xy)座標である
ゲームオーバか否か調べる関数である
とりあえず全体の開かれていない場所の個数を探索しcountに代入
もしcountが地雷の個数と一緒(地雷以外はすべて開かれた)ならクリア
また開かれた場所が地雷だったらそれは失敗となりゲームオーバとなる

#まとめ#
自分で作ったものを解説しようとするとうまい言葉がなかなか見つからず結構難しい、なかなかいい練習になったと思う
また再びコードを１から見ることになるので改善すべき点がよくわかる
今回のコードもかなりの改善点が見つかったので直していきたい