Skip to content

Instantly share code, notes, and snippets.

@petertseng
Last active August 10, 2019 01:14
Show Gist options
  • Save petertseng/731e11f82b83846439220bdb0cd48aad to your computer and use it in GitHub Desktop.
Save petertseng/731e11f82b83846439220bdb0cd48aad to your computer and use it in GitHub Desktop.
letter jam help
files, hints = ARGV.partition { |x| File.file?(x) }
hint = hints.join
dict_words = {}
hints = (files.empty? ? File.open('words_alpha.txt', ?r) : ARGF).each_line.map { |word|
dict_words[word.chomp.downcase] = true
}
non_letters = hint.chars.reject { |c| (?a..?z).cover?(c) }.uniq
non_star_non_letters = non_letters - [?*]
star_present = non_letters.include?(?*)
raise "invalid non-letters #{non_letters}" unless non_star_non_letters.size == 1
my_letter = non_star_non_letters[0]
alphabet = (?a..?z).to_a
missing_letters = 'JQVXZ'
alphabet.each { |letter|
next if missing_letters.include?(letter)
possible_star = star_present ? alphabet : ['']
possible_words = possible_star.map { |star|
candidate = hint.tr(my_letter + ?*, letter + star)
candidate if dict_words[candidate]
}.compact
next if possible_words.empty?
puts "#{letter}: #{possible_words}"
}
require 'optparse'
options = {
dummy: [],
free_human: [],
bonus: [],
}
OptionParser.new do |opts|
opts.banner = "Usage: #{$PROGRAM_NAME} [options]"
opts.on("-d", "--dummy=LETTERS", "dummy player letters - give extra clue if entire stack gone, second priority") do |v|
options[:dummy] = v.chars
end
opts.on("-f", "--finished=LETTERS", "finished human letters - become bonus card if used, third priority") do |v|
options[:free_human] = v.chars
end
opts.on("-b", "--bonus=LETTERS", "bonus letters already on table - will be used up, so will try not to use") do |v|
options[:bonus] = v.chars
end
# there may be a finished dummy, but maybe I'll just treat it as finished human and manually correct the designations.
end.parse!
def freq(letters)
letters.group_by(&:itself).transform_values(&:size)
end
files, letters = ARGV.partition { |x| File.file?(x) }
letter_freq = freq(letters.join.chars)
p letter_freq
hints = (files.empty? ? File.open('words_alpha.txt', ?r) : ARGF).each_line.map { |word|
word.chomp!
word.downcase!
word_letters = freq(word.chars)
extra_letters = word_letters.keys - (letter_freq.keys | options[:dummy] | options[:free_human] | options[:bonus])
next if extra_letters.size > 1
# matches of humans we want to help, used to calculate score but not official designation
helpful_human_matches = letter_freq.sum { |letter, freq|
[freq, (word_letters[letter] || 0)].min
}
dummy_used = ((word_letters.keys - letter_freq.keys) & options[:dummy]).size
free_human_used = ((word_letters.keys - (letter_freq.keys | options[:dummy])) & options[:free_human]).size
bonus_used = ((word_letters.keys - (letter_freq.keys | options[:dummy] | options[:free_human])) & options[:bonus]).size
{
word: word,
designation: [
"#{helpful_human_matches + free_human_used}h",
("#{bonus_used}b" if bonus_used > 0),
("#{dummy_used}d" if dummy_used > 0),
(?* unless extra_letters.empty?),
].compact.join(" "),
# we will NOT reverse, but we'll look at the bottom of the file,
# so give high scores a positive value.
score: [
helpful_human_matches,
# TODO: for each human in helpful_human_matches,
# quantify how certain they could be about their letter?
word.size,
dummy_used,
free_human_used,
-bonus_used,
extra_letters.empty? ? '_' : '*',
],
}
}.compact
hints.sort_by { |h| h[:score] }.each { |h| p h }
stub_letters = Hash.new { |h, k| h[k] = {} }
words_containing = Hash.new(0)
seven_or_more = ARGV.delete('-7')
specific_pair = if (parg = ARGV.find { |x| x.start_with?('-p') })
ARGV.delete(parg)
parg[2..-1]
end
(ARGV.empty? ? File.open('words_alpha.txt', ?r) : ARGF).each_line { |word|
word.chomp!
word.downcase!
letters = word.chars.uniq
next if letters.size > 6 && !seven_or_more
letters.each { |letter|
words_containing[letter] += 1
stub_letters[word.tr(letter, ?_)][letter] = true
}
}
stub_letters.transform_values! { |v| v.keys.sort }
if specific_pair
stub_letters.each { |stub, letters|
next unless specific_pair.each_char { |c| letters.include?(c) }
puts "#{stub}: #{specific_pair.each_char.map { |c| stub.tr(?_, c) }}"
}
Kernel.exit(0)
end
pair_freq = Hash.new(0)
ambiguous_containing = Hash.new(0)
stub_letters.each_value { |letters|
letters.combination(2) { |a, b| pair_freq[a + b] += 1 }
letters.each { |k| ambiguous_containing[k] += 1 } if letters.size > 1
}
alphabet = (?a..?z).to_a
mixup_chance = alphabet.map { |letter|
n = ambiguous_containing[letter]
d = words_containing[letter]
[letter, n = ambiguous_containing[letter], d = words_containing[letter], n.fdiv(d)]
}
max_len = words_containing.values.max.to_s.size
fmtd = "%#{max_len}d"
mixup_chance.sort_by(&:last).reverse_each { |x| puts "%s: #{fmtd} / #{fmtd} = %8.6f" % x }
max_len = pair_freq.values.max.to_s.size
fmtd = "%#{max_len}d"
fmts = "%#{max_len}s"
header = ([' ' * max_len] + alphabet.map { |x| fmts % x }).join(' ')
puts header
alphabet.each { |a|
puts ([fmts % a] + alphabet.map { |b| fmtd % pair_freq[[a, b].sort.join] } + [a]).join(' ')
}
puts header
pair_freq.to_a.sort_by(&:last).reverse_each { |x| p x }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment