-
-
Save mhriess/3950436 to your computer and use it in GitHub Desktop.
FlashCardinator
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
class Session | |
attr_reader :output_stream | |
def initialize(game, input_stream = $stdin, output_stream = $stdout) | |
@game = game | |
@input_stream = input_stream | |
@output_stream = output_stream | |
end | |
def play | |
command = '' | |
until command == 'exit' do | |
@game.evaluate_guess command unless command.empty? | |
@output_stream << @game.prompt | |
command = @input_stream.gets.chomp | |
end | |
end | |
end | |
class Game | |
def initialize(deck) | |
@deck = deck | |
@guesses = 3 | |
end | |
def prompt | |
"#{last_guess_message}\n#{current_card_message}\n#{remaining_guesses}" | |
end | |
def evaluate_guess(guess) | |
@new_card_drawn = false | |
if correct?(guess) || @guesses == 1 | |
draw_next_card | |
@last_guess_state = :correct unless @guesses == 1 | |
reset_guesses | |
else | |
decrement_guess | |
@last_guess_state = :incorrect | |
end | |
end | |
private | |
def last_guess_message | |
case @last_guess_state | |
when :correct | |
"Correct!" | |
when :incorrect | |
"Incorrect! #{give_answer if @new_card_drawn}" | |
end | |
end | |
def give_answer | |
"The correct answer was '#{@deck.previous_card.term}'." | |
end | |
def current_card_message | |
"#{"Next card!" if @new_card_drawn }The current card reads: #{current_definition}." | |
end | |
def decrement_guess | |
@guesses -= 1 | |
end | |
def current_definition | |
@deck.current_card.definition | |
end | |
def remaining_guesses | |
"You have #{human_count} guess#{'es' if @guesses > 1} left." | |
end | |
def human_count | |
[nil, 'one', 'two', 'three'][@guesses] | |
end | |
def reset_guesses | |
@guesses = 3 | |
end | |
def draw_next_card | |
@deck.next_card | |
@new_card_drawn = true | |
end | |
def correct?(guess) | |
@deck.current_card.is_correct?(guess) | |
end | |
end | |
class Deck < Array | |
def initialize | |
@current_index = 0 | |
end | |
def self.import_from_csv_file(filename) | |
import_from_csv(File.open(filename)) | |
end | |
def self.import_from_csv(csv_stream) | |
deck = Deck.new | |
csv_stream.each_line do | row | | |
row.match(/(\w+)\s(.+)/) | |
deck.push Card.new($1, $2) | |
end | |
deck | |
end | |
def next_card | |
raise RuntimeError, "no cards yet loaded" if empty? | |
@current_index += 1 | |
@current_index = 0 if @current_index >= length | |
current_card | |
end | |
def current_card | |
raise RuntimeError, "no cards yet loaded" if empty? | |
self[@current_index] | |
end | |
def previous_card | |
self[@current_index - 1] | |
end | |
end | |
class Card | |
attr_reader :definition, :term | |
def initialize(term, definition) | |
raise ArgumentError, "both term and definition must not be empty" \ | |
if term.nil? || definition.nil? || term.empty? || definition.empty? | |
@term = term | |
@definition = definition | |
end | |
def is_correct?(term) | |
@term == term | |
end | |
end | |
# deck = Deck.new | |
# deck.import_from_csv_file('flashcards_data.txt') | |
# game = Session.new(Game.new(FlashCardinator.import_from_csv_file('flashcards_data.txt'))) | |
# game.play |
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 'rspec' | |
require 'SimpleCov' | |
SimpleCov.start | |
require './card.rb' | |
require 'tempfile' | |
describe Game do | |
let(:definition_text) { "a crisp and refreshing fruit" } | |
let(:card) { double("Card", :definition => nil, :term => nil) } | |
let(:deck) { double("FlashCardinator", :current_card => card, :previous_card => nil) } | |
let(:game) { Game.new(deck) } | |
def be_wrong_thrice | |
3.times { game.evaluate_guess "wrong" } | |
deck.stub(:current_card).and_return(double("null card", :definition=>nil, :is_correct? => nil)) | |
deck.stub(:previous_card).and_return(card) | |
end | |
describe "#prompt" do | |
it "should include the current card's definition" do | |
card.should_receive(:definition).and_return(definition_text) | |
game.prompt.should include "The current card reads: #{definition_text}" | |
end | |
it "should say that you have three guesses" do | |
game.prompt.should include "You have three guesses left." | |
end | |
it "should not mention any previous guesses" do | |
game.prompt.should_not include "Correct!" | |
game.prompt.should_not include "Incorrect!" | |
end | |
context "after a correct guess" do | |
before do | |
card.stub(:is_correct?).and_return(true) | |
deck.stub(:next_card) | |
game.evaluate_guess "correct" | |
end | |
it "should congratulate your successes" do | |
game.prompt.should match /^Correct!/ | |
end | |
it "should underline that a new card has been drawn" do | |
game.prompt.scan("Next card!").should have(1).item | |
end | |
end | |
context "after an incorrect guess" do | |
before do | |
card.stub(:is_correct?).and_return(false) | |
game.evaluate_guess "wrong" | |
end | |
it "should mock your failures" do | |
game.prompt.should =~ /^Incorrect!/ | |
end | |
it "shouldn't tell you the answer" do | |
game.prompt.should_not include "The correct answer was" | |
end | |
end | |
context "after three incorrect guesses" do | |
before do | |
card.stub(:is_correct?).and_return(false) | |
deck.stub(:next_card) | |
end | |
it "should not congratulate you" do | |
be_wrong_thrice | |
game.prompt.should_not include "Correct!" | |
end | |
it "should underline that a new card has been drawn" do | |
be_wrong_thrice | |
game.prompt.should include "Next card!" | |
game.evaluate_guess "whatever" | |
game.prompt.should_not include "Next card!" | |
end | |
it "should show the answer" do | |
card.should_receive(:term).and_return "apple" | |
be_wrong_thrice | |
game.prompt.should include "The correct answer was 'apple'." | |
end | |
end | |
end | |
describe "#guess" do | |
context "when incorrect" do | |
before :each do | |
card.stub(:is_correct?).and_return(false) | |
end | |
it "decrements your guesses" do | |
game.evaluate_guess "wrong guess" | |
game.prompt.should include "You have two guesses left." | |
game.evaluate_guess "wrong guess" | |
game.prompt.should include "You have one guess left." | |
end | |
context "three times in a row" do | |
it "draws the next card" do | |
deck.should_receive(:next_card) | |
be_wrong_thrice | |
end | |
it "gives you three guesses again" do | |
deck.stub(:next_card) | |
be_wrong_thrice | |
game.prompt.should include "You have three guesses left." | |
end | |
end | |
end | |
context "when correct" do | |
let(:correct_guess) { 'apple' } | |
before :each do | |
deck.stub(:next_card => nil) | |
card.stub(:is_correct?) do |arg| | |
arg == correct_guess | |
end | |
end | |
it "resets your guesses" do | |
2.times { game.evaluate_guess "wrong guess" } | |
game.evaluate_guess correct_guess | |
game.prompt.should include "You have three guesses left." | |
end | |
it "draws the next card" do | |
deck.should_receive(:next_card) | |
game.evaluate_guess correct_guess | |
end | |
it "doesn't bother repeating the correct answer" do | |
game.evaluate_guess correct_guess | |
game.prompt.should_not include "The correct answer was" | |
end | |
end | |
end | |
end | |
describe Session do | |
let(:game) { double("Game") } | |
let(:output) { double("IO", :<< => nil) } | |
let(:input) { StringIO.new("la\nti\nda\nexit\nThis will never happen", 'r+')} | |
def run_game | |
Session.new(game, input, output).play | |
end | |
describe "#play" do | |
context "until the 'exit' command is received'" do | |
it "runs the input into game#evaluate_guess" do | |
game.stub(:prompt) | |
game.should_receive(:evaluate_guess).exactly(3).times | |
run_game | |
end | |
it "passes game#prompt into the output" do | |
game.stub(:evaluate_guess) | |
game.should_receive(:prompt).exactly(4).times.and_return(:game_info) | |
output.should_receive(:<<).with(:game_info).exactly(4).times | |
run_game | |
end | |
end | |
end | |
end | |
describe Deck do | |
subject(:deck) { Deck.import_from_csv("term definition\nterm2 definition2") } | |
describe ".import_from_csv" do | |
it { should_not be_empty } | |
end | |
describe "#current_card" do | |
it "returns the card on the top of the deck" do | |
deck.current_card.term.should eq "term" | |
deck.current_card.definition.should eq 'definition' | |
end | |
end | |
describe "#next_card" do | |
it "returns the next card" do | |
card = deck.next_card | |
card.term.should eq "term2" | |
card.definition.should eq 'definition2' | |
end | |
it "changes #current_card" do | |
deck.next_card | |
deck.current_card.term.should eq "term2" | |
deck.current_card.definition.should eq 'definition2' | |
end | |
end | |
describe "#previous_card" do | |
it "should return the previous card" do | |
deck.next_card | |
deck.previous_card.term.should eq "term" | |
deck.previous_card.definition.should eq 'definition' | |
end | |
end | |
end | |
describe Card do | |
context "when creating a new card" do | |
it "should not accept a blank string for a term" do | |
expect{Card.new("", "definition").to raise_error(ArgumentError) } | |
end | |
it "should not accept a blank string for a definition" do | |
expect{Card.new("term", "").to raise_error(ArgumentError) } | |
end | |
it "should not accept nil for a term" do | |
expect{Card.new(nil, "definition").to raise_error(ArgumentError) } | |
end | |
it "should not accept nil for a definition" do | |
expect{Card.new("term", nil).to raise_error(ArgumentError) } | |
end | |
end | |
it "should know if a given input matches its term" do | |
card = Card.new("term", "definition") | |
card.is_correct?("term").should be_true | |
end | |
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
alias To create a second name for the variable or method. | |
and A command that appends two or more objects together. | |
BEGIN Designates code that must be run unconditionally at the beginning of the program before any other. | |
begin Delimits a "begin" block of code, which can allow the use of while and until in modifier position with multi-line statements. | |
break Gives an unconditional termination to a code block, and is usually placed with an argument. | |
case starts a case statement; this block of code will output a result and end when it's terms are fulfilled, which are defined with when or else. | |
class Opens a class definition block, which can later be reopened and added to with variables and even functions. | |
def Used to define a function. | |
defined? A boolean logic function that asks whether or not a targeted expression refers to anything recognizable in Ruby; i.e. literal object, local variable that has been initialized, method name visible from the current scope, etc. | |
do Paired with end, this can delimit a code block, much like curly braces; however, curly braces retain higher precedence. | |
else Gives an "otherwise" within a function, if-statement, or for-loop, i.e. if cats = cute, puts "Yay!" else puts "Oh, a cat." | |
elsif Much like else, but has a higher precedence, and is usually paired with terms. | |
END Designates, via code block, code to be executed just prior to program termination. | |
end Marks the end of a while, until, begin, if, def, class, or other keyword-based, block-based construct. | |
ensure Marks the final, optional clause of a begin/end block, generally in cases where the block also contains a rescue clause. The code in this term's clause is guaranteed to be executed, whether control flows to a rescue block or not. | |
false denotes a special object, the sole instance of FalseClass. false and nil are the only objects that evaluate to Boolean falsehood in Ruby (informally, that cause an if condition to fail.) | |
for A loop constructor; used in for-loops. | |
if Ruby's basic conditional statement constructor. | |
in Used with for, helps define a for-loop. | |
module Opens a library, or module, within a Ruby Stream. | |
next Bumps an iterator, or a while or until block, to the next iteration, unconditionally and without executing whatever may remain of the block. | |
nil A special "non-object"; it is, in fact, an object (the sole instance of NilClass), but connotes absence and indeterminacy. nil and false are the only two objects in Ruby that have Boolean falsehood (informally, that cause an if condition to fail). | |
not Boolean negation. i.e. not true # false, not 10 # false, not false # true. | |
or Boolean or. Differs from || in that or has lower precedence. | |
redo Causes unconditional re-execution of a code block, with the same parameter bindings as the current execution. | |
rescue Designates an exception-handling clause that can occur either inside a begin<code>/<code>end block, inside a method definition (which implies begin), or in modifier position (at the end of a statement). | |
retry Inside a rescue clause, causes Ruby to return to the top of the enclosing code (the begin keyword, or top of method or block) and try executing the code again. | |
return Inside a method definition, executes the ensure clause, if present, and then returns control to the context of the method call. Takes an optional argument (defaulting to nil), which serves as the return value of the method. Multiple values in argument position will be returned in an array. | |
self The "current object" and the default receiver of messages (method calls) for which no explicit receiver is specified. Which object plays the role of self depends on the context. | |
super Called from a method, searches along the method lookup path (the classes and modules available to the current object) for the next method of the same name as the one being executed. Such method, if present, may be defined in the superclass of the object's class, but may also be defined in the superclass's superclass or any class on the upward path, as well as any module mixed in to any of those classes. | |
then Optional component of conditional statements (if, unless, when). Never mandatory, but allows for one-line conditionals without semi-colons. | |
true The sole instance of the special class TrueClass. true encapsulates Boolean truth; however, <emph>all</emph> objects in Ruby are true in the Boolean sense (informally, they cause an if test to succeed), with the exceptions of false and nil. | |
undef Undefines a given method, for the class or module in which it's called. If the method is defined higher up in the lookup path (such as by a superclass), it can still be called by instances classes higher up. | |
unless The negative equivalent of if. i.e. unless y.score > 10 puts "Sorry; you needed 10 points to win." end. | |
until The inverse of while: executes code until a given condition is true, i.e., while it is not true. The semantics are the same as those of while. | |
when Same as case. | |
while Takes a condition argument, and executes the code that follows (up to a matching end delimiter) while the condition is true. | |
yield Called from inside a method body, yields control to the code block (if any) supplied as part of the method call. If no code block has been supplied, calling yield raises an exception. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment