滝谷日記

最新

2010.05.09 Sun [長年日記]

# [音楽] 「Y(寄り集まって)M(ミニ鍵盤いぢる)O(おぢさん達)」がかっこよすぎる

ビッグバンド風のホーンと手拍子で始まるYMO「Technopolis」のカバー曲。かなり前からたびたび耳にするけど誰のかわからなかったので調査。

【ニコニコ動画】YMO - Technopolis [The Readymade - Darlin' of Discotheque Track, Remixed by Yasuharu Konishi]

「YMO-REMIXES TECHNOPOLIS 2000-01」の「TECHNOPOLIS(THE READYMADE)(DARLIN’OF DISCOTHEQUE TRACK)」。小西康陽さんの手によるもの。「うぅーーーーーやぁ」のやつでした。思い出した。開始40秒まではすきなんだけどなぁ。サンプリング元は大昔のアメリカTV番組「マニックス特捜網」の「END GAME」。

これ聴いたらブラスバンドによるYMOモノは無いものかと探したけどヒットせず。先生がど真ん中だとしても生徒さんはナニソレだし無理も無いか。

久しぶりにニコニコでYMOものを探してて見つけたのが、親子でTECHNOPOLIS 。

【ニコニコ動画】TECHNOPOLIS YMO(COVER)

出オチ&カバー絵が素敵。確かな演奏と飛び道具の絶妙なバランス。オチもあり。うらやましい親子競演。

次に見つけたのが「Y(寄り集まって)M(ミニ鍵盤いぢる)O(おぢさん達)」さんの作品群。既に8つ動画があがっていて全部聞きましたがどれも素晴らしい。特に「COSMIC SURFIN'」と「東風シダックス版」がいい。ミニ鍵盤でこんなに素敵な音が作れることにびっくり。全く弾けませんが、そんなに高くなさそうなので買おうかな。(各動画での演奏機材が紹介されてる動画あり)

KORG microKORG XL シンセサイザー

本日のツッコミ(全2件) [ツッコミを入れる]

+ 右@細野晴飲み [はじめまして。こんばんは。 Y(寄り集まって)M(ミニ鍵盤いぢる)O(おぢさん達)の主としてベース担当の右@細野晴..]

+ 滝谷 [コメントありがとうございます。東風シダックス版は本当に素晴らしいですね。 独り遊び動画も拝見しました。偽YMOさん..]

[]

2010.05.16 Sun [長年日記]

# [ゲーム] 将棋やりたい

先々週行われた羽生善治名人と三浦弘行八段の第68期名人戦第3局を少しだけテレビ観戦。中盤9五金いくのか?のあたり。解説者は渡辺明竜王、鈴木環那女流初段、木村一基八段。接戦でかつ解説が面白く(特に木村八段)急に将棋がやりたくなってしまいました。ただここで本やら将棋盤やら買い集めても冷めるのが早い私のことなので、まずは図書館で本を借りてきました。選んだのは、やりなおしの将棋 (岩波アクティブ新書)(先崎 学)将棋上達の方程式 手筋の公式 基礎編(北島 忠雄)。あとついでに ボナンザVS勝負脳―最強将棋ソフトは人間を超えるか (角川oneテーマ21)(保木 邦仁/渡辺 明)も。

いくつか本を読んだら(読めたら)詰め将棋にも定評のある柿木将棋8あたりを買う予定。

# [日常] いろいろすっきり

「書道ガールズ」という映画の宣伝番組をなんとなく見てたら、女子高生役5人組の中の1人に見覚えがあるのだけれど思い出せない。かなり地味な子。調べたら高畑充希さんという役者の方。「みつき」名義で歌手活動もしていて、竹内まりやさんプロデュースで「夏のモンタージュ」という曲を出している。記憶にあったのはこのPVでの顔だ。「大切なもの」という曲も出していて、これが「セクシーボイスアンドロボ」の主題歌。そうだったのか。いろいろつながってスッキリ。ついでに「竹内まりあ」ではなく「竹内まりや」なんだとわかってスッキリ。

最近復活した「もやもや」が、第三舞台の「朝日のような夕日をつれて」で使われていた「THE END OF ASIA」。スタジオの音だったような気がするけどかなり昔のことなので不確か。「増殖」では絶対無く、「ダンスリー」も違う。「千のナイフ」じゃないと思うんだけど・・・調べたけどやっぱり不明。もやもや継続中。

# [Ruby] 「リバーシのアルゴリズム」をRubyで

コンピュータ将棋を調べてたら、探索アルゴリズムやら評価関数が気になりだした。とはいえ将棋を題材にそれらを学ぶのはハードル高すぎるのでまずはリバーシ(オセロ)で。ちょうど手元に リバーシのアルゴリズム C++&Java対応―「探索アルゴリズム」「評価関数」の設計と実装 (I・O BOOKS)(Seal Software) という未読本もあるしね。

ひたすらロジック部分の解説が続き、表示部分の説明は200ページの書籍中わずか11ページ。しかもコマンドライン版のみ。GUIはなし。今回の目的にうってつけの本。ついでにRubyのお勉強も。

テーブルゲームには、洗練されたアルゴリズムが使われています。本書は、テーブルゲーム「リバーシ」のアルゴリズムを題材に、C++とJavaを利用したプログラムの設計と実装について解説しています。

 本書では、大きく分けて「ボードの設計と実装」、「探索アルゴリズム」、「評価関数」の順に解説しました。「ボードの設計と実装」では、オブジェクト指向をもとにリバーシのボードを表現するクラスを作成し、「探索アルゴリズム」では、テーブルゲーム必勝法探索の前提となるゲーム木について解説した後、探索の基盤となるアルゴリズムとαβ 法をはじめとするさまざまな高速化手法について解説しています。ソースリストは、C++とJavaで併記し、詳細にコメントしてあるので、入門者や中級者にも大いに参考になるでしょう。

[書籍情報―リバーシのアルゴリズムより引用]

とりあえず一読・・・難しい。サンプルコードをRubyに置き換えつつ、もう一度読む。

[]

2010.05.19 Wed [長年日記]

# [Ruby] 「リバーシのアルゴリズム」をRubyで その2

前回のつづき。2章「ボードの設計と実装」のJavaでのサンプルコードを参考にRubyで書きました。1クラス1ファイル。識別子の命名は基本的に変更しない(set, get, isなどそのまま)。コードの見直しは探索アルゴリズム・評価関数・定石など全て書いた後に行うものとして、とりあえずサンプルーコードのまま忠実に書く。サンプルコードは出版社・著者のサポートページどちらでも公開していないっぽい。

point.rb

$KCODE = "s"
#
# 石の座標を表すクラス
#
class Point
  attr_reader :x, :y
  def initialize(x = 0, y = 0)
    @x = x
    @y = y
  end
 
  def to_s
    s = ""
    s << ('a'[0] + @x - 1).chr
    s << @y.to_s
  end
end

disc.rb

$KCODE = "s"
#
# 石を表すクラス
#
class Disc < Point
  BLACK = 1
  EMPTY = 0 # 空きマス
  WHITE = -1
  WALL = 2 # 壁
  attr_accessor :x, :y, :color
 
  def initialize(x = 0, y = 0, color = EMPTY)
    super(x, y)
    @color = color
  end
end

colorstorage.rb

$KCODE = "s"
#
# 石の色(黒、白、空き)別の情報を格納するクラス
#
class ColorStorage
  def initialize
    @data = {}
  end
 
  def get(color)
    @data[color]
  end
 
  def set(color, value)
    @data[color] = value
  end
end

board.rb

$KCODE = "s"
 
require 'point'
require 'disc'
require 'colorstorage'
#
# ボードを表すクラス
#
class Board
  BOARD_SIZE = 8
  MAX_TURNS = 60
 
  # 方向
  NONE = 0
  UPPER = 1
  UPPER_LEFT = 2
  LEFT = 4
  LOWER_LEFT = 8
  LOWER = 16
  LOWER_RIGHT = 32
  RIGHT = 64
  UPPER_RIGHT = 128
 
  def initialize
    init
  end
 
  #
  # ボードをゲーム開始直後の状態にする。
  # Boardクラスのインスタンスが生成された直後は、コンストラクタに
  # よって同様の初期化処理が呼ばれているので、initを呼ぶ必要は無い
  #
  def init
    #全マスを空きマスに設定
    @raw_board = Array.new(BOARD_SIZE + 2){
      Array.new(BOARD_SIZE + 2, Disc::EMPTY)
    }
 
    #壁の設定
    @raw_board[0].fill(Disc::WALL)
    @raw_board[BOARD_SIZE + 1].fill(Disc::WALL)
    @raw_board.each do |row|
      row[0] = Disc::WALL
      row[-1] = Disc::WALL
    end
 
    #初期配置
    @raw_board[4][4] = Disc::WHITE
    @raw_board[5][5] = Disc::WHITE
    @raw_board[4][5] = Disc::BLACK
    @raw_board[5][4] = Disc::BLACK
 
    # 各色の石の数
    @discs = ColorStorage.new
 
    #石数の初期設定
    @discs.set(Disc::BLACK, 2)
    @discs.set(Disc::WHITE, 2)
    @discs.set(Disc::EMPTY, BOARD_SIZE * BOARD_SIZE - 4)
 
    # 手数は0から数える
    @turns = 0
 
    # 先手は黒
    @current_color = Disc::BLACK #先手黒
 
    # @updateをすべて消去
    @update_log = []
 
    # 打てる手をの座標を格納
    @movable_pos = Array.new(MAX_TURNS + 1){[]}
 
    # 手数/全座標のcheck_mobilityの戻り値
    @movable_dir = Array.new(MAX_TURNS + 1){
      Array.new(BOARD_SIZE + 2){
        Array.new(BOARD_SIZE + 2)
      }
    }
 
    init_movable
  end
  attr_reader :movable_pos
  #
  # 指定した位置に石を打つ。
  # 処理が成功したらtrue、失敗したらfalseが返る
  #
  def move(point)
    if (point.x <= 0 or point.x > BOARD_SIZE or \
        point.y <= 0 or point.y > BOARD_SIZE or \
        @movable_dir[@turns][point.x][point.y] == NONE)
      return false
    end
 
    flip_discs(point)
    @turns += 1
    @current_color = -@current_color
    init_movable
    return true
  end
 
  #
  # 直前の一手を元に戻す。元に戻せない場合(一手も売っていない場合)
  # falseが返る。
  #
  def undo
    return false if @turns == 0
    @current_color = -@current_color
    update = @update_log.pop
 
    if update.empty?
      #前回はパス
      #@movable_pos と @movable_dir を再構築
      @movable_pos[@turns].clear
      for x in 1..BOARD_SIZE
        for y in 1..BOARD_SIZE
          @movable_dir[@turns][x][y] = NONE
        end
      end
    else
      #前回はパスでない
      @turns -= 1
 
      #石を元に戻す
      p = update[0]
      @raw_board[p.x][p.y] = Disc::EMPTY
      for i in 1...update.size
        p = update[i]
        @raw_board[p.x][p.y] = -@current_color
      end
 
      #石数の更新
      disc_diff = update.size
      @discs.set(@current_color, @discs.get(@current_color) - disc_diff)
      @discs.set(-@current_color, @discs.get(-@current_color) + (disc_diff - 1))
      @discs.set(Disc::EMPTY, @discs.get(Disc::EMPTY) + 1)
    end
 
    return true
  end
 
  #
  # パスする。
  # 成功したらtrueが返る。パスできない場合(打つ手がある場合)は
  # falseが返る。
  #
  def pass
    unless @movable_pos[@turns].size == 0
      return false
    end
 
    if is_gameover
      return false
    end
 
    @current_color = - @current_color
    @update_log << []
    init_movable
    return true
  end
 
  #
  # 指定された位置の色を返す
  #
  def get_color(point)
    @raw_board[point.x][point.y]
  end
 
  #
  # 現在の手番の石を返す
  #
  def get_current_color
    @current_color
  end
 
  #
  # 現在の手数を返す
  #
  def get_turns
    @turns
  end
 
  #
  # ゲームが終了していればtrue、終了していなければfalseを返す
  #
  def is_gameover
    if @turns == MAX_TURNS
      return true
    end
    unless @movable_pos[@turns].size == 0
      return false
    end
 
    disc = Disc.new
    disc.color = -@current_color
    for x in 1..BOARD_SIZE
      disc.x = x
      for y in 1..BOARD_SIZE
        disc.y = y
        unless check_mobility(disc) == NONE
          return false
        end
      end
    end
 
    return true
 
  end
 
  #
  # 指定された石の数を返す。
  # 色にはBLACK、WHITE、EMPTYを指定可能
  #
  def count_disc(color)
    @discs.get(color)
  end
 
  #
  # 石を打てる座標の配列を返す
  #
  def get_movable_pos
    @movable_pos[@turns]
  end
 
  #
  # 直前の手で打った石と裏返した石が並んだ配列を返す
  #
  def get_update
    if @update_log.empty?
      return []
    else
      return @update_log.last
    end
  end
 
  private
 
  #
  # 指定した座標に石を打てるかどうか、また、
  # どの方向に石を裏返せるかを判定する。
  # 石を裏返らせる方向にフラグが立った整数値が返る
  #
  def check_mobility(disc)
    # すでに石があったら置けない
    unless @raw_board[disc.x][disc.y] == Disc::EMPTY
      return NONE
    end
    dir = NONE
 
    #上
    if @raw_board[disc.x][disc.y - 1] == -disc.color
      x = disc.x
      y = disc.y - 2
      while @raw_board[x][y] == -disc.color
        y -= 1
      end
      if @raw_board[x][y] == disc.color
        dir |= UPPER
      end
    end
 
    #下
    if @raw_board[disc.x][disc.y + 1] == -disc.color
      x = disc.x
      y = disc.y + 2
      while @raw_board[x][y] == -disc.color
        y += 1
      end
      if @raw_board[x][y] == disc.color
        dir |= LOWER
      end
    end
 
    #左
    if @raw_board[disc.x - 1][disc.y] == -disc.color
      x = disc.x - 2
      y = disc.y
      while @raw_board[x][y] == -disc.color
        x -= 1
      end
      if @raw_board[x][y] == disc.color
        dir |= LEFT
      end
    end
 
    #右
    if @raw_board[disc.x + 1][disc.y] == -disc.color
      x = disc.x + 2
      y = disc.y
      while @raw_board[x][y] == -disc.color
        x += 1
      end
      if @raw_board[x][y] == disc.color
        dir |= RIGHT
      end
    end
 
    #右上
    if @raw_board[disc.x + 1][disc.y - 1] == -disc.color
      x = disc.x + 2
      y = disc.y - 2
      while @raw_board[x][y] == -disc.color
        x += 1
        y -= 1
      end
      if @raw_board[x][y] == disc.color
        dir |= UPPER_RIGHT
      end
    end
 
    #左上
    if @raw_board[disc.x - 1][disc.y - 1] == -disc.color
      x = disc.x - 2
      y = disc.y - 2
      while @raw_board[x][y] == -disc.color
        x -= 1
        y -= 1
      end
      if @raw_board[x][y] == disc.color
        dir |= UPPER_LEFT
      end
    end
 
    #左下
    if @raw_board[disc.x - 1][disc.y + 1] == -disc.color
      x = disc.x - 2
      y = disc.y + 2
      while @raw_board[x][y] == -disc.color
        x -= 1
        y += 1
      end
      if @raw_board[x][y] == disc.color
        dir |= LOWER_LEFT
      end
    end
 
    #右下
    if @raw_board[disc.x + 1][disc.y + 1] == -disc.color
      x = disc.x + 2
      y = disc.y + 2
      while @raw_board[x][y] == -disc.color
        x += 1
        y += 1
      end
      if @raw_board[x][y] == disc.color
        dir |= LOWER_RIGHT
      end
    end
 
    return dir
  end
 
  #
  # @movable_pos[@turns]と@movable_dir[@turns]を再計算する
  def init_movable
    @movable_pos[@turns].clear
    for x in 1..BOARD_SIZE
      for y in 1..BOARD_SIZE
        disc = Disc.new(x, y, @current_color)
        dir = check_mobility(disc)
        unless dir == NONE
          @movable_pos[@turns] << disc
        end
        @movable_dir[@turns][x][y] = dir
      end
    end
  end
 
  #
  # 指定した位置に石を打ち、挟みこめる全ての石を裏返す。
  # 打った石と裏返した石を@update_logに挿入する。
  #
  def flip_discs(point)
    dir = @movable_dir[@turns][point.x][point.y]
    update = []
    @raw_board[point.x][point.y] = @current_color
    update << Disc.new(point.x, point.y, @current_color)
 
    #上
    unless (dir & UPPER) == NONE #この位置における
      y = point.y
      until @raw_board[point.x][y - 1] == @current_color
        y -= 1
        @raw_board[point.x][y] = @current_color
        update << Disc.new(point.x, y, @current_color)
      end
    end
 
    #下
    unless (dir & LOWER) == NONE
      y = point.y
      until @raw_board[point.x][y + 1] == @current_color
        y += 1
        @raw_board[point.x][y] = @current_color
        update << Disc.new(point.x, y, @current_color)
      end
    end
 
    #左
    unless (dir & LEFT) == NONE
      x = point.x
      until @raw_board[x - 1][point.y] == @current_color
        x -= 1
        @raw_board[x][point.y] = @current_color
        update << Disc.new(x, point.y, @current_color)
      end
    end
 
    #右
    unless (dir & RIGHT) == NONE
      x = point.x
      until @raw_board[x + 1][point.y] == @current_color
        x += 1
        @raw_board[x][point.y] = @current_color
        update << Disc.new(x, point.y, @current_color)
      end
    end
 
    #右上
    unless (dir & UPPER_RIGHT) == NONE
      x = point.x
      y = point.y
      until @raw_board[x + 1][y - 1] == @current_color
        x += 1
        y -= 1
        @raw_board[x][y] = @current_color
        update << Disc.new(x, y, @current_color)
      end
    end
 
    #左上
    unless (dir & UPPER_LEFT) == NONE
      x = point.x
      y = point.y
      until @raw_board[x - 1][y - 1] == @current_color
        x -= 1
        y -= 1
        @raw_board[x][y] = @current_color
        update << Disc.new(x, y, @current_color)
      end
    end
 
    #左下
    unless (dir & LOWER_LEFT) == NONE
      x = point.x
      y = point.y
      until @raw_board[x - 1][y + 1] == @current_color
        x -= 1
        y +=1
        @raw_board[x][y] = @current_color
        update << Disc.new(x, y, @current_color)
      end
    end
 
    #右下
    unless (dir & LOWER_RIGHT) == NONE
      x = point.x
      y = point.y
      until @raw_board[x + 1][y + 1] == @current_color
        x += 1
        y += 1
        @raw_board[x][y] = @current_color
        update << Disc.new(x, y, @current_color)
      end
    end
 
    disc_diff = update.size
    @discs.set(@current_color, @discs.get(@current_color) + disc_diff)
    @discs.set(-@current_color, @discs.get(-@current_color) - (disc_diff - 1))
    @discs.set(Disc::EMPTY, @discs.get(Disc::EMPTY) - 1)
    @update_log << update
  end
end

boardtest.rb

$KCODE = "s"
 
require "board"
 
class ConsoleBoard < Board
  def print_board
    y_str = %w(1 2 3 4 5 6 7 8)
    puts " abcdefgh"
    for y in 1..8
      print y_str.shift
      for x in 1..8
        case get_color(Point.new(x, y))
        when Disc::BLACK
          print "○"
        when Disc::WHITE
          print "●"
        else
          print " "
        end
      end
      puts
    end
  end
end
 
board = ConsoleBoard.new
 
def str_to_ary(str)
  if str.size < 2
    return [0, 0]
  else
    x = str[0] - 'a'[0] + 1
    y = str[1].chr.to_i
    return [x, y]
  end
end
 
loop do
  board.print_board
 
  print "黒石#{board.count_disc(Disc::BLACK)} "
  print "白石#{board.count_disc(Disc::WHITE)} "
  puts "空マス#{board.count_disc(Disc::EMPTY)}"
 
  player = board.get_current_color == 1 ? "黒" : "白"
  print "手を入力してください #{board.get_turns + 1}手目#{player}番: "
 
  input_point = board.get_movable_pos.first
  if input_point.nil?
    board.pass
    puts "pass"
    puts
    next
  else
    puts input_point.to_s
    puts
  end
 
=begin
  input = STDIN.gets
  case input.chomp!
  when "p"
    unless board.pass
      puts "パスできません"
    end
    next
  when "u"
    board.undo
    next
  when "q"
    break
  end
 
  begin
    x, y = str_to_ary(input)
    input_point = Point.new(x, y)
  rescue
    puts "リバーシ形式の手を入力してください"
    next
  end
=end
 
  if not board.move(input_point)
    puts "そこには置けません"
    next
  end
 
  if board.is_gameover
    puts "ゲーム終了"
 
    board.print_board
    print "黒石#{board.count_disc(Disc::BLACK)} "
    print "白石#{board.count_disc(Disc::WHITE)} "
    if board.count_disc(Disc::BLACK) == board.count_disc(Disc::WHITE)
      puts "引き分け"
    elsif board.count_disc(Disc::BLACK) > board.count_disc(Disc::WHITE)
      puts "黒の勝ち"
    else
      puts "白の勝ち"
    end
    #board.movable_pos.each_with_index do |ary, i|
    #  printf "%3d=>%3d\n", i, ary.size
    #end
    break
  end
end

# [Ruby] 「リバーシのアルゴリズム」をRubyで その3

つづいて第3章「探索アルゴリズム」。

この本の説明だけでは今ひとつ理解できなかったので、解説しているサイトを検索。以下のサイトの記事がわかりやすかった。

Algorithms with Python / ミニマックス法とαβ法
AIの実装/ミニマックス法 - Javaでゲーム作りますが何か?

Rubyでの実装は後ほど。

[]

2010.05.20 Thu [長年日記]

# 「リバーシのアルゴリズム」をRubyで その4

前回のつづき。第3章「探索アルゴリズム」。

# -*- coding: sjis -*-
class AI
  def move(board)
  end
 
  # 事前探索の先読み手数
  PRESEARCH_DEPTH = 3
  # 序盤・中盤の先読み手数
  NORMAL_DEPTH = 15
  # 必勝読みを始める残り手数
  WLD_DEPTH = 15
  # 完全読みを始める残り手数
  PERFECT_DEPTH = 13
 
end
 
require 'point'
require 'disc'
require 'board'
 
class AlphaBetaAI < AI
  MAX_VALUE = 2 ** 30 - 1
  MIN_VALUE = -(2 ** 30)
 
  class Move < Point
    attr_reader :e
    def initialize(x = 0, y = 0, e = 0)
      super(x, y)
      @e = e
    end
  end
 
  def move(board)
    movables = board.get_movable_pos
 
    if movables.empty?
      #打てる箇所がなければパスする
      board.pass
      return
    end
 
    if movables.size == 1
      #打てる箇所が一つだけなら探索せず、即座に打って返る
      board.move(movables[0])
      return
    end
 
    # 事前に手をよさそうな順にソート
    sort(board, movables, PRESEARCH_DEPTH)
 
    if Board::MAX_TURNS - board.get_turns <= WLD_DEPTH
      limit = MAX_VALUE
    else
      limit = NORMAL_DEPTH
    end
 
    eval_max = MIN_VALUE
    for i in 0...movables.size
      board.move(movables[i])
      eval_tmp = -alphabeta(board, limit - 1, -MAX_VALUE, -MIN_VALUE)
      board.undo
 
      if eval_tmp > eval_max
        eval_max = eval_tmp
        point = movables[i]
      end
    end
 
    board.move(point)
  end
 
  def alphabeta(board, limit, alpha, beta)
    if board.is_gameover or limit == 0
      return evaluate(board)
    end
 
    pos = board.get_movable_pos
 
    if pos.size == 0
      #パス
      board.pass
      eval_tmp = -alphabeta(board, limit, -beta, -alpha)
      board.undo
      return eval_tmp
    end
 
    for i in 0...pos.size
      board.move(pos[i])
      eval_tmp = -alphabeta(board, limit - 1, -beta, -alpha)
      board.undo
      alpha = [alpha, eval_tmp].max
      if alpha >= beta
        #beta刈り
        return alpha
      end
    end
 
    return alpha
  end
 
  def sort(board, movables, limit)
    moves = []
    for i in 0...movables.size
      point = movables[i]
      board.move(point)
      eval_tmp = -alphabeta(board, limit - 1, MIN_VALUE, MAX_VALUE)
      board.undo
      move = Move.new(point.x, point.y, eval_tmp)
      moves.push(move)
    end
    #評価tの大きい順にソート
    moves_sorted = moves.sort do |a, b|
      b.e <=> a.e
    end
    movables.clear
    moves_sorted.each do |move|
      movables << move
    end
 
    return
  end
 
  def evaluate(board)
    # 評価関数は4章で実装
    return 0
  end
end

序盤・中盤の先読み手数15は、いろいろいじらないと無理っぽい予感。

[]

2010.05.21 Fri [長年日記]

# [Ruby] 「リバーシのアルゴリズム」をRubyで その5

昨日のつづき。第4章「評価関数」。評価関数の実装にともなうBoardクラスの変更していて気がついた。x座標とy座標、逆ですやん・・・・・

[]

2010.05.22 Sat [長年日記]

# [Ruby] 再帰による組み合わせの生成

4章「評価関数」のコードに、辺8個のマスの黒/白/空きの全ての組み合わせを生成する箇所がある。

def generate_edge(edge, count)
  if count == Board::BOARD_SIZE
    # このパターンは完成したので、局面のカウント
    stat = EdgeStat.new
    stat.get(Disc::BLACK).set(eval_edge(edge, Disc::BLACK))
    stat.get(Disc::WHITE).set(eval_edge(edge, Disc::WHITE))
    @@edge_table[idx_line(edge)] = stat
 
    return
  end
 
  # 再帰的にすべてのパターンを網羅 
  edge[count] = Disc::EMPTY
  generate_edge(edge, count + 1)
 
  edge[count] = Disc::BLACK
  generate_edge(edge, count + 1)
 
  edge[count] = Disc::WHITE
  generate_edge(edge, count + 1)
 
  return
end
 
line = Array.new(8)
generate_edge(line, 0)

これで、

[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1]
.
.
[2, 2, 2, 2, 2, 2, 2, 2]

までの3 ** 8 = 6561通りの組み合わせが生成されるのだけれども、はじめ動きが追えませんでした。情けない・・・単純化してようやく理解。

#サイズ3, 値2種類, 8通りの場合
def gen(line, count)
  printf "%s%s, %s\n", "   " * count, line.to_s, count.to_s
 
  if count == 3
    return
  end
 
  line[count] = "A"
  gen(line, count + 1)
 
  line[count] = "B"
  gen(line, count + 1)
 
  return
end
 
line = [nil, nil, nil]
gen(line, 0)
 
#=>
[nil, nil, nil], 0
   ["A", nil, nil], 1
      ["A", "A", nil], 2
         ["A", "A", "A"], 3
         ["A", "A", "B"], 3
      ["A", "B", "B"], 2
         ["A", "B", "A"], 3
         ["A", "B", "B"], 3
   ["B", "B", "B"], 1
      ["B", "A", "B"], 2
         ["B", "A", "A"], 3
         ["B", "A", "B"], 3
      ["B", "B", "B"], 2
         ["B", "B", "A"], 3
         ["B", "B", "B"], 3

# [NFL] 堂々の1位

大駒4枚落ちのカーディナルス。現時点で戦力的にどうなのか、気になるけれども素人には判断不能。そんなところへNFLニュース。

NFL JAPAN.COM|2010シーズン、プレイオフ戦線を占う

「昨季から一転、プレイオフ進出を逃す可能性が高いチーム」の堂々第1位にアリゾナ・カーディナルス・・・逆に「新たにプレイオフ進出が有力なチーム」の第2位にサンフランシスコ・49ers。49ersはフロント・コーチがごたごたしているらしいというニュースがあったのでニンマリしてたんだけど、そうですか。うー。

# [Ruby] 「リバーシのアルゴリズム」をRubyで その6

第4章「評価関数」の実装終了。

# -*- coding: sjis -*-
# evaluator.rb

require 'point'
require 'board'

#
# 完全読み用評価関数
#
class PerfectEvaluator
  def evaluate(board)
    board.get_current_color \
      * (board.count_disc(Disc::BLACK) - board.count_disc(Disc::WHITE))
  end
end

#
# 必勝読み用評価関数
#
class WLDEvaluator
  WIN = 1
  DRAW = 0
  LOSE = -1

  def evaluate(board)
    disc_diff = board.get_current_color \
      * (board.count_disc(Disc::BLACK) - board.count_disc(Disc::WHITE))
    if disc_diff > 0
      WIN
    elsif disc_diff < 0
      LOSE
    else
      DRAW
    end
  end
end

#
# 中盤の評価関数
# + 着手可能手数
# - 開放度
# - ウイング
# + 確定石
# - 隅が取られていない状態でのC打ち
# - 隅が取られていない状態でのX打ち
#
class MidEvaluator
  #
  # 辺に関するパラメータ
  #
  class EdgeParam
    attr_accessor :stable, :wing, :mountain, :cmove

    def initialize
      # 確定石の個数
      @stable = 0
      # ウイングの個数
      @wing = 0
      # 山の個数
      @mountain = 0
      # EC
      # CX
      # 危険なC打ちの個数
      @cmove = 0
    end

    def set(param)
      @stable = param.stable
      @wing = param.wing
      @mountain = param.mountain
      @cmove = param.cmove
    end

    def add(param)
      @stable += param.stable
      @wing += param.wing
      @mountain += param.mountain
      @cmove += param.cmove
      return self
    end
  end

  #
  # 色別のEdgeParamオブジェクトを管理するクラス
  #
  class EdgeStat
    attr_reader :data

    def initialize
      @data = {}
      [Disc::BLACK, Disc::WHITE, Disc::EMPTY].each do |color|
        @data[color] = EdgeParam.new
      end
    end

    def add(stat)
      @data.each do |color, param|
        param.add(stat.data[color])
      end
    end

    def get(color)
      @data[color]
    end
  end

  #
  # 隅周辺に関するパラメータ
  #
  class CornerParam
    attr_accessor :corner, :xmove

    def initialize
      @corner = 0 # 隅にある石の数
      @xmove = 0 # 危険なX打ちの個数
    end
  end

  #
  # 色別のCornerオブジェクトを管理するクラス
  #

  class CornerStat
    attr_reader :data

    def initialize
      @data = {}
      [Disc::BLACK, Disc::WHITE, Disc::EMPTY].each do |color|
        @data[color] = CornerParam.new
      end
    end

    def get(color)
      @data[color]
    end
  end

  #
  # 重み係数を規定する構造体
  #
  Weight = Struct.new(
    :mobility_w, # 着手可能手数
    :liberty_w, # 開放度
    :stable_w, # 確定石
    :wing_w, # ウイング
    :xmove_w, # X打ち
    :cmove_w # C打ち
  )

  #
  # 辺のパターンの組み合わせ用インデックスのサイズ
  # 3 ** 8 = 6561
  #
  TABLE_SIZE = 6561

  # 辺のパターン毎のEdgeStatの配列
  @@edge_table = Array.new(TABLE_SIZE){ EdgeStat.new }

  @@table_init = false

  def MidEvaluator.edge_table
    @@edge_table
  end

  attr_reader :eval_weight

  def initialize
    # 初回起動時にテーブルを生成
    unless @table_init
      line = Array.new(Board::BOARD_SIZE)
      generate_edge(line, 0)
      @@table_init = true
    end

    # 重み係数の設定(全局面共通)
    @eval_weight = Weight.new
    @eval_weight.mobility_w = 67 # 着手可能手数
    @eval_weight.liberty_w = -13 # 開放度
    @eval_weight.stable_w = 101 # 安定石
    @eval_weight.wing_w = -308 # ウイング
    @eval_weight.xmove_w = -449 # X打ち
    @eval_weight.cmove_w = -552 # C打ち
  end

  #
  # 評価関数
  #
  def evaluate(board)
    edge_stat = nil
    corner_stat = nil
    result = nil

    # 辺の評価
    edge_stat = @@edge_table[idx_top(board)]
    edge_stat.add(@@edge_table[idx_bottom(board)])
    edge_stat.add(@@edge_table[idx_right(board)])
    edge_stat.add(@@edge_table[idx_left(board)])


    # 隅の評価
    corner_stat = eval_corner(board)

    # 確定石に関して、隅の石を2回数えているので補正
    edge_stat.get(Disc::BLACK).stable \
      -= corner_stat.get(Disc::BLACK).corner
    edge_stat.get(Disc::WHITE).stable \
      -= corner_stat.get(Disc::WHITE).corner

    # パラメータの線形結合
    result = \
      edge_stat.get(Disc::BLACK).stable * @eval_weight.stable_w \
      - edge_stat.get(Disc::WHITE).stable * @eval_weight.stable_w \
      + edge_stat.get(Disc::BLACK).wing * @eval_weight.wing_w \
      - edge_stat.get(Disc::WHITE).wing * @eval_weight.wing_w \
      + corner_stat.get(Disc::BLACK).xmove * @eval_weight.xmove_w \
      - corner_stat.get(Disc::WHITE).xmove * @eval_weight.xmove_w \
      + edge_stat.get(Disc::BLACK).cmove * @eval_weight.cmove_w \
      - edge_stat.get(Disc::WHITE).cmove * @eval_weight.cmove_w

    # 開放度の評価
    unless @eval_weight.liberty_w == 0
      liberty = count_liberty(board)
      result += liberty.get(Disc::BLACK) * @eval_weight.liberty_w
      result -= liberty.get(Disc::WHITE) * @eval_weight.liberty_w
    end

    # 現在の手番の色についてのみ、着手可能手数を数える
    result += \
      board.get_current_color \
      * board.get_movable_pos.size \
      * @eval_weight.mobility_w

    return board.get_current_color * result
  end

  #
  # @@edge_tableの生成
  #    generate_edge(line, 0)
  #
  def generate_edge(edge, count)
    if count == Board::BOARD_SIZE
      # このパターンは完成したので、局面のカウント
      stat = EdgeStat.new
      stat.get(Disc::BLACK).set(eval_edge(edge, Disc::BLACK))
      stat.get(Disc::WHITE).set(eval_edge(edge, Disc::WHITE))
      @@edge_table[idx_line(edge)] = stat

      return
    end

    # 再帰的にすべてのパターンを網羅
    edge[count] = Disc::EMPTY
    generate_edge(edge, count + 1)

    edge[count] = Disc::BLACK
    generate_edge(edge, count + 1)

    edge[count] = Disc::WHITE
    generate_edge(edge, count + 1)

    return
  end

  def eval_edge(line, color)
    edge_param = EdgeParam.new

    # ウィングなどのカウント
    if line[0] == Disc::EMPTY and line[7] == Disc::EMPTY
      x = 2
      while x <= 5
        unless line[x] == color
          break
        end
        x += 1
      end
      if x == 6 # 少なくてもブロックが出来ている
        if line[1] == color and line[6] == Disc::EMPTY
          edge_param.wing = 1
        elsif line[1] == Disc::EMPTY and line[6] == color
          edge_param.wing = 1
        elsif line[1] == color and line[6] == color
          edge_param.mountain = 1
        end
      else # それ以外の場合に、隅に隣接する位置に置いていたら
        if line[1] == color
          edge_param.cmove += 1
        end
        if line[6] == color
          edge_param.cmove += 1
        end
      end
    end

    # 確定石のカウント
    # 左から右に走査
    for x in 0..7
      unless line[x] == color
        break
      end
      edge_param.stable += 1
    end

    if edge_param.stable < 8
      # 右からの走査も必要
      7.downto(0) do |x|
        unless line[x] == color
          break
        end
        edge_param.stable += 1
      end
    end

    return edge_param
  end

  #
  # 隅のパラメーターを調べる。この関数は各評価時に使う。
  #
  def eval_corner(board)
    corner_stat = CornerStat.new

    corner_stat.get(Disc::BLACK).corner = 0
    corner_stat.get(Disc::BLACK).xmove = 0
    corner_stat.get(Disc::WHITE).corner = 0
    corner_stat.get(Disc::WHITE).xmove = 0

    point = Point.new

    # 左上
    point.y = 1
    point.x = 1
    corner_stat.get(board.get_color(point)).corner += 1
    if board.get_color(point) == Disc::EMPTY
      point.y = 2
      point.x = 2
      corner_stat.get(board.get_color(point)).xmove += 1
    end

    # 左下
    point.y = 8
    point.x = 1
    corner_stat.get(board.get_color(point)).corner += 1
    if board.get_color(point) == Disc::EMPTY
      point.y = 7
      point.x = 2
      corner_stat.get(board.get_color(point)).xmove += 1
    end

    # 右下
    point.y = 8
    point.x = 8
    corner_stat.get(board.get_color(point)).corner += 1
    if board.get_color(point) == Disc::EMPTY
      point.y = 7
      point.x = 7
      corner_stat.get(board.get_color(point)).xmove += 1
    end

    # 右上
    point.y = 1
    point.x = 8
    corner_stat.get(board.get_color(point)).corner += 1
    if board.get_color(point) == Disc::EMPTY
      point.y = 2
      point.x = 7
      corner_stat.get(board.get_color(point)).xmove += 1
    end

    return corner_stat
  end

  #
  # 各辺の状態についてのインデックスの計算
  #

  # 上辺
  def idx_top(board)
    index = 0
    m = 1
    point = Point.new(1, 8)
    Board::BOARD_SIZE.downto(1) do |i|
      point.x = i
      index += m * (board.get_color(point) + 1)
      m *= 3
    end

    return index
  end

  # 下辺
  def idx_bottom(board)
    index = 0
    m = 1
    point = Point.new(8, 8)
    Board::BOARD_SIZE.downto(1) do |i|
      point.x = i
      index += m * (board.get_color(point) + 1)
      m *= 3
    end

    return index
  end

  # 右辺
  def idx_right(board)
    index = 0
    m = 1
    point = Point.new(8, 8)
    Board::BOARD_SIZE.downto(1) do |i|
      point.y = i
      index += m * (board.get_color(point) + 1)
      m *= 3
    end

    return index
  end

  # 左辺
  def idx_left(board)
    index = 0
    m = 1
    point = Point.new(8, 1)
    Board::BOARD_SIZE.downto(1) do |i|
      point.y = i
      index += m * (board.get_color(point) + 1)
      m *= 3
    end

    return index
  end

  #
  # 開放度を取得
  #
  def count_liberty(board)
    liberty = ColorStorage.new
    liberty.set(Disc::BLACK, 0)
    liberty.set(Disc::WHITE, 0)
    liberty.set(Disc::EMPTY, 0)

    point = Point.new

    for y in 1..Board::BOARD_SIZE
      point.y = y
      for x in 1..Board::BOARD_SIZE
        point.x = x
        l = liberty.get(board.get_color(point))
        l += board.get_liberty(point)
        liberty.set(board.get_color(point), l)
      end
    end

    return liberty
  end

  #
  # 石のパターンのインデックスを返す
  #
  # [-1, -1, -1, -1, -1, -1, -1, -1] => 0
  # [-1, -1, -1, -1, -1, -1, -1, 0] => 1
  # [1, 1, 1, 1, 1, 1, 1, 1] => 6560
  def idx_line(line)
    return 3 * (3 * (3 * (3 * (3 * (3 * (3 * (line[0] + 1) + line[1] + 1) + line[2] + 1) + line[3] + 1) + line[4] + 1) + line[5] + 1) + line[6] + 1)+ line[7] + 1
  end
end
[]

2010.05.27 Thu [長年日記]

# [Ruby] 「リバーシのアルゴリズム」をRubyで その7

間がかなり開きましたが、前回の続き。第5章「定石」と第6章ゲームの実装。これでひとまず終了。

# -*- cording: sjis -*-
# coordinatestransformer.rb
 
require 'point'
 
class CoordinatesTransformer
  def initialize(first)
    @rotate = 0
    @mirror = false
 
    if first.equals(Point["d3"])
      @rotate = 1
      @mirror = true
    elsif first.equals(Point["c4"])
      @rotate = 2
    elsif first.equals(Point["e6"])
      @rotate = -1
      @mirror = true
    end
  end
 
  #
  # 座標f5を開始点とする座標系に正規化する
  #
  def normalize(point)
    new_point = rotate_point(point, @rotate)
    new_point = mirror_point(new_point) if @mirror
 
    return new_point
  end
 
  #
  # f5を開始点とする座標を元の座標に戻す
  #
  def denormalize(point)
    new_point = Point.new(point.y, point.x)
    new_point = mirror_point(new_point) if @mirror
    new_point = rotate_point(new_point, -@rotate)
 
    return new_point
  end
 
  #
  # 座標の回転
  # 半時計回り90度 1
  #
  def rotate_point(old_point, rotate)
    rotate %= 4
    rotate += 4 if rotate < 0
    new_point = Point.new
 
    case rotate
    when 1
      new_point.y = Board::BOARD_SIZE - old_point.x + 1
      new_point.x = old_point.y
    when 2
      new_point.y = Board::BOARD_SIZE - old_point.y + 1
      new_point.x = Board::BOARD_SIZE - old_point.x + 1
    when 3
      new_point.y = old_point.x
      new_point.x = Board::BOARD_SIZE - old_point.y + 1
    else
      new_point.y = old_point.y
      new_point.x = old_point.x
    end
 
    return new_point
  end
 
  #
  # 左右反転
  #
  def mirror_point(point)
    new_point = Point.new
    new_point.y = point.y
    new_point.x = Board::BOARD_SIZE - point.x + 1
 
    return new_point
  end
end

reversi.book(定石データ)

f5f6e6f4e3d3f3c5c4e2
f5f6e6f4e3d6g4d3c3f2
f5d6c4g5c6d3e6d7f6c7
f5d6c5f4e3c6d3f6e6d7e7c7c4b4
# -*- coding: sjis -*-
# bookmanager.rb
 
require 'point'
require 'coordinatestransformer'
 
class BookManager
 
  BOOK_FILE_NAME = "reversi.book"
 
  class Node
    attr_accessor :child, :sibling, :point
 
    def initialize
      @child = nil
      @sibling = nil
      @point = Point.new
    end
  end
 
  attr_accessor :root
 
  def initialize
    @root = Node.new
    @root.point = Point["f5"]
 
    File.open(BOOK_FILE_NAME) {|f|
      while line = f.gets
        book = []
        line.chomp!
 
        0.step(line.size - 1, 2) do |i|
          point = Point[line[i, 2]]
          book << point
        end
 
        add(book)
      end
    }
  end
 
  #
  # 定石手の検索
  #
  def find(board)
    node = @root
    history = board.get_history
 
    if history.empty?
      return board.get_movable_pos
    end
    first = history.first
    transformer = CoordinatesTransformer.new(first)
 
    # 座標を変換してf5から始まるようにする
    normalized = []
    for i in 0...history.size
      point = history[i]
      point = transformer.normalize(point)
      normalized << point
    end
 
    # 現在までの棋譜リストと定石の対応を取る
    for i in 1...normalized.size
      node = node.child
      point = normalized[i]
 
      until node.nil?
        break if node.point.equals(point)
        node = node.sibling
      end
 
      if node.nil?
        # 定石を外れている
        return board.get_movable_pos
      end
    end
 
    # 履歴と定石の終わりが一致していた場合
    if node.child.nil?
      return board.get_movable_pos
    end
    next_move = get_next_move(node)
 
    # 座標を元の形に変換する
    next_move = transformer.denormalize(next_move)
    v = []
    v << next_move
 
    return v
  end
 
   private
 
  #
  # bookで指定された定石を定石木に追加する
  # book[0] ノードはf5
  #
  def add(book)
    node = @root
 
    for i in 1...book.size
      new_point = book[i]
 
      if node.child.nil?
        # 新しい定石手
        node.child = Node.new
        node = node.child
        node.point.y = new_point.y
        node.point.x = new_point.x
      else
        # 兄弟ノードの探索
        node = node.child
        while true
          # すでにこの手はデータベース中にあり、その枝を見つけた
          break if node.point.equals(new_point)
 
          # 定石手の新しい枝
          if node.sibling.nil?
            node.sibling = Node.new
            node = node.sibling
            node.point.y = new_point.y
            node.point.x = new_point.x
            break
          end
 
          node = node.sibling
        end
      end
    end
  end
 
  #
  # 次の一手を決める。引数nodeは現在の手数の位置に対応するノード。
  # 直前にboardに打たれた手
  # node.childの世代が次の手を表すノード
  #
  def get_next_move(node)
    candidates = []
 
    target = node.child
    until target.nil?
      candidates << target.point
      target = target.sibling
    end
 
    index = rand * candidates.size
    point = candidates[index]
 
    return Point.new(point.y, point.x)
  end
end
 
if __FILE__ == $0
  require 'board'
  bm = BookManager.new
  b = Board.new
  b.move(Point["f5"])
  b.move(Point["f6"])
  b.move(Point["e6"])
  b.move(Point["f4"])
  b.move(Point["e3"])
  puts bm.find(b).to_s
end
# -*- encoding: sjis -*-
# reversigame.rb
 
require "ai"
require "consoleboard"
 
class GameException < Exception; end
class UndoException < GameException; end
class ExitException < GameException; end
class GameOverException < GameException; end
 
class Player
  def on_turn(board)
  end
end
 
class HumanPlayer < Player
  def on_turn(board)
    if board.get_movable_pos.empty?
      puts "あなたはパスです。"
      board.pass
      return
    end
 
    loop do
      puts "手を\"f5\"のように入力、もしくは(U:取り消し/X:終了)を入力してください:"
      input = gets
      input.chomp!.downcase!
 
      case input
      when "u"
        puts "取り消します。"
        board.undo
        board.undo
        board.undo while board.get_movable_pos.empty?
        return
      when "x"
        puts "終了します。"
        exit
      else
        begin
          point = Point[input]
        rescue
          puts "正しい形式で入力してください。"
          next
        end
      end
 
      unless board.move(point)
        puts "そこには置けません。"
        next
      end
 
      break
    end
  end
end
 
class AIPlayer < Player
  def initialize
    @ai = AlphaBetaAI.new
  end
 
  def on_turn(board)
    puts "コンピュータ思考中..."
    e = @ai.move(board)
 
    printf("選択:%s 評価値:%5d\n", board.get_history.last.to_s, e )
  end
end
 
board = ConsoleBoard.new
 
player = {}
player[1] = AIPlayer.new
player[-1] = AIPlayer.new
 
 
loop do
  board.print_board
  print "黒石#{board.count_disc(Disc::BLACK)} "
  print "白石#{board.count_disc(Disc::WHITE)} "
  puts "空マス#{board.count_disc(Disc::EMPTY)}"
  msg = board.get_current_color == 1 ? "黒(O)番" : "白(X)番"
  puts "手を入力してください #{board.get_turns + 1}手目#{msg}"
  player[board.get_current_color].on_turn(board)
  puts
 
  if board.is_gameover
    board.print_board
    puts "ゲーム終了"
    puts "黒石: #{board.count_disc(Disc::BLACK)}"
    puts "白石: #{board.count_disc(Disc::WHITE)}"
    break
  end
end

実行画面はこんな感じ。

  a b c d e f g h
1 _ _ _ _ _ _ _ _
2 _ _ _ X _ _ _ _
3 _ _ O O O X _ _
4 _ _ O O O O _ _
5 _ X X X O X _ _
6 _ X X X O _ _ _
7 _ _ _ O O O _ _
8 _ _ _ _ O X _ _
黒石13 白石10 空マス41
手を入力してください 20手目白(X)番
コンピュータ思考中...
候補01 d8,評価値 566
候補02 f2,評価値 644
候補03 f6,評価値 618
候補04 c2,評価値 343
候補05 b4,評価値 525
候補06 e2,評価値 540
候補07 g5,評価値 527
候補08 g3,評価値 514
候補09 c8,評価値 473
候補10 b3,評価値 157
候補11 g8,評価値 55
選択:f2 評価値:  644
 
  a b c d e f g h
1 _ _ _ _ _ _ _ _
2 _ _ _ X _ X _ _
3 _ _ O O X X _ _
4 _ _ O X O O _ _
5 _ X X X O X _ _
6 _ X X X O _ _ _
7 _ _ _ O O O _ _
8 _ _ _ _ O X _ _
黒石11 白石13 空マス40
手を入力してください 21手目黒(O)番
コンピュータ思考中...
候補01 f6,評価値 1107
候補02 b4,評価値 1263
候補03 e2,評価値 1131
候補04 g6,評価値 1029
候補05 a5,評価値 1081
候補06 e1,評価値 1038
候補07 c7,評価値 1148
候補08 g5,評価値 1029
候補09 d1,評価値 1001
候補10 g4,評価値 1031
候補11 c1,評価値 908
候補12 a4,評価値 1001
候補13 g3,評価値 1066
候補14 f1,評価値 949
候補15 a6,評価値 752
候補16 g8,評価値 650
候補17 b7,評価値 686
候補18 g2,評価値 714
選択:b4 評価値: 1263

# [Ruby] 「リバーシのアルゴリズム」をRubyで その8

一通り実装し終わったので、本書の感想など。

思考プログラムの基本が紹介されている
本書は200ページの薄い本ではありますが、思考ゲームプログラミングの入門として必要不可欠な要素を広く薄く紹介しています。解説は詳しくはないし私にはわかりにくかったですが、言及はされているので自分で調べる際に助かりました。ただ、機械学習については紹介されていません。
誤植や実装漏れが比較的多い(特にJava)
出版元の工学社には本書のサポートページがありません。ですが、著者によるサポートページがあり、そこに正誤表や参考文献が載っています。
著者によるサポートページ: 「リバーシのアルゴリズム」補足
本書の表紙には「C++&Java対応」とありますが筆者の方はC++で作成しているらしく、Javaのコードの方に正誤表に載っていない誤植や実装漏れが目立ちます。私が持っているのは第1版第3刷ですが修正されていませんでした。

いろいろ文句を書きましたが入門書としては良い本であると思います。値段も安いですし。
本書の他に特に役立ったのが以下のページ
リバーシプログラムの作り方 サンプル

あと、Rubyで書くに当ってよく見たのは

  • Rubyリファレンスマニュアル Ruby 1.9.1版(全般)
    昔のは右上に検索窓があったような気がするけど、今は無くてちと使いにくい。
  • プログラミング言語Ruby第2版言語編 (第13章 デバッガ)
  • オブジェクト指向スクリプト言語Ruby (第6章3節 life.rb)

[]

2010.05.28 Fri [長年日記]

# [] ボナンザVS勝負脳―最強将棋ソフトは人間を超えるか 保木邦仁, 渡辺明

読了。

2007年3月21日、若きタイトルホルダーと最強コンピュータの歴史的一戦。多くの人の予想を裏切り、あと一歩というところまで攻め込んだ「ボナンザ」。どのようにしてこの最強ソフトは生まれたのか?

[ボナンザVS勝負脳: 書籍: 保木邦仁 渡辺明 | 角川書店・角川グループより引用]

探索アルゴリズムについては全幅探索ということなので「リバーシのアルゴリズム」でも解説されていたミニマックス法に枝刈りの手法(alpha-beta法など)を組み合わせたものであることは本書を読む前から予想できていたので、個人的には評価関数について知りたかったのだけれどあまり書かれていなくて残念でした。

予想以上に面白かったのが渡辺明竜王のパート。プロ棋士からみた従来のコンピュータ将棋とボナンザの差、ボナンザの癖、コンピュータ将棋の今後の予想など。あと、プロ棋士が対局前にどう準備し研究をつづけているのかなど。将棋読み物として面白かったです。

日本将棋連盟への挑戦状で少し前に話題になりましたが、プロ棋士とコンピュータ将棋の対局が、今秋再び行われるそうです。相手は清水市代女流二冠。対戦するコンピュータ将棋は、ご近所電気通信大学の方も開発に携わっているらしい合議アルゴリズム「文殊」に各種コンピュータ将棋の予定(単独のほうが強ければ、単独の可能性もあり)。今回もし勝てば、今後プロ四段からトッププロ、最終的には名人か竜王と対戦もあるかもしれないそうで、今から楽しみです。

合議システムと文殊
挑戦状に関するFAQ

[]

2010.05.29 Sat [長年日記]

# [スポーツ] 世界卓球2010 女子準々決勝 日本 × 韓国

平野早矢香選手の第1試合は残念ながら見逃してしまいましたが、福原愛選手の第2試合途中以降見ました。

5時間にも及ぶ大接戦、大熱戦。韓国のキム・キョンア選手の強さというかしぶとさに苦しめ続けられました。平野早矢香選手、福原愛選手とももうほんの少しだったのに・・・。でもよくがんばった。

この試合で一番心震えたのはやはり、第3試合。石川佳純選手が2ゲーム落としてからの大逆転劇。よー頑張った。おっちゃん泣いたよ。

結局平野早矢香選手・福原愛選手・石川佳純選手とも1勝しての準決勝進出。めでたい。次は男子・女子とも中国戦。厳しいとは思いますが頑張ってください。

あとテレビ東京さん。お願いだから今日みたいに途中で放送終了はやめてね。

[]

最新