TicTacToe 重赢条件

TicTacToe Heavy Win Condition

我在 Ruby 做了一个井字棋游戏。我是 Ruby OOP 的新手。我检查行、列和对角线的获胜条件非常繁重,它们占用太多代码行,看起来非常糟糕。我的代码也总是重复。

所以我的问题是如何在我的代码中实现以下获胜条件?

 WIN_COMBINATIONS = [ 
[0,1,2], # top_row 
[3,4,5], # middle_row 
[6,7,8], # bottom_row 
[0,3,6], # left_column 
[1,4,7], # center_column 
[2,5,8], # right_column 
[0,4,8], # left_diagonal 
[6,4,2] # right_diagonal 
]

这是我的获胜条件:

module CheckWinner
  def check_horizontal(player1, player2)
    player1_win = false
    player2_win = false
    @board.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_vertical(player1, player2)
    player1_win = false
    player2_win = false
    @board.transpose.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_diagonal(player1, player2)
    if @board[0][0] == player1 && board[1][1] == player1 && board[2][2] == player1 ||
       @board[0][2] == player1 && board[1][1] == player1 && board[2][0] == player1
      puts "#{@player1} won!"
      true

    elsif @board[0][0] == player2 && board[1][1] == player2 && board[2][2] == player2 ||
          @board[0][2] == player2 && board[1][1] == player2 && board[2][0] == player2
      puts "#{@player2} won!"
      true
    end
  end
end

我从这行代码调用这些条件:

def game
    choosing_player
    loop do
      player1(@player1)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?

      player2(@player2)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?
    end
  end
end

这是我的完整项目,它可以正常工作。

module CheckWinner
  def check_horizontal(player1, player2)
    player1_win = false
    player2_win = false
    @board.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_vertical(player1, player2)
    player1_win = false
    player2_win = false
    @board.transpose.each do |row|
      player1_win = row.all?(player1)
      player2_win = row.all?(player2)
      break if player1_win || player2_win
    end
    puts "#{player1} won!" if player1_win
    puts "#{player2} won!" if player2_win
    player1_win || player2_win
  end

  def check_diagonal(player1, player2)
    if @board[0][0] == player1 && board[1][1] == player1 && board[2][2] == player1 ||
       @board[0][2] == player1 && board[1][1] == player1 && board[2][0] == player1
      puts "#{@player1} won!"
      true

    elsif @board[0][0] == player2 && board[1][1] == player2 && board[2][2] == player2 ||
          @board[0][2] == player2 && board[1][1] == player2 && board[2][0] == player2
      puts "#{@player2} won!"
      true
    end
  end
end

# TicTacToe Board
class Board
  include CheckWinner
  attr_accessor :board

  def initialize
    @board = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
  end

  def print_board
    puts '-------------'
    @board.each do |row|
      print '|'
      row.each do |col|
        print " #{col}"
        print ' | '
      end
      puts
    end
    puts '-------------'
  end

  def twodimentional_board
    @board = @board.each_slice(3).map { |el| el }
  end

  def occupied_error(value)
    puts 'There is a value or wrong place! Try Again!'
    twodimentional_board
    print_board
    value == @player1 ? player2(@player1) : player1(@player2) # Stay same player
  end

  def move_if_possible(place, value)
    @board.flatten!
    if @board[place - 1] == 'X' || @board[place - 1] == 'O' || !place.between?(1, 9)
      occupied_error(value)
    else
      @board[place - 1] = value
      twodimentional_board
      @board
    end
  end

  def full?
    if @board.flatten.all?(String)
      puts 'Draw!'
      true
    end
  end

  def choosing_player
    puts 'Choose for Player1(X or O)'
    loop do
      @player1 = gets.chomp!
      break if @player1 == 'X' || @player1 == 'O'

      puts 'Try Again!(X or O)'
    end
    puts "Player 1 is: #{@player1}"

    @player1 == 'X' ? @player2 = 'O' : @player2 = 'X'
    puts "Player 2 is: #{@player2}"
    print_board
  end

  def player1(player1)
    puts "Choice #{player1} Place on a board(1 to 10)"
    @place = gets.chomp!.to_i
    move_if_possible(@place, player1)
    print_board
  end

  def player2(player2)
    puts "Choice #{player2} Place on a board(1 to 10)"
    @place = gets.chomp!.to_i
    move_if_possible(@place, player2)
    print_board
  end

  def game
    choosing_player
    loop do
      player1(@player1)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?

      player2(@player2)
      break if check_vertical(@player1,@player2) == true || check_diagonal(@player1,@player2) == true || check_horizontal(@player1,@player2) == true || full?
    end
  end
