#A class representing the internal state of a single game of flip flop # state represents the list of X's (A in the original code) # guesses represents the number of guesses the user has made (C in the original code) # seed represents the random seed for an instance of the game (Q in the original code) Game = Struct.new(:state, :guesses, :seed) do #The original BASIC program used 1 indexed arrays while Ruby has 0-indexed arrays. #We can't use 0 indexed arrays for the flip functions or we'll get divide by zero errors. #These convenience functions allow us to modify and access internal game state in a 1-indexed fashion def flip_letter(letter_number) index = letter_number -1 if self.state[index] == 'X' self.state[index] = 'O' else self.state[index] = 'X' end end def letter_at(letter_number) self.state[letter_number - 1] end end def print_welcome puts 'FLIPFLOP'.center(72) puts 'CREATIVE COMPUTING MORRISTOWN, NEW JERSEY'.center(72) puts <<~EOS THE OBJECT OF THIS PUZZLE IS TO CHANGE THIS: X X X X X X X X X X TO THIS: O O O O O O O O O O BY TYPING THE NUMBER CORRESPONDING TO THE POSITION OF THE LETTER ON SOME NUMBERS, ONE POSITION WILL CHANGE, ON OTHERS, TWO WILL CHANGE. TO RESET LINE TO ALL X'S, TYPE 0 (ZERO) AND TO START OVER IN THE MIDDLE OF A GAME, TYPE 11 (ELEVEN). EOS end def print_starting_message puts <<~EOS HERE IS THE STARTING LINE OF X'S. 1 2 3 4 5 6 7 8 9 10 X X X X X X X X X X EOS end #Create a new game with [X,X,X,X,X,X,X,X,X,X] as the state #0 as the number of guesses and a random seed between 0 and 1 def generate_new_game Game.new(Array.new(10, 'X'), 0, rand()) end #Given a game, an index, and a shuffle function, flip one or more letters def shuffle_board(game, index, shuffle_function) n = method(shuffle_function).call(game, index) if game.letter_at(n) == "O" game.flip_letter(n) if index == n n = shuffle_board(game, index, shuffle_function) end else game.flip_letter(n) end return n end #Shuffle logic copied from original BASIC code def shuffle_function1(game, index) r = Math.tan(game.seed + index / game.seed - index) - Math.sin(game.seed / index) + 336 * Math.sin(8 * index) n = r - r.floor (10 * n).floor end def shuffle_function2(game, index) r = 0.592 * (1/ Math.tan(game.seed / index + game.seed)) / Math.sin(index * 2 + game.seed) - Math.cos(index) n = r - r.floor (10 * n) end def play_game print_starting_message game = generate_new_game working_index = nil loop do puts "INPUT THE NUMBER" input = gets.chomp.downcase #See if the user input a valid integer, fail otherwise if numeric_input = Integer(input, exception: false) #If 11 is entered, we're done with this version of the game if numeric_input == 11 return :restart end if numeric_input > 11 puts 'ILLEGAL ENTRY--TRY AGAIN.' next #illegal entries don't count towards your guesses end if working_index == numeric_input game.flip_letter(numeric_input) working_index = shuffle_board(game, numeric_input, :shuffle_function2) #If 0 is entered, we want to reset the state, but not the random seed or number of guesses and keep playing elsif numeric_input == 0 game.state = Array.new(10, 'X') elsif game.letter_at(numeric_input) == "O" game.flip_letter(numeric_input) if numeric_input == working_index working_index = shuffle_board(game, numeric_input, :shuffle_function1) end else game.flip_letter(numeric_input) working_index = shuffle_board(game, numeric_input, :shuffle_function1) end else puts 'ILLEGAL ENTRY--TRY AGAIN.' next #illegal entries don't count towards your guesses end game.guesses += 1 puts '1 2 3 4 5 6 7 8 9 10' puts game.state.join(' ') if game.state.all? { |x| x == 'O' } if game.guesses > 12 puts "TRY HARDER NEXT TIME. IT TOOK YOU #{game.guesses} GUESSES." else puts "VERY GOOD. YOU GUESSED IT IN ONLY #{game.guesses} GUESSES." end #game is complete return end end end #Execution starts print_welcome loop do result = play_game if result == :restart next end puts 'DO YOU WANT TO TRY ANOTHER PUZZLE' if gets.chomp.downcase[0] == 'n' break end end