-- DESCRIPTION: Guess the five-letter word in six tries local WORDS_PATH = "/.apps/Wordle/words.txt" local WORD_LENGTH = 5 local MAX_GUESSES = 6 local keyboard = { { "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P" }, { "A", "S", "D", "F", "G", "H", "J", "K", "L" }, { "OK", "Z", "X", "C", "V", "B", "N", "M", "DEL" } } local answer = nil local current = "" local guesses = {} local results = {} local keyStates = {} local cursorRow = 1 local cursorColumn = 1 local message = "" local gameOver = false local firstDraw = true local needsDraw = true -- Word Source - Replace only this function to fetch answers from another source. local function getRandomWord() local size = fs.fileSize(WORDS_PATH) if not size then return nil, "Missing " .. WORDS_PATH end if size == 0 then return nil, "Word list is empty" end for _ = 1, 8 do local word = fs.readLineAt(WORDS_PATH, math.random(0, size - 1)) or fs.readLineAt(WORDS_PATH, 0) if word then word = word:match("^%s*(.-)%s*$") if #word == WORD_LENGTH and word:match("^%a+$") then return word:upper() end end end return nil, "Word list has no five-letter words" end local function startGame() local errorMessage answer, errorMessage = getRandomWord() current = "" guesses = {} results = {} keyStates = {} cursorRow = 1 cursorColumn = 1 gameOver = not answer message = errorMessage or "Pick a letter" firstDraw = true needsDraw = true end local function evaluateGuess(guess) local status = {} local remaining = {} for index = 1, WORD_LENGTH do local letter = answer:sub(index, index) if guess:sub(index, index) == letter then status[index] = 2 else remaining[letter] = (remaining[letter] or 0) + 1 end end for index = 1, WORD_LENGTH do if not status[index] then local letter = guess:sub(index, index) if (remaining[letter] or 0) > 0 then status[index] = 1 remaining[letter] = remaining[letter] - 1 else status[index] = 0 end end end return status end local function submitGuess() if #current ~= WORD_LENGTH then message = "Enter five letters" return end local status = evaluateGuess(current) guesses[#guesses + 1] = current results[#results + 1] = status for index = 1, WORD_LENGTH do local letter = current:sub(index, index) keyStates[letter] = math.max(keyStates[letter] or 0, status[index]) end if current == answer then message = "Solved! Press OK for a new word" gameOver = true elseif #guesses == MAX_GUESSES then message = answer .. " - Press OK for a new word" gameOver = true else message = tostring(MAX_GUESSES - #guesses) .. " guesses left" end current = "" end local function useKey(key) if key == "OK" then submitGuess() elseif key == "DEL" then current = current:sub(1, -2) message = "Pick a letter" elseif #current < WORD_LENGTH then current = current .. key message = "Pick a letter" end needsDraw = true end local function centeredText(font, x, y, width, text, color, style) local textWidth = gui.getTextWidth(font, text, style) gui.drawText(font, x + math.floor((width - textWidth) / 2), y, text, color, style) end local function drawTile(letter, status, x, y, size) local fill = COLOR_WHITE local textColor = COLOR_BLACK if status == 2 then fill = COLOR_BLACK textColor = COLOR_WHITE elseif status == 1 then fill = COLOR_LIGHT_GRAY elseif status == 0 then fill = COLOR_DARK_GRAY textColor = COLOR_WHITE end if status ~= nil then gui.fillRoundedRect(x, y, size, size, 5, fill) end gui.drawRoundedRect(x, y, size, size, 2, 5, COLOR_BLACK) -- Present Marker - Gray shades alone are hard to tell apart on e-ink, so "in word, wrong spot" also gets an inset ring. if status == 1 then gui.drawRoundedRect(x + 5, y + 5, size - 10, size - 10, 2, 3, COLOR_BLACK) end if letter ~= "" then centeredText(FONT_NOTOSANS_16, x, y + math.floor(size / 2) - 10, size, letter, textColor, STYLE_BOLD) end end local function drawGrid(width, height) local gap = 5 local size = math.min(62, math.floor((width - 60 - gap * (WORD_LENGTH - 1)) / WORD_LENGTH), math.floor((height * 0.53 - gap * (MAX_GUESSES - 1)) / MAX_GUESSES)) local gridWidth = size * WORD_LENGTH + gap * (WORD_LENGTH - 1) local startX = math.floor((width - gridWidth) / 2) local startY = 72 for row = 1, MAX_GUESSES do local word = guesses[row] or (row == #guesses + 1 and current or "") local status = results[row] for column = 1, WORD_LENGTH do drawTile(word:sub(column, column), status and status[column] or nil, startX + (column - 1) * (size + gap), startY + (row - 1) * (size + gap), size) end end return startY + MAX_GUESSES * size + (MAX_GUESSES - 1) * gap end local function drawKeyboard(width, height, top) local gap = 4 local margin = 12 local keyWidth = math.floor((width - margin * 2 - gap * 9) / 10) local keyHeight = math.min(48, math.floor((height - top - 70 - gap * 2) / 3)) for rowIndex, row in ipairs(keyboard) do local rowWidth = #row * keyWidth + (#row - 1) * gap local startX = math.floor((width - rowWidth) / 2) local y = top + (rowIndex - 1) * (keyHeight + gap) for columnIndex, key in ipairs(row) do local x = startX + (columnIndex - 1) * (keyWidth + gap) local status = keyStates[key] local fill = status == 2 and COLOR_BLACK or status == 1 and COLOR_LIGHT_GRAY or status == 0 and COLOR_DARK_GRAY or COLOR_WHITE local textColor = (status == 2 or status == 0) and COLOR_WHITE or COLOR_BLACK gui.fillRoundedRect(x, y, keyWidth, keyHeight, 4, fill) gui.drawRoundedRect(x, y, keyWidth, keyHeight, 1, 4, COLOR_BLACK) if status == 1 then gui.drawRoundedRect(x + 4, y + 4, keyWidth - 8, keyHeight - 8, 1, 3, COLOR_BLACK) end if rowIndex == cursorRow and columnIndex == cursorColumn then gui.drawRoundedRect(x + 2, y + 2, keyWidth - 4, keyHeight - 4, 3, 3, textColor) end centeredText(FONT_SMALL, x, y + math.floor(keyHeight / 2) - 7, keyWidth, key, textColor, STYLE_BOLD) end end end local function render() local width, height = gui.width(), gui.height() gui.clear() gui.drawCenteredText(FONT_NOTOSANS_16, 16, "WORDLE", COLOR_BLACK, STYLE_BOLD) gui.drawCenteredText(FONT_SMALL, 48, message, COLOR_BLACK, STYLE_REGULAR) if answer then local gridBottom = drawGrid(width, height) drawKeyboard(width, height, gridBottom + 14) end gui.drawButtonHints("<<", "Select", "<", ">") gui.refresh(firstDraw and REFRESH_HALF or REFRESH_FAST) firstDraw = false needsDraw = false end function init() startGame() end function draw() if input.wasPressed("back") then sys.exit() return end if gameOver and answer then if input.wasPressed("confirm") then startGame() end elseif answer then if input.wasPressed("left") then cursorColumn = cursorColumn == 1 and #keyboard[cursorRow] or cursorColumn - 1 needsDraw = true elseif input.wasPressed("right") then cursorColumn = cursorColumn == #keyboard[cursorRow] and 1 or cursorColumn + 1 needsDraw = true elseif input.wasPressed("up") then cursorRow = cursorRow == 1 and #keyboard or cursorRow - 1 cursorColumn = math.min(cursorColumn, #keyboard[cursorRow]) needsDraw = true elseif input.wasPressed("down") then cursorRow = cursorRow == #keyboard and 1 or cursorRow + 1 cursorColumn = math.min(cursorColumn, #keyboard[cursorRow]) needsDraw = true elseif input.wasPressed("confirm") then useKey(keyboard[cursorRow][cursorColumn]) end end if needsDraw then render() end end