require 'strscan'

# Prints a number according to Vintage Basic's PRINT statement
# @param n The number to print
def print_number(n)
  # PRINT adds padding after a number and before a positive number
  print ' ' if n >= 0
  print n.to_s
  print ' '
end

# Mimic the INPUT statement using Vintage Basic as a reference
# @param prompt The prompt to show to the user
# @return An array of strings representing the inputted values
def input(prompt)
  prompt_suffix = '? '
  print "#{prompt}#{prompt_suffix}"

  input = gets.chomp.strip
  scanner = StringScanner.new(input)
  input_values = []

  until scanner.eos?
    scanner.scan(/\s+/)

    if scanner.check(/"/)
      scanner.scan(/"/)
      next_string = scanner.scan_until(/"/)

      if next_string
        # Remove the trailing close quote
        next_string.chomp!('"')
      else
        # No close quote – Vintage Basic crashes in this case
        raise 'Unmatched quotes in input'
      end
    elsif scanner.exist?(/,/)
      next_string = scanner.scan_until(/,/).chomp(',')
    else
      next_string = scanner.scan_until(/\s+|$/).rstrip
    end

    input_values << next_string
  end

  input_values << '' if input_values.empty?

  input_values
end

class Game
  def initialize(history, non_win_count)
    @beans = Array.new(13, 3)
    @beans[6] = 0
    @beans[13] = 0

    @turn_counter = 0

    @history = history
    @non_win_count = non_win_count
  end

  # @return [Boolean] True if the computer did not win the game
  def play
    while true
      print_beans

      move = get_move("YOUR MOVE")
      home_pit = 6
      computer_home_pit = 13

      last_pit = perform_move(move, home_pit)

      print_beans

      break if game_over

      if home_pit == last_pit
        second_move = get_move("AGAIN")

        perform_move(second_move, home_pit)

        print_beans

        break if game_over
      end

      computer_move, computer_last_pit = get_computer_move
      print "MY MOVE IS #{computer_move - 6}"

      break if game_over

      if computer_last_pit == computer_home_pit
        second_computer_move, _ = get_computer_move
        print ",#{second_computer_move - 6}"

        break if game_over
      end
    end

    end_game
  end

  private

  def game_over
    @beans[0...6].all? { |b| b == 0 } || @beans[7...13].all? { |b| b == 0 }
  end

  # @return [Boolean] True if the computer did not win
  def end_game
    puts
    puts "GAME OVER"

    difference = @beans[6] - @beans[13]

    if difference < 0
      puts "I WIN BY #{-difference} POINTS"

      return
    end

    puts "YOU WIN BY #{difference} POINTS" if difference > 0
    puts "DRAWN GAME" if difference == 0

    difference >= 0
  end

  # @param [Integer] move
  # @param [Integer] home_pit
  def perform_move(move, home_pit)
    last_pit = distribute_beans(move, home_pit)

    update_history(move)

    last_pit
  end

  def update_history(current_move)
    k = current_move % 7
    @turn_counter += 1

    # Add the move to the history
    @history[@non_win_count] = @history[@non_win_count] * 6 + k if @turn_counter < 9
  end

  def print_beans
    puts

    # Print computer beans
    print ' ' * 3
    @beans[7...13].reverse.each { |bean_count| print_bean(bean_count) }
    puts

    # Print home beans
    print_bean(@beans[13])
    print ' ' * 23
    print_number(@beans[6]) # This is not print_bean in line with the original version
    puts

    # Print player beans
    print ' ' * 3
    @beans[0...6].each { |bean_count| print_bean(bean_count) }
    puts

    puts
  end

  def get_move(prompt)
    move = get_integer_input(prompt)

    while move < 1 || move > 6 || @beans[move - 1] == 0
      puts "ILLEGAL MOVE"
      move = get_integer_input("AGAIN")
    end

    move - 1
  end

  def distribute_beans(start_pit, home_pit, beans = @beans)
    beans_to_distribute = beans[start_pit]
    beans[start_pit] = 0

    current_pit = start_pit

    (0...beans_to_distribute).each do
      current_pit = (current_pit + 1) % beans.size
      beans[current_pit] += 1
    end

    # If the last pit was empty before we put a bean in it (and it's not a scoring pit), add beans to score
    if beans[current_pit] == 1 && current_pit != 6 && current_pit != 13 && beans[12 - current_pit] != 0
      beans[home_pit] = beans[home_pit] + beans[12 - current_pit] + 1
      beans[current_pit] = 0
      beans[12 - current_pit] = 0
    end

    current_pit
  end

  def print_bean(bean_count)
    print ' ' if bean_count < 10
    print_number(bean_count)
  end

  def get_integer_input(prompt)
    integer_value = nil

    input_values = input(prompt)

    while integer_value.nil?
      print '!EXTRA INPUT IGNORED' if (input_values.size > 1)

      value = input_values.first

      begin
        integer_value = Integer(value)
      rescue
        puts '!NUMBER EXPECTED - RETRY INPUT LINE'
        input_values = input('')
      end
    end

    integer_value
  end

  def get_computer_move
    d = -99
    home_pit = 13

    chosen_move = 7

    # Test all possible moves
    (7...13).each do |move_under_test|
      # Create a copy of the beans to test against
      beans_copy = @beans.dup

      # If the move is not legal, skip it
      next if beans_copy[move_under_test] == 0

      # Determine the best response the player may make to this move
      player_max_score = 0

      # Make the move under test against the copy
      distribute_beans(move_under_test, home_pit, beans_copy)

      # Test every player response
      (0...6).each do |i|
        # Skip the move if it would be illegal
        next if beans_copy[i] == 0

        # Determine the last
        landing_with_overflow = beans_copy[i] + i
        # If landing > 13 the player has put a bean in both home pits
        player_move_score = (landing_with_overflow > 14) ? 1 : 0
        # Find the actual pit
        landing = landing_with_overflow % 14

        # If the landing pit is empty, the player will steal beans
        if beans_copy[landing] == 0 && landing != 6 && landing != 13
          player_move_score = beans_copy[12 - landing] + player_move_score
        end

        # Update the max score if this move is the best player move
        player_max_score = player_move_score if player_move_score > player_max_score
      end

      # Final score for move is computer score, minus the player's score and any player gains from their best move
      final_score = beans_copy[13] - beans_copy[6] - player_max_score

      if @turn_counter < 9
        k = move_under_test % 7

        (0...@non_win_count).each do |i|
          # Penalise move if it was used in a losing game
          final_score = final_score - 2 if @history[@non_win_count] * 6 + k == ((Float(@history[i]) / 6 ** (7 - @turn_counter)) + 0.1).floor
        end
      end

      # Choose the move if it is the best move found so far
      if final_score >= d
        chosen_move = move_under_test
        d = final_score
      end
    end

    last_pit = perform_move(chosen_move, home_pit)

    [chosen_move, last_pit]
  end
end

puts 'AWARI'.center(80)
puts 'CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY'.center(80)

# Initialise stable variables
history = Array.new(50)
non_win_count = 0

# APPLICATION LOOP
while true
  puts
  puts

  history[non_win_count] = 0

  game = Game.new(history, non_win_count)

  computer_didnt_win = game.play
  non_win_count += 1 if computer_didnt_win
end
