#!/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())