一些琐碎的代码片段,没有固定的主题。

部分代码有问题,注意检查一下。

算 24

def calc24(nums, expr = nums)
  if nums.length == 1
    nums[0] == 24 ? expr[0] : false
  else
    nums.length.times do |i|
      nums.length.times do |j|
        next if i == j
        new_nums = nums.each_with_index.reject { |_, idx| idx == i || idx == j }.map(&:first)
        new_expr = expr.each_with_index.reject { |_, idx| idx == i || idx == j }.map(&:first)
        ['+', '-', '*', '/'].each do |op|
          next if op == '/' && nums[j] == 0
          next if (op == '+' || op == '*') && i > j
          new_nums.push nums[i].to_r.send(op, nums[j])
          a = expr[i].is_a?(Integer) ? expr[i].to_s : "(#{expr[i]})"
          b = expr[j].is_a?(Integer) ? expr[j].to_s : "(#{expr[j]})"
          new_expr.push "#{a}#{op}#{b}"
          result = calc24(new_nums, new_expr)
          return result if result
          new_nums.pop
          new_expr.pop
        end
      end
    end
    false
  end
end

p calc24([6, 6, 8, 8]) # "(6*8)/(8-6)"
p calc24([1, 2, 3, 4]) # "4*(3+(1+2))"
p calc24([1, 5, 5, 5]) # "5*(5-(1/5))"
p calc24([3, 3, 8, 8]) # "8/(3-(8/3))"
p calc24([1, 3, 4, 6]) # "6/(1-(3/4))"

数独

def is_valid(board, row, col, num)
    # 检查行
    (0..8).each do |i|
      return false if board[row][i] == num
    end
    # 检查列
    (0..8).each do |i|
      return false if board[i][col] == num
    end
    # 检查3x3子网格
    start_row = (row / 3) * 3
    start_col = (col / 3) * 3
    (0..2).each do |i|
      (0..2).each do |j|
        return false if board[start_row + i][start_col + j] == num
      end
    end
    true
  end
  
  def solve_sudoku(board)
    (0..8).each do |row|
      (0..8).each do |col|
        if board[row][col] == 0
          (1..9).each do |num|
            if is_valid(board, row, col, num)
              board[row][col] = num
              if solve_sudoku(board)
                return true
              else
                board[row][col] = 0
              end
            end
          end
          return false
        end
      end
    end
    true
  end
  
  # 示例数独棋盘,0表示空白单元格
  sudoku_board = [
    [5, 3, 0, 0, 7, 0, 0, 0, 0],
    [6, 0, 0, 1, 9, 5, 0, 0, 0],
    [0, 9, 8, 0, 0, 0, 0, 6, 0],
    [8, 0, 0, 0, 6, 0, 0, 0, 3],
    [4, 0, 0, 8, 0, 3, 0, 0, 1],
    [7, 0, 0, 0, 2, 0, 0, 0, 6],
    [0, 6, 0, 0, 0, 0, 2, 8, 0],
    [0, 0, 0, 4, 1, 9, 0, 0, 5],
    [0, 0, 0, 0, 8, 0, 0, 7, 9]
  ]
  
  if solve_sudoku(sudoku_board)
    sudoku_board.each do |row|
      puts row.join(' ')
    end
  else
    puts "该数独无解"
  end

黑白棋

# 初始化棋盘
def init_board
  board = Array.new(8) { Array.new(8, 0) }
  board[3][3] = 1
  board[3][4] = -1
  board[4][3] = -1
  board[4][4] = 1
  board
end

# 打印棋盘,标记可用移动的编号
def print_board(board, moves)
  puts "="*18
  puts "| X: #{board.flatten.select(&:positive?).size}  O: #{board.flatten.select(&:negative?).size}"
  puts "+"+("-"*17)+"+"
  move_index = 1
  8.times do |i|
    row = "|"
    8.times do |j|
      if moves.include?([i, j])
        row += "%2d"%move_index
        
        move_index += 1
      else
        case board[i][j]
        when 1
          row += ' X'
        when -1
          row += ' O'
        else
          row += '  '
        end
      end
    end
    row += ' |'
    puts row
  end
  puts "+"+("-"*17)+"+"
end

