Skip to content

Commit 7446008

Browse files
committed
feat: three-phase improvement of hacking minigame
Phase 1 - Code quality: - Fix LENGHT_PER_WORD typo -> LENGTH_PER_WORD in config.json and code - Remove unused randomword() function - Inline trivial verifyword() equality check - Pass config dict into generate() instead of reading globals - Guard all input() calls against EOFError/KeyboardInterrupt - Consolidate win/loss into single result block, remove duplicate 'A WINNAR IS YUO' Phase 2 - Gameplay accuracy: - Fix letinword() to use positional matching (zip-based) not letter presence - Normalize word list and player input to uppercase - Add --config CLI argument via argparse Phase 3 - Fallout faithful features: - Two-column hex-dump word layout with fake memory addresses - ANSI color output (green=win, red=error/loss, yellow=hints, cyan=UI) - REMOVE command: removes a random dud word from the active list - REPLENISH command: restores attempts to max once per game https://claude.ai/code/session_01MQnNXtMmBiQvwoFoNdEQDb
1 parent 4852c90 commit 7446008

2 files changed

Lines changed: 159 additions & 135 deletions

File tree

config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
"EFFECT_WAIT_SEC": 0.1,
44
"MAX_NUM_WORD": 10,
55
"MIN_NUM_WORD": 4,
6-
"LENGHT_PER_WORD": 5,
6+
"LENGTH_PER_WORD": 5,
77
"DIFFICULTY": 2
88
}

main.py

Lines changed: 158 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,195 +1,219 @@
1-
"""
2-
importing the random library and time just for some things
3-
"""
1+
import argparse
42
import random
53
import time
64
import json
75

6+
# ---------------------------------------------------------------------------
7+
# ANSI color helpers
8+
# ---------------------------------------------------------------------------
9+
GREEN = "\033[32m"
10+
RED = "\033[31m"
11+
YELLOW = "\033[33m"
12+
CYAN = "\033[36m"
13+
RESET = "\033[0m"
814

9-
def load_config(config_file):
10-
"""
11-
Load configuration from a JSON file.
12-
13-
Args:
14-
config_file (str): The path to the JSON configuration file.
1515

16-
Returns:
17-
dict: The configuration data loaded from the file.
18-
"""
19-
with open(config_file, 'r', encoding='utf-8') as file:
20-
return json.load(file)
16+
def green(s): return f"{GREEN}{s}{RESET}"
17+
def red(s): return f"{RED}{s}{RESET}"
18+
def yellow(s): return f"{YELLOW}{s}{RESET}"
19+
def cyan(s): return f"{CYAN}{s}{RESET}"
2120

2221

23-
config = load_config('config.json')
22+
# ---------------------------------------------------------------------------
23+
# Config / IO
24+
# ---------------------------------------------------------------------------
2425

25-
NAME_FILE = config['NAME_FILE']
26-
EFFECT_WAIT_SEC = config['EFFECT_WAIT_SEC']
27-
MAX_NUM_WORD = config['MAX_NUM_WORD']
28-
MIN_NUM_WORD = config['MIN_NUM_WORD']
29-
LENGHT_PER_WORD = config['LENGHT_PER_WORD']
30-
DIFFICULTY = config['DIFFICULTY']
26+
def load_config(config_file):
27+
with open(config_file, 'r', encoding='utf-8') as file:
28+
return json.load(file)
3129

3230

3331
def load_file_word_list(dirfile):
34-
"""
35-
Load the word list from a file.
36-
37-
Args:
38-
dirfile (str): The path to the file from which to load the words.
39-
40-
Returns:
41-
list: A list of words loaded from the file.
42-
"""
4332
words = []
4433
try:
4534
with open(dirfile, 'r', encoding='utf-8') as fil:
46-
words = fil.read().splitlines()
35+
words = [line.strip().upper() for line in fil if line.strip()]
4736
except FileNotFoundError:
48-
print(f"Error: The file {dirfile} was not found.")
37+
print(red(f"Error: The file {dirfile} was not found."))
4938
except IOError:
50-
print(f"Error: Unable to read the file {dirfile}.")
39+
print(red(f"Error: Unable to read the file {dirfile}."))
5140
return words
5241

5342

54-
def randomword(wordlist):
55-
"""
56-
Select a random word from the word list.
57-
58-
Args:
59-
wordlist (list): The list of words to select from.
60-
61-
Returns:
62-
str: A random word from the list.
63-
"""
64-
ind = random.randint(0, len(wordlist)-1)
65-
return wordlist[ind]
43+
# ---------------------------------------------------------------------------
44+
# Word generation
45+
# ---------------------------------------------------------------------------
6646

47+
def generate(wordlist, config):
48+
max_words = random.randint(config['MIN_NUM_WORD'], config['MAX_NUM_WORD'])
49+
valid_words = [w for w in wordlist if len(w) == config['LENGTH_PER_WORD']]
6750

