8女王問題 その1

8女王問題。
単純な(力技の)実装
セルを順番に試しておければ置いてみて、全て置ければ表示する。
全てのセルを試してみてダメだったら、1つ戻って(1つセルを取り除いて)リトライする。
アルゴリズムはsolveメソッドで実装している。

・・・但し重複を検出できないので同じ解が何度も出てくる。
解くには解けるが非常に間抜けな方法。
セル数が8×8だと結構待っても終わらない。

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
#
# queen1.rb

#-- セルの数を表す。
#-- 縦、横同じ長さである。
NUMBEROFQUEEN = 8

#-- ボードを表す。
#-- 実際のデータは@boardに格納される。
#-- 空白セルは0、女王がいるセルは1で表す。
class Queen

  #-- 初期化
  #-- ボードを表す@boardに初期値を設定する。
  def initialize
    @board = []

    x = 0
    while x < NUMBEROFQUEEN do
      y = 0
      tmp = []
      while y < NUMBEROFQUEEN do
        tmp << 0
        y = y+1
      end

      @board << tmp
      x = x+1
    end

  end

  #-- 与えられた位置に女王が置けるかどうか調べる。
  #-- 置ければtrueを返す。
  def possible(row, col)
    @board[row][col] == 0 &&
    row_possible(row, col) &&
    col_possible(row, col) &&
    slant_possible(row, col)
  end

  #-- 指定されたセルを含む行に既に女王がいないかを確認する。
  #-- 1つもいなければtrueを返す。
  def row_possible(row, col)
    @board[row].each do |x|
      if x == 1
        return false
      end
    end
    true
  end

  #-- 指定されたセルを含む列に既に女王がいないかを確認する。
  #-- 1つもいなければtrueを返す。
  def col_possible(row, col)
    @board.each do |x|
      if x[col] == 1
        return false
      end
    end
    true
  end

  #-- 指定されたセルを含む斜めに既に女王がいないかを確認する。
  #-- 1つもいなければtrueを返す。
  #-- 斜めの4方向に力技で調べている。
  #-- もう少し効率的なやり方があるのでは。。。
  def slant_possible(row, col)

    x = row
    y = col
    while x >= 0 && y >= 0
      if @board[x][y] == 1
        return false
      end
      x = x-1
      y = y-1
    end

    x = row
    y = col
    while x >= 0 && y < NUMBEROFQUEEN
      if @board[x][y] == 1
         return false
      end
      x = x-1
      y = y+1
    end

    x = row
    y = col
    while x < NUMBEROFQUEEN && y >= 0
      if @board[x][y] == 1
        return false
      end
      x = x+1
      y = y-1
    end

    x = row
    y = col
    while x < NUMBEROFQUEEN && y < NUMBEROFQUEEN
      if @board[x][y] == 1
        return false
      end
      x = x+1
      y = y+1
    end

    true
  end

  #-- 自身のコピーを生成する。
  #-- 単なるdupだと、@boardはコピーされない。
  #-- 同じ@boardを2つのQueenオブジェクトが参照する異になってしまう。
  #-- そのため、@boradのコピーを明示的に生成する。
  #-- 1行目のsuperはこのメソッド(dup)を意味する。
  #-- 即ち、copyにQueenオブジェクトのコピーを生成する。
  #-- 更に2行目で@boardのコピーを生成し、新たに生成した@boardを
  #-- 元のQueenが参照するようにしている。
  #-- 新しく生成したQueenは元の@boardを参照している。
  #-- 2次元配列なので配列の配列をコピーするためwhile文で回して
  #-- 個々の要素をコピーしている。
  #-- 最後の行で生成したQueenオブジェクトを返す。
  def dup
    copy = super
    @board = @board.dup
    x = 0
    while x < NUMBEROFQUEEN do
      @board[x] = @board[x].dup
      x = x + 1
    end
    copy
  end




#-----------------------------------
#-- FOR DEBUG
#-----------------------------------

  #-- ボードを縦横並べて表示する。
  def print_board
    #print "-- print board --\n"
    @board.each do |x|
      x.each do |y|
        print y
      end
      #print "\n"
    end
    print "\n"
  end

  #-- していれたセルの値を表示する。
  def print_cell(row, col)
    print "cell[#{row}][#{col}] :  ", @board[row][col], "\n"
  end

  def set_cell(row, col, val)
    @board[row][col] = val
  end

end #-- END OF CLASS Queen



#-----------------------------------
#-- 片っ端から1つづつqueenを置いていく。
#-- あるセルにqueenが置けたら、更に次の1個を置くため
#-- 再帰的にこのメソッドを実行する。
#-- 全てのセルに試したらリターンする。
def solve(queen, num)

  #-- 全てのqueenをおいた状態ならばそれを表示してリターンする。
  if num >= NUMBEROFQUEEN
    queen.print_board
    return
  end

  x = 0
  while x < NUMBEROFQUEEN do
    y = 0
    while y < NUMBEROFQUEEN do
      # print "  num: ", num+1, " x: ", x, " y: ", y, "\n"
      if queen.possible(x, y)
        princess = queen.dup
        princess.set_cell(x, y, 1)
        # print "! num: ", num+1, " x: ", x, " y: ", y, "\n"
        solve(princess, num+1)
      end
      y = y + 1
    end
    x = x + 1
  end

 
end



# =====================================

a = Queen.new
solve(a, 0)