Wordiff: Difference between revisions

From Rosetta Code
Content added Content deleted
m (added the "Games" category to the (draft) task. .)
m (→‎{{header|REXX}}: shown a different game.)
Line 414: Line 414:




──────── The current word in play is: ─── sewn ───
──────── The current word in play is: ─── tame ───


──────── What's your word to be played, Teddy?
──────── What's your word to be played, Teddy?
sown ◄■■■■■■■ user input
fame ◄■■■■■■■ user input


──────── The current word in play is: ─── sown ───
──────── The current word in play is: ─── fame ───


──────── What's your word to be played, Jasmine?
──────── What's your word to be played, Jasmine?
down ◄■■■■■■■ user input
lame ◄■■■■■■■ user input


──────── The current word in play is: ─── down ───
──────── The current word in play is: ─── lame ───


──────── What's your word to be played, Cy?
──────── What's your word to be played, Cy?
downs ◄■■■■■■■ user input
lam ◄■■■■■■■ user input


──────── The current word in play is: ─── lam ───


──────── The current word in play is: ─── downs ───


──────── What's your word to be played, Teddy?
──────── What's your word to be played, Teddy?
dawns ◄■■■■■■■ user input
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 ─── dawns ─── doesn't exist in the dictionary: unixdict.txt.
──────── ***error*** word ─── climbs ─── doesn't exist in the dictionary: unixdict.txt.


──────── game over, Teddy
──────── game over, Teddy

Revision as of 03:43, 31 July 2021

Wordiff is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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:

  1. a deletion of one letter;
  2. addition of one letter;
  3. 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.

Nim

Translation of: Python

<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

Library: Wren-ioutil
Library: Wren-str
Library: Wren-sort

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!