Use dependency injection to provide various things
This commit is contained in:
parent
b4a061c6d7
commit
9755aed007
7 changed files with 245 additions and 0 deletions
12
driver.rb
Normal file
12
driver.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
require_relative "lib/game"
|
||||||
|
|
||||||
|
game = Game.new
|
||||||
|
|
||||||
|
trap "SIGINT" do
|
||||||
|
game.exit!
|
||||||
|
exit 130
|
||||||
|
end
|
||||||
|
|
||||||
|
game.play
|
91
lib/board/wordle_unlimited.rb
Normal file
91
lib/board/wordle_unlimited.rb
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
require "capybara"
|
||||||
|
require "selenium-webdriver"
|
||||||
|
require "webdrivers"
|
||||||
|
|
||||||
|
module Board
|
||||||
|
class WordleUnlimited
|
||||||
|
attr :guesses, :session
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@guesses = []
|
||||||
|
@session = Capybara::Session.new(:selenium_chrome)
|
||||||
|
end
|
||||||
|
|
||||||
|
def start
|
||||||
|
session.visit("https://www.wordleunlimited.com/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def answer(guess)
|
||||||
|
guess.chars.map(&:upcase).each { |letter| click(letter) }
|
||||||
|
click("Enter")
|
||||||
|
|
||||||
|
answer_invalid? ? clear_answer! : @guesses << guess
|
||||||
|
end
|
||||||
|
|
||||||
|
def reset!
|
||||||
|
@guesses = []
|
||||||
|
click("Enter")
|
||||||
|
end
|
||||||
|
|
||||||
|
def winner?
|
||||||
|
session.has_text?(:visible, "Winner!", wait: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def loser?
|
||||||
|
session.has_text?(:visible, "You lost!", wait: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
def allowed_letters
|
||||||
|
(exact_letters + elsewhere_letters).uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def bad_letters
|
||||||
|
session
|
||||||
|
.find_all('div.Game-keyboard-button.letter-absent', wait: 0)
|
||||||
|
.map(&:text)
|
||||||
|
.map(&:downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
def close!
|
||||||
|
@session.quit
|
||||||
|
end
|
||||||
|
|
||||||
|
def first_guess?
|
||||||
|
guesses.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def correct_answer
|
||||||
|
session.find('div.feedback > div > b').text
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def exact_letters
|
||||||
|
session
|
||||||
|
.find_all('div.Game-keyboard-button.letter-correct', wait: 0)
|
||||||
|
.map(&:text)
|
||||||
|
.map(&:downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
def elsewhere_letters
|
||||||
|
session
|
||||||
|
.find_all('div.Game-keyboard-button.letter-elsewhere', wait: 0)
|
||||||
|
.map(&:text)
|
||||||
|
.map(&:downcase)
|
||||||
|
end
|
||||||
|
|
||||||
|
def answer_invalid?
|
||||||
|
session.has_text?(:visible, "Not a valid word", wait: 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def click(key)
|
||||||
|
session
|
||||||
|
.find('div.Game-keyboard-button', text: /\A#{key}\Z/, wait: 0)
|
||||||
|
.click()
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear_answer!
|
||||||
|
5.times { click("⌫") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
lib/dictionary/dictionary.rb
Normal file
13
lib/dictionary/dictionary.rb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module Dictionary
|
||||||
|
class Dictionary
|
||||||
|
attr_accessor :file
|
||||||
|
|
||||||
|
def initialize(file: "dictionary.txt")
|
||||||
|
@file = file
|
||||||
|
end
|
||||||
|
|
||||||
|
def words
|
||||||
|
@words ||= File.readlines(file).map(&:strip)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
69
lib/game.rb
Normal file
69
lib/game.rb
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
require "debug"
|
||||||
|
|
||||||
|
require_relative "outcome"
|
||||||
|
|
||||||
|
require_relative "board/wordle_unlimited"
|
||||||
|
require_relative "dictionary/dictionary"
|
||||||
|
|
||||||
|
require_relative "strategy/naive"
|
||||||
|
require_relative "strategy/wheel_of_fortune"
|
||||||
|
|
||||||
|
class Game
|
||||||
|
attr_reader :board, :dictionary, :start_strategy, :strategy, :outcomes
|
||||||
|
def initialize(
|
||||||
|
board: Board::WordleUnlimited,
|
||||||
|
dictionary: Dictionary::Dictionary,
|
||||||
|
start_strategy: Strategy::WheelOfFortune,
|
||||||
|
strategy: Strategy::Naive
|
||||||
|
)
|
||||||
|
@board = board.new
|
||||||
|
@dictionary = dictionary.new
|
||||||
|
@start_strategy = start_strategy.new(dictionary: @dictionary)
|
||||||
|
@strategy = strategy.new(dictionary: @dictionary)
|
||||||
|
|
||||||
|
@outcomes = []
|
||||||
|
end
|
||||||
|
|
||||||
|
def play
|
||||||
|
board.start
|
||||||
|
|
||||||
|
loop do
|
||||||
|
guess_strategy = board.first_guess? ? start_strategy : strategy
|
||||||
|
|
||||||
|
guess = guess_strategy
|
||||||
|
.guess(
|
||||||
|
good_letters: board.allowed_letters,
|
||||||
|
bad_letters: board.bad_letters,
|
||||||
|
guesses: board.guesses,
|
||||||
|
)
|
||||||
|
|
||||||
|
board.answer(guess)
|
||||||
|
|
||||||
|
if board.winner?
|
||||||
|
@outcomes << Outcome.new(
|
||||||
|
state: :win,
|
||||||
|
correct: board.correct_answer,
|
||||||
|
guesses: board.guesses,
|
||||||
|
)
|
||||||
|
board.reset!
|
||||||
|
elsif board.loser?
|
||||||
|
@outcomes << Outcome.new(
|
||||||
|
state: :loss,
|
||||||
|
correct: board.correct_answer,
|
||||||
|
guesses: board.guesses,
|
||||||
|
)
|
||||||
|
board.reset!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def exit!
|
||||||
|
board.close!
|
||||||
|
|
||||||
|
puts
|
||||||
|
puts "=" * 80
|
||||||
|
puts "Won: #{outcomes.select(&:win?).count}"
|
||||||
|
puts "Lost: #{outcomes.select(&:loss?).count}"
|
||||||
|
puts "=" * 80
|
||||||
|
end
|
||||||
|
end
|
17
lib/outcome.rb
Normal file
17
lib/outcome.rb
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
class Outcome
|
||||||
|
attr :correct, :guesses, :state
|
||||||
|
|
||||||
|
def initialize(state:, correct:, guesses:)
|
||||||
|
@state = state
|
||||||
|
@correct = correct
|
||||||
|
@guesses = guesses
|
||||||
|
end
|
||||||
|
|
||||||
|
def win?
|
||||||
|
state == :win
|
||||||
|
end
|
||||||
|
|
||||||
|
def loss?
|
||||||
|
state == :loss
|
||||||
|
end
|
||||||
|
end
|
19
lib/strategy/naive.rb
Normal file
19
lib/strategy/naive.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
module Strategy
|
||||||
|
class Naive
|
||||||
|
WORD_SIZE = 5
|
||||||
|
|
||||||
|
attr :dictionary
|
||||||
|
|
||||||
|
def initialize(dictionary:)
|
||||||
|
@dictionary = dictionary
|
||||||
|
end
|
||||||
|
|
||||||
|
def guess(good_letters:, bad_letters:, **args)
|
||||||
|
dictionary.words
|
||||||
|
.select { |word| word.length == WORD_SIZE }
|
||||||
|
.reject { |word| bad_letters.any? { |letter| word.chars.include?(letter) } }
|
||||||
|
.select { |word| (good_letters - word.chars).length == 0 }
|
||||||
|
.sample
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
24
lib/strategy/wheel_of_fortune.rb
Normal file
24
lib/strategy/wheel_of_fortune.rb
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
module Strategy
|
||||||
|
class WheelOfFortune
|
||||||
|
WORD_SIZE = 5
|
||||||
|
|
||||||
|
attr :dictionary
|
||||||
|
|
||||||
|
def initialize(dictionary:)
|
||||||
|
@dictionary = dictionary
|
||||||
|
end
|
||||||
|
|
||||||
|
def guess(**args)
|
||||||
|
dictionary.words
|
||||||
|
.select { |word| word.length == WORD_SIZE }
|
||||||
|
.select { |word| (start_letters - word.chars).length == 1 }
|
||||||
|
.sample
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def start_letters
|
||||||
|
%w(r s t l n e)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue