abc123 / crossword-app /backend-py /debug_index_error.py
vimalk78's picture
Add complete Python backend with AI-powered crossword generation
38c016b
raw
history blame
14.4 kB
#!/usr/bin/env python3
"""
Debug the recurring index error by adding comprehensive bounds checking.
"""
import asyncio
import sys
import logging
from pathlib import Path
# Add project root to path
project_root = Path(__file__).parent
sys.path.insert(0, str(project_root))
from src.services.crossword_generator_fixed import CrosswordGeneratorFixed
from src.services.vector_search import VectorSearchService
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
class DebugCrosswordGenerator(CrosswordGeneratorFixed):
"""Debug version with comprehensive bounds checking."""
def _can_place_word(self, grid, word, row, col, direction):
"""Enhanced _can_place_word with comprehensive bounds checking."""
try:
size = len(grid)
logger.debug(f"_can_place_word: word={word}, row={row}, col={col}, direction={direction}, grid_size={size}")
# Check initial boundaries
if row < 0 or col < 0 or row >= size or col >= size:
logger.debug(f"Initial bounds check failed: row={row}, col={col}, size={size}")
return False
if direction == "horizontal":
if col + len(word) > size:
logger.debug(f"Horizontal bounds check failed: col+len(word)={col + len(word)} > size={size}")
return False
# Check word boundaries (no adjacent letters) - with bounds check
if col > 0:
if row >= size or col - 1 >= size or row < 0 or col - 1 < 0:
logger.debug(f"Horizontal left boundary check failed: row={row}, col-1={col-1}, size={size}")
return False
if grid[row][col - 1] != ".":
logger.debug(f"Horizontal left boundary has adjacent letter")
return False
if col + len(word) < size:
if row >= size or col + len(word) >= size or row < 0 or col + len(word) < 0:
logger.debug(f"Horizontal right boundary check failed: row={row}, col+len={col + len(word)}, size={size}")
return False
if grid[row][col + len(word)] != ".":
logger.debug(f"Horizontal right boundary has adjacent letter")
return False
# Check each letter position
for i, letter in enumerate(word):
check_row = row
check_col = col + i
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.debug(f"Horizontal letter position check failed: letter {i}, row={check_row}, col={check_col}, size={size}")
return False
current_cell = grid[check_row][check_col]
if current_cell != "." and current_cell != letter:
logger.debug(f"Horizontal letter conflict: expected {letter}, found {current_cell}")
return False
else: # vertical
if row + len(word) > size:
logger.debug(f"Vertical bounds check failed: row+len(word)={row + len(word)} > size={size}")
return False
# Check word boundaries - with bounds check
if row > 0:
if row - 1 >= size or col >= size or row - 1 < 0 or col < 0:
logger.debug(f"Vertical top boundary check failed: row-1={row-1}, col={col}, size={size}")
return False
if grid[row - 1][col] != ".":
logger.debug(f"Vertical top boundary has adjacent letter")
return False
if row + len(word) < size:
if row + len(word) >= size or col >= size or row + len(word) < 0 or col < 0:
logger.debug(f"Vertical bottom boundary check failed: row+len={row + len(word)}, col={col}, size={size}")
return False
if grid[row + len(word)][col] != ".":
logger.debug(f"Vertical bottom boundary has adjacent letter")
return False
# Check each letter position
for i, letter in enumerate(word):
check_row = row + i
check_col = col
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.debug(f"Vertical letter position check failed: letter {i}, row={check_row}, col={check_col}, size={size}")
return False
current_cell = grid[check_row][check_col]
if current_cell != "." and current_cell != letter:
logger.debug(f"Vertical letter conflict: expected {letter}, found {current_cell}")
return False
logger.debug(f"_can_place_word: SUCCESS for word={word}")
return True
except Exception as e:
logger.error(f"❌ ERROR in _can_place_word: {e}")
logger.error(f" word={word}, row={row}, col={col}, direction={direction}")
logger.error(f" grid_size={len(grid) if grid else 'None'}")
import traceback
traceback.print_exc()
return False
def _place_word(self, grid, word, row, col, direction):
"""Enhanced _place_word with comprehensive bounds checking."""
try:
size = len(grid)
logger.debug(f"_place_word: word={word}, row={row}, col={col}, direction={direction}, grid_size={size}")
original_state = []
if direction == "horizontal":
for i, letter in enumerate(word):
check_row = row
check_col = col + i
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.error(f"❌ _place_word horizontal bounds error: row={check_row}, col={check_col}, size={size}")
raise IndexError(f"Grid index out of range: [{check_row}][{check_col}] in grid of size {size}")
original_state.append({
"row": check_row,
"col": check_col,
"value": grid[check_row][check_col]
})
grid[check_row][check_col] = letter
else:
for i, letter in enumerate(word):
check_row = row + i
check_col = col
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.error(f"❌ _place_word vertical bounds error: row={check_row}, col={check_col}, size={size}")
raise IndexError(f"Grid index out of range: [{check_row}][{check_col}] in grid of size {size}")
original_state.append({
"row": check_row,
"col": check_col,
"value": grid[check_row][check_col]
})
grid[check_row][check_col] = letter
logger.debug(f"_place_word: SUCCESS for word={word}")
return original_state
except Exception as e:
logger.error(f"❌ ERROR in _place_word: {e}")
logger.error(f" word={word}, row={row}, col={col}, direction={direction}")
logger.error(f" grid_size={len(grid) if grid else 'None'}")
import traceback
traceback.print_exc()
raise
def _remove_word(self, grid, original_state):
"""Enhanced _remove_word with comprehensive bounds checking."""
try:
size = len(grid)
logger.debug(f"_remove_word: restoring {len(original_state)} positions, grid_size={size}")
for state in original_state:
check_row = state["row"]
check_col = state["col"]
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.error(f"❌ _remove_word bounds error: row={check_row}, col={check_col}, size={size}")
raise IndexError(f"Grid index out of range: [{check_row}][{check_col}] in grid of size {size}")
grid[check_row][check_col] = state["value"]
logger.debug(f"_remove_word: SUCCESS")
except Exception as e:
logger.error(f"❌ ERROR in _remove_word: {e}")
logger.error(f" grid_size={len(grid) if grid else 'None'}")
logger.error(f" original_state={original_state}")
import traceback
traceback.print_exc()
raise
def _create_simple_cross(self, word_list, word_objs):
"""Enhanced _create_simple_cross with comprehensive bounds checking."""
try:
logger.debug(f"_create_simple_cross: words={word_list}")
if len(word_list) < 2:
logger.debug("Not enough words for simple cross")
return None
word1, word2 = word_list[0], word_list[1]
intersections = self._find_word_intersections(word1, word2)
if not intersections:
logger.debug("No intersections found")
return None
# Use first intersection
intersection = intersections[0]
size = max(len(word1), len(word2)) + 4
logger.debug(f"Creating grid of size {size} for simple cross")
grid = [["." for _ in range(size)] for _ in range(size)]
# Place first word horizontally in center
center_row = size // 2
center_col = (size - len(word1)) // 2
logger.debug(f"Placing word1 '{word1}' at row={center_row}, col={center_col}")
for i, letter in enumerate(word1):
check_row = center_row
check_col = center_col + i
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.error(f"❌ _create_simple_cross word1 bounds error: row={check_row}, col={check_col}, size={size}")
raise IndexError(f"Grid index out of range: [{check_row}][{check_col}] in grid of size {size}")
grid[check_row][check_col] = letter
# Place second word vertically at intersection
intersection_col = center_col + intersection["word_pos"]
word2_start_row = center_row - intersection["placed_pos"]
logger.debug(f"Placing word2 '{word2}' at row={word2_start_row}, col={intersection_col}")
for i, letter in enumerate(word2):
check_row = word2_start_row + i
check_col = intersection_col
if check_row >= size or check_col >= size or check_row < 0 or check_col < 0:
logger.error(f"❌ _create_simple_cross word2 bounds error: row={check_row}, col={check_col}, size={size}")
raise IndexError(f"Grid index out of range: [{check_row}][{check_col}] in grid of size {size}")
grid[check_row][check_col] = letter
placed_words = [
{"word": word1, "row": center_row, "col": center_col, "direction": "horizontal", "number": 1},
{"word": word2, "row": word2_start_row, "col": intersection_col, "direction": "vertical", "number": 2}
]
logger.debug(f"_create_simple_cross: SUCCESS")
trimmed = self._trim_grid(grid, placed_words)
clues = self._generate_clues(word_objs[:2], trimmed["placed_words"])
return {
"grid": trimmed["grid"],
"placed_words": trimmed["placed_words"],
"clues": clues
}
except Exception as e:
logger.error(f"❌ ERROR in _create_simple_cross: {e}")
import traceback
traceback.print_exc()
raise
async def test_debug_generator():
"""Test the debug generator to catch index errors."""
try:
print("🧪 Testing debug crossword generator...")
# Create mock vector service
vector_service = VectorSearchService()
# Create debug generator
generator = DebugCrosswordGenerator(vector_service)
# Test with various topics and difficulties
test_cases = [
(["animals"], "medium"),
(["science"], "hard"),
(["technology"], "easy"),
(["animals", "science"], "medium"),
]
for i, (topics, difficulty) in enumerate(test_cases):
print(f"\n🔬 Test {i+1}: topics={topics}, difficulty={difficulty}")
try:
result = await generator.generate_puzzle(topics, difficulty, use_ai=False)
if result:
print(f"✅ Test {i+1} succeeded")
grid_size = len(result['grid'])
word_count = len(result['clues'])
print(f" Grid: {grid_size}x{grid_size}, Words: {word_count}")
else:
print(f"⚠️ Test {i+1} returned None")
except Exception as e:
print(f"❌ Test {i+1} failed: {e}")
import traceback
traceback.print_exc()
return False
print(f"\n✅ All debug tests completed!")
return True
except Exception as e:
print(f"❌ Debug test setup failed: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
asyncio.run(test_debug_generator())