generate crosswords using crossword algorithm
This commit is contained in:
@ -1,10 +1,8 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Tuple, Optional
|
from typing import List, Tuple, Optional
|
||||||
from multiplayer_crosswords.dictionary import Dictionary, Word
|
from multiplayer_crosswords.dictionary import Dictionary, Word
|
||||||
|
from multiplayer_crosswords.crossword_algorithm import CrosswordGeneratorStep, Slot, Orientation
|
||||||
class Orientation(Enum):
|
import random
|
||||||
HORIZONTAL = 1
|
|
||||||
VERTICAL = 2
|
|
||||||
|
|
||||||
class PlacedWord:
|
class PlacedWord:
|
||||||
def __init__(self, word: Word, row: int, col: int, orientation: Orientation):
|
def __init__(self, word: Word, row: int, col: int, orientation: Orientation):
|
||||||
@ -14,12 +12,72 @@ class PlacedWord:
|
|||||||
self.orientation = orientation
|
self.orientation = orientation
|
||||||
|
|
||||||
class Crossword:
|
class Crossword:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate(
|
||||||
|
rows: int,
|
||||||
|
cols: int,
|
||||||
|
dictionary: Dictionary,
|
||||||
|
seed: Optional[int] = None,
|
||||||
|
block_ratio: float = 0.4,
|
||||||
|
) -> "Crossword":
|
||||||
|
generator = CrosswordGeneratorStep(
|
||||||
|
dictionary=dictionary,
|
||||||
|
seed=seed,
|
||||||
|
grid_width=cols,
|
||||||
|
grid_height=rows,
|
||||||
|
grid_block_ratio=block_ratio
|
||||||
|
)
|
||||||
|
final_step = generator.generate(
|
||||||
|
max_tries_per_step=2,
|
||||||
|
show_progress=False,
|
||||||
|
max_allowed_single_choice=1,
|
||||||
|
max_allowed_threshold_choice=3,
|
||||||
|
max_allowed_threshold=5
|
||||||
|
)
|
||||||
|
crossword = Crossword(rows, cols, dictionary)
|
||||||
|
|
||||||
|
horizontal_slots = final_step.get_horizontal_slots()
|
||||||
|
vertical_slots = final_step.get_vertical_slots()
|
||||||
|
|
||||||
|
grid = final_step.get_grid()
|
||||||
|
|
||||||
|
horizontal_words = []
|
||||||
|
vertical_words = []
|
||||||
|
|
||||||
|
for slot in horizontal_slots:
|
||||||
|
word_str = ''.join(grid[slot.row][slot.col + i] for i in range(slot.length))
|
||||||
|
horizontal_words.append((word_str, slot.row, slot.col, Orientation.HORIZONTAL))
|
||||||
|
|
||||||
|
word_candidates = list(dictionary.find_by_pattern(word_str))
|
||||||
|
if len(word_candidates) <= 0:
|
||||||
|
raise ValueError(f"No word found for pattern '{word_str}' at ({slot.row}, {slot.col})")
|
||||||
|
|
||||||
|
# choose a candidate at random
|
||||||
|
chosen_word = random.choice(word_candidates).copy_with_random_hint()
|
||||||
|
crossword.add_word(chosen_word, slot.row, slot.col, Orientation.HORIZONTAL)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
for slot in vertical_slots:
|
||||||
|
word_str = ''.join(grid[slot.row + i][slot.col] for i in range(slot.length))
|
||||||
|
vertical_words.append((word_str, slot.row, slot.col, Orientation.VERTICAL))
|
||||||
|
word_candidates = list(dictionary.find_by_pattern(word_str))
|
||||||
|
if len(word_candidates) <= 0:
|
||||||
|
raise ValueError(f"No word found for pattern '{word_str}' at ({slot.row}, {slot.col})")
|
||||||
|
|
||||||
|
chosen_word = random.choice(word_candidates).copy_with_random_hint()
|
||||||
|
crossword.add_word(chosen_word, slot.row, slot.col, Orientation.VERTICAL)
|
||||||
|
|
||||||
|
return crossword
|
||||||
|
|
||||||
def __init__(self, rows: int, cols: int, dictionary: Dictionary):
|
def __init__(self, rows: int, cols: int, dictionary: Dictionary):
|
||||||
self.rows = rows
|
self.rows = rows
|
||||||
self.cols = cols
|
self.cols = cols
|
||||||
self.grid = [['' for _ in range(cols)] for _ in range(rows)]
|
self.grid = [['' for _ in range(cols)] for _ in range(rows)]
|
||||||
self.dictionary = dictionary
|
self.dictionary = dictionary
|
||||||
self.placed_words: List[PlacedWord] = []
|
self.placed_words: List[PlacedWord] = []
|
||||||
|
|
||||||
|
|
||||||
def _in_bounds(self, row: int, col: int) -> bool:
|
def _in_bounds(self, row: int, col: int) -> bool:
|
||||||
return 0 <= row < self.rows and 0 <= col < self.cols
|
return 0 <= row < self.rows and 0 <= col < self.cols
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
# algorithms will be implemnented here
|
# algorithms will be implemnented here
|
||||||
|
|
||||||
from typing import List, Tuple, Dict, Set, Optional
|
from typing import List, Tuple, Dict, Set, Optional
|
||||||
from multiplayer_crosswords.crossword import Crossword, Orientation
|
|
||||||
from multiplayer_crosswords.dictionary import Dictionary, Word
|
from multiplayer_crosswords.dictionary import Dictionary, Word
|
||||||
from multiplayer_crosswords.utils import load_en_dictionary, load_de_dictionary
|
from multiplayer_crosswords.utils import load_en_dictionary, load_de_dictionary
|
||||||
from copy import copy, deepcopy
|
from copy import copy, deepcopy
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Orientation(Enum):
|
||||||
|
HORIZONTAL = 1
|
||||||
|
VERTICAL = 2
|
||||||
|
|
||||||
class Slot:
|
class Slot:
|
||||||
def __init__(self, row: int, col: int, orientation: Orientation, length: int):
|
def __init__(self, row: int, col: int, orientation: Orientation, length: int):
|
||||||
@ -429,6 +433,14 @@ class CrosswordGeneratorStep(object):
|
|||||||
result += row_str
|
result += row_str
|
||||||
|
|
||||||
print(result, flush=True)
|
print(result, flush=True)
|
||||||
|
|
||||||
|
def get_horizontal_slots(self) -> List[Slot]:
|
||||||
|
return [slot for slot in self._known_slots if slot.orientation == Orientation.HORIZONTAL]
|
||||||
|
def get_vertical_slots(self) -> List[Slot]:
|
||||||
|
return [slot for slot in self._known_slots if slot.orientation == Orientation.VERTICAL]
|
||||||
|
|
||||||
|
def get_grid(self) -> List[List[str]]:
|
||||||
|
return self._grid
|
||||||
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -8,6 +8,8 @@ import numpy as np
|
|||||||
import cProfile
|
import cProfile
|
||||||
import pstats
|
import pstats
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
class Word:
|
class Word:
|
||||||
|
|
||||||
@ -15,6 +17,13 @@ class Word:
|
|||||||
self.word = word
|
self.word = word
|
||||||
self.hints = hints
|
self.hints = hints
|
||||||
self.difficulty = difficulty
|
self.difficulty = difficulty
|
||||||
|
|
||||||
|
def copy_with_random_hint(self) -> "Word":
|
||||||
|
if not self.hints:
|
||||||
|
return Word(self.word, [], self.difficulty)
|
||||||
|
hint = random.choice(self.hints)
|
||||||
|
return Word(self.word, [hint], self.difficulty)
|
||||||
|
|
||||||
|
|
||||||
def to_dict(self) -> dict[str, any]:
|
def to_dict(self) -> dict[str, any]:
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from multiplayer_crosswords.crossword import Crossword, Orientation, PlacedWord
|
from multiplayer_crosswords.crossword import Crossword, Orientation, PlacedWord
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
from multiplayer_crosswords.dictionary import Dictionary
|
||||||
|
from multiplayer_crosswords.utils import load_en_dictionary, load_de_dictionary
|
||||||
|
|
||||||
class DummyWord:
|
class DummyWord:
|
||||||
def __init__(self, word):
|
def __init__(self, word):
|
||||||
@ -96,4 +98,18 @@ def test_valid_hello_world_crossword():
|
|||||||
assert str(cw).count('O') == 1
|
assert str(cw).count('O') == 1
|
||||||
assert str(cw).count('W') == 1
|
assert str(cw).count('W') == 1
|
||||||
assert str(cw).count('R') == 1
|
assert str(cw).count('R') == 1
|
||||||
assert str(cw).count('D') == 1
|
assert str(cw).count('D') == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_crossword_generation():
|
||||||
|
dictionary = load_de_dictionary()
|
||||||
|
crossword = Crossword.generate(15, 15, dictionary)
|
||||||
|
assert crossword is not None
|
||||||
|
|
||||||
|
# Basic checks on the generated crossword
|
||||||
|
assert len(crossword.grid) == 15
|
||||||
|
assert all(len(row) == 15 for row in crossword.grid)
|
||||||
|
assert len(crossword.placed_words) > 0
|
||||||
|
|
||||||
|
# print the crossword for visual inspection
|
||||||
|
print(crossword)
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from multiplayer_crosswords.crossword_generators import extract_slots, slot_pattern, Slot
|
from multiplayer_crosswords.crossword_algorithm import extract_slots, slot_pattern, Slot
|
||||||
from multiplayer_crosswords.crossword import Orientation
|
from multiplayer_crosswords.crossword import Orientation
|
||||||
|
|
||||||
def test_extract_slots_simple():
|
def test_extract_slots_simple():
|
||||||
|
|||||||
Reference in New Issue
Block a user