Last active
September 15, 2017 08:47
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To run:
After the first clone:
Run tests: