WIP lakectf
This commit is contained in:
215
2025/lake/misc/wordler/tmp.py
Normal file
215
2025/lake/misc/wordler/tmp.py
Normal file
@@ -0,0 +1,215 @@
|
||||
import re
|
||||
import os
|
||||
from collections import Counter, defaultdict
|
||||
import itertools
|
||||
|
||||
def parse_ansi_feedback(raw_bytes):
|
||||
"""
|
||||
Parses the raw bytes containing ANSI codes to extract letters and their colors.
|
||||
Returns a list of word_segments.
|
||||
Each segment is a list of tuples: (color_code, char)
|
||||
"""
|
||||
try:
|
||||
decoded = raw_bytes.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
decoded = raw_bytes.decode('utf-8', errors='ignore')
|
||||
|
||||
print("--- Raw Decoded String ---")
|
||||
print(decoded)
|
||||
print("--------------------------")
|
||||
|
||||
# Split into word chunks based on the underscore
|
||||
raw_segments = decoded.strip().split('_')
|
||||
|
||||
parsed_segments = []
|
||||
|
||||
# Regex to find ANSI code and the character immediately following
|
||||
# Matches: \x1b[93mU (captures '93' and 'U')
|
||||
ansi_pattern = re.compile(r'\x1b\[(\d+)m([A-Z])')
|
||||
|
||||
for raw_seg in raw_segments:
|
||||
matches = ansi_pattern.findall(raw_seg)
|
||||
if matches:
|
||||
# matches is a list of ('93', 'U'), ('90', 'V'), etc.
|
||||
parsed_segments.append(matches)
|
||||
|
||||
return parsed_segments
|
||||
|
||||
def analyze_global_constraints(all_segments):
|
||||
"""
|
||||
Derives global letter counts from all segments combined based on the rule:
|
||||
- Green/Yellow count contributes to the total.
|
||||
- Presence of Grey implies the count is EXACTLY (Green + Yellow).
|
||||
- Absence of Grey implies the count is AT LEAST (Green + Yellow).
|
||||
"""
|
||||
# Flatten all entries to count global occurrences in the guess
|
||||
all_entries = [item for seg in all_segments for item in seg]
|
||||
|
||||
char_data = defaultdict(lambda: {'92': 0, '93': 0, '90': 0})
|
||||
|
||||
for color, char in all_entries:
|
||||
char_data[char.lower()][color] += 1
|
||||
|
||||
global_constraints = {}
|
||||
|
||||
for char, counts in char_data.items():
|
||||
greens = counts['92']
|
||||
yellows = counts['93']
|
||||
greys = counts['90']
|
||||
|
||||
required = greens + yellows
|
||||
|
||||
if greys > 0:
|
||||
# We hit the limit (Grey exists), so the total count in solution is exactly 'required'
|
||||
global_constraints[char] = {'min': required, 'max': required}
|
||||
else:
|
||||
# No Greys seen, so there could be more. Count is at least 'required'
|
||||
global_constraints[char] = {'min': required, 'max': float('inf')}
|
||||
|
||||
return global_constraints
|
||||
|
||||
def generate_local_constraints(segment_data):
|
||||
"""
|
||||
Generates positional constraints for a single word slot.
|
||||
"""
|
||||
constraints = {
|
||||
'length': len(segment_data),
|
||||
'fixed': {}, # index: char (Green)
|
||||
'not_at_pos': [], # list of (index, char) (Yellow or Grey)
|
||||
}
|
||||
|
||||
for i, (color, char) in enumerate(segment_data):
|
||||
char = char.lower()
|
||||
if color == '92': # Green
|
||||
constraints['fixed'][i] = char
|
||||
elif color == '93': # Yellow
|
||||
constraints['not_at_pos'].append((i, char))
|
||||
elif color == '90': # Grey
|
||||
# Grey means "not a match at this position".
|
||||
constraints['not_at_pos'].append((i, char))
|
||||
|
||||
return constraints
|
||||
|
||||
def get_candidates_for_slots(wordlist_path, segments, global_constraints):
|
||||
"""
|
||||
1. Filters wordlist for each slot based on local constraints.
|
||||
2. Prunes words containing letters that are globally forbidden (max=0).
|
||||
"""
|
||||
if not os.path.exists(wordlist_path):
|
||||
print(f"Error: {wordlist_path} not found.")
|
||||
return []
|
||||
|
||||
try:
|
||||
with open(wordlist_path, 'r', encoding='utf-8') as f:
|
||||
all_words = [w.strip().lower() for w in f if w.strip()]
|
||||
except Exception as e:
|
||||
print(f"Error reading wordlist: {e}")
|
||||
return []
|
||||
|
||||
# Identify globally forbidden characters (max count is 0)
|
||||
forbidden_chars = {c for c, limits in global_constraints.items() if limits['max'] == 0}
|
||||
|
||||
all_slots_candidates = []
|
||||
|
||||
for i, seg in enumerate(segments):
|
||||
local = generate_local_constraints(seg)
|
||||
candidates = []
|
||||
|
||||
print(f"\nScanning candidates for Slot {i+1} (Length {local['length']})...")
|
||||
|
||||
for w in all_words:
|
||||
# 1. Length
|
||||
if len(w) != local['length']: continue
|
||||
|
||||
# 2. Global "Zero Tolerance" check
|
||||
if any(c in forbidden_chars for c in w): continue
|
||||
|
||||
# 3. Green (Fixed Position)
|
||||
if any(w[idx] != char for idx, char in local['fixed'].items()): continue
|
||||
|
||||
# 4. Not At Position (Yellow/Grey)
|
||||
if any(w[idx] == char for idx, char in local['not_at_pos']): continue
|
||||
|
||||
candidates.append(w)
|
||||
|
||||
print(f"-> {len(candidates)} candidates found.")
|
||||
all_slots_candidates.append(candidates)
|
||||
|
||||
return all_slots_candidates
|
||||
|
||||
def solve_combination(candidate_lists, global_constraints):
|
||||
"""
|
||||
Finds a combination of one word from each list such that the total letter counts
|
||||
satisfy the global constraints.
|
||||
"""
|
||||
print("\n=== Solving for Valid Combination ===")
|
||||
|
||||
# We use a recursive backtracking approach or itertools.product.
|
||||
# Given the likely search space, product might be heavy if candidates are many.
|
||||
# However, Python generators handle it decently if solutions appear early.
|
||||
|
||||
# Pre-check: if any list is empty, fail early
|
||||
if any(not lst for lst in candidate_lists):
|
||||
print("Error: One or more slots have no candidates.")
|
||||
return None
|
||||
|
||||
# Limit total combinations to avoid hanging if constraints are loose
|
||||
max_checks = 2000000
|
||||
checks = 0
|
||||
|
||||
import time
|
||||
start_time = time.time()
|
||||
|
||||
for combination in itertools.product(*candidate_lists):
|
||||
checks += 1
|
||||
if checks % 100000 == 0:
|
||||
print(f"Checked {checks} combinations...")
|
||||
if checks > max_checks:
|
||||
print("Time limit exceeded searching for combinations.")
|
||||
break
|
||||
|
||||
# Combine all letters
|
||||
combined_text = "".join(combination)
|
||||
counts = Counter(combined_text)
|
||||
|
||||
valid = True
|
||||
# Check against global constraints
|
||||
for char, limits in global_constraints.items():
|
||||
c_count = counts[char]
|
||||
if c_count < limits['min']:
|
||||
valid = False; break
|
||||
if limits['max'] is not None and c_count != limits['max']:
|
||||
valid = False; break
|
||||
|
||||
if valid:
|
||||
print(f"Solution Found in {time.time() - start_time:.2f}s!")
|
||||
return "_".join(combination)
|
||||
|
||||
print("No valid combination found matching all constraints.")
|
||||
return None
|
||||
|
||||
# --- Main Execution ---
|
||||
|
||||
# The specific ANSI byte string provided
|
||||
raw_ansi_output = b'\x1b[93mU\x1b[0m\x1b[93mN\x1b[0m\x1b[93mI\x1b[0m\x1b[90mV\x1b[0m\x1b[92mE\x1b[0m\x1b[93mR\x1b[0m\x1b[93mS\x1b[0m\x1b[90mI\x1b[0m\x1b[93mT\x1b[0m\x1b[90mY\x1b[0m_\x1b[90mC\x1b[0m\x1b[93mO\x1b[0m\x1b[93mN\x1b[0m\x1b[93mT\x1b[0m\x1b[90mA\x1b[0m\x1b[90mC\x1b[0m\x1b[90mT\x1b[0m_\x1b[90mI\x1b[0m\x1b[93mN\x1b[0m\x1b[90mT\x1b[0m\x1b[92mE\x1b[0m\x1b[90mR\x1b[0m\x1b[93mN\x1b[0m\x1b[90mA\x1b[0m\x1b[90mT\x1b[0m\x1b[90mI\x1b[0m\x1b[93mO\x1b[0m\x1b[90mN\x1b[0m\x1b[90mA\x1b[0m\x1b[93mL\x1b[0m\n'
|
||||
|
||||
# 1. Parse
|
||||
segments = parse_ansi_feedback(raw_ansi_output)
|
||||
|
||||
# 2. Analyze Global Constraints
|
||||
global_cons = analyze_global_constraints(segments)
|
||||
print("\nGlobal Constraints (Total Counts in Solution):")
|
||||
for c, lim in global_cons.items():
|
||||
mx = lim['max'] if lim['max'] is not None else "Inf"
|
||||
print(f" {c.upper()}: {lim['min']} - {mx}")
|
||||
|
||||
# 3. Get Candidates per Slot
|
||||
candidate_lists = get_candidates_for_slots('wordlist.txt', segments, global_cons)
|
||||
|
||||
# 4. Find valid combination
|
||||
final_guess = solve_combination(candidate_lists, global_cons)
|
||||
|
||||
if final_guess:
|
||||
print("\n--------------------------------")
|
||||
print(f"FORMATTED GUESS: {final_guess}")
|
||||
print("--------------------------------")
|
||||
Reference in New Issue
Block a user