Files
basic-computer-games/04_Awari/ruby/awari.rb
2022-01-09 14:29:39 +00:00

312 lines
7.2 KiB
Ruby
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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