Implement hard mode, plus customization
game: - New flag `--hard`: Select from 3 levels of difficulty: - Greenie: only check greens - Hard: classic Wordle* hard mode - Grayless: All hints are checked, even gray letters - New flag: `--nokeyboard` disables the keyboard - Improved customization: - Box characters, colors, and keyboard layouts moved into wordlists! - Support for non-English alpha characters to be determined. It should work though, I think. - Most things are no longer hardcoded! The main function is still a bit of a spaghetti mess right now, though. To be fixed! dummy: - New customization parameters: - `"boxes"`: Strings to be printed when assembling the share string (from least to most significant order) - `"colors"`: Text colors, to be printed on-screen. Order: Gray, Yellow, Green, Text Color, Keyboard background - `"keyboard"`: The keyboard layout. One string per line, of keyboard.
This commit is contained in:
parent
b874cfc101
commit
86280a4b55
@ -1,3 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
# lol
|
||||
import game
|
||||
|
||||
game.init()
|
||||
game.game()
|
||||
game.end()
|
10
src/dummy.py
10
src/dummy.py
@ -5,11 +5,13 @@ Metadata = {
|
||||
"name": "Dummy",
|
||||
"version": "dummy",
|
||||
"guesses": 4,
|
||||
"launch": (2222, 2, 16)
|
||||
"launch": (2222, 2, 16),
|
||||
"boxes": ["[]", "!!", "<3", " ", " "],
|
||||
"colors": [0x1666666, 0x1bb9900, 0x1008800, 0xe8e8e8, 0x1222222],
|
||||
"keyboard": ["aaaaaaaaaa"], # max 10 keys per row, please
|
||||
}
|
||||
# Your custom list of non-answer words goes here
|
||||
Words = [
|
||||
"b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
|
||||
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
|
||||
Words = ["b", "c", "d", "e", "f"]
|
||||
|
||||
# Your custom list of answer-words goes here
|
||||
Answers = ["a"]
|
195
src/game.py
195
src/game.py
@ -18,9 +18,9 @@ from signal import SIGINT
|
||||
from os import get_terminal_size, terminal_size
|
||||
|
||||
# Provide a clean getaway
|
||||
def end():
|
||||
def end(*args, **kwargs):
|
||||
ui.clear()
|
||||
exit()
|
||||
exit(*args, **kwargs)
|
||||
|
||||
# Set up sigint handler
|
||||
def handler(signum, frame):
|
||||
@ -33,14 +33,16 @@ parser = argparse.ArgumentParser(
|
||||
description='A barebones CLI Wordle clone, using official Wardle solutions. You have been warned.'
|
||||
)
|
||||
parser.add_argument('day', nargs="?", type=int, help="Wordle day number")
|
||||
parser.add_argument('-g', metavar="guesses", type=int, help="number of guesses (limited by terminal size)")
|
||||
parser.add_argument('-g', metavar="guesses", type=int, help="number of guesses (bounded by terminal size)")
|
||||
parser.add_argument('--hard', metavar="0-3", \
|
||||
nargs="?", const=2, type=int, help="0:normal 1:~greenie 2:*hard 3:+greyless")
|
||||
parser.add_argument('--word', metavar="solution", help="force a particular word")
|
||||
parser.add_argument('--list', metavar="wordlist", help="use a custom wordlist")
|
||||
parser.add_argument('--classic', action='store_true', help="use the classic Wordle word list")
|
||||
parser.add_argument('--nonsense', action='store_true', help="allow nonsensical guesses")
|
||||
parser.add_argument('--hard', action='store_true', help="enable hard mode (wip)")
|
||||
parser.add_argument('--center', action='store_true', help="center the screen")
|
||||
# TODO: Implement hard mode properly (more than just a *)
|
||||
parser.add_argument('--classic', action='store_true', help="use the classic Wordle word list")
|
||||
#parser.add_argument('-c', action='store_true', help="enable colorblind mode") # TODO
|
||||
parser.add_argument("--nokeyboard", action='store_true', help="disable the keyboard")
|
||||
parser.add_argument('--nonsense', action='store_true', help="allow nonsensical guesses")
|
||||
parser.add_argument('--center', action='store_true', help="center the screen")
|
||||
|
||||
# Parse the args
|
||||
args = parser.parse_args()
|
||||
@ -48,6 +50,16 @@ args = parser.parse_args()
|
||||
# Handle the args
|
||||
# Select the correct word list
|
||||
Words, Answers, data = [], [], {"name":"", "version":"", "guesses":-1, "launch":(1970,1,1)}
|
||||
default_data = {
|
||||
"name": "Wardle.py",
|
||||
"version": "-1",
|
||||
"guesses": 6,
|
||||
"launch": (2022, 2, 13),
|
||||
"boxes": ["⬛", "🟨", "🟩", " ", " "],
|
||||
"colors": [0x13a3a3c, 0x1b59f3b, 0x1538d4e, 0xd7dadc, 0x1121213],
|
||||
"keyboard": ["qwertyuiop", "asdfghjkl", "zxcvbnm"+u"\u23CE"]
|
||||
}
|
||||
data = dict(default_data)
|
||||
if not args.list:
|
||||
args.list = "w2"
|
||||
args.list = "w" if args.classic else args.list
|
||||
@ -76,20 +88,32 @@ if args.word:
|
||||
else:
|
||||
d = len(Answers) - 1 if d >= len(Answers) else 0 if d < 0 else d
|
||||
solution = Answers[d]
|
||||
|
||||
# Bound hardmode
|
||||
if not args.hard:
|
||||
args.hard = 0
|
||||
elif args.hard < 0:
|
||||
args.hard = 0
|
||||
elif args.hard > 3:
|
||||
args.hard = 3
|
||||
# End arg parsing
|
||||
|
||||
|
||||
# ! The game is below this point
|
||||
|
||||
# Good data structures to have
|
||||
# Load data
|
||||
for key in default_data:
|
||||
if key not in data:
|
||||
data[key] = default_data[key]
|
||||
|
||||
# Box characters!
|
||||
GRAY, YELLOW, GREEN, WHITE, BLACK = range(5)
|
||||
boxes = ["⬛", "🟨", "🟩", " ", " "]
|
||||
colors= [0x13a3a3c, 0x1b59f3b, 0x1538d4e, 0xd7dadc, 0x1121213]
|
||||
boxes = data["boxes"] if "boxes" in data else default_data["boxes"]
|
||||
colors = data["colors"]
|
||||
|
||||
# Guesses go here
|
||||
guesses = []
|
||||
letters = [4] * 27
|
||||
|
||||
|
||||
# Letter is in boxes
|
||||
def wordbox(word, showword = 0):
|
||||
@ -101,7 +125,8 @@ def wordbox(word, showword = 0):
|
||||
if guess[i] == sol[i]:
|
||||
# Mark the letter 'green' (2)
|
||||
line[i] = GREEN
|
||||
letters[letter_num(word[i])] = GREEN
|
||||
if word[i] in letters:
|
||||
letters[word[i]] = GREEN
|
||||
# Remove letter from solution and guess
|
||||
sol[i] = guess[i] = GRAY
|
||||
# Yellow pass
|
||||
@ -109,82 +134,135 @@ def wordbox(word, showword = 0):
|
||||
if guess[i] and word[i] in sol:
|
||||
# Mark the letter 'yellow' (1)
|
||||
line[i] = YELLOW
|
||||
letters[letter_num(word[i])] = YELLOW
|
||||
if word[i] in letters:
|
||||
l = letters[word[i]]
|
||||
if l in (BLACK, GRAY): letters[word[i]] = YELLOW
|
||||
# Remove letter from solution and guess
|
||||
sol[sol.index(word[i])] = guess[i] = GRAY
|
||||
# Gray pass
|
||||
for i in range(len(word)):
|
||||
if line[i] == GRAY:
|
||||
letters[letter_num(word[i])] = GRAY
|
||||
if word[i] in letters and \
|
||||
line[i] == GRAY and letters[word[i]] == BLACK:
|
||||
if word[i] in letters:
|
||||
letters[word[i]] = GRAY
|
||||
# Turn blocks into a string, and print it
|
||||
output = ""
|
||||
if showword:
|
||||
# Move up to replace the input box
|
||||
output += ui.m(0,-1)
|
||||
for i in range(len(word)):
|
||||
output += f"{c.c24(colors[WHITE])}{c.c24(colors[line[i]])} {word[i].upper()} {c.RESET}"
|
||||
#output += c.c24(colors[3]) + c.c24(colors[line[i]]) + " " + word[i].upper() + " " + c.RESET
|
||||
else:
|
||||
for i in line:
|
||||
output += f"{c.c24(colors[WHITE])}{c.c24(colors[i])}{boxes[i]}{c.RESET}"
|
||||
return output
|
||||
return f"{output}\n"
|
||||
|
||||
def letter_num(char):
|
||||
if ord('a') <= ord(char.lower()) <= ord('z'):
|
||||
return ord(char.lower()) - ord('a')
|
||||
return 26
|
||||
|
||||
letters = {}
|
||||
keyboard = data["keyboard"]
|
||||
|
||||
def init_keeb():
|
||||
for s in keyboard:
|
||||
for char in s:
|
||||
letters[char] = BLACK
|
||||
return
|
||||
|
||||
def keeb(x, y):
|
||||
if args.nokeyboard:
|
||||
return
|
||||
def colorify(string):
|
||||
s = ""
|
||||
for char in string:
|
||||
s += c.c24(colors[letters[letter_num(char)]]) + " " + char + " " + c.RESET
|
||||
s += c.c24(colors[letters[char]]) + " " + char + " " + c.RESET
|
||||
return s
|
||||
S = ["qwertyuiop", "asdfghjkl", "zxcvbnm"+u"\u23CE"]
|
||||
for i in range(len(S)):
|
||||
ui.uprint(x-(3*len(S[i])//2), y+i, colorify(S[i]))
|
||||
for i in range(len(keyboard)):
|
||||
ui.uprint(x-(3*len(keyboard[i])//2), y+i, colorify(keyboard[i]))
|
||||
|
||||
ORDINAL = ["1st", "2nd", "3rd", "4th", "5th"]
|
||||
|
||||
def hardmode(word, solution:str, level):
|
||||
# If hard mode disabled, Continue
|
||||
if not args.hard:
|
||||
return
|
||||
hints = {
|
||||
GREEN: [],
|
||||
YELLOW: [],
|
||||
GRAY: [],
|
||||
BLACK: [],
|
||||
}
|
||||
# For each letter, record which state it has
|
||||
for letter in word:
|
||||
if letter not in letters:
|
||||
letters[letter] = BLACK
|
||||
hints[letters[letter]].append(letter)
|
||||
# Green pass
|
||||
if level >= 1:
|
||||
for l in hints[GREEN]:
|
||||
i = solution.find(l)
|
||||
if i >= 0 and word[i] is not l:
|
||||
ORDINAL = ["1st", "2nd", "3rd", "4th", "5th"]
|
||||
return f"{ORDINAL[i]} letter must be {l}"
|
||||
# Yellow pass
|
||||
if level >= 2:
|
||||
for l in hints[YELLOW]:
|
||||
if l not in word:
|
||||
return f"Guess must contain {l}"
|
||||
# Gray pass
|
||||
if level >= 3:
|
||||
for l in hints[GRAY]:
|
||||
if l in word:
|
||||
return f"Guess must not contain {l}"
|
||||
return
|
||||
|
||||
|
||||
# intro: Print the intro text
|
||||
def intro():
|
||||
title = f"{data['name']} {d}{'*' if args.hard else ''}"
|
||||
center = ui.m(-len(title)//2,0)
|
||||
#spaces = ' ' * ((14 - len(title)) // 2)
|
||||
# [ Wordle 100* ]
|
||||
intro = f"{c.RESET}{center}{title}"
|
||||
return intro
|
||||
def intro(x, y):
|
||||
# Assemble the text
|
||||
HARDCHARS = [" ", "~", "*", "+"]
|
||||
title = f"{data['name']} {d}{HARDCHARS[args.hard]}"
|
||||
# Center the text
|
||||
ui.uprint(x-(len(title)//2), y, f"{c.RESET}{title}")
|
||||
|
||||
def game():
|
||||
thicc = {"top":0, "intro": 2, "field":max_guesses+1, "keeb": 0 if args.nokeyboard else 3}
|
||||
x = ui.size[0]//2
|
||||
ui.uprint(x, 0, intro())
|
||||
# Print the title (2 rows)
|
||||
intro(x,thicc["top"])
|
||||
# Field goes here (max_guesses+1) rows
|
||||
# Print the keyboard
|
||||
init_keeb()
|
||||
keeb(x, thicc["intro"] + thicc["field"])
|
||||
|
||||
words = Words + Answers
|
||||
guess = 0
|
||||
keeb(x, 3 + max_guesses)
|
||||
while guess < max_guesses:
|
||||
while len(guesses) < max_guesses:
|
||||
# Calculate board's starting depth
|
||||
health = 2 + len(guesses)
|
||||
# lol i should probably use curses
|
||||
user_input = ui.uinput(x-(len(solution)//2), len(guesses) + 2, c.clear()).lower()
|
||||
# This provides some leniency for multiple-length-word lists,
|
||||
user_input = ui.uinput(x-(len(solution)//2), health, f"{c.clear()}{c.c24(colors[WHITE])}").lower()
|
||||
# This provides some leniency for multiple-length word lists,
|
||||
# by first checking if the input is the length of the solution,
|
||||
# and discarding without penalty otherwise.
|
||||
if len(user_input) is len(solution) and (user_input in words or args.nonsense):
|
||||
|
||||
if s := hardmode(user_input, solution, args.hard):
|
||||
ui.uprint(x-(len(s)//2), health, s)
|
||||
sleep(1)
|
||||
elif len(user_input) is len(solution)and (user_input in words or args.nonsense):
|
||||
# Add guesses to the guessed list, and put the word in boxes
|
||||
guesses.append(user_input)
|
||||
ui.uprint(x-(len(solution)*3//2), len(guesses) + 2, wordbox(user_input, 1))
|
||||
# Print the new keyboard
|
||||
keeb(x+1, 3 + max_guesses)
|
||||
# win condition
|
||||
ui.uprint(x-(len(solution)*3//2), health, wordbox(user_input, 1))
|
||||
# update the keeb
|
||||
keeb(x, thicc["intro"] + thicc["field"])
|
||||
if (user_input == solution):
|
||||
# win
|
||||
win(True)
|
||||
return
|
||||
else:
|
||||
guess += 1
|
||||
else:
|
||||
# Allow the user to make it stop
|
||||
if user_input == "exit":
|
||||
end()
|
||||
# Alert the user that the word was not found in the list, and wait for them to read it
|
||||
ui.uprint (x-(3*len(solution)*3//2), len(guesses) + 2, "not in word list.")
|
||||
sleep(1.5)
|
||||
s = "Not in word list."
|
||||
ui.uprint (x-(len(s)//2), health, s)
|
||||
sleep(1)
|
||||
# lose
|
||||
win(False)
|
||||
|
||||
@ -192,16 +270,15 @@ def win(won):
|
||||
used_guesses = len(guesses) if won else "X"
|
||||
share = f"{data['name']} {d} {used_guesses}/{max_guesses}{'*' if args.hard else ''}\n"
|
||||
for gi in guesses:
|
||||
share += f"\n{wordbox(gi)}"
|
||||
share += f"{wordbox(gi)}"
|
||||
# ui.move to the end of the screen, and print the sharable boxes
|
||||
print(f"{ui.m(0, ui.size[1] + 1)}{share}", end="")
|
||||
|
||||
|
||||
if (args.center):
|
||||
w, h, stty = max(30, 3*len(solution)), 7 + max_guesses, get_terminal_size()
|
||||
# You can init a "screen" 'anywhere'!
|
||||
|
||||
ui.init(w, h, (stty.columns - w) // 2, 0)
|
||||
else:
|
||||
ui.init(max(30, 3*len(solution)), 7 + max_guesses)
|
||||
game()
|
||||
def init():
|
||||
h = 2 + max_guesses + 1 + (0 if args.nokeyboard else 4)
|
||||
if (args.center):
|
||||
w, stty = max(30, 3*len(solution)), get_terminal_size()
|
||||
# You can init a "screen" 'anywhere'!
|
||||
ui.init(w, h, (stty.columns - w) // 2, 0)
|
||||
else:
|
||||
ui.init(max(30, 3*len(solution)), h)
|
2
src/w.py
2
src/w.py
@ -8,7 +8,7 @@ Metadata = {
|
||||
"name": "Wordle Classic",
|
||||
"version": "powerlanguage",
|
||||
"guesses": 6,
|
||||
"launch": (2021, 6, 19)
|
||||
"launch": (2021, 6, 19),
|
||||
}
|
||||
|
||||
# These are not answers
|
||||
|
Loading…
Reference in New Issue
Block a user