Skip to content

Instantly share code, notes, and snippets.

@schrockwell
Created August 15, 2012 13:07
Show Gist options
  • Save schrockwell/3359964 to your computer and use it in GitHub Desktop.
Save schrockwell/3359964 to your computer and use it in GitHub Desktop.
Yahtzee CLI v0.0.2
#!/usr/bin/env ruby
# encoding: utf-8
# Yahtzee CLI
# Version 0.0.2
#
# By Rockwell Schrock
# [email protected]
# https://gist.github.com/gists/3359964
#
# Run it and have fun!
@dice = [6] * 5;
@scores = [{}]
@player_count = 1
@current_player = 0
TopCategories = [:ones, :twos, :threes, :fours, :fives, :sixes]
BottomCategories = [:three_of_a_kind, :four_of_a_kind, :full_house, :small_straight, :large_straight, :yahtzee, :chance]
Categories = TopCategories + [:bonus, :top_total] + BottomCategories + [:bottom_total]
Titles = ['Ones', 'Twos', 'Threes', 'Fours', 'Fives', 'Sixes', 'Bonus', 'Top Total', 'Three of a Kind', 'Four of a Kind', 'Full House', 'Small Straight', 'Large Straight', 'Yahtzee', 'Chance', 'Bottom Total']
Letters = ['a', 'b', 'c', 'd', 'e', 'f', nil, nil, 'g', 'h', 'i', 'j', 'k', 'l', 'm', nil]
TitleColumnWidth = 24
ScoreColumnWidth = 12
class Array
def histogram
self.inject(Hash.new(0)) { |total, e| total[e] += 1; total }
end
def sum
self.inject(0) { |sum, i| sum += i; sum }
end
def max_index
self.each_with_index.max[1]
end
end
def draw_board(dice, scores, current_player, roll, show_total)
puts " ---- PLAYER #{current_player + 1} -----" if current_player
puts " ---- ROLL #{roll} -----" if roll
puts
puts ' ' * (3 + TitleColumnWidth) + (1..@player_count).to_a.inject('') { |s, p| s += "P#{p}" + ' ' * (ScoreColumnWidth - 2); s }
Categories.each_with_index do |c, i|
# Print letter
if Letters[i]
line = "#{Letters[i]}. "
else
line = ' '
end
# Print title
line += Titles[i]
line += ' ' * (TitleColumnWidth - Titles[i].length)
scores.each_with_index do |player_scores, player_i|
# Print score
if player_scores[c]
line += player_scores[c].to_s
elsif can_score?(c, player_scores) && player_i == @current_player
line += "___ (#{score(dice, c, player_scores)})"
else
line += "___"
end
line += ' ' * ((TitleColumnWidth + 3 + (player_i + 1) * 12) - line.length)
end
puts line
puts if c == :top_total || c == :bottom_total
end
if show_total
line = ' Final Score' + ' ' * (TitleColumnWidth - 11)
total_scores(@scores).each do |total|
line << total.to_s + ' ' * (ScoreColumnWidth - total.to_s.length)
end
puts line
end
end
def total_scores(scores)
scores.map { |s| (s[:top_total] || 0) + (s[:bottom_total] || 0) }
end
def can_score?(category, scores)
return false if [:bonus, :top_total, :bottom_total].index(category)
if category == :yahtzee
return scores[:yahtzee] == nil || scores[:yahtzee] == 50
else
return scores[category] == nil
end
end
def score(dice, category, scores)
case category
when :ones, :twos, :threes, :fours, :fives, :sixes
i = Categories.index(category) + 1
return dice.count(i) * i
when :bonus
if top_total(scores) >= 63
return 35
else
return 0
end
when :three_of_a_kind
(1..6).each do |i|
return dice.sum if dice.count(i) >= 3
end
return 0
when :four_of_a_kind
(1..6).each do |i|
return dice.sum if dice.count(i) >= 4
end
return 0
when :full_house
return 25 if dice.histogram.values.sort == [2, 3]
return 0
when :small_straight
(1..3).each do |start|
return 30 if (dice & (start..(start + 3)).to_a).length == 4
end
return 0
when :large_straight
(1..2).each do |start|
return 40 if (dice & (start..(start + 4)).to_a).length == 5
end
return 0
when :yahtzee
(1..6).each do |i|
if dice.count(i) == 5
return 50 if scores[:yahtzee] == nil
return 150 if scores[:yahtzee] == 50
end
end
return 0
when :top_total
return top_total(scores)
when :bottom_total
return bottom_total(scores)
when :chance
return dice.sum
end
end
def done?(scores)
(scores.keys & Categories).length == Categories.length
end
def update_scores(scores)
if (scores.keys & TopCategories).length == TopCategories.length
scores[:bonus] = score(nil, :bonus, scores)
scores[:top_total] = score(nil, :top_total, scores)
end
if (scores.keys & BottomCategories).length == BottomCategories.length
scores[:bottom_total] = score(nil, :bottom_total, scores)
end
scores
end
def draw_dice(dice)
lines = [''] * 5
dice.each do |i|
lines[0] += ' _____ '
lines[4] += ' ----- '
case i
when 1
lines[1] += '| | '
lines[2] += '| • | '
lines[3] += '| | '
when 2
lines[1] += '| • | '
lines[2] += '| | '
lines[3] += '| • | '
when 3
lines[1] += '| • | '
lines[2] += '| • | '
lines[3] += '| • | '
when 4
lines[1] += '| • • | '
lines[2] += '| | '
lines[3] += '| • • | '
when 5
lines[1] += '| • • | '
lines[2] += '| • | '
lines[3] += '| • • | '
when 6
lines[1] += '| • • | '
lines[2] += '| • • | '
lines[3] += '| • • | '
end
end
lines.each { |line| puts line }
end
def top_total(scores)
(TopCategories + [:bonus]).inject(0) { |sum, c| sum += scores[c] || 0 ; sum }
end
def bottom_total(scores)
BottomCategories.inject(0) { |sum, c| sum += scores[c] || 0 ; sum }
end
def roll_dice(dice, keepers)
new_dice = [];
keepers.each do |k|
i = dice.index(k)
if i
new_dice << k
dice.delete_at(i)
end
end
(new_dice.count..4).each do
new_dice << rand(6) + 1
end
new_dice
end
MaxRolls = 3
MaxDice = 5
def valid_input?(input, scores, dice, roll)
if input.length == 1 and input.match(/[a-mA-M]/)
# Scoring input
category_index = Letters.index(input.downcase)
category = Categories[category_index]
if can_score?(category, scores)
return true
else
puts "Already scored a #{Titles[category_index]}"
return false
end
elsif input.length <= MaxDice and roll < MaxRolls
# Keeper input
matches = input.match(/[1-6]*/)
if matches == nil or matches[0] != input
puts 'Invalid input'
return false
end
return true
else
puts 'You must select a scoring category' if roll == MaxRolls
puts 'Invalid input: too many dice' if input.length > MaxDice
return false
end
end
def play
puts 'Number of players?'
@player_count = gets.to_i
@scores = []
(1..@player_count).each do
@scores << {}
end
while true do
keepers = []
(1..3).each do |roll|
@dice = roll_dice(@dice, keepers)
draw_board(@dice, @scores, @current_player, roll, false)
draw_dice(@dice)
while true do
if roll == 3
puts 'Enter a letter to score'
input_string = gets.chomp
else
puts 'Enter keeper dice or a letter to score'
input_string = gets.chomp
end
break if valid_input?(input_string, @scores[@current_player], @dice, roll)
end # input loop
if Letters.index(input_string.downcase)
c = Categories[Letters.index(input_string.downcase)]
@scores[@current_player][c] = score(@dice, c, @scores[@current_player])
@scores[@current_player] = update_scores(@scores[@current_player])
break
else
keepers = []
input_string.each_char do |c|
keepers << c.to_i
end
end
end # roll loop
game_over = true
(1..@player_count).each do
@current_player = (@current_player + 1) % @player_count
if not done?(@scores[@current_player])
game_over = false
break
end
end
break if game_over
end # turn loop
draw_board(@dice, @scores, nil, nil, true)
totals = total_scores(@scores)
winner = totals.max_index
puts
puts "PLAYER #{winner + 1} " + ['DOMINATED!!!', 'KICKED ASS!!!', 'PWNED!!!', 'NAILED IT!!!', 'GOT A HEADSHOT!!!', 'IS KING!!!', 'GETS AN UPVOTE!!!'].sample
end
play
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment