Skip to content

Instantly share code, notes, and snippets.

@fidelisrafael
Last active September 15, 2017 08:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fidelisrafael/c953c60471e05e022b08f48f0a936b82 to your computer and use it in GitHub Desktop.
Save fidelisrafael/c953c60471e05e022b08f48f0a936b82 to your computer and use it in GitHub Desktop.
A very simple and multi language "Rock-Paper-Scissor" (and "Rock-Paper-Scissor-Spock-Lizard") game written in plain Ruby only for fun ;) https://www.wikiwand.com/en/Rock%E2%80%93paper%E2%80%93scissors
require 'ostruct'
require 'irb'
# Some monkey patch to add colorized output in console
class String
COLORS_CODE = {
red: 31,
green: 32,
yellow: 33,
blue: 34,
pink: 35,
light_blue: 36
}.freeze
# colorization
def colorize(color_code)
"\e[#{COLORS_CODE[color_code.to_sym]}m#{self}\e[0m"
end
def underline
"\e[4m#{self}\e[0m"
end
def bold
"\e[1m#{self}\e[0m"
end
end
module RockPaperScissor
class Game
# Localized messages
I18n_TRANSLATIONS = {
weapons: {
:'pt-BR' => {
rock: 'Pedra',
paper: 'Papel',
scissor: 'Tesoura',
lizard: 'Lagarto',
spock: 'Spock'
},
:en => {
rock: 'Rock',
paper: 'Paper',
scissor: 'Scissor',
lizard: 'Lizard',
spock: 'Spock'
}
},
weapons_actions: {
:'pt-BR' => {
rock: 'amassa',
paper: {
_default: 'embrulha',
spock: 'refuta'
},
scissor: {
_default: 'corta',
lizard: 'decapita'
},
lizard: {
_default: 'come',
spock: 'envenena',
},
spock: {
_default: 'amassa',
rock: 'vaporiza'
}
},
:en => {
rock: 'crushes',
paper: {
_default: 'covers',
spock: 'disproves'
},
scissor: {
_default: 'cuts',
lizard: 'decapitates'
},
lizard: {
_default: 'eats',
spock: 'poisons',
},
spock: {
_default: 'smashes',
rock: 'vaporizes'
}
}
},
messages: {
:'pt-BR' => {
init: 'Olá! Digite um número da lista abaixo para selecionar sua arma:',
choose_again: 'Você deve digitar um número válido para sua arma. Tente novamente',
you_choosed: 'Você selecionou: "%{weapon}"',
machine_choosed: 'O bot selecionou: "%{weapon}"',
withdraw: 'Ih rapaz, deu empate! Jogue novamente!',
player_wins: 'Boa! Você ganhou!',
machine_wins: 'Puts, deu ruim! O Bot ganhou de você'
},
:en => {
init: 'Hello there, enter a number to choose your weapon from the list below:',
choose_again: 'You must choose a valid weapon number. Try again.',
you_choosed: 'You choosed: "%{weapon}"',
machine_choosed: 'The bot choosed: "%{weapon}"',
withdraw: 'Withdraw with bot, play again!',
player_wins: 'Nice! You win',
machine_wins: "Bad news...you've lost."
}
}
}
DEFAULT_LANG = :'pt-BR'
# If user run as: `ruby rock.rb pt-BR` it can change the language
CURRENT_LANG = (ARGV.shift || DEFAULT_LANG).to_sym
# All weapons allowed to be performed
# Each weapon has a identifier, in this case is a simple integer which
# is used ONLY to identify that weapon (user is prompted to chose from 1 to 3)
# This integer do not implies "higher value/priority" or something like this
WEAPONS = {
rock: 1,
paper: 2,
scissor: 3,
lizard: 4,
spock: 5
}.freeze
# Just a more syntax sugar access to WEAPONS values, as: `WEAPONS_STRUCT.rock` for example
WEAPONS_STRUCT = OpenStruct.new(WEAPONS).freeze
# Maps each weapon and the others weapons that it beats, for example if "rock" beats
# "scissor" it must be declared as:
# WEAPONS_MAP[:rock] = [WEAPONS_STRUCT.scissor] # WEAPONS_MAP[:rock] = [3]
# This hash is basically this image represented as object:
# https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Rock_Paper_Scissors_Lizard_Spock_en.svg/640px-Rock_Paper_Scissors_Lizard_Spock_en.svg.png
WEAPONS_MAP = {
# Rock crushes ["scissor" and "lizard"]
rock: [WEAPONS_STRUCT.scissor, WEAPONS_STRUCT.lizard],
# Paper covers [ "rock" ] and disproves ["spock"]
paper: [WEAPONS_STRUCT.rock, WEAPONS_STRUCT.spock],
# Scissor cuts [ "paper" ] and decapitates [ "lizard" ]
scissor: [WEAPONS_STRUCT.paper, WEAPONS_STRUCT.lizard],
# Lizard can poison ["spock"] and eats ["paper"]
lizard: [WEAPONS_STRUCT.spock, WEAPONS_STRUCT.paper],
# Spock can smashes ["scissor"] and vaporizes ["rock"],
spock: [WEAPONS_STRUCT.scissor, WEAPONS_STRUCT.rock]
}.freeze
# Check if given `first_weapon` beats `last_weapon`
def weapon_win?(first_weapon, last_weapon)
weapons = WEAPONS_MAP[first_weapon.to_sym]
weapon = WEAPONS[last_weapon.to_sym]
weapons.member?(weapon)
end
# Check if given weapons are the same, so it's a withdraw
def weapon_withdraw?(first_weapon, last_weapon)
return first_weapon.to_s == last_weapon.to_s
end
# Translate a given `weapon` name in given language, ex:
# => translate_weapon(:rock, 'pt-BR') # "Pedra"
def translate_weapon(weapon, language)
I18n_TRANSLATIONS[:weapons][language.to_sym][weapon.to_sym]
end
# Translate a given `weapon` name in given language, ex:
# => translate_weapon(:rock, 'pt-BR') # "Pedra"
def translate_weapon_action(weapon, target_weapon, language)
translations = I18n_TRANSLATIONS[:weapons_actions][language.to_sym][weapon.to_sym]
return translations if translations.is_a?(String)
return translations[target_weapon.to_sym] || translations[:_default] if translations.is_a?(Hash)
return nil
end
def weapon_choose_label(index, weapon, language)
"[#{index}] #{translate_weapon(weapon, language)}"
end
def format_init_message(language)
WEAPONS.keys.each_with_index.map {|weapon, index| weapon_choose_label(index.next, weapon, language) }
end
def display_init_message(language)
puts # new line
puts i18n_message(:init, language).colorize(:blue)
puts format_init_message(language)
end
# simple helper to translate a given `message` key in given `language`, eg:
# => i18n_message(:player_wins, :en) # 'Nice! You win',
def i18n_message(message, language)
I18n_TRANSLATIONS[:messages][language.to_sym][message.to_sym]
end
def format_i18n_message(message, language, replace)
i18n_message(message, language) % replace
end
# Find the weapon associated with given `weapon_value`(Integer), ex:
# weapon_from_number(3) # { weapon: :rock, identifier: 3 }
# weapon_from_number(4) # { weapon: nil, identifier: nil }
def weapon_from_number(weapon_value)
weapon = WEAPONS.find {|_, value| value.to_i == weapon_value.to_i } || []
{ weapon: weapon.first, identifier: weapon.last }
end
def select_random_weapon
weapon_from_number(WEAPONS.values.sample)
end
# Creates a loop and only exists when user select a valid option
def select_weapon_loop(language)
weapon = nil
loop do
display_init_message(language)
weapon = weapon_from_number(gets.chomp)
break if weapon && weapon[:weapon]
puts i18n_message(:choose_again, language)
end
puts
return weapon
end
def display_choosed_weapons(translated_player_weapon, translated_machine_weapon, language)
puts format_i18n_message(:you_choosed, language, weapon: translated_player_weapon)
puts format_i18n_message(:machine_choosed, language, weapon: translated_machine_weapon)
end
def finish_game_message(winner_weapon, lose_weapon, language)
i18n_weapon_action = translate_weapon_action(winner_weapon[:weapon], lose_weapon[:weapon], language)
winner_name = translate_weapon(winner_weapon[:weapon], language)
lose_name = translate_weapon(lose_weapon[:weapon], language)
puts "[#{winner_name.bold}] **#{i18n_weapon_action.underline.bold}** [#{lose_name.bold}]"
end
def compare_weapons(player_weapon, machine_weapon)
return nil if weapon_withdraw?(player_weapon, machine_weapon)
return true if weapon_win?(player_weapon, machine_weapon)
return false
end
# Main entry point
def play!
# Obtain user weapon from input(keyboard)
player_weapon = select_weapon_loop(CURRENT_LANG)
# Choose a random weapon for bot
machine_weapon = select_random_weapon
# Translate the selected weapons for user and machine
translated_player_weapon = translate_weapon(player_weapon[:weapon], CURRENT_LANG)
translated_machine_weapon = translate_weapon(machine_weapon[:weapon], CURRENT_LANG)
# Display feedback information for user
display_choosed_weapons(
translated_player_weapon.colorize(:green).underline,
translated_machine_weapon.colorize(:red).underline,
CURRENT_LANG
)
puts # new line
case compare_weapons(player_weapon[:weapon], machine_weapon[:weapon])
when nil # Withdraw
puts i18n_message(:withdraw, CURRENT_LANG).colorize(:yellow).underline.bold
play! # Start it over again
when true # User wins
puts i18n_message(:player_wins, CURRENT_LANG).colorize(:green).underline.bold
finish_game_message(player_weapon, machine_weapon, CURRENT_LANG)
when false # User loses
puts i18n_message(:machine_wins, CURRENT_LANG).colorize(:red).underline.bold
finish_game_message(machine_weapon, player_weapon, CURRENT_LANG)
end
end
end
end
if $PROGRAM_NAME == __FILE__
game = RockPaperScissor::Game.new
game.play!
end
require_relative 'game'
require 'minitest/autorun'
class GameTests < Minitest::Test
def setup
@game = RockPaperScissor::Game.new
end
def test_weapon_win_rock
# Weapons that "Rock" dont beats
assert_equal false, @game.weapon_win?(:rock, :rock)
assert_equal false, @game.weapon_win?(:rock, :paper)
assert_equal false, @game.weapon_win?(:rock, :spock)
# Weapons that "Rock" beats
assert_equal true, @game.weapon_win?(:rock, :scissor)
assert_equal true, @game.weapon_win?(:rock, :lizard)
end
def test_compare_weapons_rock
# Withdraw
assert_nil @game.compare_weapons(:rock, :rock)
# Loses
assert_equal false, @game.compare_weapons(:rock, :paper)
assert_equal false, @game.compare_weapons(:rock, :spock)
# Win
assert_equal true, @game.compare_weapons(:rock, :scissor)
assert_equal true, @game.compare_weapons(:rock, :lizard)
end
def test_weapon_win_paper
# Weapons that "Paper" dont beats
assert_equal false, @game.weapon_win?(:paper, :paper)
assert_equal false, @game.weapon_win?(:paper, :scissor)
assert_equal false, @game.weapon_win?(:paper, :lizard)
# Weapons that "Paper" beats
assert_equal true, @game.weapon_win?(:paper, :rock)
assert_equal true, @game.weapon_win?(:paper, :spock)
end
def test_compare_weapons_paper
# Withdraw
assert_nil @game.compare_weapons(:paper, :paper)
# Loses
assert_equal false, @game.compare_weapons(:paper, :scissor)
assert_equal false, @game.compare_weapons(:paper, :lizard)
# Wins
assert_equal true, @game.compare_weapons(:paper, :rock)
assert_equal true, @game.compare_weapons(:paper, :spock)
end
def test_weapon_win_scissor
# Weapons that "Paper" dont beats
assert_equal false, @game.weapon_win?(:scissor, :scissor)
assert_equal false, @game.weapon_win?(:scissor, :rock)
assert_equal false, @game.weapon_win?(:scissor, :spock)
# Weapons that "Paper" beats
assert_equal true, @game.weapon_win?(:scissor, :paper)
assert_equal true, @game.weapon_win?(:scissor, :lizard)
end
def test_compare_weapons_scissor
# Withdraw
assert_nil @game.compare_weapons(:scissor, :scissor)
# Loses
assert_equal false, @game.compare_weapons(:scissor, :rock)
assert_equal false, @game.compare_weapons(:scissor, :spock)
# Wins
assert_equal true, @game.compare_weapons(:scissor, :paper)
assert_equal true, @game.compare_weapons(:scissor, :lizard)
end
def test_weapon_win_lizard
# Weapons that "Lizard" dont beats
assert_equal false, @game.weapon_win?(:lizard, :lizard)
assert_equal false, @game.weapon_win?(:lizard, :rock)
assert_equal false, @game.weapon_win?(:lizard, :scissor)
# Weapons that "Lizard" beats
assert_equal true, @game.weapon_win?(:lizard, :spock)
assert_equal true, @game.weapon_win?(:lizard, :paper)
end
def test_compares_weapons_lizard
# Withdraws
assert_nil @game.compare_weapons(:lizard, :lizard)
# Loses
assert_equal false, @game.compare_weapons(:lizard, :rock)
assert_equal false, @game.compare_weapons(:lizard, :scissor)
# Wins
assert_equal true, @game.compare_weapons(:lizard, :spock)
assert_equal true, @game.compare_weapons(:lizard, :paper)
end
def test_weapon_win_spock
# Weapons that "Spock" dont beats
assert_equal false, @game.weapon_win?(:spock, :spock)
assert_equal false, @game.weapon_win?(:spock, :paper)
assert_equal false, @game.weapon_win?(:spock, :lizard)
# Weapons that "Spock" beats
assert_equal true, @game.weapon_win?(:spock, :scissor)
assert_equal true, @game.weapon_win?(:spock, :rock)
end
def test_compare_weapons_spock
# Withdraws
assert_nil @game.compare_weapons(:spock, :spock)
# Loses
assert_equal false, @game.compare_weapons(:spock, :paper)
assert_equal false, @game.compare_weapons(:spock, :lizard)
# Wins
assert_equal true, @game.compare_weapons(:spock, :scissor)
assert_equal true, @game.compare_weapons(:spock, :rock)
end
def test_weapons_withdraw_true
assert_equal true, @game.weapon_withdraw?(:rock, :rock)
assert_equal true, @game.weapon_withdraw?(:scissor, :scissor)
end
def test_weapons_withdraw_false
assert_equal false, @game.weapon_withdraw?(:rock, :scissor)
assert_equal false, @game.weapon_withdraw?(:scissor, :rock)
end
def test_weapon_from_number
assert_equal({ weapon: :rock, identifier: 1 }, @game.weapon_from_number(1))
assert_equal({ weapon: :paper, identifier: 2 }, @game.weapon_from_number(2))
assert_equal({ weapon: :scissor, identifier: 3 }, @game.weapon_from_number(3))
assert_equal({ weapon: :lizard, identifier: 4 }, @game.weapon_from_number(4))
assert_equal({ weapon: :spock, identifier: 5 }, @game.weapon_from_number(5))
assert_equal({ weapon: nil, identifier: nil }, @game.weapon_from_number(0))
end
end
@fidelisrafael
Copy link
Author

fidelisrafael commented Sep 15, 2017

To run:

$ ➜ git clone https://gist.github.com/fidelisrafael/c953c60471e05e022b08f48f0a936b82 rock-paper-scissor-rb && ruby rock-paper-scissor-rb/game.rb

After the first clone:

$ ➜ ruby rock-paper-scissor-rb/game.rb pt-BR # Português do Brasil
$ ➜ ruby rock-paper-scissor-rb/game.rb en # English

Run tests:

$ ➜ ruby rock-paper-scissor-rb/tests.rb                                                                                                       
Run options: --seed 6974

# Running:

.............

Finished in 0.001533s, 8480.3146 runs/s, 39139.9134 assertions/s.

13 runs, 60 assertions, 0 failures, 0 errors, 0 skips

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment