# 5e26bc86890e055e6548a3566125dd3ce842ec170e2c5febb4a5efd2e62a9330 Elan 2.0.0-alpha valid

constant allWords set to "AAHED ZYMIC"

constant allValidAnswers set to "ABACK ZONAL"

main
  while true
    variable choice set to inputIntBetween("1 to solve puzzle set by computer\n2 to set a puzzle for computer to solve\n3 to test test_effectiveness of computer's algorithm\n4 to look up word", 1, 4)
    call clearAllDisplays()
    call executeOption(choice)
    call pressAnyKeyToContinue()
    call clearAllDisplays()
  end while
end main

procedure executeOption(choice as Int)
  if choice is 1 then
    call print("Reduce screen magnification if you can't see all the keys")
    call playGame()
  elif choice is 2 then
    call print("Mark using: 0 (grey), 1 (yellow), 2 (green), Enter")
    call playReverseGame()
  elif choice is 3 then
    call print("Please wait for analysis to complete ...")
    call analyse()
  else
    variable word set to inputStringWithLimits("Enter word: ", 5, 5).upperCase()
    if allValidAnswers.contains(word) then
      call print($"{word} is a valid answer")
    elif allWords.contains(word) then
      call print($"{word} is not a valid answer, but is a valid guess word")
    else
      call print($"{word} is not a recognised word")
    end if
  end if
end procedure

procedure playGame()
  variable grid set to new List<of List<of String>>()
  call initialiseGrid(grid)
  variable used set to new Dictionary<of String, String>()
  for letter in "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    set used[letter] to " "
  end for
  call displayHtml(drawGrid(grid) + drawKeyboard(used))
  variable target set to allValidAnswers.split(" ")[randint(0, 2308)]
  variable attemptNo set to 0
  variable solved set to false
  while (attemptNo < 6) and (not solved)
    call enterAttempt(attemptNo, grid, used)
    variable solvedRef set to new AsRef<of Boolean>(solved)
    call colourAttempt(attemptNo, grid, target, solvedRef, used)
    set solved to solvedRef.value()
    set attemptNo to attemptNo + 1
  end while
  call clearPrintedText()
  if solved then
    call print("Well done!")
  end if
end procedure

procedure initialiseGrid(grid as List<of List<of String>>)
  for i in range(0, 5)
    variable sa set to new List<of String>()
    for j in range(0, 6)
      call sa.append("")
    end for
    call grid.append(sa)
  end for
end procedure

procedure enterAttempt(attemptNo as Int, grid as List<of List<of String>>, used as Dictionary<of String, String>)
  variable chNo set to 0
  variable word set to ""
  while chNo <= 5
    variable k set to waitForKey().upperCase()
    if (k.equals("BACKSPACE")) and (chNo > 0) then
      set chNo to chNo - 1
      set grid[chNo][attemptNo] to ""
      set word to word[0..chNo]
      call clearPrintedText()
    elif (chNo < 5) and isUCLetter(k) then
      set grid[chNo][attemptNo] to k
      set chNo to chNo + 1
      set word to word + k
    elif (chNo is 5) and (k.equals("ENTER")) then
      if allWords.contains(word) then
        set chNo to chNo + 1
      else
        call print("Invalid word")
      end if
    end if
    call displayHtml(drawGrid(grid) + drawKeyboard(used))
  end while
end procedure

procedure colourAttempt(attemptNo as Int, grid as List<of List<of String>>, target as String, solved as AsRef<of Boolean>, used as Dictionary<of String, String>)
  variable attempt set to getWord(attemptNo, grid)
  variable marks set to markAttempt(attempt, target)
  for i in range(0, 5)
    variable letter set to grid[i][attemptNo]
    variable mark set to marks[i]
    set grid[i][attemptNo] to letter + mark
    if mark.isAfter(used[letter]) then
      set used[letter] to mark
    end if
  end for
  if marks.equals("22222") then
    call solved.set(true)
  end if
  call displayHtml(drawGrid(grid) + drawKeyboard(used))
end procedure

