mirror of
https://github.com/coding-horror/basic-computer-games.git
synced 2026-01-09 03:43:01 -08:00
@@ -1,3 +1,3 @@
|
||||
Original source downloaded [from Vintage Basic](http://www.vintage-basic.net/games.html)
|
||||
|
||||
Conversion to [Ruby](https://www.ruby-lang.org/en/)
|
||||
Conversion to [Ruby](https://www.ruby-lang.org/en/) by [Alex Scown](https://github.com/TheScown)
|
||||
|
||||
311
04_Awari/ruby/awari.rb
Normal file
311
04_Awari/ruby/awari.rb
Normal file
@@ -0,0 +1,311 @@
|
||||
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
|
||||
Reference in New Issue
Block a user