68-
def generate(wordlist):
69-
"""
70-
Generates a list of words where the player
71-
has to guess the correct one.
51+
if not valid_words:
52+
raise ValueError(
53+
f"No words found with length {config['LENGTH_PER_WORD']}. "
54+
"Please check your word list."
55+
)
7256

73-
Args:
74-
wordlist (list): The list of words to generate from.
57+
selected = set()
58+
while len(selected) < max_words and len(selected) < len(valid_words):
59+
selected.add(random.choice(valid_words))
7560

76-
Returns:
77-
tuple: A list of randomly generated words and the correct word
78-
"""
79-
debuglist = set()
80-
max_words = random.randint(MIN_NUM_WORD, MAX_NUM_WORD)
81-
82-
# First, filter words of correct length
83-
valid_words = [word for word in wordlist if len(word) == LENGHT_PER_WORD]
84-
85-
if not valid_words:
86-
raise ValueError(f"No words found with length {LENGHT_PER_WORD}. Please check your word list.")
87-
88-
# Randomly select words from valid words
89-
while len(debuglist) < max_words and len(debuglist) < len(valid_words):
90-
word = random.choice(valid_words)
91-
debuglist.add(word)
92-
93-
newlistwords = list(debuglist)
61+
newlistwords = list(selected)
9462
if not newlistwords:
95-
raise ValueError("Could not generate enough words. Please check your word list and configuration.")
96-
63+
raise ValueError("Could not generate enough words.")
64+
9765
return (newlistwords, random.choice(newlistwords))
9866

9967

100-
def verifyword(word, masterpassword):
101-
"""
102-
Verifies if the entered word is correct.
68+
# ---------------------------------------------------------------------------
69+
# Hint
70+
# ---------------------------------------------------------------------------
10371

104-
Args:
105-
word (str): The word entered by the user.
106-
masterpassword (str): The correct word.
72+
def letinword(word, masterpassword):
73+
"""Positional match count: letters correct AND in correct position."""
74+
nletter = sum(a == b for a, b in zip(word, masterpassword))
75+
return str(nletter) + "/" + str(len(masterpassword))
10776

108-
Returns:
109-
bool: True if the word is correct, False otherwise.
110-
"""
111-
return word == masterpassword
11277

78+
# ---------------------------------------------------------------------------
79+
# Display
80+
# ---------------------------------------------------------------------------
11381