procedure playReverseGame()
  variable grid set to new List<of List<of String>>()
  call initialiseGrid(grid)
  variable attemptNo set to 0
  variable solved set to false
  variable possible set to allValidAnswers.split(" ")
  variable attempt set to "ARISE"
  while (attemptNo < 6) and (not solved)
    for i in range(0, 5)
      set grid[i][attemptNo] to attempt[i]
    end for
    call displayHtml(drawGrid(grid))
    variable mark set to ""
    variable markRef set to new AsRef<of String>(mark)
    call enterMark(attemptNo, grid, markRef)
    set mark to markRef.value()
    if mark.equals("22222") then
      set solved to true
    else
      set attemptNo to attemptNo + 1
      set possible to possibleAnswersAfterAttempt(possible, attempt, mark)
    end if
    if possible.length() is 0 then
      call print("No possible answer matches marks so far. Press any key to continue")
      set attemptNo to 6
    else
      set attempt to possible[divAsInt(possible.length(), 2)]
    end if
  end while
end procedure

procedure enterMark(attemptNo as Int, grid as List<of List<of String>>, markRef as AsRef<of String>)
  variable mark set to markRef.value()
  variable guess set to getWord(attemptNo, grid)
  variable chNo set to 0
  while chNo <= 5
    variable k set to waitForKey().upperCase()
    if (k.equals("BACKSPACE")) and (chNo > 0) then
      set chNo to chNo - 1
      variable entry set to grid[chNo][attemptNo]
      set grid[chNo][attemptNo] to entry[0]
      set mark to mark.subString(0, chNo)
    elif (chNo < 5) and "012".contains(k) then
      variable ch set to grid[chNo][attemptNo]
      set grid[chNo][attemptNo] to ch + k
      set chNo to chNo + 1
      set mark to mark + k
    elif (chNo is 5) and (k.equals("ENTER")) then
      set chNo to chNo + 1
    end if
    call displayHtml(drawGrid(grid))
  end while
  call markRef.set(mark)
end procedure

procedure analyse()
  variable outcomes set to new List<of Int>()
  call outcomes.initialise(10, 0)
  for word in allValidAnswers.split(" ")
    variable possible set to allValidAnswers.split(" ")
    variable mark set to ""
    variable attempt set to "RAISE"
    variable attempts set to 0
    while (not mark.equals("22222"))
      set attempts to attempts + 1
      set mark to markAttempt(attempt, word)
      set possible to possibleAnswersAfterAttempt(possible, attempt, mark)
      set attempt to possible[0]
    end while
    set outcomes[attempts] to outcomes[attempts] + 1
  end for
  variable success set to 0
  variable weightedSum set to 0
  for i in range(1, 7)
    set success to success + outcomes[i]
    set weightedSum to weightedSum + (i*outcomes[i])
  end for
  call clearPrintedText()
  variable solved set to (success/2309.0*100).floor()
  variable avg set to divAsFloat(weightedSum, success).round(2)
  variable pc set to "%"
  call print($"For all 2309 possible answers,\nthe current reverse-game algorithm \nsolved {solved}{pc} within 6 attempts,\nwith an average of {avg} attempts.")
end procedure

function isUCLetter(k as String) returns Boolean
  variable unicode set to k.asUnicode()
  return (k.length() is 1) and (unicode > 64) and (unicode < 91)
end function

test test_isUCLetter
  assert isUCLetter("A") is true
  assert isUCLetter("Z") is true
  assert isUCLetter("a") is false
  assert isUCLetter("1") is false
  assert isUCLetter(" ") is false
end test

function getWord(attemptNo as Int, grid as List<of List<of String>>) returns String
  variable guessWord set to ""
  for i in range(0, 5)
    set guessWord to guessWord + grid[i][attemptNo]
  end for
  return guessWord
end function

function setChar(word as String, n as Int, newChar as String) returns String
  return word.subString(0, n) + newChar + word.subString(n + 1, word.length())
end function

test test_setChar
  assert setChar("ABCDE", 0, "_") is "_BCDE"
  assert setChar("ABCDE", 4, "_") is "ABCD_"
end test

function markAttempt(attempt as String, target as String) returns String
  variable mark set to "00000"
  variable unused set to target
  for n in range(0, 5)
    if attempt[n].equals(unused[n]) then
      set mark to setChar(mark, n, "2")
      set unused to setChar(unused, n, " ")
    end if
  end for
  for n in range(0, 5)
    if (not mark[n].equals("2")) and unused.contains(attempt[n]) then
      set mark to setChar(mark, n, "1")
      set unused to setChar(unused, unused.indexOf(attempt[n]), " ")
    end if
  end for
  return mark
