finalize crossword class
This commit is contained in:
@ -1,115 +1,91 @@
|
||||
import pytest
|
||||
from multiplayer_crosswords.crossword import Crossword, Orientation, PlacedWord
|
||||
from unittest.mock import MagicMock
|
||||
from multiplayer_crosswords.dictionary import Dictionary
|
||||
from multiplayer_crosswords.utils import load_en_dictionary, load_de_dictionary
|
||||
|
||||
class DummyWord:
|
||||
def __init__(self, word):
|
||||
self.word = word
|
||||
|
||||
class DummyDictionary:
|
||||
def __init__(self, words):
|
||||
self.words = words
|
||||
|
||||
def make_crossword(rows=5, cols=5):
|
||||
dictionary = DummyDictionary([DummyWord("HELLO"), DummyWord("WORLD")])
|
||||
return Crossword(rows, cols, dictionary)
|
||||
|
||||
def test_can_place_word_empty_grid_horizontal():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HELLO")
|
||||
assert cw.can_place_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||
assert not cw.can_place_word(word, 0, 1, Orientation.HORIZONTAL) # would go out of bounds
|
||||
|
||||
def test_can_place_word_empty_grid_vertical():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HELLO")
|
||||
assert cw.can_place_word(word, 0, 0, Orientation.VERTICAL)
|
||||
assert not cw.can_place_word(word, 1, 0, Orientation.VERTICAL) # would go out of bounds
|
||||
|
||||
def test_add_word_and_grid_update():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HELLO")
|
||||
assert cw.add_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||
# Check grid contents
|
||||
assert cw.grid[0][0] == "H"
|
||||
assert cw.grid[0][1] == "E"
|
||||
assert cw.grid[0][2] == "L"
|
||||
assert cw.grid[0][3] == "L"
|
||||
assert cw.grid[0][4] == "O"
|
||||
# Start and end blocks
|
||||
assert cw.grid[0][0-0][0-1] == "#" if cw._in_bounds(0, -1) else True
|
||||
assert cw.grid[0][5] == "#" if cw._in_bounds(0, 5) else True
|
||||
|
||||
def test_cannot_place_overlapping_different_letter():
|
||||
cw = make_crossword()
|
||||
word1 = DummyWord("HELLO")
|
||||
word2 = DummyWord("WORLD")
|
||||
assert cw.add_word(word1, 0, 0, Orientation.HORIZONTAL)
|
||||
# Try to place "WORLD" vertically overlapping 'E' with 'O'
|
||||
assert not cw.can_place_word(word2, 0, 1, Orientation.VERTICAL)
|
||||
|
||||
def test_can_place_overlapping_same_letter():
|
||||
cw = make_crossword()
|
||||
word1 = DummyWord("HELLO")
|
||||
word2 = DummyWord("EVE")
|
||||
assert cw.add_word(word1, 0, 0, Orientation.HORIZONTAL)
|
||||
# "EVE" vertical, E overlaps at (0,1)
|
||||
word2 = DummyWord("EVE")
|
||||
assert cw.can_place_word(word2, 0, 1, Orientation.VERTICAL)
|
||||
assert cw.add_word(word2, 0, 1, Orientation.VERTICAL)
|
||||
|
||||
def test_blocks_are_placed_correctly():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HI")
|
||||
assert cw.add_word(word, 2, 2, Orientation.HORIZONTAL)
|
||||
# Start block
|
||||
assert cw.grid[2][1] == "#"
|
||||
# End block
|
||||
assert cw.grid[2][4] == "#"
|
||||
|
||||
def test_str_representation():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HI")
|
||||
cw.add_word(word, 0, 0, Orientation.HORIZONTAL)
|
||||
s = str(cw)
|
||||
assert "HI" in s
|
||||
assert "." in s
|
||||
|
||||
def test_placed_words_tracking():
|
||||
cw = make_crossword()
|
||||
word = DummyWord("HI")
|
||||
cw.add_word(word, 1, 1, Orientation.VERTICAL)
|
||||
assert len(cw.placed_words) == 1
|
||||
placed = cw.placed_words[0]
|
||||
assert placed.word == word
|
||||
assert placed.row == 1
|
||||
assert placed.col == 1
|
||||
assert placed.orientation == Orientation.VERTICAL
|
||||
|
||||
def test_valid_hello_world_crossword():
|
||||
cw = make_crossword()
|
||||
assert cw.add_word(DummyWord("HELLO"), 1, 0, Orientation.HORIZONTAL)
|
||||
assert cw.add_word(DummyWord("WORLD"), 0, 4, Orientation.VERTICAL)
|
||||
assert str(cw).count('H') == 1
|
||||
assert str(cw).count('E') == 1
|
||||
assert str(cw).count('L') == 3
|
||||
assert str(cw).count('O') == 1
|
||||
assert str(cw).count('W') == 1
|
||||
assert str(cw).count('R') == 1
|
||||
assert str(cw).count('D') == 1
|
||||
from multiplayer_crosswords.crossword import Crossword, CrosswordWord
|
||||
from multiplayer_crosswords.dictionary import Dictionary, Word
|
||||
|
||||
|
||||
def test_crossword_generation():
|
||||
dictionary = load_de_dictionary()
|
||||
crossword = Crossword.generate(15, 15, dictionary)
|
||||
assert crossword is not None
|
||||
def make_test_dictionary():
|
||||
d = Dictionary()
|
||||
d.add_word(Word("dog", hints=["bark"], difficulty=1.0))
|
||||
d.add_word(Word("cat", hints=["meow"], difficulty=1.0))
|
||||
return d
|
||||
|
||||
# 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)
|
||||
def test_extract_words_and_positions():
|
||||
# small 5x5 grid with two placed words: "dog" horizontal at (0,0)
|
||||
# and "cat" vertical starting at (4,1)
|
||||
grid = [
|
||||
["d", "o", "g", "#", "#"],
|
||||
["#", "#", "#", "#", "c"],
|
||||
["#", "#", "#", "#", "a"],
|
||||
["#", "#", "#", "#", "t"],
|
||||
["#", "#", "#", "#", "#"],
|
||||
]
|
||||
|
||||
d = make_test_dictionary()
|
||||
|
||||
cw = Crossword(dictionary=d, grid=grid)
|
||||
|
||||
# Two words should be extracted
|
||||
words = cw.words
|
||||
assert len(words) == 2
|
||||
|
||||
words_by_text = {w.word: w for w in words}
|
||||
assert "dog" in words_by_text and "cat" in words_by_text
|
||||
|
||||
dog = words_by_text["dog"]
|
||||
assert dog.start_x == 0 and dog.start_y == 0
|
||||
assert dog.orientation.name == "HORIZONTAL"
|
||||
# Hint should come from dictionary
|
||||
assert dog.hist in ("bark",) or dog.hist.startswith("No hint available") is False
|
||||
|
||||
cat = words_by_text["cat"]
|
||||
assert cat.start_x == 4 and cat.start_y == 1
|
||||
assert cat.orientation.name == "VERTICAL"
|
||||
|
||||
# get_words_by_y_x_position uses x,y ordering
|
||||
by_pos = cw.get_words_by_y_x_position(0, 0)
|
||||
assert any(w.word == "dog" for w in by_pos)
|
||||
|
||||
by_pos_cat = cw.get_words_by_y_x_position(4, 1)
|
||||
assert any(w.word == "cat" for w in by_pos_cat)
|
||||
|
||||
|
||||
def test_place_letter_and_check_solved():
|
||||
grid = [
|
||||
["d", "o", "g", "#"],
|
||||
["#", "#", "#", "#"],
|
||||
]
|
||||
d = make_test_dictionary()
|
||||
cw = Crossword(dictionary=d, grid=grid)
|
||||
|
||||
# Initially not solved
|
||||
dog = next(w for w in cw.words if w.word == "dog")
|
||||
assert dog.solved is False
|
||||
|
||||
# Place letters one by one
|
||||
assert cw.place_letter(0, 0, "d") is True
|
||||
assert cw.place_letter(0, 1, "o") is True
|
||||
assert cw.place_letter(0, 2, "g") is True
|
||||
|
||||
# Now the word should be marked solved
|
||||
assert dog.solved is True
|
||||
|
||||
# Cannot place on a wall
|
||||
assert cw.place_letter(3, 0, "x") is False
|
||||
|
||||
|
||||
def test_serialize_and_from_serialized_roundtrip():
|
||||
grid = [
|
||||
["d", "o", "g", "#"],
|
||||
["#", "#", "#", "#"],
|
||||
]
|
||||
d = make_test_dictionary()
|
||||
cw = Crossword(dictionary=d, grid=grid)
|
||||
|
||||
data = cw.serialize()
|
||||
reconstructed = Crossword.from_serialized(data, dictionary=d)
|
||||
|
||||
assert reconstructed is not None
|
||||
assert len(reconstructed.words) == len(cw.words)
|
||||
assert reconstructed.serialize()["solved_grid"] == cw.serialize()["solved_grid"]
|
||||
|
||||
Reference in New Issue
Block a user