# encoding: utf-8 require 'set' require 'prime' module ExtremeStartup class Question class << self def generate_uuid @uuid_generator ||= UUID.new @uuid_generator.generate.to_s[0..7] end end def result if @answer && self.answered_correctly?(answer) "correct" elsif @answer "wrong" else @problem end end def delay_before_next case result when "correct" then 5 when "wrong" then 10 else 20 end end def was_answered_correctly result == "correct" end def was_answered_wrongly result == "wrong" end def display_result "\tquestion: #{self.to_s}\n\tanswer: #{answer}\n\tresult: #{result}" end def id @id ||= Question.generate_uuid end def to_s "#{id}: #{as_text}" end def answer=(answer) @answer = answer.force_encoding("UTF-8") end def answer @answer && @answer.downcase.strip end def answered_correctly?(answer) correct_answer.to_s.downcase.strip == answer end def points 10 end end class GetQuestion < Question def ask(player) url = player.url + '?q=' + URI.escape(self.to_s) puts "GET: " + url begin response = get(url) if (response.success?) then self.answer = response.to_s else @problem = "error_response" end rescue => exception puts exception @problem = "no_server_response" end end def get(url) HTTParty.get(url) end end class PostQuestion < Question def ask(player) url = player.url + '?q=' + URI.escape(self.to_s) puts "Post: " + url begin response = post(url, {data: self.get_data}) if (response.success?) then self.answer = response.to_s else @problem = "error_response" end rescue => exception puts exception @problem = "no_server_response" end end def post(url, data) HTTParty.post(url, data) end end class BinaryMathsQuestion < GetQuestion def initialize(player, *numbers) if numbers.any? @n1, @n2 = *numbers else @n1, @n2 = rand(20), rand(20) end end end class TernaryMathsQuestion < GetQuestion def initialize(player, *numbers) if numbers.any? @n1, @n2, @n3 = *numbers else @n1, @n2, @n3 = rand(20), rand(20), rand(20) end end end class SelectFromListOfNumbersQuestion < GetQuestion def initialize(player, *numbers) if numbers.any? @numbers = *numbers else size = rand(2) @numbers = random_numbers[0..size].concat(candidate_numbers.shuffle[0..size]).shuffle end end def random_numbers randoms = Set.new loop do randoms << rand(1000) return randoms.to_a if randoms.size >= 5 end end def correct_answer @numbers.select do |x| should_be_selected(x) end.join(', ') end end class RectangleIntersectionQuestion < GetQuestion def initialize(player, *numbers) if numbers.any? @x1, @y1, @w1, @h1, @x2, @y2, @w2, @h2 = *numbers else @x1, @y1, @w1, @h1 = rand(100), rand(100), rand(100), rand(100) @x2, @y2, @w2, @h2 = rand(100), rand(100), rand(100), rand(100) end end def as_text "give the intersection of those rectangles (x0,y0,width,height): (#{@x1},#{@y1},#{@w1},#{@h1}) x (#{@x2},#{@y2},#{@w2},#{@h2})" end def answered_correctly?(answer) correct_answer.to_s.downcase.gsub(/[ \\t\\r\\n]+/, '') == answer.gsub(/[ \\t\\r\\n]+/, '') end def points 50 end private def correct_answer _x1 = @x1 + @w1 _y1 = @y1 + @h1 _x2 = @x2 + @w2 _y2 = @y2 + @w2 x = [@x1, @x2].max y = [@y1, @y2].max _x = [_x1, _x2].min _y = [_y1, _y2].min w = _x - x h = _y - y if (w >= 0) and (h >= 0) "(#{x},#{y},#{w},#{h})" else "" end end end class RectangleUnionQuestion < GetQuestion def initialize(player, *numbers) if numbers.any? @x1, @y1, @w1, @h1, @x2, @y2, @w2, @h2 = *numbers else @x1, @y1, @w1, @h1 = rand(100), rand(100), rand(100), rand(100) @x2, @y2, @w2, @h2 = rand(100), rand(100), rand(100), rand(100) end end def as_text "give the union of those rectangles (x0,y0,width,height): (#{@x1},#{@y1},#{@w1},#{@h1}) + (#{@x2},#{@y2},#{@w2},#{@h2})" end def answered_correctly?(answer) correct_answer.to_s.downcase.gsub(/[ \\t\\r\\n]+/, '') == answer.gsub(/[ \\t\\r\\n]+/, '') end def points 50 end private def correct_answer _x1 = @x1 + @w1 _y1 = @y1 + @h1 _x2 = @x2 + @w2 _y2 = @y2 + @w2 x = [@x1, @x2].min y = [@y1, @y2].min _x = [_x1, _x2].max _y = [_y1, _y2].max w = _x - x h = _y - y "(#{x},#{y},#{w},#{h})" end end class ListQuestion < GetQuestion def random_numbers randoms = Set.new loop do randoms << rand(1000) return randoms.to_a if randoms.size >= 5 end end def candidate_numbers (1..100).to_a end def generate_numbers(size) random_numbers[0..size].concat(candidate_numbers.shuffle[0..size]).shuffle end def answered_correctly?(answer) correct_answer.to_s.downcase.gsub(/[ \\t\\r\\n]+/, '') == answer.gsub(/[ \\t\\r\\n]+/, '') end end class ReverseListQuestion < ListQuestion def initialize(player, *numbers) if numbers.any? @numbers = numbers else size = rand(5) @numbers = generate_numbers(size) end end def as_text "reverse this list: " + @numbers.join(', ') end private def correct_answer @numbers.reverse.join(',') end end class SumListQuestion < ListQuestion def initialize(player, *numbers) if numbers.any? @numbers = numbers else size = rand(4) + 1 @numbers = generate_numbers(size) end end def as_text "give the sum of elements in: " + @numbers.join(', ') end def points 20 end private def correct_answer @numbers.reduce(:+) end end class MultiplyElementsInListQuestion < ListQuestion def initialize(player, *numbers) if numbers.any? @n = numbers[0] @numbers = *numbers[1..-1] else @n = rand(1000) size = rand(5) @numbers = generate_numbers(size).concat(candidate_numbers.shuffle[0..size]).shuffle end end def as_text "multiply by #{@n} those elements: " + @numbers.join(', ') end def points 20 end private def correct_answer @numbers.map { |e| e * @n }.join(',') end end class FibonacciListQuestion < ListQuestion def initialize(player, *numbers) if numbers.any? @n = numbers[0] + 1 else @n = rand(19) + 1 end end def as_text count = "are the #{@n} first numbers" if (@n == 1) count = "is the first number" end "what #{count} of the Fibonacci sequence" end def points 60 end private def fibonacci(n) a, b = 0, 1 n.times { a, b = b, a + b } a end def correct_answer (1..@n).map { |e| fibonacci(e) }.join(',') end end class MaximumQuestion < SelectFromListOfNumbersQuestion def as_text "which of the following numbers is the largest: " + @numbers.join(', ') end def points 40 end private def should_be_selected(x) x == @numbers.max end def candidate_numbers (1..100).to_a end end class AdditionQuestion < BinaryMathsQuestion def as_text "what is #{@n1} plus #{@n2}" end private def correct_answer @n1 + @n2 end end class SubtractionQuestion < BinaryMathsQuestion def as_text "what is #{@n1} minus #{@n2}" end private def correct_answer @n1 - @n2 end end class MultiplicationQuestion < BinaryMathsQuestion def as_text "what is #{@n1} multiplied by #{@n2}" end private def correct_answer @n1 * @n2 end end class AdditionAdditionQuestion < TernaryMathsQuestion def as_text "what is #{@n1} plus #{@n2} plus #{@n3}" end def points 30 end private def correct_answer @n1 + @n2 + @n3 end end class AdditionMultiplicationQuestion < TernaryMathsQuestion def as_text "what is #{@n1} plus #{@n2} multiplied by #{@n3}" end def points 60 end private def correct_answer @n1 + @n2 * @n3 end end class MultiplicationAdditionQuestion < TernaryMathsQuestion def as_text "what is #{@n1} multiplied by #{@n2} plus #{@n3}" end def points 50 end private def correct_answer @n1 * @n2 + @n3 end end class PowerQuestion < BinaryMathsQuestion def as_text "what is #{@n1} to the power of #{@n2}" end def points 20 end private def correct_answer @n1 ** @n2 end end class SquareCubeQuestion < SelectFromListOfNumbersQuestion def as_text "which of the following numbers is both a square and a cube: " + @numbers.join(', ') end def points 60 end private def should_be_selected(x) is_square(x) and is_cube(x) end def candidate_numbers square_cubes = (1..100).map { |x| x ** 3 }.select{ |x| is_square(x) } squares = (1..50).map { |x| x ** 2 } square_cubes.concat(squares) end def is_square(x) if (x ==0) return true end (x % (Math.sqrt(x).round(4))) == 0 end def is_cube(x) if (x ==0) return true end (x % (Math.cbrt(x).round(4))) == 0 end end class PrimesQuestion < SelectFromListOfNumbersQuestion def as_text "which of the following numbers are primes: " + @numbers.join(', ') end def points 60 end private def should_be_selected(x) Prime.prime? x end def candidate_numbers Prime.take(100) end end class FibonacciQuestion < BinaryMathsQuestion def ordinal(number) abs_number = number.to_i.abs if (11..13).include?(abs_number% 100) "th" else case abs_number % 10 when 1; "st" when 2; "nd" when 3; "rd" else "th" end end end def ordinalize(number) "#{number}#{ordinal(number)}" end def as_text n = @n1 + 4 return "what is the #{ordinalize(n)} number in the Fibonacci sequence" end def points 50 end private def correct_answer n = @n1 + 4 a, b = 0, 1 n.times { a, b = b, a + b } a end end class GeneralKnowledgeQuestion < GetQuestion class << self def question_bank [ ["who is the president of France", "François Hollande"], ["which city is the Oriental Pearl Tower in", "Shanghai"], ["what currency did Spain use before the Euro", "peseta"], ["what colour is a banana", "yellow"], ["where does the killer rabbit comes from", "Caerbannog"] ] end end def initialize(player) question = GeneralKnowledgeQuestion.question_bank.sample @question = question[0] @correct_answer = question[1] end def as_text @question end def correct_answer @correct_answer end end require 'yaml' class AnagramQuestion < GetQuestion def as_text possible_words = [@anagram["correct"]] + @anagram["incorrect"] %Q{which of the following is an anagram of "#{@anagram["anagram"]}": #{possible_words.shuffle.join(", ")}} end def initialize(player, *words) if words.any? @anagram = {} @anagram["anagram"], @anagram["correct"], *@anagram["incorrect"] = words else anagrams = YAML.load_file(File.join(File.dirname(__FILE__), "anagrams.yaml")) @anagram = anagrams.sample end end def correct_answer @anagram["correct"] end end class ScrabbleQuestion < GetQuestion def as_text "what is the english scrabble score of #{@word}" end def initialize(player, word=nil) if word @word = word else @word = ["banana", "september", "cloud", "zoo", "ruby", "buzzword"].sample end end def correct_answer @word.chars.inject(0) do |score, letter| score += scrabble_scores[letter.downcase] end end private def scrabble_scores scores = {} %w{e a i o n r t l s u}.each {|l| scores[l] = 1 } %w{d g}.each {|l| scores[l] = 2 } %w{b c m p}.each {|l| scores[l] = 3 } %w{f h v w y}.each {|l| scores[l] = 4 } %w{k}.each {|l| scores[l] = 5 } %w{j x}.each {|l| scores[l] = 8 } %w{q z}.each {|l| scores[l] = 10 } scores end end class QuestionFactory attr_reader :round def initialize @round = 1 @question_types = [ AdditionQuestion, MaximumQuestion, MultiplicationQuestion, SquareCubeQuestion, GeneralKnowledgeQuestion, PrimesQuestion, SubtractionQuestion, FibonacciQuestion, PowerQuestion, AdditionAdditionQuestion, AdditionMultiplicationQuestion, MultiplicationAdditionQuestion, AnagramQuestion, ScrabbleQuestion ] end def available_question_types window_end = (@round * 2 - 1) window_start = [0, window_end - 4].max @question_types[window_start..window_end] end def next_question(player) available_question_types.sample.new(player) end def advance_round @round += 1 end end class SecondPhaseQuestionFactory < GetQuestion attr_reader :round def initialize @round = 1 @question_types = [ ReverseListQuestion, SumListQuestion, MultiplyElementsInListQuestion, FibonacciListQuestion ] end def available_question_types window_end = (@round * 2 - 1) window_start = [0, window_end - 4].max @question_types[window_start..window_end] end def next_question(player) available_question_types.sample.new(player) end def advance_round @round += 1 end end class WarmupQuestion < GetQuestion def initialize(player) @player = player end def correct_answer @player.name end def as_text "what is your name" end end class WarmupQuestionFactory def available_question_types [WarmupQuestion] end def next_question(player) WarmupQuestion.new(player) end def advance_round raise("please just restart the server") end end end