end

board = Board.new
board.game

如果要实时预览这里是repllink

我不确定这是否正是您想要的,但这里有一些东西。

  1. 您的 @board 是一个 3 行的数组,但您可能获胜的只是一个点数数组。 flatten使用@board 意味着您可以按原样使用索引。
  2. 你已经有了一组获胜条件,然后忽略这个数组并手动完成所有选项

定义

# example winning board for 'O'
@board = [['O','X','O'], ['X','O','X'], ['X','O','O']]
player1 = 'X'
player2 = 'O'

首先,遍历 WIN_COMBINATIONS 中的可能组合,对于每个组合,从 @board 中获取这些单元格作为单独的数组,以供稍后检查。将数组数组存储为另一个数组(使用 #map)。 * 字符是 ruby splat 运算符,它将数组转换为被调用过程的参数。

shorthand:

winmap = WIN_COMBINATIONS.map {|w| @board.flatten.values_at(*w)}

手写:

flat_board = @board.flatten
winmap = WIN_COMBINATIONS.map do |win|
  flat_board.values_at(*win)
end

设置如下数组:

winmap = [ 
  ["O", "X", "O"], # top_row 
  ["X", "O", "X"], # middle_row 
  ["X", "O", "O"], # bottom_row 
  ["O", "X", "X"], # left_column 
  ["X", "O", "O"], # center_column 
  ["O", "X", "O"], # right_column 
  ["O", "O", "O"], # left_diagonal 
  ["X", "O", "O"] # right_diagonal 
]

一旦你有了棋盘上所有可能获胜的单元格的列表,你就可以使用你现有的 row 搜索代码,除了循环遍历每个数组,我们使用数组操作 #any?识别匹配项

player1_win = winmap.any?{|m| m.all?(player1)}
player2_win = winmap.any?{|m| m.all?(player2)}

这应该消除所有单独的子程序,并给你一个 true/false 答案,看看是否有人赢了。

这里有一种不同的方法来确定棋盘是否表明获胜,如果是,谁获胜。

board为三个元素的数组,每个元素为三个元素的数组,每个元素为'X''O''_',最后一个表示单元格尚未标记。

让我们创建一些方法。

def row_win?(board)
  board.each { |row| return row.first if row_uniq.size == 1 }
  nil
end

这个方法returns 'X'如果'X'连胜,'O'如果'O'连胜,nil如果没有玩家连续获胜。

def column_win?(board)
  row_win?(board.transpose)
end

此方法类似returns'X''O'nil,表示某列是否有赢,如果有,谁赢

def diagonal_win?(board)
  row_win? [[board[0][0], board[1][1], board[2][2]],
            [board[0][2], board[1][1], board[2][0]]]
end

此方法对两条对角线执行相同的操作。

最后,

def win?(board)
  row_win?(board) || column_win?(board) || diagonal_win?(board)
end

nil无胜负返回

我们来试试吧。

win? [['X', '_', 'X'],
      ['_', 'X', 'O'],
      ['X', 'O', 'O']]
  #=> 'X'
win? [['X', 'O', 'X'],
      ['_', 'O', 'X'],
      ['X', 'O', 'O']]
  #=> 'O'
win? [['X', 'O', 'X'],
      ['O', 'O', 'X'],
      ['X', 'X', 'O']]
  #=> nil          
 

为什么不跟踪状态来帮助您在标记新动作时确定获胜条件?这样你就不必重新计算你已经知道的所有内容。

例如,如果我告诉你我的棋盘尺寸是 3,第一行已经有 2 X,现在 X 在导致 3 Xs 的那一行下了一个新的棋子,你知道 X 马上就赢了吗?您甚至不需要了解有关董事会状态的任何其他信息。

您可以在此处应用相同的逻辑,只需跟踪每行的 X & O 计数,cols & diags 作为标记记录下来,您甚至不必循环查看谁赢了。

above-described 方法易于实施,并且需要 O(1) 时间复杂度来检查获胜条件。您用一点内存 (O(n)) 来换取计算速度。