Wordiff: Difference between revisions
(julia example) |
m (spacing) |
||
Line 35: | Line 35: | ||
=={{header|Julia}}== |
=={{header|Julia}}== |
||
⚫ | |||
<lang julia> |
|||
⚫ | |||
isonemore(nw, ow) = isoneless(ow, nw) |
isonemore(nw, ow) = isoneless(ow, nw) |
||
isonechanged(x, y) = length(x) == length(y) && count(i -> x[i] != y[i], eachindex(y)) == 1 |
isonechanged(x, y) = length(x) == length(y) && count(i -> x[i] != y[i], eachindex(y)) == 1 |
||
Line 112: | Line 110: | ||
sam: 6.447 seconds average |
sam: 6.447 seconds average |
||
</pre> |
</pre> |
||
=={{header|Nim}}== |
=={{header|Nim}}== |
Revision as of 00:47, 1 August 2021
Wordiff is an original game in which contestants take turns spelling new dictionary words that only differ from the last by a change in one letter.
The change can be either:
- a deletion of one letter;
- addition of one letter;
- or change in one letter.
Note:
- All words must be in the dictionary.
- No word in a game can be repeated.
- The first word must be three or four letters long.
- Task
Create a program to aid in the playing of the game by:
- Asking for contestants names.
- Choosing an initial random three or four letter word from the dictionary.
- Asking each contestant in their turn for a wordiff word.
- Checking the wordiff word is:
- in the dictionary,
- not a repetition of past words,
- and differs from the last appropriately.
- Optional stretch goal
Add timing.
- Allow players to set a maximum playing time for the game.
- An internal timer accumulates how long each user takes to respond in their turns.
- Play is halted if the maximum playing time is exceeded on a players input.
- That last player must have entered a wordiff or loses.
- If the game is timed-out, the loser is the person who took the longest `average` time to answer in their rounds.
Julia
<lang julia>isoneless(nw, ow) = any(i -> nw == ow[begin:i-1] * ow[i+1:end], eachindex(ow)) isonemore(nw, ow) = isoneless(ow, nw) isonechanged(x, y) = length(x) == length(y) && count(i -> x[i] != y[i], eachindex(y)) == 1 onefrom(nw, ow) = isoneless(nw, ow) || isonemore(nw, ow ) || isonechanged(nw, ow)
function askprompt(prompt)
ans = "" while isempty(ans) print(prompt) ans = strip(readline()) println() end return ans
end
function wordiff(dictfile = "unixdict.txt")
wordlist = [w for w in split(read(dictfile, String), r"\s+") if !occursin(r"\W", w)] starters = [w for w in wordlist if 3 <= length(w) <= 4]
timelimit = something(tryparse(Float64, askprompt("Time limit (min) or 0 for none: ")), 0)
players = split(askprompt("Enter players' names. Separate by commas: "), r"\s*,\s*") times, word = Dict(player => Float32[] for player in players), rand(starters) used = [word] totalsecs, timestart = timelimit * 60, time() while length(players) > 1 player = popfirst!(players) playertimestart = time() newword = askprompt("$player, your move. The current word is $word. Your worddiff? ") if timestart + totalsecs > time() if onefrom(newword, word) && !(newword in used) && lowercase(newword) in wordlist println("Correct.") push!(players, player) word = newword push!(used, newword) push!(times[player], time() - playertimestart) else println("Wordiff choice incorrent. Player $player exits game.") end else # out of time println("Sorry, time was up. Timing ranks for remaining players:") avtimes = Dict(p => isempty(times[p]) ? NaN : sum(times[p]) / length(times[p]) for p in players) sort!(players, lt = (x, y) -> avtimes[x] < avtimes[y]) foreach(p -> println(" $p:", lpad(avtimes[p], 10), " seconds average"), players) break end sleep(rand() * 10) end length(players) < 2 && println("Player $(first(players)) is the only one left, and wins the game.")
end
wordiff()
</lang>
- Output:
Time limit (min) or 0 for none: 0.4 Enter players' names. Separate by commas: sam,mary,ann,ron sam, your move. The current word is shod. Your worddiff? shot Correct. mary, your move. The current word is shot. Your worddiff? hot Correct. ann, your move. The current word is hot. Your worddiff? hog Correct. ron, your move. The current word is hog. Your worddiff? log Sorry, time was up. Timing ranks for remaining players: ann: 3.1 seconds average mary: 4.573 seconds average sam: 6.447 seconds average
Nim
<lang Nim>import httpclient, sequtils, sets, strutils, sugar from unicode import capitalize
const
DictFname = "unixdict.txt" DictUrl1 = "http://wiki.puzzlers.org/pub/wordlists/unixdict.txt" # ~25K words DictUrl2 = "https://raw.githubusercontent.com/dwyl/english-words/master/words.txt" # ~470K words
type Dictionary = HashSet[string]
proc loadDictionary(fname = DictFname): Dictionary =
## Return appropriate words from a dictionary file. for word in fname.lines(): if word.len >= 3 and word.allCharsInSet(Letters): result.incl word.toLowerAscii
proc loadWebDictionary(url: string): Dictionary =
## Return appropriate words from a dictionary web page. let client = newHttpClient() for word in client.getContent(url).splitLines(): if word.len >= 3 and word.allCharsInSet(Letters): result.incl word.toLowerAscii
proc getPlayers(): seq[string] =
## Return inputted ordered list of contestant names. try: stdout.write "Space separated list of contestants: " stdout.flushFile() result = stdin.readLine().splitWhitespace().map(capitalize) if result.len == 0: quit "Empty list of names. Quitting.", QuitFailure except EOFError: echo() quit "Encountered end of file. Quitting.", QuitFailure
proc isWordiffRemoval(word, prev: string; comment = true): bool =
## Is "word" derived from "prev" by removing one letter? for i in 0..prev.high: if word == prev[0..<i] & prev[i+1..^1]: return true if comment: echo "Word is not derived from previous by removal of one letter." result = false
proc isWordiffInsertion(word, prev: string; comment = true): bool =
## Is "word" derived from "prev" by adding one letter? for i in 0..word.high: if prev == word[0..<i] & word[i+1..^1]: return true if comment: echo "Word is not derived from previous by insertion of one letter." return false
proc isWordiffChange(word, prev: string; comment = true): bool =
## Is "word" derived from "prev" by changing exactly one letter? var diffcount = 0 for i in 0..word.high: diffcount += ord(word[i] != prev[i]) if diffcount != 1: if comment: echo "More or less than exactly one character changed." return false result = true
proc isWordiff(word: string; wordiffs: seq[string]; dict: Dictionary; comment = true): bool =
## Is "word" a valid wordiff from "wordiffs[^1]"? if word notin dict: if comment: echo "That word is not in my dictionary." return false if word in wordiffs: if comment: echo "That word was already used." return false result = if word.len < wordiffs[^1].len: word.isWordiffRemoval(wordiffs[^1], comment) elif word.len > wordiffs[^1].len: word.isWordiffInsertion(wordiffs[^1], comment) else: word.isWordiffChange(wordiffs[^1], comment)
proc couldHaveGot(wordiffs: seq[string]; dict: Dictionary): seq[string] =
for word in dict - wordiffs.toHashSet: if word.isWordiff(wordiffs, dict, comment = false): result.add word
when isMainModule:
import random
randomize() let dict = loadDictionary(DictFname) let dict34 = collect(newSeq): for word in dict: if word.len in [3, 4]: word let start = sample(dict34) var wordiffs = @[start] let players = getPlayers() var iplayer = 0 var word: string while true: let name = players[iplayer] while true: stdout.write "$1, input a wordiff from “$2”: ".format(name, wordiffs[^1]) stdout.flushFile() try: word = stdin.readLine().strip() if word.len > 0: break except EOFError: quit "Encountered end of file. Quitting.", QuitFailure if word.isWordiff(wordiffs, dict): wordiffs.add word else: echo "You have lost, $#.".format(name) let possibleWords = couldHaveGot(wordiffs, dict) if possibleWords.len > 0: echo "You could have used: ", possibleWords[0..min(possibleWords.high, 20)].join(" ") break iplayer = (iplayer + 1) mod players.len</lang>
- Output:
Space separated list of contestants: Paddy Maggie Paddy, input a wordiff from “beta”: bet Maggie, input a wordiff from “bet”: bee Paddy, input a wordiff from “bee”: tee Maggie, input a wordiff from “tee”: teen Paddy, input a wordiff from “teen”: teeny That word is not in my dictionary. You have lost, Paddy. You could have used: then steen tern keen teem teet been seen ten
Python
This is without timing, but ends by showing some wordiffs from the dictionary that could have worked on failure. <lang python># -*- coding: utf-8 -*-
from typing import List, Tuple, Dict, Set from itertools import cycle, islice from collections import Counter import re import random import urllib
dict_fname = 'unixdict.txt' dict_url1 = 'http://wiki.puzzlers.org/pub/wordlists/unixdict.txt' # ~25K words dict_url2 = 'https://raw.githubusercontent.com/dwyl/english-words/master/words.txt' # ~470K words
word_regexp = re.compile(r'^[a-z]{3,}$') # reduce dict words to those of three or more a-z characters.
def load_dictionary(fname: str=dict_fname) -> Set[str]:
"Return appropriate words from a dictionary file" with open(fname) as f: return {lcase for lcase in (word.strip().lower() for word in f) if word_regexp.match(lcase)}
def load_web_dictionary(url: str) -> Set[str]:
"Return appropriate words from a dictionary web page" words = urllib.request.urlopen(url).read().decode().strip().lower().split() return {word for word in words if word_regexp.match(word)}
def get_players() -> List[str]:
"Return inputted ordered list of contestant names." names = input('Space separated list of contestants: ') return [n.capitalize() for n in names.strip().split()]
def is_wordiff(wordiffs: List[str], word: str, dic: Set[str], comment=True) -> bool:
"Is word a valid wordiff from wordiffs[-1] ?" if word not in dic: if comment: print('That word is not in my dictionary') return False if word in wordiffs: if comment: print('That word was already used.') return False if len(word) < len(wordiffs[-1]): return is_wordiff_removal(word, wordiffs[-1], comment) elif len(word) > len(wordiffs[-1]): return is_wordiff_insertion(word, wordiffs[-1], comment) return is_wordiff_change(word, wordiffs[-1], comment)
def is_wordiff_removal(word: str, prev: str, comment=True) -> bool:
"Is word derived from prev by removing one letter?" ... ans = word in {prev[:i] + prev[i+1:] for i in range(len(prev))} if not ans: if comment: print('Word is not derived from previous by removal of one letter.') return ans
def is_wordiff_insertion(word: str, prev: str, comment=True) -> bool:
"Is word derived from prev by adding one letter?" diff = Counter(word) - Counter(prev) diffcount = sum(diff.values()) if diffcount != 1: if comment: print('More than one character insertion difference.') return False insert = list(diff.keys())[0] ans = word in {prev[:i] + insert + prev[i:] for i in range(len(prev) + 1)} if not ans: if comment: print('Word is not derived from previous by insertion of one letter.') return ans
def is_wordiff_change(word: str, prev: str, comment=True) -> bool:
"Is word derived from prev by changing exactly one letter?" ... diffcount = sum(w != p for w, p in zip(word, prev)) if diffcount != 1: if comment: print('More or less than exactly one character changed.') return False return True
def could_have_got(wordiffs: List[str], dic: Set[str]):
return (word for word in (dic - set(wordiffs)) if is_wordiff(wordiffs, word, dic, comment=False))
if __name__ == '__main__':
dic = load_web_dictionary(dict_url2) dic_3_4 = [word for word in dic if len(word) in {3, 4}] start = random.choice(dic_3_4) wordiffs = [start] players = get_players() for name in cycle(players): word = input(f"{name}: Input a wordiff from {wordiffs[-1]!r}: ").strip() if is_wordiff(wordiffs, word, dic): wordiffs.append(word) else: print(f'YOU HAVE LOST {name}!') print("Could have used:", ', '.join(islice(could_have_got(wordiffs, dic), 10)), '...') break</lang>
- Output:
Space separated list of contestants: Paddy Maggie Paddy: Input a wordiff from 'sett': sets Maggie: Input a wordiff from 'sets': bets Paddy: Input a wordiff from 'bets': buts Maggie: Input a wordiff from 'buts': bits Paddy: Input a wordiff from 'bits': bit Maggie: Input a wordiff from 'bit': bite Paddy: Input a wordiff from 'bite': biter Maggie: Input a wordiff from 'biter': bitter Paddy: Input a wordiff from 'bitter': sitter Maggie: Input a wordiff from 'sitter': titter Paddy: Input a wordiff from 'titter': tutter That word is not in my dictionary YOU HAVE LOST Paddy! Could have used: titfer, witter, tittery, totter, titler, kitter, twitter, tilter, gitter, jitter ...
REXX
<lang rexx>/*REXX program acts as a host and allows two or more people to play the WORDIFF game.*/ signal on halt /*allow the user(s) to halt the game. */ parse arg iFID seed . /*obtain optional arguments from the CL*/ if iFID== | iFID=="," then iFID='unixdict.txt' /*Not specified? Then use the default.*/ if datatype(seed, 'W') then call random ,,seed /*If " " " " seed. */ call read call IDs first= random(1, min(100000, starters) ) /*get a random start word for the game.*/ list= $$$.first say; say eye "OK, let's play the WORDIFF game."; say; say
do round=1 do player=1 for players call show; ou= o; upper ou call CBLF word(names, player) end /*players*/ end /*round*/
halt: say; say; say eye 'The WORDIFF game has been halted.' done: exit 0 /*stick a fork in it, we're all done. */ quit: say; say; say eye 'The WORDDIF game is quitting.'; signal done /*──────────────────────────────────────────────────────────────────────────────────────*/ isMix: return datatype(arg(1), 'M') /*return unity if arg has mixed letters*/ ser: say; say eye '***error*** ' arg(1).; say; return /*issue error message. */ last: parse arg y; return word(y, words(y) ) /*get last word in list.*/ over: call ser 'word ' _ x _ arg(1); say eye 'game over,' you; signal done /*game over*/ show: o= last(list); say; call what; say; L= length(o); return verE: m= 0; do v=1 for L; m= m + (substr(ou,v,1)==substr(xu,v,1)); end; return m==L-1 verL: do v=1 for L; if space(overlay(' ', ou, v), 0)==xu then return 1; end; return 0 verG: do v=1 for w; if space(overlay(' ', xu, v), 0)==ou then return 1; end; return 0 what: say eye 'The current word in play is: ' _ o _; return /*──────────────────────────────────────────────────────────────────────────────────────*/ CBLF: parse arg you /*ask carbon-based life form for a word*/
do getword=0 by 0 until x\== say eye "What's your word to be played, " you'?' parse pull x; x= space(x); #= words(x); if #==0 then iterate; w= length(x) if #>1 then do; call ser 'too many words given: ' x x=; iterate getword end if \isMix(x) then do; call ser 'the name' _ x _ " isn't alphabetic" x=; iterate getword end end /*getword*/
if wordpos(x, list)>0 then call over " has already been used" xu= x; upper xu /*obtain an uppercase version of word. */ if \@.xu then call over " doesn't exist in the dictionary: " iFID if w <L then if \verL() then call over " isn't a legal letter deletion." if w==L then if \verE() then call over " isn't a legal letter substitution." if w >L then if \verG() then call over " isn't a legal letter addition." list= list x /*add word to the list of words used. */ return
/*──────────────────────────────────────────────────────────────────────────────────────*/ IDs: ?= "Enter the names of the people that'll be playing the WORDIFF game (or Quit):"
names= /*start with a clean slate (of names). */ do getIDs=0 by 0 until words(names)>1 say; say eye ? parse pull ids; ids= space( translate(ids, , ',') ) /*elide any commas. */ if ids== then iterate; q= ids; upper q /*use uppercase QUIT*/ if abbrev('QUIT', q, 1) then signal quit do j=1 for words(ids); x= word(ids, j) if \isMix(x) then do; call ser 'the name' _ x _ " isn't alphabetic" names=; iterate getIDs end if wordpos(x, names)>0 then do; call ser 'the name' _ x _ " is already taken" names=; iterate getIDs end names= space(names x) end /*j*/ end /*getIDs*/ say players= words(names) do until ans\== say eye 'The ' players " player's names are: " names say eye 'Is this correct?'; pull ans; ans= space(ans) end /*until*/ yeahs= 'yah yeah yes ja oui si da'; upper yeahs do ya=1 for words(yeahs) if abbrev( word(yeahs, ya), ans, 2) | ans=='Y' then return end /*ya*/ call IDS; return
/*──────────────────────────────────────────────────────────────────────────────────────*/ read: _= '───'; eye= copies('─', 8) /*define a couple of eye catchers. */
say; say eye eye eye 'Welcome to the WORDIFF word game.' eye eye eye; say @.= 0; starters= 0 do r=1 while lines(iFID)\==0 /*read each word in the file (word=X).*/ x= strip(linein(iFID)); y=x; upper x /*pick off a word from the input line. */ @.x= 1; L= length(x) /*set a semaphore for uppercased word. */ if L<3 | L>4 then iterate /*only use short words for the start. */ starters= starters + 1 /*bump the count of starter words. */ $$$.starters= y /*save short words for the starter word*/ end /*#*/ if r>100 & starters> 10 then return /*is the dictionary satisfactory ? */ call ser 'Dictionary file ' _ iFID _ "wasn't found or isn't satisfactory."; exit 13</lang>
- output when using the default inputs:
──────── ──────── ──────── Welcome to the WORDIFF word game. ──────── ──────── ──────── ──────── Enter the names of the people that'll be playing the WORDIFF game (or Quit): Teddy Jasmine Cy ◄■■■■■■■ user input ──────── The 3 player's names are: Teddy Jasmine Cy ──────── Is this correct? yes ◄■■■■■■■ user input ──────── OK, let's play the WORDIFF game. ──────── The current word in play is: ─── tame ─── ──────── What's your word to be played, Teddy? fame ◄■■■■■■■ user input ──────── The current word in play is: ─── fame ─── ──────── What's your word to be played, Jasmine? lame ◄■■■■■■■ user input ──────── The current word in play is: ─── lame ─── ──────── What's your word to be played, Cy? lam ◄■■■■■■■ user input ──────── The current word in play is: ─── lam ─── ──────── What's your word to be played, Teddy? lamb ◄■■■■■■■ user input ──────── The current word in play is: ─── lamb ─── ──────── What's your word to be played, Jasmine? limb ◄■■■■■■■ user input ──────── The current word in play is: ─── limb ─── ──────── What's your word to be played, Cy? climb ◄■■■■■■■ user input ──────── The current word in play is: ─── climb ─── ──────── What's your word to be played, Teddy? climbs ◄■■■■■■■ user input ──────── ***error*** word ─── climbs ─── doesn't exist in the dictionary: unixdict.txt. ──────── game over, Teddy
Wren
Due to a bug in the System.clock method (basically it gets suspended whilst waiting for user input), it is not currently possible to add timings. <lang ecmascript>import "random" for Random import "/ioutil" for File, Input import "/str" for Str import "/sort" for Find
var rand = Random.new() var words = File.read("unixdict.txt").trim().split("\n")
var player1 = Input.text("Player 1, please enter your name : ", 1) var player2 = Input.text("Player 2, please enter your name : ", 1) if (player2 == player1) player2 = player2 + "2"
var words3or4 = words.where { |w| w.count == 3 || w.count == 4 }.toList var n = words3or4.count var firstWord = words3or4[rand.int(n)] var prevLen = firstWord.count var prevWord = firstWord var used = [] var player = player1 System.print("\nThe first word is %(firstWord)\n") while (true) {
var word = Str.lower(Input.text("%(player), enter your word : ", 1)) var len = word.count var ok = false if (Find.first(words, word) == -1) { System.print("Not in dictionary.") } else if (used.contains(word)) { System.print("Word has been used before.") } else if (word == prevWord) { System.print("You must change the previous word.") } else if (len == prevLen) { var changes = 0 for (i in 0...len) { if (word[i] != prevWord[i]) { changes = changes + 1 } } if (changes > 1) { System.print("Only one letter can be changed.") } else ok = true } else if (len == prevLen + 1) { var addition = false var temp = word for (i in 0...prevLen) { if (word[i] != prevWord[i]) { addition = true temp = Str.delete(temp, i) if (temp == prevWord) { ok = true } break } } if (!addition) ok = true if (!ok) System.print("Invalid addition.") } else if (len == prevLen - 1) { var deletion = false var temp = prevWord for (i in 0...len) { if (word[i] != prevWord[i]) { deletion = true temp = Str.delete(temp, i) if (temp == word) { ok = true } break } } if (!deletion) ok = true if (!ok) System.print("Invalid deletion.") } else { System.print("Invalid change.") } if (ok) { prevLen = word.count prevWord = word used.add(word) player = (player == player1) ? player2 : player1 } else { System.print("So, sorry %(player), you've lost!") return }
}</lang>
- Output:
Sample game:
Player 1, please enter your name : Paddy Player 2, please enter your name : Maggie The first word is pan Paddy, enter your word : pen Maggie, enter your word : pin Paddy, enter your word : pint Maggie, enter your word : pant Paddy, enter your word : pane Maggie, enter your word : pang Paddy, enter your word : bang Maggie, enter your word : rang Paddy, enter your word : sang Maggie, enter your word : sing Paddy, enter your word : ling Not in dictionary. So, sorry Paddy, you've lost!