# 判断是否在棋盘内
def is_on_board(x, y)
  x >= 0 && x < 8 && y >= 0 && y < 8
end

# 获取有效落子位置
def get_valid_moves(board, player)
  moves = []
  8.times do |x|
    8.times do |y|
      if board[x][y] == 0 && is_valid_move(board, player, x, y)
        moves << [x, y]
      end
    end
  end
  moves
end

# 判断落子是否有效
def is_valid_move(board, player, x, y)
  return false if board[x][y] != 0
  opponent = -player
  directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
  directions.each do |dx, dy|
    new_x, new_y = x + dx, y + dy
    if is_on_board(new_x, new_y) && board[new_x][new_y] == opponent
      new_x += dx
      new_y += dy
      while is_on_board(new_x, new_y) && board[new_x][new_y] != 0
        if board[new_x][new_y] == player
          return true
        end
        new_x += dx
        new_y += dy
      end
    end
  end
  false
end

# 执行落子
def make_move(board, player, x, y)
  board[x][y] = player
  opponent = -player
  directions = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1]]
  directions.each do |dx, dy|
    # p [dx, dy]
    new_x, new_y = x + dx, y + dy
    # tiles_to_flip = []
    if is_on_board(new_x, new_y) && board[new_x][new_y] == opponent
      tiles_to_flip = [[new_x, new_y]]
      new_x += dx
      new_y += dy
      while is_on_board(new_x, new_y) && board[new_x][new_y] != 0
        #p [:a,tiles_to_flip,[x,y],[new_x,new_y],player]
        if board[new_x][new_y] == player
          #p x: tiles_to_flip # , board:board
          tiles_to_flip.each do |tx, ty|
            #p tx:tx,ty:ty
            board[tx][ty] = player
          end
          break
        end
        tiles_to_flip << [new_x, new_y]
        new_x += dx
        new_y += dy
      end
    end
  end
end

# 位置权重矩阵
WEIGHT_MATRIX = [
  [120, -20, 20, 5, 5, 20, -20, 120],
  [-20, -40, -5, -5, -5, -5, -40, -20],
  [20, -5, 15, 3, 3, 15, -5, 20],
  [5, -5, 3, 3, 3, 3, -5, 5],
  [5, -5, 3, 3, 3, 3, -5, 5],
  [20, -5, 15, 3, 3, 15, -5, 20],
  [-20, -40, -5, -5, -5, -5, -40, -20],
  [120, -20, 20, 5, 5, 20, -20, 120]
]

# 计算得分,加上位置权重
def calculate_score(board)
  score = 0
  8.times do |i|
    8.times do |j|
      cell = board[i][j]
      score += cell * WEIGHT_MATRIX[i][j]
    end
  end
  score
end

# Minimax 算法 with Alpha-Beta 剪枝
def minimax(board, depth, alpha, beta, maximizing_player)
  current_player = maximizing_player ? 1 : -1
  if depth == 0 || get_valid_moves(board, 1).empty? && get_valid_moves(board, -1).empty?
    return current_player * calculate_score(board)
  end
  moves = get_valid_moves(board, current_player)
  if moves.empty?
    return -minimax(board, depth, -beta, -alpha, !maximizing_player)
  end
  best_eval = -Float::INFINITY
  moves.each do |move|
    new_board = board.map(&:dup)
    make_move(new_board, current_player, move[0], move[1])
    eval_score = -minimax(new_board, depth - 1, -beta, -alpha, !maximizing_player)
    best_eval = [best_eval, eval_score].max
    alpha = [alpha, eval_score].max
    break if beta <= alpha
  end
  return best_eval
end

# 获取最佳移动
def get_best_move(board, depth)
  best_score = -Float::INFINITY
  best_move = nil
  get_valid_moves(board, 1).shuffle.each do |move|
    new_board = board.map(&:dup)
    make_move(new_board, 1, move[0], move[1])
    score = -minimax(new_board, depth, -Float::INFINITY, Float::INFINITY, false)
    # puts "位置 #{move} 打分 #{score}"
    if score > best_score
      best_score = score
      best_move = move
    end
  end
  best_move
end

# 游戏主循环
def play_game
  board = init_board
  loop do
    player_moves = get_valid_moves(board, -1)
     print_board(board, player_moves)
    if player_moves.empty?
      puts "玩家无可用移动,电脑继续。"
    else
      loop do
        puts "请输入你的移动编号 (1 - #{player_moves.size}): "
        # input = gets.chomp.to_i
        input = rand(1..player_moves.size)
        puts "> #{input}"
        if (1..player_moves.size).include?(input)
          x, y = player_moves[input-1]
          make_move(board, -1, x, y)
          break
        else
          puts "无效编号,请重新输入。"
        end
      end
    end
    computer_moves = get_valid_moves(board, 1)
    print_board(board, computer_moves)
    if computer_moves.empty?
      puts "电脑无可用移动,游戏结束。"
      break
    end
    computer_move = get_best_move(board, 3+1-1 )
    make_move(board, 1, computer_move[0], computer_move[1])
    puts "电脑移动\n> #{computer_moves.index(computer_move)+1}"
  end
end

play_game if __FILE__ == $PROGRAM_NAME

八皇后

# 八皇后问题 - 递归回溯解法(找到一个解即停止)
# queen_pos[i] = j 表示第 i 行的皇后放在第 j 列

def solve_queen(row, queen_pos)
  n = queen_pos.length
  if row == n
    print_board(queen_pos)
    return true
  end
  n.times do |col|
    if is_valid(row, col, queen_pos)
      queen_pos[row] = col
      return true if solve_queen(row + 1, queen_pos)
      queen_pos[row] = -1
    end
  end
  false
end

def is_valid(row, col, queen_pos)
  row.times do |i|
    return false if queen_pos[i] == col || (row - i).abs == (col - queen_pos[i]).abs
  end
  true
end

def print_board(solution)
  solution.each do |col|
    puts (['.'] * solution.length).tap { |r| r[col] = 'Q' }.join(' ')
  end
end

queen_positions = [-1] * 8
solve_queen(0, queen_positions)

汉诺塔

def hanoi(n, from, to, aux)
  if n == 1
    puts "移动盘子 1 从 #{from}#{to}"
  else
    hanoi(n - 1, from, aux, to)
    puts "移动盘子 #{n}#{from}#{to}"
    hanoi(n - 1, aux, to, from)
  end
end

# 示例:3个盘子,从 A 柱移动到 C 柱,B 柱作为辅助
hanoi(3, 'A', 'C', 'B')

递归思路:先将 n-1 个盘子从 from 移到 aux,然后将第 n 个盘子从 from 移到 to,最后将 n-1 个盘子从 aux 移到 to

走迷宫

# 字符串迷宫 + BFS + parent 回溯路径
maze = <<~MAZE.split("\n")
#########
#S..#...#
###.#.#.#
#.....#.#
#.#####.#
#......E#
#########
MAZE

dirs = [[-1,0],[1,0],[0,-1],[0,1]]
start = nil
goal = nil

# 找到起点 S 和终点 E
maze.each_with_index { |r,i| r.chars.each_with_index { |c,j| start = [i,j] if c == ?S; goal = [i,j] if c == ?E } }

# BFS 核心(用 parent 记录前驱)
queue = [start]
parent = {}
visited = [start]

while !queue.empty?
  x, y = queue.shift
  break if [x, y] == goal

  dirs.each do |dx, dy|
    nx, ny = x + dx, y + dy
    if nx.between?(0, maze.size-1) && ny.between?(0, maze[0].size-1) && maze[nx][ny] != ?# && !visited.include?([nx, ny])
      visited << [nx, ny]
      parent[[nx, ny]] = [x, y]
      queue << [nx, ny]
    end
  end
end

# 从 E 回溯到 S 生成路径
path = []
curr = goal
while curr
  path << curr
  curr = parent[curr]
end
path.reverse!

# 输出
puts "完整路径坐标:"
p path

# 画迷宫
path.each { |x,y| maze[x][y] = ?* unless maze[x][y] == ?S || maze[x][y] == ?E }
puts "\n迷宫路径:"
puts maze.map { |l| l.gsub('#', '█') }

推箱子

参考

  • Ruby Quiz https://rubyquiz.com/
  • Rosetta Code https://rosettacode.org/wiki/Category:Ruby
  • LeetCode https://leetcode.com/

注意