114-
def letinword(word, masterpassword):
115-
"""
116-
Counts the number of correct letters in the masterpassword from the
117-
entered word.
82+
FILLER = "......"
83+
HEX_BASE = 0xF4A0
84+
HEX_STRIDE = 12
11885

119-
Args:
120-
word (str): The word entered by the user.
121-
masterpassword (str): The correct word.
12286

123-
Returns:
124-
str: The number of correct letters relative to the length of the
125-
correct word.
87+
def display_words(words, removed):
12688
"""
127-
nletter = 0
128-
for letter in word:
129-
if letter in masterpassword:
130-
nletter += 1
131-
return str(nletter) + "/" + str(len(masterpassword))
89+
Show words in a two-column Fallout-style hex-dump layout.
90+
Removed duds are replaced with filler dots.
91+
"""
92+
print()
93+
half = (len(words) + 1) // 2
94+
left_col = words[:half]
95+
right_col = words[half:]
96+
97+
for i, left_word in enumerate(left_col):
98+
left_addr = cyan(f"0x{HEX_BASE + i * HEX_STRIDE:04X}")
99+
left_display = FILLER if left_word in removed else left_word
100+
101+
if i < len(right_col):
102+
right_word = right_col[i]
103+
right_addr = cyan(f"0x{HEX_BASE + (half + i) * HEX_STRIDE:04X}")
104+
right_display = FILLER if right_word in removed else right_word
105+
print(f" {left_addr} {left_display:<12} {right_addr} {right_display}")
106+
else:
107+
print(f" {left_addr} {left_display}")
108+
print()
109+
132110

111+
# ---------------------------------------------------------------------------
112+
# Main game
113+
# ---------------------------------------------------------------------------
133114

134115
def main():
135-
"""
136-
Main function of the program.
137-
"""
138-
print("Excecuting Debug of the secretCodes.db", "")
139-
print("")
116+
parser = argparse.ArgumentParser(description="Fallout Hacking Minigame")
117+
parser.add_argument(
118+
'--config', default='config.json',
119+
help='Path to the JSON config file (default: config.json)'
120+
)
121+
args = parser.parse_args()
122+
config = load_config(args.config)
123+
124+
print(cyan("Excecuting Debug of the secretCodes.db"))
125+
print()
140126
print("initiating preliminary processes")
141127

142-
listwords = load_file_word_list(NAME_FILE)
143-
time.sleep(EFFECT_WAIT_SEC)
128+
listwords = load_file_word_list(config['NAME_FILE'])
129+
time.sleep(config['EFFECT_WAIT_SEC'])
144130
print("secretCodes.db -> loaded ")
145131

146-
debuglist = generate(listwords)
147-
time.sleep(EFFECT_WAIT_SEC)
132+
debuglist = generate(listwords, config)
133+
time.sleep(config['EFFECT_WAIT_SEC'])
148134
print("debug process phase 1/2 -> 'OK' ")
149135

150136
try:
151-
attempt = int(len(debuglist[0]) / DIFFICULTY)
137+
max_attempts = int(len(debuglist[0]) / config['DIFFICULTY'])
152138
except ZeroDivisionError:
153-
attempt = int(len(debuglist[0]))
154-
time.sleep(EFFECT_WAIT_SEC)
139+
max_attempts = len(debuglist[0])
140+
time.sleep(config['EFFECT_WAIT_SEC'])
155141
print("debug process phase 2/2 -> taking just from 0,122 line ")
156142

157143
listwords = debuglist[0]
158-
time.sleep(EFFECT_WAIT_SEC)
144+
time.sleep(config['EFFECT_WAIT_SEC'])
159145
print(" ...PROCESSING TEXT.. ")
160146

161147
masterpass = debuglist[1]
162-
time.sleep(EFFECT_WAIT_SEC)
148+
time.sleep(config['EFFECT_WAIT_SEC'])
163149
print(" decrypting MASTER_PASSWORD ")
164-
print("")
165-
print("Extract from the secretCodes.db")
166-
print("")
167-
for word in listwords:
168-
print(word + " ", end='')
169-
while attempt != 0:
170-
print("\n\n attempt num > " + str(attempt))
171-
inser = str(input("verifyFun() insert string -> "))
172-
if inser in listwords:
173-
if verifyword(inser, masterpass):
174-
# you found the right word
175-
print("ACCESS GRANTED")
150+
151+
attempt = max_attempts
152+
removed = set() # duds removed by REMOVE command
153+
replenish_used = False
154+
155+
print()
156+
print(cyan("Extract from the secretCodes.db"))
157+
display_words(listwords, removed)
158+
159+
won = False
160+
while attempt > 0:
161+
attempt_color = red(str(attempt)) if attempt <= 2 else str(attempt)
162+
print(f"\n attempt num > {attempt_color}")
163+
print(yellow(" Commands: REMOVE (remove a dud) | REPLENISH (restore attempts, once)"))
164+
165+
try:
166+
inser = input(" > ").strip().upper()
167+
except (EOFError, KeyboardInterrupt):
168+
print("\nAborted.")
169+
break
170+
171+
# --- Special commands ---
172+
if inser == "REMOVE":
173+
candidates = [w for w in listwords if w != masterpass and w not in removed]
174+
if candidates:
175+
dud = random.choice(candidates)
176+
removed.add(dud)
177+
print(yellow(f" Dud removed: {dud}"))
178+
display_words(listwords, removed)
179+
else:
180+
print(yellow(" No duds left to remove."))
181+
continue
182+
183+
if inser == "REPLENISH":
184+
if replenish_used:
185+
print(yellow(" Replenish already used this game."))
186+
else:
187+
attempt = max_attempts
188+
replenish_used = True
189+
print(yellow(f" Attempts replenished to {max_attempts}."))
190+
continue
191+
192+
# --- Word guess ---
193+
active_words = [w for w in listwords if w not in removed]
194+
if inser in active_words:
195+
if inser == masterpass:
196+
print(green("ACCESS GRANTED"))
197+
won = True
176198
break
177199
else:
178-
print("ERROR " + str(inser) +
179-
' -> ' + str(letinword(inser, masterpass)))
180-
print("WRONG WORD attempt num > " + str(attempt-1))
200+
hint = letinword(inser, masterpass)
201+
print(red(f" ERROR {inser}") + " -> " + yellow(f"Likeness: {hint}"))
202+
print(f" WRONG WORD attempt num > {attempt - 1}")
181203
attempt -= 1
182204
else:
183-
print("INPUT NOT VALID")
184-
if attempt > 0:
185-
print("A WINNAR IS YUO")
186-
else:
187-
print("COMPUTER LOCKED")
188-
print("")
189-
print("NO MORE ATTEMPTS AVAILABLE")
205+
print(red(" INPUT NOT VALID"))
206+
207+
if not won:
208+
print(red("COMPUTER LOCKED"))
209+
print()
210+
print(red("NO MORE ATTEMPTS AVAILABLE"))
190211

191212

192213
if __name__ == '__main__':
193214
main()
194-
print('')
195-
input("Press return to continue ...")
215+
print()
216+
try:
217+
input("Press return to continue ...")
218+
except (EOFError, KeyboardInterrupt):
219+
pass

0 commit comments

Comments
 (0)