|
1 | | -""" |
2 | | -importing the random library and time just for some things |
3 | | -""" |
| 1 | +import argparse |
4 | 2 | import random |
5 | 3 | import time |
6 | 4 | import json |
7 | 5 |
|
| 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" |
8 | 14 |
|
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. |
15 | 15 |
|
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}" |
21 | 20 |
|
22 | 21 |
|
23 | | -config = load_config('config.json') |
| 22 | +# --------------------------------------------------------------------------- |
| 23 | +# Config / IO |
| 24 | +# --------------------------------------------------------------------------- |
24 | 25 |
|
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) |
31 | 29 |
|
32 | 30 |
|
33 | 31 | 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 | | - """ |
43 | 32 | words = [] |
44 | 33 | try: |
45 | 34 | 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()] |
47 | 36 | except FileNotFoundError: |
48 | | - print(f"Error: The file {dirfile} was not found.") |
| 37 | + print(red(f"Error: The file {dirfile} was not found.")) |
49 | 38 | except IOError: |
50 | | - print(f"Error: Unable to read the file {dirfile}.") |
| 39 | + print(red(f"Error: Unable to read the file {dirfile}.")) |
51 | 40 | return words |
52 | 41 |
|
53 | 42 |
|
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 | +# --------------------------------------------------------------------------- |
66 | 46 |
|
| 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']] |
67 | 50 |
|
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 | + ) |
72 | 56 |
|
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)) |
75 | 60 |
|
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) |
94 | 62 | 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 | + |
97 | 65 | return (newlistwords, random.choice(newlistwords)) |
98 | 66 |
|
99 | 67 |
|
100 | | -def verifyword(word, masterpassword): |
101 | | - """ |
102 | | - Verifies if the entered word is correct. |
| 68 | +# --------------------------------------------------------------------------- |
| 69 | +# Hint |
| 70 | +# --------------------------------------------------------------------------- |
103 | 71 |
|
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)) |
107 | 76 |
|
108 | | - Returns: |
109 | | - bool: True if the word is correct, False otherwise. |
110 | | - """ |
111 | | - return word == masterpassword |
112 | 77 |
|
| 78 | +# --------------------------------------------------------------------------- |
| 79 | +# Display |
| 80 | +# --------------------------------------------------------------------------- |
113 | 81 |
|
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 |
118 | 85 |
|
119 | | - Args: |
120 | | - word (str): The word entered by the user. |
121 | | - masterpassword (str): The correct word. |
122 | 86 |
|
123 | | - Returns: |
124 | | - str: The number of correct letters relative to the length of the |
125 | | - correct word. |
| 87 | +def display_words(words, removed): |
126 | 88 | """ |
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 | + |
132 | 110 |
|
| 111 | +# --------------------------------------------------------------------------- |
| 112 | +# Main game |
| 113 | +# --------------------------------------------------------------------------- |
133 | 114 |
|
134 | 115 | 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() |
140 | 126 | print("initiating preliminary processes") |
141 | 127 |
|
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']) |
144 | 130 | print("secretCodes.db -> loaded ") |
145 | 131 |
|
146 | | - debuglist = generate(listwords) |
147 | | - time.sleep(EFFECT_WAIT_SEC) |
| 132 | + debuglist = generate(listwords, config) |
| 133 | + time.sleep(config['EFFECT_WAIT_SEC']) |
148 | 134 | print("debug process phase 1/2 -> 'OK' ") |
149 | 135 |
|
150 | 136 | try: |
151 | | - attempt = int(len(debuglist[0]) / DIFFICULTY) |
| 137 | + max_attempts = int(len(debuglist[0]) / config['DIFFICULTY']) |
152 | 138 | 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']) |
155 | 141 | print("debug process phase 2/2 -> taking just from 0,122 line ") |
156 | 142 |
|
157 | 143 | listwords = debuglist[0] |
158 | | - time.sleep(EFFECT_WAIT_SEC) |
| 144 | + time.sleep(config['EFFECT_WAIT_SEC']) |
159 | 145 | print(" ...PROCESSING TEXT.. ") |
160 | 146 |
|
161 | 147 | masterpass = debuglist[1] |
162 | | - time.sleep(EFFECT_WAIT_SEC) |
| 148 | + time.sleep(config['EFFECT_WAIT_SEC']) |
163 | 149 | 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 |
176 | 198 | break |
177 | 199 | 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}") |
181 | 203 | attempt -= 1 |
182 | 204 | 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")) |
190 | 211 |
|
191 | 212 |
|
192 | 213 | if __name__ == '__main__': |
193 | 214 | 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