end function

test test_markAttempt
  assert markAttempt("ABCDE", "XXXXX") is "00000"
  assert markAttempt("ABCDE", "BCDEA") is "11111"
  assert markAttempt("ABCDE", "ABCDE") is "22222"
  assert markAttempt("SAINT", "LADLE") is "02000"
  assert markAttempt("IDEAL", "LADLE") is "01111"
  assert markAttempt("CABAL", "RECAP") is "10020"
  assert markAttempt("COLON", "GLORY") is "01100"
  assert markAttempt("AORTA", "RATTY") is "10120"
  assert markAttempt("RATTY", "AORTA") is "11020"
  assert markAttempt("FAIRY", "RATTY") is "02012"
end test

function possibleAnswersAfterAttempt(possible as List<of String>, attempt as String, mark as String) returns List<of String>
  variable newPossible set to new List<of String>()
  for word in possible
    variable markForWord set to markAttempt(attempt, word)
    if markForWord.equals(mark) then
      set newPossible to newPossible.withAppend(word)
    end if
  end for
  return newPossible
end function

test test_possibleAnswersAfterAttempt
  variable prior set to ["ABCDE", "BCDEA", "CDEAB", "DEABC", "EABCD"]
  assert possibleAnswersAfterAttempt(prior, "AAAAA", "20000") is ["ABCDE"]
  assert possibleAnswersAfterAttempt(prior, "AXXXX", "10000") is ["BCDEA", "CDEAB", "DEABC", "EABCD"]
  assert possibleAnswersAfterAttempt(prior, "AXXBX", "10010") is ["BCDEA", "CDEAB", "EABCD"]
  assert possibleAnswersAfterAttempt(["RATTY"], "AORTA", "10120") is ["RATTY"]
  assert possibleAnswersAfterAttempt(["FAIRY", "HAIRY", "RAINY", "RASPY", "RATTY"], "FAIRY", "02012") is ["RASPY", "RATTY"]
end test

function drawGrid(grid as List<of List<of String>>) returns String
  variable html set to $"<style>{style}</style> <grid>"
  for row in range(0, 6)
    set html to html + "<word>"
    for col in range(0, 5)
      variable entry set to grid[col][row]
      variable ch set to if(entry.length() > 0, entry[0], "")
      variable mark set to if(entry.length() > 1, entry.subString(1, entry.length()), "")
      set html to html + $"<ch class='_{mark}'>{ch}</ch>"
    end for
    set html to html + "</word>"
  end for
  return html + "</grid>"
end function

function drawKeyboard(used as Dictionary<of String, String>) returns String
  variable html set to "<keyboard><div>"
  for k in "QWERTYUIOP-ASDFGHJKL-ZXCVBNM"
    if k.equals("-") then
      set html to html + "</div><div>"
    else
      set html to html + $"<key class='_{used[k]}'>{k}</key>"
    end if
  end for
  return html + "<key></key></div></keyboard>"
end function

constant style set to "grid { display: flex; flex-direction: column; margin-top: 40px; width: 500px;}word { display: flex; flex-direction: row; margin: auto;}ch, key { font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; font-weight: bold; background-color: white;}ch { text-align: center; font-size: 18pt; border: solid, 1.5px, black; margin: 2px; width: 37.5px; height: 37.5px; line-height: 33px;}ch:empty, key:empty { border-color: lightgrey;}key._ { background-color: lightgrey;}ch._2, key._2 { background-color: #6aaa64; border-color: #6aaa64; color: white;}ch._1, key._1 { background-color: #c9b458; border-color: #c9b458; color: white;}ch._0, key._0 { background-color: #787c7e; border-color: #787c7e; color: white;}keyboard { width: 500px; display: flex; flex-direction: column; margin-top:5px;}keyboard div { display: flex; flex-direction: row; margin:auto;}key { display: block; float: left; font-size: 10pt; width: 23px; margin: 2px; padding-bottom: 6px; padding-top:5px; text-align: center; border-radius: 5px;}"
