108 lines
3.2 KiB
Text
108 lines
3.2 KiB
Text
include "MRG32k3a" {search: "."}; # see comment above
|
|
|
|
### Generic functions
|
|
|
|
# Determine if stream is non-decreasing
|
|
def is_sorted(stream):
|
|
first(foreach stream as $s ( null;
|
|
if . == null or $s >= .[0] then [$s]
|
|
else 0
|
|
end;
|
|
select(. == 0) ) )
|
|
// 1
|
|
| . == 1;
|
|
|
|
# Write to stderr
|
|
def inform(msg):
|
|
. as $in
|
|
| ("\(msg)\n" | stderr)
|
|
| $in;
|
|
|
|
# q means quit
|
|
def read($prompt; $regex):
|
|
def r:
|
|
($prompt | stderr | empty),
|
|
(try ((input
|
|
| if . == "q" then halt
|
|
else select(test($regex))
|
|
end) // r)
|
|
catch if . == "break" then halt else r end );
|
|
r;
|
|
|
|
# Returns levenshteinDistance(s1; $s2) <= $max
|
|
# A recursive algorithm is good enough if $max is small
|
|
def levenshteinDistance($s1; $s2; $max):
|
|
def lev:
|
|
. as [$s1, $s2, $max]
|
|
| if ($s1|length) == 0 then ($s2|length) <= $max
|
|
elif ($s2|length) == 0 then ($s1|length) <= $max
|
|
elif $s1[:1] == $s2[:1]
|
|
then [$s1[1:], $s2[1:], $max] | lev
|
|
else ($s1|length) <= $max
|
|
or ($s2|length) <= $max
|
|
or ([$s1[1:], $s2, $max-1] | lev)
|
|
or ([$s1, $s2[1:], $max-1] | lev)
|
|
or ([$s1[1:], $s2[1:], $max-1] | lev)
|
|
end ;
|
|
[$s1, $s2, $max] | lev;
|
|
|
|
##### The Wordiff Game
|
|
|
|
# Output: the sorted list of words
|
|
# The sort is skipped if we can readily determine the list is already sorted
|
|
def words:
|
|
[$dict | splits(" *\n *")]
|
|
| if is_sorted(.[]) then .
|
|
else sort
|
|
end;
|
|
|
|
def player1: read("Player 1, please enter your name : "; ".");
|
|
def player2: read("Player 2, please enter your name : "; ".");
|
|
|
|
def round:
|
|
.words as $words
|
|
| read("\(.player), enter your word: "; ".") as $word
|
|
| ($word|length) as $len
|
|
| .ok = false
|
|
| if $len < 3
|
|
then inform("Words must be at least three letters long.")
|
|
elif $word == .prevWord
|
|
then inform("You must change the previous word.")
|
|
elif .used[$word]
|
|
then inform("The word \"\($word)\" has been used before.")
|
|
elif # not in dictionary
|
|
# if $words is not sorted: ($word | IN($words[]) | not)
|
|
# binary search:
|
|
($words | bsearch($word) < 0)
|
|
then inform("Not in dictionary.")
|
|
|
|
elif levenshteinDistance($word; .prevWord; 1)
|
|
#### Good to go
|
|
then .ok = true
|
|
| .prevLen = ($word|length)
|
|
| .prevWord = $word
|
|
| .used[$word] = true
|
|
| .player = (if .player == .player1 then .player2 else .player1 end)
|
|
else inform("Sorry. Only one addition, deletion or alteration is allowed.")
|
|
end
|
|
| if (.ok|not) then inform("Please retry. The current word is: \(.prevWord)") end
|
|
| round ;
|
|
|
|
def play:
|
|
player1 as $player1
|
|
| { player1: $player1,
|
|
player2: ( player2 | if . == $player1 then . + "2" else . end)
|
|
}
|
|
| inform("Reading and perhaps sorting the list of words takes a few seconds... ")
|
|
| inform("Meanwhile note that you can quit by entering q at a prompt.")
|
|
| .words = words
|
|
# Avoid storing the subset of words needed to make a random selection:
|
|
| (.words | map(select(length | IN(3,4))) | .[length | prn(1)[]]) as $firstWord
|
|
| .prevLen = ($firstWord|length)
|
|
| .prevWord = $firstWord
|
|
| .used = {($firstWord): true} # a JSON object for efficiency
|
|
| .player = .player1
|
|
| "\nThe first word is: \($firstWord)",
|
|
round;
|
|
|
|
play
|