Help us understand the problem. What is going on with this article?

とあるサイトでオセロのプログラムの問題があったんだけど,全然できないからQiita始める

More than 3 years have passed since last update.

1.途中まで書いたやつ

othelo.rb
class Field < Array
    def initialize
        @size = 8
        super(@size) do |y|
            Array.new(@size) do |x|
                if y == 3 and x == 3
                    Stone.new(:W)
                elsif y == 3 and x == 4
                    Stone.new(:B)
                elsif y == 4 and x == 3
                    Stone.new(:B)
                elsif y == 4 and x == 4
                    Stone.new(:W)
                end
            end
        end
    end
end

class Stone
    attr_accessor :color
    def initialize( sym, y, x )
        @color = sym
    end
end

class Othelo
    attr_reader :field
    def initialize
        @field = Field.new
    end
    def put_stone(sym, y, x)
        x -= 1
        y -= 1
        @field[y][x] = Stone.new(sym)
                #ここに石をひっくり返すメソッドを書きたい
    end
    def format
        puts "*12345678"
        @field.each_with_index do |row,i|
            print i+1
            row.each do |stone|
                if stone and stone.color == :B
                    print "B"
                else 
                    print "W"
                end
                print "_"
            end
            print "\n"
        end
    end
end

 という感じで途中まで書いてはみたのだけれど,結局オセロの肝となる石をひっくり返すアルゴリズムをコードに表現することができずに断念。
 いろいろ書いては見たが,全く石がひっくり返らなかったりとか,全然関係ないところの石がひっくり返ったりとか,もうやんなっちゃう。楽勝だろうと思って取り組んだのに全然できないから,凄い凹んだ。

2.1.分からなかったところ

 ひっくり返すべき石をどのように判定するべきかをコードにすることができなかったと分かっているので,そこをどうしようか考えている。
 タテ・ヨコ・ナナメと探索しなければならないのだが,どうもコードがごちゃごちゃとしてしまい,結果どうして動作しないのかがはっきりと分からない。

 このコードは一体何をしようとしているんだと言われてももう説明できないくらい,しっちゃかめっちゃかになっているコードだけど一応晒しておく。多分まともに動作しない。

reverse.rb
        #タテ・ヨコ・ナナメに位置する石の座標を簡単に取得しようと思って作ったと思う
    def generator(y, x, fy, fx)
        ret_y = y
        ret_x = x
        Enumerator.new do |yeel|
            @field.height.times do
                ret_y = fy.call(ret_y) % @field.height      
                ret_x = fx.call(ret_x) % @field.width
                yeel << [ret_y,ret_x]
            end
        end
    end
        #挟んでひっくり返す石の配列を返して欲しい,返してくれない
    def stones_in_line(sym, y, x, fy:->i{i}, fx:->i{i})
        max_y, max_x = 0, 0
        min_y, min_x = 7, 7
        generator(y,x,fy,fx).each do |_y,_x|
            if @field[_y][_x] and @field[_y][_x].color == sym
                max_y = _y if max_y < _y
                max_x = _x if max_x < _x
                min_y = _y if min_y > _y
                min_x = _x if min_x > _x
            end
        end
        arr = []
        generator(y,x,fy,fx).each do |_y,_x|
            if _y.between?(min_y,max_y) and _x.between?(min_x,max_x)
                if @field[_y][_x] and @field[_y][_x].color != sym
                    arr.push(@field[_y][_x])
                else
                    return
                end
            end
        end
        return arr
    end
        # 石をひっくり返すはず,そうして欲しい
    def reverse(sym, y, x)
        reverse_stones = []
        inc = ->i{i+=1}
        dec = ->i{i-=1}
        reverse_stones.concat(
            stones_in_line(sym, y, x, fy:inc)
        )
        reverse_stones.concat(
            stones_in_line(sym, y, x, fx:inc)
        )
        reverse_stones.concat(
            stones_in_line(sym, y, x, fx:inc, fy:inc)
        )
        reverse_stones.concat(
            stones_in_line(sym, y, x, fx:inc, fy:dec)
        )
        reverse_stones.uniq.each do |stone|
            stone.color = sym
        end 
    end

2.Qiitaを始める

 自分のプログラミングの実力があるのか無いのか,試そうとしたら,オセロすらプログラミングできず,結果が白黒はっきりついてしまったわけでがっかり。意識高そうなブログで気分を更に沈めようとして記事を読んでいると,プログラミングの学習にはアウトプットが欠かせませんとかあったので,始めてみることにした。

 未熟なコードを晒すことを以って戒めとし,技術の向上に努めるために利用する予定。

 基本3日坊主なので,続かないかもしれない。

3.完成した(はず)

 @sclvola さんありがとうございます。完成しました!(多分)

othelo.rb
class OthelloBoard
    BOARD_SIZE = 8
    STONE_CHARS = {none: ".", white: "o", black: "x"}

    def initialize
        @grid = Array.new(BOARD_SIZE) {Array.new(BOARD_SIZE){:none}}
        put_initial_stones
    end

    def put_initial_stones
        i0 = BOARD_SIZE.div(2) - 1
        i1 = i0 + 1
        @grid[i0][i0] = :white
        @grid[i0][i1] = :black
        @grid[i1][i0] = :black
        @grid[i1][i1] = :white
    end

    def to_s
        result = "  "
        result << (1..BOARD_SIZE).to_a.join(" ")
        result << "\n"
        BOARD_SIZE.times do |i|
            result << "#{i+1} "
            result << BOARD_SIZE.times.map { |j|
                STONE_CHARS[@grid[i][j]]
            }.join(" ")
            result << "\n"
        end
        result
    end

    def put_stone(players_color, x, y)
        u = x - 1
        v = y - 1
        @grid[u][v] = players_color
        reverse(players_color, u, v)
    end

    def reverse(color, u, v)
        directions = [:nw, :n_, :ne, :e_, :se, :s_, :sw, :w_]   
        directions.each do |dir|
            reversible_positions(color, u, v, dir).each do |_u,_v|
                @grid[_u][_v] = color
            end
        end
    end

    def reversible_positions(players_color, u, v, direction)
        temp = []
        [].tap do |positions|
            generator(u, v, direction).each do |_u,_v|
                if @grid[_u][_v] == :none
                    break
                elsif @grid[_u][_v] != players_color
                    temp.push([_u, _v])
                elsif @grid[_u][_v] == players_color
                    positions.concat(temp)
                end
            end
            positions.compact!
        end
    end

    def generator(u, v, direction)
        fu = ->i{i}
        fv = ->i{i}
        inc = ->i{i+1}
        dec = ->i{i-1}

        case direction
        when :nw; fu, fv = dec, dec
        when :n_; fv = dec
        when :ne; fu, fv = inc, dec
        when :e_; fu = inc
        when :se; fu, fv = inc, inc
        when :s_; fv = inc
        when :sw; fu, fv = dec, inc
        when :w_; fu = dec
        end

        Enumerator.new do |yeel|
            while u.between?(0, BOARD_SIZE-1) and 
                  v.between?(0, BOARD_SIZE-1)

                u = fu.call(u)
                v = fv.call(v)
                yeel << [u,v]
            end
        end
    end
end

 石を並べてみる。

play.rb
board = OthelloBoard.new
puts board
board.put_stone(:black, 6, 5)
puts board
board.put_stone(:white, 4, 6)
puts board
board.put_stone(:black, 3, 7)
puts board
board.put_stone(:white, 4, 7)
puts board
board.put_stone(:black, 3, 3)
puts board
board.put_stone(:white, 6, 4)
puts board
board.put_stone(:black, 5, 7)
puts board
board.put_stone(:white, 2, 2)
puts board
board.put_stone(:black, 5, 6)
puts board
board.put_stone(:white, 6, 8)
puts board
board.put_stone(:black, 1, 1)
puts board

ちゃんとひっくり返るのか確かめる。

result.txt
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . o x . . .
5 . . . x o . . .
6 . . . . . . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . o x . . .
5 . . . x x . . .
6 . . . . x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . . .
4 . . . o o o . .
5 . . . x x . . .
6 . . . . x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . x .
4 . . . o o x . .
5 . . . x x . . .
6 . . . . x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . . . . . x .
4 . . . o o o o .
5 . . . x x . . .
6 . . . . x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . x . . . x .
4 . . . x o o o .
5 . . . x x . . .
6 . . . . x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . x . . . x .
4 . . . x o o o .
5 . . . x o . . .
6 . . . o x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . . . . . . . .
3 . . x . . . x .
4 . . . x o o x .
5 . . . x o . x .
6 . . . o x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . o . . . . . .
3 . . o . . . x .
4 . . . o o o x .
5 . . . x o . x .
6 . . . o x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . o . . . . . .
3 . . o . . . x .
4 . . . o o o x .
5 . . . x x x x .
6 . . . o x . . .
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 . . . . . . . .
2 . o . . . . . .
3 . . o . . . x .
4 . . . o o o x .
5 . . . x x x o .
6 . . . o x . . o
7 . . . . . . . .
8 . . . . . . . .
  1 2 3 4 5 6 7 8
1 x . . . . . . .
2 . x . . . . . .
3 . . x . . . x .
4 . . . x o o x .
5 . . . x x x o .
6 . . . o x . . o
7 . . . . . . . .
8 . . . . . . . .

 できた!

盤外ならもうダメ。裏返せないので空配列を返します。
空きセルだったらもうダメ。裏返せないので空配列を返します。
players_color と同じだったらもうダメ。空配列を返します。
反対の色だったら,座標値を候補に入れます。

 という @sclvola さんの簡潔な指摘で,ひっくり返す石の判定をするアルゴリズムを書くことができました。こんなにシンプルなのにどうして思いつかなかったんだろう。

 その他,学ばせていただいたこと
- Array を継承する意味を考えるとき,オセロ盤は配列ではないので,継承せずインスタンス変数に配列を入れる。
- to_s って便利。
- 意味のないクラスは使わない。

 大変参考になりました。ありがとうございます。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした