From f86edb97bb96d15542853d92f58e54628bc8f78c Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:27:19 +0100 Subject: [PATCH 01/74] feat: add 10 generated boards (#15) #5 closes #5 --- bot/resources/fun/mathdoku_boards.txt | 244 ++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 bot/resources/fun/mathdoku_boards.txt diff --git a/bot/resources/fun/mathdoku_boards.txt b/bot/resources/fun/mathdoku_boards.txt new file mode 100644 index 0000000000..aaab280cc2 --- /dev/null +++ b/bot/resources/fun/mathdoku_boards.txt @@ -0,0 +1,244 @@ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3CG +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 + +5x5:d5 +.KK "2:(d=5)" +FFI2D +3GICD +EGCCA +EBJHA +EBJHH + +A 2- +B 12x +C 12x +D 1- +E 8+ +F 3- +G 3- +H 11+ +I 5+ +J 6+ + +4 1 3 2 5 +3 5 2 1 4 +5 2 4 3 1 +2 4 1 5 3 +1 3 5 4 2 + +5x5:d5 +.KK "3:(d=5)" +ICAAG +ICCGG +BBCE4 +F1DEH +FFD5H + +A 1- +B 1- +C 16+ +D 1- +E 3- +F 8+ +G 15x +H 5+ +I 1- + +3 4 1 2 5 +4 5 2 3 1 +2 3 5 1 4 +5 1 3 4 2 +1 2 4 5 3 + +5x5:d7 +.KK "4:(d=7)" +DDIIC +DHIBC +AHHBJ +A4GGJ +FFEE4 + +A 3+ +B 7+ +C 6+ +D 10+ +E 1- +F 2- +G 6+ +H 10+ +I 9+ +J 6x + +4 1 3 2 5 +5 2 4 3 1 +1 3 5 4 2 +2 4 1 5 3 +3 5 2 1 4 + +5x5:d6 +.KK "5:(d=6)" +FFADI +BJADI +BJEEE +GCCCC +G4CHH + +A 3- +B 20x +C 180x +D 2/ +E 15x +F 2- +G 3+ +H 7+ +I 3- +J 6x + +3 1 5 2 4 +5 3 2 4 1 +4 2 1 3 5 +2 5 4 1 3 +1 4 3 5 2 + +5x5:d5 +.KK "6:(d=5)" +CHGJJ +CHGFE +DKFFE +DKBII +2BBAA + +A 6+ +B 8+ +C 1- +D 6+ +E 5+ +F 9+ +G 1- +H 6+ +I 12x +J 5+ +K 1- + +4 1 5 3 2 +3 5 4 2 1 +1 3 2 5 4 +5 2 1 4 3 +2 4 3 1 5 + +5x5:d5 +.KK "7:(d=5)" +HHEG5 +DHEGJ +DDAAJ +FIIIC +FFBBC + +A 6+ +B 3- +C 7+ +D 12+ +E 15x +F 8+ +G 4+ +H 7+ +I 10+ +J 3+ + +2 4 3 1 5 +4 1 5 3 2 +3 5 4 2 1 +1 3 2 5 4 +5 2 1 4 3 + +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 + +5x5:d5 +.KK "9:(d=5)" +EGGGG +EIIAD +ECIAD +1CFFJ +HHBBJ + +A 5+ +B 2- +C 1- +D 8+ +E 11+ +F 7+ +G 30x +H 2- +I 12x +J 5+ + +4 1 5 3 2 +2 4 3 1 5 +5 2 1 4 3 +1 3 2 5 4 +3 5 4 2 1 + +5x5:d5 +.KK "10:(d=5)" +FFBBC +HFAGC +HHAGE +1IIGE +IIDDE + +A 8x +B 8+ +C 3+ +D 2- +E 60x +F 9+ +G 8x +H 9+ +I 200x + +4 2 3 5 1 +5 3 4 1 2 +3 1 2 4 5 +1 4 5 2 3 +2 5 1 3 4 From 21536f779b04588a57fcea33710b1001b07345cc Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:40:15 +0100 Subject: [PATCH 02/74] feat: create game datastructure (#16) #4 closes #4 --- bot/exts/fun/mathdoku.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bot/exts/fun/mathdoku.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py new file mode 100644 index 0000000000..096a3c3ae4 --- /dev/null +++ b/bot/exts/fun/mathdoku.py @@ -0,0 +1,25 @@ + +class Cell: + """Represents a single cell in the grid.""" + + def __init__(self, column: int, row: int) -> None: + self.column = column + self.row = row + +class Block: + """Represents a block in the puzzle, with its cells, operation and colour.""" + + def __init__(self, cells: list[Cell], label: str, label_cell: Cell, colour: tuple[int, int, int]) -> None: + self.cells = cells + self.label = label + self.label_cell = label_cell + self.colour = colour + +class Grid: + """Represents the full game board, with all blocks and player guesses.""" + + def __init__(self, size: int, blocks: list[Block]) -> None: + self.size = size + self.blocks = blocks + self.guesses: dict[tuple[int, int], int] = {} + self.cell_block_map: dict[tuple[int, int], Block] = {} From 4b7d2de75312e3df4d77e5e8be31be0849367341 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Mon, 23 Feb 2026 14:48:42 +0100 Subject: [PATCH 03/74] feat: add test suit that uses pytest (#19) close #9 --- tests/test_pytest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/test_pytest.py diff --git a/tests/test_pytest.py b/tests/test_pytest.py new file mode 100644 index 0000000000..208191c14c --- /dev/null +++ b/tests/test_pytest.py @@ -0,0 +1,12 @@ +""" +How to run pytest: +1. Check python --version and see that it is 3.13.X +2. run pip install pytest +3. run pip install pytest-asyncio +4. run this to actually run the tests: python -m pytest tests/ +""" + + +def test_pytest(): + """A simple sanity check to ensure pytest is working.""" + assert 3 == 3 \ No newline at end of file From bbf1a911152686a632df34e2075a7fb8ea740355 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Mon, 23 Feb 2026 14:43:22 +0100 Subject: [PATCH 04/74] feat: implement the draft of the kenken API The ./kenken command can now be used closes #12 --- bot/exts/fun/kenken.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 bot/exts/fun/kenken.py diff --git a/bot/exts/fun/kenken.py b/bot/exts/fun/kenken.py new file mode 100644 index 0000000000..85d270df4a --- /dev/null +++ b/bot/exts/fun/kenken.py @@ -0,0 +1,27 @@ +from collections.abc import Iterator +from dataclasses import dataclass +from random import randint, random + +import discord +from discord.ext import commands +from pydis_core.utils.logging import get_logger + +from bot.bot import Bot +from bot.constants import Client +from bot.utils.converters import CoordinateConverter +from bot.utils.exceptions import UserNotPlayingError + +class Kenken(commands.Cog): + """Play a game of Kenken.""" + + def __init__(self, bot: Bot): + self.bot = bot + + @commands.group(name="Kenken", invoke_without_command=True) + async def kenken_group(self, ctx: commands.Context) -> None: + """Commands for Playing Kenken.""" + await ctx.send("The Kenken API is working!") + +async def setup(bot: Bot) -> None: + """Load the Kenken cog.""" + await bot.add_cog(Kenken(bot)) From 4039fd3f7d7c84640dc923e5484f5f299c074a44 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Mon, 23 Feb 2026 15:23:03 +0100 Subject: [PATCH 05/74] refactor: rename kenken to mathdoku in the kenken.py file closes #22 --- .../fun/{kenken.py => mathdoku_integration.py} | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) rename bot/exts/fun/{kenken.py => mathdoku_integration.py} (55%) diff --git a/bot/exts/fun/kenken.py b/bot/exts/fun/mathdoku_integration.py similarity index 55% rename from bot/exts/fun/kenken.py rename to bot/exts/fun/mathdoku_integration.py index 85d270df4a..8036a250c8 100644 --- a/bot/exts/fun/kenken.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -11,17 +11,17 @@ from bot.utils.converters import CoordinateConverter from bot.utils.exceptions import UserNotPlayingError -class Kenken(commands.Cog): - """Play a game of Kenken.""" +class Mathdoku(commands.Cog): + """Play a game of Mathdoku.""" def __init__(self, bot: Bot): self.bot = bot - @commands.group(name="Kenken", invoke_without_command=True) - async def kenken_group(self, ctx: commands.Context) -> None: - """Commands for Playing Kenken.""" - await ctx.send("The Kenken API is working!") + @commands.group(name="Mathdoku", invoke_without_command=True) + async def Mathdoku_group(self, ctx: commands.Context) -> None: + """Commands for Playing Mathdoku.""" + await ctx.send("The Mathdoku API is working!") async def setup(bot: Bot) -> None: - """Load the Kenken cog.""" - await bot.add_cog(Kenken(bot)) + """Load the Mathdoku cog.""" + await bot.add_cog(Mathdoku(bot)) From 844a225f8789197afc50f8d346e687ad4c19aa02 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:27:11 +0100 Subject: [PATCH 06/74] docs: Create report.md documentation file (#24) #18 closes #18 --- report.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 report.md diff --git a/report.md b/report.md new file mode 100644 index 0000000000..930c56170f --- /dev/null +++ b/report.md @@ -0,0 +1,45 @@ +# Report for assignment 4 This is a template for your report. You are free to modify it as needed. It is not required to use markdown for your report either, but the report has to be delivered in a standard, cross-platform format. + +## Project Name: URL: One or two sentences describing it + +## Onboarding experience Did you choose a new project or continue on the previous one? If you changed the project, how did your experience differ from before? + +## Effort spent For each team member, how much time was spent in +1. plenary discussions/meetings; +2. discussions within parts of the group; +3. reading documentation; +4. configuration and setup; +5. analyzing code/output; +6. writing documentation; +7. writing code; +8. running code? + +For setting up tools and libraries (step 4), enumerate all dependencies you took care of and where you spent your time, if that time exceeds 30 minutes. + +## Overview of issue(s) and work done. Title: URL: Summary in one or two sentences Scope (functionality and code affected). + +## Requirements for the new feature or requirements affected by functionality being refactored + +Optional (point 3): trace tests to requirements. + +## Code changes + +### Patch (copy your changes or the add git command to show them) git diff ... + +Optional (point 4): the patch is clean. + +Optional (point 5): considered for acceptance (passes all automated checks). + +## Test results Overall results with link to a copy or excerpt of the logs (before/after refactoring). + +## UML class diagram and its description ### Key changes/classes affected + +Optional (point 1): Architectural overview. + +Optional (point 2): relation to design pattern(s). + +## Overall experience What are your main take-aways from this project? What did you learn? How did you grow as a team, using the Essence standard to evaluate yourself? + +Optional (point 6): How would you put your work in context with best software engineering practice? + +Optional (point 7): Is there something special you want to mention here? From 68e228103d0aa10e1e8434028ca22671e4f33f4d Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:55:52 +0100 Subject: [PATCH 07/74] feat: restucture of the mathdoku classes and new creation methods (#25) close #21 --- bot/exts/fun/mathdoku.py | 38 +++++++++++++++++++++++++++++++------- tests/test_grid_class.py | 7 +++++++ tests/test_pytest.py | 11 +++++++---- 3 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 tests/test_grid_class.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 096a3c3ae4..9e347bc005 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,25 +1,49 @@ + class Cell: """Represents a single cell in the grid.""" def __init__(self, column: int, row: int) -> None: self.column = column self.row = row + self.block = None + self.guess = 0 class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" - def __init__(self, cells: list[Cell], label: str, label_cell: Cell, colour: tuple[int, int, int]) -> None: - self.cells = cells - self.label = label + def __init__(self, operation: str, number: int, label_cell: Cell, colour: tuple[int, int, int]) -> None: + self.cells = [] + self.operation = operation + self.number = number self.label_cell = label_cell self.colour = colour class Grid: """Represents the full game board, with all blocks and player guesses.""" - def __init__(self, size: int, blocks: list[Block]) -> None: + def __init__(self, size: int) -> None: self.size = size - self.blocks = blocks - self.guesses: dict[tuple[int, int], int] = {} - self.cell_block_map: dict[tuple[int, int], Block] = {} + self.blocks = [] + self.cells = [] # 2D array for cells [row][col] + + self.__create_cells__() + + def __create_cells__(self) -> None: + for row in range(self.size): + self.cells.append([]) + for col in range(self.size): + self.cells[row].append(Cell(col, row)) + + def __str__(self) -> str: + print_str = "\n" + for row in range(self.size): + print_str += "[" + for col in range(self.size - 1): + print_str += str(self.cells[row][col].guess) + " " + print_str += str(self.cells[row][col].guess) + "]\n" + return print_str + + + + diff --git a/tests/test_grid_class.py b/tests/test_grid_class.py new file mode 100644 index 0000000000..ae2ce195c9 --- /dev/null +++ b/tests/test_grid_class.py @@ -0,0 +1,7 @@ +from bot.exts.fun.mathdoku import Grid, Block, Cell + + +def test_grid(): + grid = Grid(5) + assert grid.cells[0][0].guess == 0 + print(grid) \ No newline at end of file diff --git a/tests/test_pytest.py b/tests/test_pytest.py index 208191c14c..9e38378c32 100644 --- a/tests/test_pytest.py +++ b/tests/test_pytest.py @@ -1,9 +1,12 @@ """ How to run pytest: -1. Check python --version and see that it is 3.13.X -2. run pip install pytest -3. run pip install pytest-asyncio -4. run this to actually run the tests: python -m pytest tests/ +1. add venv or uv or install all dependecies uising: +pip install pydis-core[all]==11.8.0 arrow==1.3.0 +beautifulsoup4==4.12.3 colorama==0.4.6 coloredlogs==15.0.1 +emoji==2.14.0 emojis==0.7.0 lxml==6.0.0 pillow==12.1.1 pydantic==2.10.1 +pydantic-settings==2.8.1 pyjokes==0.8.3 PyYAML==6.0.2 rapidfuzz==3.12.2 sentry-sdk==2.19.2 + +2. run this to actually run the tests: python -m pytest -s """ From acbf0ac36bf56b3b9c9eec6be26ea874ad55a892 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:59:38 +0100 Subject: [PATCH 08/74] docs: Add onboarding documentation (#26) #2 closes #2 --- report.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/report.md b/report.md index 930c56170f..eaccd1c0ba 100644 --- a/report.md +++ b/report.md @@ -1,8 +1,24 @@ -# Report for assignment 4 This is a template for your report. You are free to modify it as needed. It is not required to use markdown for your report either, but the report has to be delivered in a standard, cross-platform format. +# Lab 4 Report: **Issue Resolution** + +## Project Description + +### Project Name: **Sir Lancebot** + +### URL: https://github.com/python-discordsir-lancebot + +### One or two sentences describing it: + +Sir Lancebot is an open-source Discord bot written in Python and maintained by the Python Discord community. . On the project, contributors can implement new commands and features, or fix issues. + +## Onboarding experience + +### Did you choose a new project or continue on the previous one? If you changed the project, how did your experience differ from before? + +We decided to select a new project because the remaining open issues in the mockito project were very complex to resolve. Therefore, we chose to siwtch to `Sir Lancebot`. + +The experience of selecting an issue in this new project was more satisfactory and straightforward. We chose one that the whole team found particularly interesting. The issue was clearly described, and provide to us more flexibility in terms of implementation and decision-making. -## Project Name: URL: One or two sentences describing it -## Onboarding experience Did you choose a new project or continue on the previous one? If you changed the project, how did your experience differ from before? ## Effort spent For each team member, how much time was spent in 1. plenary discussions/meetings; From 26bb5665970d362d58da741fbfd551c37c3d5cbf Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Tue, 24 Feb 2026 01:25:31 +0100 Subject: [PATCH 09/74] feat: implement parser and corresponding unit tests (#28) #6 closes #6 --- bot/exts/fun/mathdoku.py | 22 +++-- bot/exts/fun/mathdoku_integration.py | 13 +-- bot/exts/fun/mathdoku_parser.py | 123 +++++++++++++++++++++++++ tests/exts/fun/test_mathdoku_parser.py | 79 ++++++++++++++++ 4 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 bot/exts/fun/mathdoku_parser.py create mode 100644 tests/exts/fun/test_mathdoku_parser.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 9e347bc005..7c3e30006e 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,5 +1,3 @@ - - class Cell: """Represents a single cell in the grid.""" @@ -8,28 +6,32 @@ def __init__(self, column: int, row: int) -> None: self.row = row self.block = None self.guess = 0 + self.correct = 0 + class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" - def __init__(self, operation: str, number: int, label_cell: Cell, colour: tuple[int, int, int]) -> None: + def __init__(self, id: str, operation: str, number: int, label_cell: Cell, colour: tuple[int, int, int]) -> None: + self.id = id self.cells = [] self.operation = operation self.number = number self.label_cell = label_cell self.colour = colour + class Grid: """Represents the full game board, with all blocks and player guesses.""" def __init__(self, size: int) -> None: self.size = size self.blocks = [] - self.cells = [] # 2D array for cells [row][col] + self.cells = [] # 2D array for cells [row][col] - self.__create_cells__() + self._create_cells() - def __create_cells__(self) -> None: + def _create_cells(self) -> None: for row in range(self.size): self.cells.append([]) for col in range(self.size): @@ -44,6 +46,10 @@ def __str__(self) -> str: print_str += str(self.cells[row][col].guess) + "]\n" return print_str + def __getitem__(self, i) -> list[Cell]: + """ + Defines the indexing operator for the Grid class. - - + Grid[i] will return the i:th row. + """ + return self.cells[i] diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 8036a250c8..3961c671ed 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,15 +1,11 @@ -from collections.abc import Iterator -from dataclasses import dataclass -from random import randint, random -import discord from discord.ext import commands -from pydis_core.utils.logging import get_logger +from mathdoku_parser import create_grids from bot.bot import Bot -from bot.constants import Client -from bot.utils.converters import CoordinateConverter -from bot.utils.exceptions import UserNotPlayingError + +grids = create_grids() + class Mathdoku(commands.Cog): """Play a game of Mathdoku.""" @@ -22,6 +18,7 @@ async def Mathdoku_group(self, ctx: commands.Context) -> None: """Commands for Playing Mathdoku.""" await ctx.send("The Mathdoku API is working!") + async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" await bot.add_cog(Mathdoku(bot)) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py new file mode 100644 index 0000000000..c4c7b08138 --- /dev/null +++ b/bot/exts/fun/mathdoku_parser.py @@ -0,0 +1,123 @@ +import re +from pathlib import Path + +from .mathdoku import Block, Grid + +FILE_PATH = Path("bot/resources/fun/mathdoku_boards.txt") + + +def create_grids(file_path: Path = FILE_PATH) -> list[Grid]: + """ + Creates the board from the result of searching the board file for matches. + + The creation happens in this order: + 1) Create the blocks, set the cells' blocks references to the newly created block (for that cell) + 2) Set the newly created blocks' operations + 3) Read the suggested solution into each cell + + If an error occurs when parsing a board, it is skipped. This function assumes that the appropriate regex has ensured + safe integer casting. + + Returns a list of succesfully parsed boards. + """ + matched_grid_strs = _search_for_grids_in_file(file_path) + grids = [] + for m_board in matched_grid_strs: + expected_size = int(m_board[0][0]) + grid = Grid(expected_size) + grid_str = m_board[1] + operation_str = m_board[2] + solution_str = m_board[3] + try: + created_blocks = _create_cells_and_blocks(expected_size, grid, grid_str) + _read_block_operations(created_blocks, operation_str) + _read_solution(grid, solution_str) + except IndexError: + continue + grids.append(grid) + + return grids + + +def _search_for_grids_in_file(file_path: Path = FILE_PATH) -> list[tuple[str, str, str, str]]: + """ + The function searches the mathdoku file with regex and returns a list of any found boards. + + Regex groups are in the following order: + 1) size & difficulty, e.g. 5x5:d5 + 2) board cells & blocks, e.g. EAACC + 3) blocks and their operations, e.g. A 1- + 4) proposed solution, e.g. 1 5 4 3 2. + """ + matched_boards = re.findall( + r"""(\d+x\d+:d\d)\r?\n + \.KK\ "\d+:\(d=\d+\)"\r?\n + ((?:[\d\w]+\r?\n)+) + \r?\n + ((?:\w\ \d+[\+\-x\/]\r?\n)+) + \r?\n + ((?:\d+(?:\s+\d+)*\r?\n)+) + """, + file_path.read_text(), + flags=re.VERBOSE, + ) + return matched_boards + + +def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: str) -> dict[str, Block]: + """ + The function creates blocks from grid_str, the created grid's cells' blocks are set to reference them. + + Returns a dictionary which maps block ids (i.e. their letter) to their created reference. + """ + rows = grid_str.split() + seen_blocks = {} # seen block "ids" + if len(rows) != expected_size: + raise IndexError + for i, row in enumerate(rows): # splits on newline + if len(row) != expected_size: + raise IndexError + for j, col in enumerate(row): + cell = created_grid[i][j] + if col.isdigit(): # has no block + cell.block = None + continue + if col not in seen_blocks: + block = Block(col, None, None, cell, (255, 255, 255)) + seen_blocks[col] = block + created_grid.blocks.append(block) + else: + block = seen_blocks[col] + cell.block = block + block.cells.append(cell) + return seen_blocks + + +def _read_block_operations(created_blocks: dict[str, Block], operation_str: str) -> None: + """The function reads and sets the block operations of the created blocks.""" + for row in operation_str.splitlines(): + if not row: # empty + continue + + row = row.split() + if len(row) != 2: + raise IndexError + id = row[0] + num, op = int(row[1][:-1]), row[1][-1] + if id not in created_blocks: # block hasn't been created + raise IndexError + created_blocks[id].number = num + created_blocks[id].operation = op + + +def _read_solution(grid: Grid, solution_str: str) -> None: + """The function reads the solution and sets each cell's proposed correct value.""" + rows = solution_str.splitlines() + if len(rows) != grid.size: + raise IndexError + for i, row in enumerate(rows): + cols = row.split() + if len(cols) != grid.size: + raise IndexError + for j, col in enumerate(cols): + grid[i][j].correct = int(col) diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py new file mode 100644 index 0000000000..a8676cf7bc --- /dev/null +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -0,0 +1,79 @@ +from pathlib import Path + +from bot.exts.fun.mathdoku_parser import create_grids + + +def test_load_valid_5x5_grid(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3CG +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + assert len(grids) == 1 + grid = grids[0] + assert grid.size == 5 + assert len(grid.blocks) == 7 + assert grid.cells[0][0].correct == 1 + assert grid.cells[0][0].block.id == "E" + assert grid.cells[0][0].block.operation == "x" + assert grid.cells[0][0].block.number == 30 + + assert grid.cells[4][4].correct == 5 + assert grid.cells[4][4].block is None + + +def test_load_invalid_5x5_grid(tmp_path: Path) -> None: + """Creates a temp file with an invalid grid in it and attempts to load a grid from that file.""" + content = """\ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3C +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + assert len(grids) == 0 From 052b8d6bee35ca16f157b6198627b3b9e612c53e Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:23:27 +0100 Subject: [PATCH 10/74] feat: add victory check to Grid class (#31) #11 unit tests added close #11 --- bot/exts/fun/mathdoku.py | 97 +++++++++++++-- tests/exts/fun/test_grid_class.py | 31 +++++ tests/exts/fun/test_victory_check.py | 177 +++++++++++++++++++++++++++ tests/test_grid_class.py | 7 -- tests/test_pytest.py | 7 +- 5 files changed, 300 insertions(+), 19 deletions(-) create mode 100644 tests/exts/fun/test_grid_class.py create mode 100644 tests/exts/fun/test_victory_check.py delete mode 100644 tests/test_grid_class.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 7c3e30006e..bec7bfcd60 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -5,9 +5,16 @@ def __init__(self, column: int, row: int) -> None: self.column = column self.row = row self.block = None - self.guess = 0 + self._guess = 0 self.correct = 0 + @property + def guess(self): + return self._guess + + @guess.setter + def guess(self, new_guess): + self._guess = new_guess class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" @@ -27,15 +34,14 @@ class Grid: def __init__(self, size: int) -> None: self.size = size self.blocks = [] - self.cells = [] # 2D array for cells [row][col] + self._cells = tuple( + tuple(Cell(col, row) for col in range(size)) + for row in range(size) + ) # 2D tupple for cells [row][col] - self._create_cells() - - def _create_cells(self) -> None: - for row in range(self.size): - self.cells.append([]) - for col in range(self.size): - self.cells[row].append(Cell(col, row)) + @property + def cells(self): + return self._cells def __str__(self) -> str: print_str = "\n" @@ -43,8 +49,79 @@ def __str__(self) -> str: print_str += "[" for col in range(self.size - 1): print_str += str(self.cells[row][col].guess) + " " - print_str += str(self.cells[row][col].guess) + "]\n" + print_str += str(self.cells[row][col + 1].guess) + "]\n" return print_str + + def _latin_square_check(self) -> bool: + """ + Checks if the grid is filled correctly in terms of a latin square.\n + I.e all numbers in the range per colum and row exist. + """ + check_structure = [[False for col in range(self.size)] for row in range(self.size)] + + for row in range(self.size): + for col in range(self.size): + guess = self.cells[row][col].guess + if guess == 0: + return False + check_structure[row][guess - 1] = True + if all(check_structure[row]) is False: + return False + + return True + + def _blocks_fufilled_check(self) -> list[Block] | bool: + """ + Checks if all the blocks are filled correctly and meets the requirements. \n + Returns the blocks that are wrong or True if all blocks meet the requirements. \n + Will return False if the input is invalid. + """ + wrong_blocks = [] + for block in self.blocks: + result = block.cells[0].guess + if len(block.cells) < 1: + return False + if len(block.cells) == 1: + if block.cells[0].guess != block.number: + wrong_blocks.append(block) + continue + for cell in block.cells[1:]: + match block.operation: + case "+": + result += cell.guess + case "-": + result -= cell.guess + case "x": + result *= cell.guess + case "/": + # Should only be a block of 2 cells for devision + result1 = block.cells[0].guess / block.cells[1].guess + result2 = block.cells[1].guess / block.cells[0].guess + + if result1 != block.number and result2 != block.number: + wrong_blocks.append(block) + else: + result = block.number + break + + if abs(result) != block.number: + wrong_blocks.append(block) + + if len(wrong_blocks) == 0: + return True + return wrong_blocks + + + def check_victory(self) -> bool: + """ + Checks if the board is in a state where the player has won and will + return True or False + """ + if self._latin_square_check() and isinstance(self._blocks_fufilled_check(), bool) and self._blocks_fufilled_check(): + return True + return False + + def __getitem__(self, i) -> list[Cell]: """ diff --git a/tests/exts/fun/test_grid_class.py b/tests/exts/fun/test_grid_class.py new file mode 100644 index 0000000000..f4b8a5ede0 --- /dev/null +++ b/tests/exts/fun/test_grid_class.py @@ -0,0 +1,31 @@ +from bot.exts.fun.mathdoku import Grid, Block, Cell + + +def test_grid(): + """ + Contract: No errors should be thrown when setting up the grid and the initial + guess of a cell should be 0. + """ + grid = Grid(5) + assert grid.cells[0][0].guess == 0 + print(grid) + +def test_latin_square_check(): + """ + Contract: _latin_square_check() should only return true if + the grid is a latin square + """ + grid = Grid(5) + + #Makes a latin square + for row in range(grid.size): + for col in range(grid.size): + grid.cells[row][col].guess = ((row + col) % grid.size) + 1 + + print(grid) + + assert grid._latin_square_check() + + grid.cells[3][3].guess = 1 + + assert grid._latin_square_check() is False diff --git a/tests/exts/fun/test_victory_check.py b/tests/exts/fun/test_victory_check.py new file mode 100644 index 0000000000..e3562f218b --- /dev/null +++ b/tests/exts/fun/test_victory_check.py @@ -0,0 +1,177 @@ +from pathlib import Path + +from bot.exts.fun.mathdoku_parser import create_grids + +def test_victory_check_won(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3CG +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + assert grid.check_victory() + +def test_victory_check_lost(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3CG +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + grid.cells[0][1].guess = 4 + + assert grid.check_victory() is False + + assert grid._latin_square_check() is False + + assert grid._blocks_fufilled_check()[0].id == "A" + + +def test_victory_check_won_with_division(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + assert grid.check_victory() + +def test_victory_check_lost_with_division(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + + grid.cells[4][4].guess = 2 + + assert grid.check_victory() is False + + assert grid._latin_square_check() is False + + assert grid._blocks_fufilled_check()[0].id == "H" diff --git a/tests/test_grid_class.py b/tests/test_grid_class.py deleted file mode 100644 index ae2ce195c9..0000000000 --- a/tests/test_grid_class.py +++ /dev/null @@ -1,7 +0,0 @@ -from bot.exts.fun.mathdoku import Grid, Block, Cell - - -def test_grid(): - grid = Grid(5) - assert grid.cells[0][0].guess == 0 - print(grid) \ No newline at end of file diff --git a/tests/test_pytest.py b/tests/test_pytest.py index 9e38378c32..7c5440e3cd 100644 --- a/tests/test_pytest.py +++ b/tests/test_pytest.py @@ -1,12 +1,15 @@ """ How to run pytest: -1. add venv or uv or install all dependecies uising: +For venv: +1. python venv venv +2. add venv or uv or install all dependecies uising: pip install pydis-core[all]==11.8.0 arrow==1.3.0 beautifulsoup4==4.12.3 colorama==0.4.6 coloredlogs==15.0.1 emoji==2.14.0 emojis==0.7.0 lxml==6.0.0 pillow==12.1.1 pydantic==2.10.1 pydantic-settings==2.8.1 pyjokes==0.8.3 PyYAML==6.0.2 rapidfuzz==3.12.2 sentry-sdk==2.19.2 -2. run this to actually run the tests: python -m pytest -s +3. run pip install pytest +4. run this to actually run the tests: python -m pytest -s """ From cdb47d295e2e358e16407ff19e43773b5ee0aeda Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Tue, 24 Feb 2026 12:51:22 +0100 Subject: [PATCH 11/74] feat: implement color property for blocks (#33) #30 closes #30 --- bot/exts/fun/mathdoku.py | 99 ++++++++++++++++++++++++++++++++- bot/exts/fun/mathdoku_parser.py | 2 +- tests/exts/fun/test_mathdoku.py | 15 +++++ 3 files changed, 112 insertions(+), 4 deletions(-) create mode 100644 tests/exts/fun/test_mathdoku.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index bec7bfcd60..34a74793c4 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,3 +1,88 @@ +COLORS = [ + (235, 59, 59), + (199, 61, 50), + (235, 85, 59), + (199, 83, 50), + (235, 111, 59), + (199, 105, 50), + (235, 137, 59), + (199, 127, 50), + (235, 163, 59), + (199, 149, 50), + (235, 189, 59), + (199, 171, 50), + (235, 215, 59), + (199, 193, 50), + (228, 235, 59), + (182, 199, 50), + (202, 235, 59), + (160, 199, 50), + (176, 235, 59), + (138, 199, 50), + (150, 235, 59), + (116, 199, 50), + (124, 235, 59), + (94, 199, 50), + (98, 235, 59), + (72, 199, 50), + (72, 235, 59), + (50, 199, 50), + (59, 235, 72), + (50, 199, 72), + (59, 235, 98), + (50, 199, 94), + (59, 235, 124), + (50, 199, 116), + (59, 235, 150), + (50, 199, 138), + (59, 235, 176), + (50, 199, 160), + (59, 235, 202), + (50, 199, 182), + (59, 235, 228), + (50, 193, 199), + (59, 215, 235), + (50, 171, 199), + (59, 189, 235), + (50, 149, 199), + (59, 163, 235), + (50, 127, 199), + (59, 137, 235), + (50, 105, 199), + (59, 111, 235), + (50, 83, 199), + (59, 85, 235), + (50, 61, 199), + (59, 59, 235), + (61, 50, 199), + (85, 59, 235), + (83, 50, 199), + (111, 59, 235), + (105, 50, 199), + (137, 59, 235), + (127, 50, 199), + (163, 59, 235), + (149, 50, 199), + (189, 59, 235), + (171, 50, 199), + (215, 59, 235), + (193, 50, 199), + (235, 59, 228), + (199, 50, 182), + (235, 59, 202), + (199, 50, 160), + (235, 59, 176), + (199, 50, 138), + (235, 59, 150), + (199, 50, 116), + (235, 59, 124), + (199, 50, 94), + (235, 59, 98), + (199, 50, 72), + (235, 59, 72), +] + + class Cell: """Represents a single cell in the grid.""" @@ -19,13 +104,21 @@ def guess(self, new_guess): class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" - def __init__(self, id: str, operation: str, number: int, label_cell: Cell, colour: tuple[int, int, int]) -> None: + def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> None: self.id = id self.cells = [] self.operation = operation self.number = number self.label_cell = label_cell - self.colour = colour + + @property + def color(self) -> tuple[int, int, int]: + """Returns the block's color.""" + c_a = ord(self.id[0]) - ord("A") + if c_a > 0 and c_a < len(COLORS): + return COLORS[c_a] + + return COLORS[-1] class Grid: @@ -123,7 +216,7 @@ def check_victory(self) -> bool: - def __getitem__(self, i) -> list[Cell]: + def __getitem__(self, i: int) -> list[Cell]: """ Defines the indexing operator for the Grid class. diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index c4c7b08138..0a73403fd6 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -83,7 +83,7 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s cell.block = None continue if col not in seen_blocks: - block = Block(col, None, None, cell, (255, 255, 255)) + block = Block(col, None, None, cell) seen_blocks[col] = block created_grid.blocks.append(block) else: diff --git a/tests/exts/fun/test_mathdoku.py b/tests/exts/fun/test_mathdoku.py new file mode 100644 index 0000000000..eb398b26d1 --- /dev/null +++ b/tests/exts/fun/test_mathdoku.py @@ -0,0 +1,15 @@ +from bot.exts.fun.mathdoku import Block + + +def test_block_with_id_gets_color() -> None: + """Tests that a block with an id of A gets a color.""" + block = Block("A", None, None, None) + assert type(block.color) is tuple + assert len(block.color) == 3 + + +def test_block_with_unexpected_id_gets_color() -> None: + """Tests that a block with an unexpected id still gets a color.""" + block = Block("9ZZZ", None, None, None) + assert type(block.color) is tuple + assert len(block.color) == 3 From 90a03d778b00beab964ba4b96c9aa754aaaa4833 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Tue, 24 Feb 2026 13:09:49 +0100 Subject: [PATCH 12/74] feat: add blocks for singleton cells (#36) #34 closes #34 --- bot/exts/fun/mathdoku_parser.py | 2 +- tests/exts/fun/test_mathdoku_parser.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index 0a73403fd6..66230be92b 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -80,7 +80,7 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s for j, col in enumerate(row): cell = created_grid[i][j] if col.isdigit(): # has no block - cell.block = None + cell.block = Block("0", "", col, cell) continue if col not in seen_blocks: block = Block(col, None, None, cell) diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py index a8676cf7bc..4e752592b7 100644 --- a/tests/exts/fun/test_mathdoku_parser.py +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -1,5 +1,6 @@ from pathlib import Path +from bot.exts.fun.mathdoku import Block from bot.exts.fun.mathdoku_parser import create_grids @@ -43,7 +44,8 @@ def test_load_valid_5x5_grid(tmp_path: Path) -> None: assert grid.cells[0][0].block.number == 30 assert grid.cells[4][4].correct == 5 - assert grid.cells[4][4].block is None + assert type(grid.cells[4][4].block) is Block + assert grid.cells[4][4].block.id == "0" def test_load_invalid_5x5_grid(tmp_path: Path) -> None: From 24e0d06248431b4b25436dddb34dadd71c72461d Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Tue, 24 Feb 2026 13:21:23 +0100 Subject: [PATCH 13/74] feat: add start command draft to the mathdoku_integration file closes #29 --- bot/exts/fun/mathdoku_integration.py | 72 ++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 4 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 3961c671ed..9d4dbae937 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,10 +1,20 @@ +from collections.abc import Iterator +from dataclasses import dataclass +from random import randint, random +import discord from discord.ext import commands -from mathdoku_parser import create_grids +from pydis_core.utils.logging import get_logger from bot.bot import Bot +from bot.constants import Client +from bot.utils.converters import CoordinateConverter +from bot.utils.exceptions import UserNotPlayingError -grids = create_grids() + +# These 2 commands make the API not work for some reason if uncommented +# from .mathdoku_parser import create_grids +# grids = create_grids() class Mathdoku(commands.Cog): @@ -12,11 +22,65 @@ class Mathdoku(commands.Cog): def __init__(self, bot: Bot): self.bot = bot + self.playing = False - @commands.group(name="Mathdoku", invoke_without_command=True) - async def Mathdoku_group(self, ctx: commands.Context) -> None: + @commands.group(name="Mathdoku",aliases=("md",), invoke_without_command=True) + async def mathdoku_group(self, ctx: commands.Context) -> None: """Commands for Playing Mathdoku.""" await ctx.send("The Mathdoku API is working!") + await self.bot.invoke_help_command(ctx) + + # def predicate(self, message: discord.Message) -> bool: + # """Predicate checking the message typed for each turn.""" + # if message.author == self.turn.user and message.channel == self.turn.user.dm_channel: + # if message.content.lower() == "surrender": + # self.surrender = True + # return True + # self.match = re.fullmatch("([A-J]|[a-j]) ?((10)|[1-9])", message.content.strip()) + # if not self.match: + # self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) + # return bool(self.match) + # return None + + @mathdoku_group.command(name="start") + async def start_command(self, ctx: commands.Context, size: int = 5) -> None: + """Start a game of Mathdoku.""" + await ctx.send("Game of Mathdoku has been started!") + + self.playing = True + while self.playing == True: + self.input_number_on_board() + + await ctx.send("Game of Mathdoku is over!") + + async def input_number_on_board(self) -> None: #None might need to be changed later + # """Lets the player choose a square and input a number if it exists.""" + # input_message = await self.send( + # "Type the square and what number you want to input. Format it like this: A1 3\n" + # "Type `end` to end game." + # ) + # while True: + # try: + # await self.bot.wait_for("message", check=self.predicate, timeout=30.0) + # except TimeoutError: + # await self.send("You took too long. Game over!") + # self.gameover = True + # break + # else: + # if self.surrender: + # await self.next.user.send(f"{self.turn.user} surrendered. Game over!") + # await self.public_channel.send( + # f"Game over! {self.turn.user.mention} surrendered to {self.next.user.mention}!" + # ) + # self.gameover = True + # break + # square = self.get_square(self.next.grid, self.match.string) + # if square.aimed: + # await self.turn.user.send("You've already aimed at this square!", delete_after=3.0) + # else: + # break + # await turn_message.delete() + # return square async def setup(bot: Bot) -> None: From 917fe6a6f7c4ee79006df1c5f8553a2dc33593d8 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Tue, 24 Feb 2026 15:02:40 +0100 Subject: [PATCH 14/74] feat: add input number functionality for the mathdoku API closes #37 --- bot/exts/fun/mathdoku_integration.py | 83 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 9d4dbae937..b5e7e2e6fb 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -11,12 +11,17 @@ from bot.utils.converters import CoordinateConverter from bot.utils.exceptions import UserNotPlayingError +import re + # These 2 commands make the API not work for some reason if uncommented # from .mathdoku_parser import create_grids # grids = create_grids() + +CROSS_EMOJI = "\u274e" + class Mathdoku(commands.Cog): """Play a game of Mathdoku.""" @@ -30,17 +35,6 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: await ctx.send("The Mathdoku API is working!") await self.bot.invoke_help_command(ctx) - # def predicate(self, message: discord.Message) -> bool: - # """Predicate checking the message typed for each turn.""" - # if message.author == self.turn.user and message.channel == self.turn.user.dm_channel: - # if message.content.lower() == "surrender": - # self.surrender = True - # return True - # self.match = re.fullmatch("([A-J]|[a-j]) ?((10)|[1-9])", message.content.strip()) - # if not self.match: - # self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) - # return bool(self.match) - # return None @mathdoku_group.command(name="start") async def start_command(self, ctx: commands.Context, size: int = 5) -> None: @@ -49,38 +43,45 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: self.playing = True while self.playing == True: - self.input_number_on_board() + await self.input_number_on_board(ctx) await ctx.send("Game of Mathdoku is over!") - - async def input_number_on_board(self) -> None: #None might need to be changed later - # """Lets the player choose a square and input a number if it exists.""" - # input_message = await self.send( - # "Type the square and what number you want to input. Format it like this: A1 3\n" - # "Type `end` to end game." - # ) - # while True: - # try: - # await self.bot.wait_for("message", check=self.predicate, timeout=30.0) - # except TimeoutError: - # await self.send("You took too long. Game over!") - # self.gameover = True - # break - # else: - # if self.surrender: - # await self.next.user.send(f"{self.turn.user} surrendered. Game over!") - # await self.public_channel.send( - # f"Game over! {self.turn.user.mention} surrendered to {self.next.user.mention}!" - # ) - # self.gameover = True - # break - # square = self.get_square(self.next.grid, self.match.string) - # if square.aimed: - # await self.turn.user.send("You've already aimed at this square!", delete_after=3.0) - # else: - # break - # await turn_message.delete() - # return square + + async def input_number_on_board(self, ctx: commands.Context,) -> None: #None might need to be changed later + """Lets the player choose a square and input a number if it is valid.""" + turn_message = await ctx.send( + "Type the square and what number you want to input. Format it like this: A1 3\n" + "Type `end` to end game." + ) + while True: + try: + await self.bot.wait_for("message", check=self.predicate, timeout=60.0) + except TimeoutError: + await ctx.send("You took too long. Game over!") + self.playing = False + break + else: + if not self.playing: + await ctx.send("The game has been ended") + break + else: + break + await turn_message.delete() + + def predicate(self, message: discord.Message) -> bool: + """Predicate checking the message typed for each turn.""" + input_text = message.content.strip() + + if input_text.lower() == "end": + self.playing = False + return True + match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) + + #might wanna change to another format + if not match: + self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) + + return bool(match) async def setup(bot: Bot) -> None: From 418499a41a875ca88b4df9aff5aefd7d19992d8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:31:30 +0100 Subject: [PATCH 15/74] feat: add method to generate image (#42) #10 add a generate image method to the Grid and a correpsonding test closes #10 --- bot/exts/fun/mathdoku.py | 58 +++++++++++++++++++++++- tests/exts/fun/test_image_generation.py | 60 +++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 tests/exts/fun/test_image_generation.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 34a74793c4..bf51edc13c 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,3 +1,5 @@ +from PIL import Image, ImageDraw, ImageFont + COLORS = [ (235, 59, 59), (199, 61, 50), @@ -82,7 +84,6 @@ (235, 59, 72), ] - class Cell: """Represents a single cell in the grid.""" @@ -223,3 +224,58 @@ def __getitem__(self, i: int) -> list[Cell]: Grid[i] will return the i:th row. """ return self.cells[i] + + def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png") -> None: + """Print the Grid to """ + fontLable = ImageFont.load_default(15) + fontGuess = ImageFont.load_default(30) + img = Image.new("RGB", (cellSize * len(self.cells) + 2*margin, cellSize * len(self.cells) + 2*margin), "white") + draw = ImageDraw.Draw(img) + + for row in self.cells: + for cell in row: + # 1) The block color + x_start = (cell.column) * cellSize + margin + margin//2 + y_start = (cell.row) * cellSize + margin + margin//2 + x_end = (cell.column) * cellSize + cellSize + margin + margin//2 + y_end = (cell.row) * cellSize + cellSize + margin + margin//2 + color = cell.block.color + draw.rectangle((x_start, y_start, x_end, y_end),fill=color) + + # 2) the guess + guess = cell.guess + if (guess != 0): + draw.text((x_start+30, y_start+22), str(guess), + fill="black", font=fontGuess) + + # 3) the lines between the cells + draw.line((x_start, y_start, x_start + cellSize, y_start), fill="black", width=2) + draw.line((x_end, y_start, x_end, y_end), fill="black", width=2) + draw.line((x_start, y_start, x_start, y_start + cellSize), fill="black", width=2) + draw.line((x_start, y_end, x_end, y_end), fill="black", width=2) + + for block in self.blocks: + # 4) the lable of the block - in the top left corner of the lable cell + label_cell = block.label_cell + label = str(block.number) + " " + str(block.operation) + x_start = (label_cell.column) * cellSize + margin + margin//2 + y_start = (label_cell.row) * cellSize + margin + margin//2 + print("Label:" + label ) + draw.text((x_start+4, y_start+2), str(label), + fill="black", font=fontLable) + + # 5) the x axis description A-... + for i in range(self.size): + text = chr(ord("A") + i) + draw.text((margin + 30 + i * cellSize + margin//2, 4), str(text), + fill="black", font=fontGuess) + + # 6) the y axis description 1-... + for j in range(self.size): + text = str(j+1) + draw.text((13, margin + 22 + j * cellSize + margin//2), str(text), + fill="black", font=fontGuess) + + img.save(outfile) + print("Saved " + outfile) + return diff --git a/tests/exts/fun/test_image_generation.py b/tests/exts/fun/test_image_generation.py new file mode 100644 index 0000000000..1ec120ed83 --- /dev/null +++ b/tests/exts/fun/test_image_generation.py @@ -0,0 +1,60 @@ +import os + +from bot.exts.fun.mathdoku import Grid, Block + +def test_image_generation(): + # Contract: The generate image function should when called generate an image file named + # "mathdoku.png" + + filepath = "testdoku.png" + if (os.path.exists(filepath)): + os.remove(filepath) + assert os.path.exists(filepath) is False + + # setup testGrid + testingGrid = Grid(3) + cell_one = testingGrid.cells[0][0] + cell_two = testingGrid.cells[0][1] + cell_three = testingGrid.cells[0][2] + cell_four = testingGrid.cells[1][0] + cell_five = testingGrid.cells[1][1] + cell_six = testingGrid.cells[1][2] + cell_seven = testingGrid.cells[2][0] + cell_eight = testingGrid.cells[2][1] + cell_nine = testingGrid.cells[2][2] + testBlock_1 = Block("1", "+", 3, cell_one) + testBlock_2 = Block("2", "/", 30, cell_four) + testBlock_3 = Block("3", "-", 300, cell_five) + testingGrid.blocks.append(testBlock_1) + testingGrid.blocks.append(testBlock_2) + testingGrid.blocks.append(testBlock_3) + + cell_one.guess = 1 + cell_three.guess = 3 + cell_seven.guess = 4 + + testBlock_1.cells.append(cell_one) + testBlock_1.cells.append(cell_two) + testBlock_1.cells.append(cell_three) + cell_one.block = testBlock_1 + cell_two.block = testBlock_1 + cell_three.block = testBlock_1 + + testBlock_2.cells.append(cell_four) + testBlock_2.cells.append(cell_seven) + testBlock_2.cells.append(cell_eight) + testBlock_2.cells.append(cell_nine) + cell_four.block = testBlock_2 + cell_seven.block = testBlock_2 + cell_eight.block = testBlock_2 + cell_nine.block = testBlock_2 + + testBlock_3.cells.append(cell_five) + testBlock_3.cells.append(cell_six) + cell_five.block = testBlock_3 + cell_six.block = testBlock_3 + + testingGrid._generate_image(outfile=filepath) + assert os.path.exists(filepath) + if (os.path.exists(filepath)): + os.remove(filepath) \ No newline at end of file From 7ca077559f6bd55ef9256bea9636e3d3fd6f3540 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:53:52 +0100 Subject: [PATCH 16/74] feat: create grids in mathdoku_integration (#43) #35 closes #35 --- bot/exts/fun/mathdoku_integration.py | 29 +++++++++++++++++++--------- bot/exts/fun/mathdoku_parser.py | 7 +++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index b5e7e2e6fb..b2476fdb8c 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,3 +1,4 @@ +import asyncio from collections.abc import Iterator from dataclasses import dataclass from random import randint, random @@ -19,23 +20,24 @@ # grids = create_grids() - CROSS_EMOJI = "\u274e" +log = get_logger(__name__) + class Mathdoku(commands.Cog): """Play a game of Mathdoku.""" - def __init__(self, bot: Bot): + def __init__(self, bot: Bot, grids: list): self.bot = bot + self.grids = grids self.playing = False - @commands.group(name="Mathdoku",aliases=("md",), invoke_without_command=True) + @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: """Commands for Playing Mathdoku.""" await ctx.send("The Mathdoku API is working!") await self.bot.invoke_help_command(ctx) - @mathdoku_group.command(name="start") async def start_command(self, ctx: commands.Context, size: int = 5) -> None: """Start a game of Mathdoku.""" @@ -47,11 +49,13 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await ctx.send("Game of Mathdoku is over!") - async def input_number_on_board(self, ctx: commands.Context,) -> None: #None might need to be changed later + async def input_number_on_board( + self, + ctx: commands.Context, + ) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" turn_message = await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" - "Type `end` to end game." + "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." ) while True: try: @@ -77,7 +81,7 @@ def predicate(self, message: discord.Message) -> bool: return True match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) - #might wanna change to another format + # might wanna change to another format if not match: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) @@ -86,4 +90,11 @@ def predicate(self, message: discord.Message) -> bool: async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" - await bot.add_cog(Mathdoku(bot)) + from .mathdoku_parser import create_grids + + try: + grids = await asyncio.to_thread(create_grids) + except Exception: + log.exception("Failed to create Mathdoku grids during setup()") + grids = [] + await bot.add_cog(Mathdoku(bot, grids)) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index 66230be92b..b16a0b6c43 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -1,11 +1,18 @@ import re from pathlib import Path +from bot.bot import Bot + from .mathdoku import Block, Grid FILE_PATH = Path("bot/resources/fun/mathdoku_boards.txt") +async def setup(bot: Bot) -> None: + """Missing setup function was causing issues.""" + return + + def create_grids(file_path: Path = FILE_PATH) -> list[Grid]: """ Creates the board from the result of searching the board file for matches. From 5de5be639ff6c374f936feb8c025efdc27f90c08 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:09:06 +0100 Subject: [PATCH 17/74] feat: Add hint feature with Cooldown method (#44) #14 Add hint feature with Cooldown and its unit tests closes #14 --- bot/exts/fun/mathdoku.py | 38 +++++++++++ tests/exts/fun/test_mathdoku_hint.py | 95 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 tests/exts/fun/test_mathdoku_hint.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index bf51edc13c..07f86888d5 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,4 +1,5 @@ from PIL import Image, ImageDraw, ImageFont +from datetime import datetime COLORS = [ (235, 59, 59), @@ -84,6 +85,7 @@ (235, 59, 72), ] + class Cell: """Represents a single cell in the grid.""" @@ -125,6 +127,8 @@ def color(self) -> tuple[int, int, int]: class Grid: """Represents the full game board, with all blocks and player guesses.""" + HINT_COOLDOWN_SECONDS = 180 # 3 minutes of hint cooldown. + def __init__(self, size: int) -> None: self.size = size self.blocks = [] @@ -279,3 +283,37 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png") img.save(outfile) print("Saved " + outfile) return + def _find_first_empty_cell(self): + """Return the first empty cell (`guess == 0`) in row-major order, or `None` if all cells are filled.""" + for row in self.cells: + for cell in row: + if cell.guess == 0: + return cell, "empty" + + return None + + def hint(self, now: datetime | None = None): + """Return a hint for the first empty cell, or cooldown/all-filled info if a hint cannot be given.""" + current_time = datetime.now() if now is None else now + + if self._last_hint_timestamp is not None: + elapsed = (current_time - self._last_hint_timestamp).total_seconds() + if elapsed < self.HINT_COOLDOWN_SECONDS: + return { + "type": "cooldown", + "remaining_seconds": int(self.HINT_COOLDOWN_SECONDS - elapsed), + } + + found = self._find_first_empty_cell() + if found is None: + return {"type": "all filled cells"} + + cell, _reason = found + + self._last_hint_timestamp = current_time + return { + "type": "hint", + "row": cell.row, + "column": cell.column, + "value": cell.correct, + } diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py new file mode 100644 index 0000000000..000e2d8da0 --- /dev/null +++ b/tests/exts/fun/test_mathdoku_hint.py @@ -0,0 +1,95 @@ +from datetime import datetime, timedelta +from pathlib import Path + +from bot.exts.fun.mathdoku_parser import create_grids + + +def _load_5x5_grid(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" + content = """\ +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + return grid + +def test_hint_find_first_empty_cell(tmp_path: Path) -> None: + grid = _load_5x5_grid(tmp_path) + + result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) + + assert result["type"] == "hint" + assert result["row"] == 0 + assert result["column"] == 0 + assert result["value"] == 4 + +def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: + grid = _load_5x5_grid(tmp_path) + + grid.cells[0][0].guess = 4 + grid.cells[0][1].guess = 2 + grid.cells[0][2].guess = 1 + + result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) + + assert result["type"] == "hint" + assert result["row"] == 0 + assert result["column"] == 3 + assert result["value"] == 3 + +def test_hint_cooldown(tmp_path: Path) -> None: + grid = _load_5x5_grid(tmp_path) + t0 = datetime(2026, 2, 25, 14, 0, 0) + grid.hint(now=t0) + + result = grid.hint(now=t0 + timedelta(seconds=30)) + + assert result["type"] == "cooldown" + assert result["remaining_seconds"] == 150 + +def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: + grid = _load_5x5_grid(tmp_path) + t0 = datetime(2026, 2, 25, 14, 0, 0) + + first_hint = grid.hint(now=t0) + second_hint = grid.hint(now=t0 + timedelta(seconds=180)) + + assert first_hint["type"] == "hint" + assert second_hint["type"] == "hint" + +def test_hint_all_cells_filled(tmp_path: Path) -> None: + grid = _load_5x5_grid(tmp_path) + + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) + + assert result["type"] == "all filled cells" From 3fc62acaa521ba94de3a2733cde89dc011adca41 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:18:58 +0100 Subject: [PATCH 18/74] fix: add self._last_hint_timestamp attribute to class Grid (#46) #45 closes #45 --- bot/exts/fun/mathdoku.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 07f86888d5..938a641f46 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -136,6 +136,8 @@ def __init__(self, size: int) -> None: tuple(Cell(col, row) for col in range(size)) for row in range(size) ) # 2D tupple for cells [row][col] + + self._last_hint_timestamp = None @property def cells(self): From 3147dcf1fbf4149de75ac6ab03cd284f6c0b16a0 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Wed, 25 Feb 2026 16:21:24 +0100 Subject: [PATCH 19/74] feat: add start user guard clause to the integration file Ensure only the user starting can play the game closes #48 --- bot/exts/fun/mathdoku_integration.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index b2476fdb8c..847050e3fd 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -31,6 +31,7 @@ def __init__(self, bot: Bot, grids: list): self.bot = bot self.grids = grids self.playing = False + self.player_id = None @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -41,6 +42,7 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: @mathdoku_group.command(name="start") async def start_command(self, ctx: commands.Context, size: int = 5) -> None: """Start a game of Mathdoku.""" + self.player_id = ctx.author.id await ctx.send("Game of Mathdoku has been started!") self.playing = True @@ -74,18 +76,20 @@ async def input_number_on_board( def predicate(self, message: discord.Message) -> bool: """Predicate checking the message typed for each turn.""" - input_text = message.content.strip() + if self.player_id == message.author.id: + input_text = message.content.strip() - if input_text.lower() == "end": - self.playing = False - return True - match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) - - # might wanna change to another format - if not match: - self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) - - return bool(match) + if input_text.lower() == "end": + self.playing = False + return True + match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) + + # might wanna change to another format + if not match: + self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) + return bool(match) + else: + return None async def setup(bot: Bot) -> None: From 1efbade4953971fb0d3e7b5e401b65a5e21820ce Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:30:06 +0100 Subject: [PATCH 20/74] fix: singleton blocks added to grid's list of blocks (#55) #53 closes #53 --- bot/exts/fun/mathdoku_parser.py | 4 ++- tests/exts/fun/test_mathdoku_parser.py | 34 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index b16a0b6c43..e25a0bb48f 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -87,7 +87,9 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s for j, col in enumerate(row): cell = created_grid[i][j] if col.isdigit(): # has no block - cell.block = Block("0", "", col, cell) + block = Block("0", "", int(col), cell) + cell.block = block + created_grid.blocks.append(block) continue if col not in seen_blocks: block = Block(col, None, None, cell) diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py index 4e752592b7..47ff2c089e 100644 --- a/tests/exts/fun/test_mathdoku_parser.py +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -79,3 +79,37 @@ def test_load_invalid_5x5_grid(tmp_path: Path) -> None: grids = create_grids(file_path=grids_file) assert len(grids) == 0 + + +def test_load_valid_5x5_grid_and_check_singletons_have_blocks(tmp_path: Path) -> None: + """Loads a grid and checks that singleton cells have blocks that have correct numbers.""" + content = """\ +5x5:d5 +.KK "1:(d=5)" +EAACC +EB3CG +EBF5G +E1FDD +FFFD5 + +A 1- +B 6+ +C 12x +D 8+ +E 30x +F 15+ +G 3- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + grid = grids[0] + assert type(grid[2][3].block) is Block + assert type(grid[2][3].block.number) is int From 2816c04db3a75ae62cb81e23395cada9666b01e0 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:46:20 +0100 Subject: [PATCH 21/74] fix: add singleton cells to their blocks' list of cells (#57) #56 closes #56 --- bot/exts/fun/mathdoku_parser.py | 1 + tests/exts/fun/test_mathdoku_parser.py | 50 +++++++++++--------------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index e25a0bb48f..39d82ab549 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -89,6 +89,7 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s if col.isdigit(): # has no block block = Block("0", "", int(col), cell) cell.block = block + block.cells.append(cell) created_grid.blocks.append(block) continue if col not in seen_blocks: diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py index 47ff2c089e..54b56f5f09 100644 --- a/tests/exts/fun/test_mathdoku_parser.py +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -4,9 +4,7 @@ from bot.exts.fun.mathdoku_parser import create_grids -def test_load_valid_5x5_grid(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ +VALID_CONTENT = """\ 5x5:d5 .KK "1:(d=5)" EAACC @@ -29,15 +27,19 @@ def test_load_valid_5x5_grid(tmp_path: Path) -> None: 2 1 5 4 3 4 3 2 1 5 """ + + +def test_load_valid_5x5_grid(tmp_path: Path) -> None: + """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") + grids_file.write_text(VALID_CONTENT, encoding="utf-8") grids = create_grids(file_path=grids_file) assert len(grids) == 1 grid = grids[0] assert grid.size == 5 - assert len(grid.blocks) == 7 + assert len(grid.blocks) == 11 assert grid.cells[0][0].correct == 1 assert grid.cells[0][0].block.id == "E" assert grid.cells[0][0].block.operation == "x" @@ -83,33 +85,23 @@ def test_load_invalid_5x5_grid(tmp_path: Path) -> None: def test_load_valid_5x5_grid_and_check_singletons_have_blocks(tmp_path: Path) -> None: """Loads a grid and checks that singleton cells have blocks that have correct numbers.""" - content = """\ -5x5:d5 -.KK "1:(d=5)" -EAACC -EB3CG -EBF5G -E1FDD -FFFD5 - -A 1- -B 6+ -C 12x -D 8+ -E 30x -F 15+ -G 3- - -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 -""" grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") + grids_file.write_text(VALID_CONTENT, encoding="utf-8") grids = create_grids(file_path=grids_file) grid = grids[0] assert type(grid[2][3].block) is Block assert type(grid[2][3].block.number) is int + + +def test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells(tmp_path: Path) -> None: + """Loads a grid and checks that singleton cells have blocks that contain a list with only them.""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(VALID_CONTENT, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + grid = grids[0] + cell = grid[2][3] + block = cell.block + assert len(block.cells) == 1 + assert block.cells[0] is cell From 6f9e8b9e10ea988e11fbf4ad1de00b7f0aaca730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:55:21 +0100 Subject: [PATCH 22/74] feat: add initial grid to be send as discord message (#59) #49 closes #49 --- bot/exts/fun/mathdoku.py | 14 +++-- bot/exts/fun/mathdoku_integration.py | 54 +++++++++++++++++- kenken_colored.png | Bin 0 -> 66349 bytes testdoku2.png | Bin 0 -> 6656 bytes ...n.py => test_mathdoku_image_generation.py} | 8 +-- 5 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 kenken_colored.png create mode 100644 testdoku2.png rename tests/exts/fun/{test_image_generation.py => test_mathdoku_image_generation.py} (88%) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 938a641f46..d5d7a30a6b 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,3 +1,4 @@ +from io import BytesIO from PIL import Image, ImageDraw, ImageFont from datetime import datetime @@ -231,7 +232,7 @@ def __getitem__(self, i: int) -> list[Cell]: """ return self.cells[i] - def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png") -> None: + def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", saveToFile = False) -> None: """Print the Grid to """ fontLable = ImageFont.load_default(15) fontGuess = ImageFont.load_default(30) @@ -282,9 +283,14 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png") draw.text((13, margin + 22 + j * cellSize + margin//2), str(text), fill="black", font=fontGuess) - img.save(outfile) - print("Saved " + outfile) - return + if (saveToFile): + img.save(outfile) + + buffer = BytesIO() + img.save(buffer, "PNG") + buffer.seek(0) + return buffer + def _find_first_empty_cell(self): """Return the first empty cell (`guess == 0`) in row-major order, or `None` if all cells are filled.""" for row in self.cells: diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 847050e3fd..734e21b1bf 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -11,6 +11,8 @@ from bot.constants import Client from bot.utils.converters import CoordinateConverter from bot.utils.exceptions import UserNotPlayingError +# TODO remove this import when merging files +from bot.exts.fun.mathdoku import Grid, Block import re @@ -19,6 +21,49 @@ # from .mathdoku_parser import create_grids # grids = create_grids() +# TODO remove this testing Grid +testingGrid = Grid(3) +cell_one = testingGrid.cells[0][0] +cell_two = testingGrid.cells[0][1] +cell_three = testingGrid.cells[0][2] +cell_four = testingGrid.cells[1][0] +cell_five = testingGrid.cells[1][1] +cell_six = testingGrid.cells[1][2] +cell_seven = testingGrid.cells[2][0] +cell_eight = testingGrid.cells[2][1] +cell_nine = testingGrid.cells[2][2] +testBlock_1 = Block("A", "+", 3, cell_one) +testBlock_2 = Block("B", "/", 30, cell_four) +testBlock_3 = Block("C", "-", 300, cell_five) +testingGrid.blocks.append(testBlock_1) +testingGrid.blocks.append(testBlock_2) +testingGrid.blocks.append(testBlock_3) + +cell_one.guess = 1 +cell_three.guess = 3 +cell_seven.guess = 4 + +testBlock_1.cells.append(cell_one) +testBlock_1.cells.append(cell_two) +testBlock_1.cells.append(cell_three) +cell_one.block = testBlock_1 +cell_two.block = testBlock_1 +cell_three.block = testBlock_1 + +testBlock_2.cells.append(cell_four) +testBlock_2.cells.append(cell_seven) +testBlock_2.cells.append(cell_eight) +testBlock_2.cells.append(cell_nine) +cell_four.block = testBlock_2 +cell_seven.block = testBlock_2 +cell_eight.block = testBlock_2 +cell_nine.block = testBlock_2 + +testBlock_3.cells.append(cell_five) +testBlock_3.cells.append(cell_six) +cell_five.block = testBlock_3 +cell_six.block = testBlock_3 + CROSS_EMOJI = "\u274e" log = get_logger(__name__) @@ -29,9 +74,10 @@ class Mathdoku(commands.Cog): def __init__(self, bot: Bot, grids: list): self.bot = bot - self.grids = grids + self.grids = grids # The game Grid self.playing = False self.player_id = None + self.board = None # The message that the board is posten on @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -45,6 +91,12 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: self.player_id = ctx.author.id await ctx.send("Game of Mathdoku has been started!") + # TODO Create an actual Grid: + self.grid = testingGrid + file = discord.File(self.grid._generate_image(), filename="mathdoku.png") + self.board = await ctx.send(file=file) + + self.playing = True while self.playing == True: await self.input_number_on_board(ctx) diff --git a/kenken_colored.png b/kenken_colored.png new file mode 100644 index 0000000000000000000000000000000000000000..eb06156abcefac54effa69e8bc91b940c2f38a64 GIT binary patch literal 66349 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

(O%&dtO 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 literal 0 HcmV?d00001 diff --git a/testdoku2.png b/testdoku2.png new file mode 100644 index 0000000000000000000000000000000000000000..224f7a38a88e18f15df4fcd9604d2198e4202502 GIT binary patch literal 6656 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 Date: Wed, 25 Feb 2026 16:59:18 +0100 Subject: [PATCH 23/74] chor: delete testdoku2.png --- testdoku2.png | Bin 6656 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 testdoku2.png diff --git a/testdoku2.png b/testdoku2.png deleted file mode 100644 index 224f7a38a88e18f15df4fcd9604d2198e4202502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6656 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 Date: Wed, 25 Feb 2026 16:59:51 +0100 Subject: [PATCH 24/74] chore: delete kenken_colored.png --- kenken_colored.png | Bin 66349 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 kenken_colored.png diff --git a/kenken_colored.png b/kenken_colored.png deleted file mode 100644 index eb06156abcefac54effa69e8bc91b940c2f38a64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66349 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

(O%&dtO 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 From f92d89351f0150aeedac67e60e462cd898821ff8 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Wed, 25 Feb 2026 16:59:25 +0100 Subject: [PATCH 25/74] docs: write overview of issue(s) and work done in the report closes #58 --- report.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/report.md b/report.md index eaccd1c0ba..ebd8a64140 100644 --- a/report.md +++ b/report.md @@ -32,7 +32,12 @@ The experience of selecting an issue in this new project was more satisfactory a For setting up tools and libraries (step 4), enumerate all dependencies you took care of and where you spent your time, if that time exceeds 30 minutes. -## Overview of issue(s) and work done. Title: URL: Summary in one or two sentences Scope (functionality and code affected). +## Overview of issue(s) and work done. + +### Title: Kenken command #989 +### URL: https://github.com/python-discord/sir-lancebot/issues/989 (link to the original issue, should it be the issue to the pullrequest or the fork instead?) +### Summary in one or two sentences Scope (functionality and code affected). +We created a Mathdoku game command for the Sir Lancebot Discord bot. We only created new files in the sir-lancebot repository and did not change any existing files. ## Requirements for the new feature or requirements affected by functionality being refactored From b162797d1e4a1df478c60564a3ede340067ea00c Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:30:48 +0100 Subject: [PATCH 26/74] feat: add thick lines around blocks (#61) close #52 --- bot/exts/fun/mathdoku.py | 44 +++++++++++++++---- .../fun/test_mathdoku_image_generation.py | 2 +- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index d5d7a30a6b..c018738e4b 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -239,12 +239,12 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", img = Image.new("RGB", (cellSize * len(self.cells) + 2*margin, cellSize * len(self.cells) + 2*margin), "white") draw = ImageDraw.Draw(img) - for row in self.cells: - for cell in row: + for i, row in enumerate(self.cells): + for j, cell in enumerate(row): # 1) The block color - x_start = (cell.column) * cellSize + margin + margin//2 + x_start = (cell.column) * cellSize + margin + margin//2 y_start = (cell.row) * cellSize + margin + margin//2 - x_end = (cell.column) * cellSize + cellSize + margin + margin//2 + x_end = (cell.column) * cellSize + cellSize + margin + margin//2 y_end = (cell.row) * cellSize + cellSize + margin + margin//2 color = cell.block.color draw.rectangle((x_start, y_start, x_end, y_end),fill=color) @@ -256,10 +256,38 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", fill="black", font=fontGuess) # 3) the lines between the cells - draw.line((x_start, y_start, x_start + cellSize, y_start), fill="black", width=2) - draw.line((x_end, y_start, x_end, y_end), fill="black", width=2) - draw.line((x_start, y_start, x_start, y_start + cellSize), fill="black", width=2) - draw.line((x_start, y_end, x_end, y_end), fill="black", width=2) + thin_line_width = 2 + draw.line((x_start, y_start, x_start + cellSize, y_start), fill="black", width=thin_line_width) + draw.line((x_end, y_start, x_end, y_end), fill="black", width=thin_line_width) + draw.line((x_start, y_start, x_start, y_start + cellSize), fill="black", width=thin_line_width) + draw.line((x_start, y_end, x_end, y_end), fill="black", width=thin_line_width) + + n_over = 1 + n_under = 1 + n_right = 1 + n_left = 1 + + if i == 0: + n_over = 0 + if i == len(self.cells) - 1: + n_under = 0 + if j == 0: + n_left = 0 + if j == len(row) - 1: + n_right = 0 + + thick_line_width = 5 + offset = 2 + + if self.cells[i - n_over][j].block.id != cell.block.id or self.cells[i - n_over][j] is cell: + draw.line((x_start - offset, y_start, x_end + offset, y_start), fill="black", width=thick_line_width) + if self.cells[i + n_under][j].block.id != cell.block.id or self.cells[i + n_under][j] is cell: + draw.line((x_start - offset, y_end, x_end + offset, y_end), fill="black", width=thick_line_width) + if self.cells[i][j - n_left].block.id != cell.block.id or self.cells[i][j - n_left] is cell: + draw.line((x_start, y_start, x_start, y_end), fill="black", width=thick_line_width) + if self.cells[i][j + n_right].block.id != cell.block.id or self.cells[i][j + n_right] is cell: + draw.line((x_end, y_start, x_end, y_end), fill="black", width=thick_line_width) + for block in self.blocks: # 4) the lable of the block - in the top left corner of the lable cell diff --git a/tests/exts/fun/test_mathdoku_image_generation.py b/tests/exts/fun/test_mathdoku_image_generation.py index a6ad9e299b..6db43bba65 100644 --- a/tests/exts/fun/test_mathdoku_image_generation.py +++ b/tests/exts/fun/test_mathdoku_image_generation.py @@ -57,4 +57,4 @@ def test_image_generation(): testingGrid._generate_image(outfile=filepath, saveToFile=True) assert os.path.exists(filepath) if (os.path.exists(filepath)): - os.remove(filepath) \ No newline at end of file + os.remove(filepath) From 2a98cf518223493e0a31ad18ead173516eccaa21 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:43:32 +0100 Subject: [PATCH 27/74] feat: add handler for when board is filled that color in wrong blocks (#62) #32 close #32 --- bot/exts/fun/mathdoku.py | 34 ++++++++--- tests/exts/fun/test_board_filled_handler.py | 62 +++++++++++++++++++++ tests/exts/fun/test_mathdoku_parser.py | 1 - 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 tests/exts/fun/test_board_filled_handler.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index c018738e4b..92bfa83233 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -114,15 +114,12 @@ def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> No self.operation = operation self.number = number self.label_cell = label_cell + self.color = self.compute_color() - @property - def color(self) -> tuple[int, int, int]: - """Returns the block's color.""" - c_a = ord(self.id[0]) - ord("A") - if c_a > 0 and c_a < len(COLORS): - return COLORS[c_a] - - return COLORS[-1] + def compute_color(self) -> tuple[int, int, int]: + """Computes the block's color.""" + c_a = ord(self.id[0])**2 - ord("A") + return COLORS[c_a % len(COLORS)] class Grid: @@ -222,7 +219,26 @@ def check_victory(self) -> bool: return True return False - + + def board_filled_handler(self) -> bool: + """ + Handler for when board is filled.\n + The method calls the victory check and colors in the blocks that are not fufilled if any,\n + and returns True or False if the board is solved. + """ + if self.check_victory(): + return True + + wrong_blocks = self._blocks_fufilled_check() + for block in self.blocks: + if block in wrong_blocks: + block.color = (255, 0, 0) + else: + block.color = (100, 255, 100) + + return False + + def __getitem__(self, i: int) -> list[Cell]: """ diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py new file mode 100644 index 0000000000..a20b8ac6ed --- /dev/null +++ b/tests/exts/fun/test_board_filled_handler.py @@ -0,0 +1,62 @@ +from bot.exts.fun.mathdoku import Grid, Block +from bot.exts.fun.mathdoku_parser import create_grids +from pathlib import Path + + +def test_board_filled_handler(tmp_path: Path): + # Contract: The generate image function should when called generate an image file named + # "mathdoku.png" + + filepath = "testdokuboardfilled2.png" + + content = """\ +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + + grid.cells[4][4].guess = 2 + grid.cells[0][0].guess = 1 + + assert grid.board_filled_handler() is False + + assert grid.check_victory() is False + + assert grid._latin_square_check() is False + + assert grid._blocks_fufilled_check()[0].id == "F" + assert grid._blocks_fufilled_check()[1].id == "H" + + grid._generate_image(outfile=filepath, saveToFile=True) \ No newline at end of file diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py index 54b56f5f09..4afc46a7a1 100644 --- a/tests/exts/fun/test_mathdoku_parser.py +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -47,7 +47,6 @@ def test_load_valid_5x5_grid(tmp_path: Path) -> None: assert grid.cells[4][4].correct == 5 assert type(grid.cells[4][4].block) is Block - assert grid.cells[4][4].block.id == "0" def test_load_invalid_5x5_grid(tmp_path: Path) -> None: From 71e5d01b1541666d8d8ddcb9c048dcd41b192695 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:57:00 +0100 Subject: [PATCH 28/74] feat: add support for guess input (#63) #50 the user guess is validated, if correct it is applied and the image is updated closes #50 --- bot/exts/fun/mathdoku.py | 17 ++++++++ bot/exts/fun/mathdoku_integration.py | 18 ++++++--- tests/exts/fun/test_valid_guess.py | 60 ++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 tests/exts/fun/test_valid_guess.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 92bfa83233..baf3180459 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -369,3 +369,20 @@ def hint(self, now: datetime | None = None): "column": cell.column, "value": cell.correct, } + + def add_guess(self, guess): + """Takes the user guess and checks if its valid, if it is -> add to cell + A guess is in format A5 4, where A = column, 5 = row and 4 = guessed value""" + guess = guess.split() + column = ord(guess[0][0].lower()) - 97 + row = int(guess[0][1]) - 1 + value = int(guess[1]) + + if (column < 0 or row < 0 or value < 1): + return False + + if (column >= self.size or row >= self.size or value > self.size): + return False + + self.cells[row][column].guess = value + return True diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 734e21b1bf..6d1da11a38 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -65,7 +65,7 @@ cell_six.block = testBlock_3 -CROSS_EMOJI = "\u274e" +CROSS_EMOJI = '\u274C' #"\u274e" log = get_logger(__name__) @@ -92,8 +92,8 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await ctx.send("Game of Mathdoku has been started!") # TODO Create an actual Grid: - self.grid = testingGrid - file = discord.File(self.grid._generate_image(), filename="mathdoku.png") + self.grids = testingGrid + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) @@ -123,6 +123,8 @@ async def input_number_on_board( await ctx.send("The game has been ended") break else: + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + await self.board.edit(content=None, attachments=[file]) break await turn_message.delete() @@ -135,11 +137,15 @@ def predicate(self, message: discord.Message) -> bool: self.playing = False return True match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) - - # might wanna change to another format if not match: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) - return bool(match) + return bool(match) + + valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies + # might wanna change to another format + if not valid_match: + self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) + return valid_match else: return None diff --git a/tests/exts/fun/test_valid_guess.py b/tests/exts/fun/test_valid_guess.py new file mode 100644 index 0000000000..38569d3a94 --- /dev/null +++ b/tests/exts/fun/test_valid_guess.py @@ -0,0 +1,60 @@ +import os + +from bot.exts.fun.mathdoku import Grid, Block + +def test_addguess(): + # Contract: A guess is applied when its a valid guess. The rows and columns are within limits + # and the guess is within a valid range + + # setup testGrid + testingGrid = Grid(3) + cell_one = testingGrid.cells[0][0] + cell_two = testingGrid.cells[0][1] + cell_three = testingGrid.cells[0][2] + cell_four = testingGrid.cells[1][0] + cell_five = testingGrid.cells[1][1] + cell_six = testingGrid.cells[1][2] + cell_seven = testingGrid.cells[2][0] + cell_eight = testingGrid.cells[2][1] + cell_nine = testingGrid.cells[2][2] + testBlock_1 = Block("A", "+", 3, cell_one) + testBlock_2 = Block("B", "/", 30, cell_four) + testBlock_3 = Block("C", "-", 300, cell_five) + testingGrid.blocks.append(testBlock_1) + testingGrid.blocks.append(testBlock_2) + testingGrid.blocks.append(testBlock_3) + + cell_one.guess = 1 + cell_three.guess = 3 + cell_seven.guess = 4 + + testBlock_1.cells.append(cell_one) + testBlock_1.cells.append(cell_two) + testBlock_1.cells.append(cell_three) + cell_one.block = testBlock_1 + cell_two.block = testBlock_1 + cell_three.block = testBlock_1 + + testBlock_2.cells.append(cell_four) + testBlock_2.cells.append(cell_seven) + testBlock_2.cells.append(cell_eight) + testBlock_2.cells.append(cell_nine) + cell_four.block = testBlock_2 + cell_seven.block = testBlock_2 + cell_eight.block = testBlock_2 + cell_nine.block = testBlock_2 + + testBlock_3.cells.append(cell_five) + testBlock_3.cells.append(cell_six) + cell_five.block = testBlock_3 + cell_six.block = testBlock_3 + + assert cell_three.guess == 3 + testingGrid.add_guess("C1 2") + assert cell_three.guess == 2 + assert testingGrid.add_guess("A2 2") + assert testingGrid.add_guess("D2 2") is False + assert testingGrid.add_guess("A4 2") is False + assert testingGrid.add_guess("A2 4") is False + assert testingGrid.add_guess("A2 0") is False + assert testingGrid.add_guess("A2 -1") is False From 2fc68e86fd50d939b7a86cad162f1c2b05d73a7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Wed, 25 Feb 2026 21:09:25 +0100 Subject: [PATCH 29/74] feat: turn message changed (#67) #66 changed so the turn message is only printed at the beginning, prevents the chat from jumping closes #66 --- bot/exts/fun/mathdoku_integration.py | 8 ++++---- tests/exts/fun/test_board_filled_handler.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 6d1da11a38..d45e9c105b 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -95,6 +95,10 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: self.grids = testingGrid file = discord.File(self.grids._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) + + await ctx.send( + "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." + ) self.playing = True @@ -108,9 +112,6 @@ async def input_number_on_board( ctx: commands.Context, ) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" - turn_message = await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." - ) while True: try: await self.bot.wait_for("message", check=self.predicate, timeout=60.0) @@ -126,7 +127,6 @@ async def input_number_on_board( file = discord.File(self.grids._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) break - await turn_message.delete() def predicate(self, message: discord.Message) -> bool: """Predicate checking the message typed for each turn.""" diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index a20b8ac6ed..63677ae3f2 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -59,4 +59,4 @@ def test_board_filled_handler(tmp_path: Path): assert grid._blocks_fufilled_check()[0].id == "F" assert grid._blocks_fufilled_check()[1].id == "H" - grid._generate_image(outfile=filepath, saveToFile=True) \ No newline at end of file + grid._generate_image(outfile=filepath, saveToFile=False) \ No newline at end of file From 78918e31276c4e081aeb7a5e1c7a5ba5215ac4a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Wed, 25 Feb 2026 23:52:20 +0100 Subject: [PATCH 30/74] feat: add icon so player can verify their game status (#68) #51 once the grid is filled a magnifying glass appears and the user can verify their game status by clicking it. The grid then updates and shows which blocks are good and which are bad closes #51 --- bot/exts/fun/mathdoku.py | 10 +++- bot/exts/fun/mathdoku_integration.py | 85 ++++++++++++++++++++-------- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index baf3180459..1dca1a5697 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -237,8 +237,14 @@ def board_filled_handler(self) -> bool: block.color = (100, 255, 100) return False - - + + def check_full_grid(self) -> bool: + "Helper that checks if a grid is completely filled" + for i in range(self.size): + for cell in self.cells[i]: + if cell.guess <= 0: + return False + return True def __getitem__(self, i: int) -> list[Cell]: """ diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index d45e9c105b..9170331351 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -32,16 +32,16 @@ cell_seven = testingGrid.cells[2][0] cell_eight = testingGrid.cells[2][1] cell_nine = testingGrid.cells[2][2] -testBlock_1 = Block("A", "+", 3, cell_one) -testBlock_2 = Block("B", "/", 30, cell_four) -testBlock_3 = Block("C", "-", 300, cell_five) +testBlock_1 = Block("A", "+", 6, cell_one) +testBlock_2 = Block("B", "+", 9, cell_four) +testBlock_3 = Block("C", "+", 3, cell_five) testingGrid.blocks.append(testBlock_1) testingGrid.blocks.append(testBlock_2) testingGrid.blocks.append(testBlock_3) cell_one.guess = 1 cell_three.guess = 3 -cell_seven.guess = 4 +cell_seven.guess = 2 testBlock_1.cells.append(cell_one) testBlock_1.cells.append(cell_two) @@ -66,6 +66,8 @@ CROSS_EMOJI = '\u274C' #"\u274e" +MAGNIFYING_EMOJI = '๐Ÿ”' +PARTY_EMOJI = "๐ŸŽ‰" log = get_logger(__name__) @@ -107,26 +109,32 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await ctx.send("Game of Mathdoku is over!") - async def input_number_on_board( - self, - ctx: commands.Context, - ) -> None: # None might need to be changed later + async def input_number_on_board(self, ctx: commands.Context,) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" - while True: - try: - await self.bot.wait_for("message", check=self.predicate, timeout=60.0) - except TimeoutError: - await ctx.send("You took too long. Game over!") - self.playing = False - break - else: - if not self.playing: - await ctx.send("The game has been ended") - break - else: - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") - await self.board.edit(content=None, attachments=[file]) - break + + msg_task = asyncio.create_task(self.bot.wait_for("message", check=self.predicate, timeout=60.0)) + react_task = asyncio.create_task(self.bot.wait_for("reaction_add", check=self.reaction_predicate, timeout=60.0)) + + _, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when = asyncio.FIRST_COMPLETED) + + for task in pending: + task.cancel() + + try: + pass + + except TimeoutError: + await ctx.send("You took too long. Game over!") + self.playing = False + return + + if not self.playing: + await ctx.send("The game has been ended") + return + else: + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + await self.board.edit(content=None, attachments=[file]) + return def predicate(self, message: discord.Message) -> bool: """Predicate checking the message typed for each turn.""" @@ -136,18 +144,47 @@ def predicate(self, message: discord.Message) -> bool: if input_text.lower() == "end": self.playing = False return True + match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) if not match: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) return bool(match) valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies + full_grid = self.grids.check_full_grid() + if (full_grid): + self.bot.loop.create_task(self.board.add_reaction(MAGNIFYING_EMOJI)) # might wanna change to another format if not valid_match: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) return valid_match else: - return None + return False + + + + def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> bool: + """Predicate checking the reaction""" + if self.player_id == user.id and self.board.id == reaction.message.id: + emoji = str(reaction.emoji) + if emoji == MAGNIFYING_EMOJI: + if self.grids.check_full_grid(): + result = self.grids.board_filled_handler() + self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + self.bot.loop.create_task(self.board.edit(content=None, attachments=[file])) + if result: + self.bot.loop.create_task(self.board.add_reaction(PARTY_EMOJI)) + return result + else: + self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) + return False + + else: + self.bot.loop.create_task(self.board.remove_reaction(emoji, user)) + return False + + async def setup(bot: Bot) -> None: From ac9e4b5b215a08ba70ea7bfe13106071e1d42c0d Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:06:10 +0100 Subject: [PATCH 31/74] feat: add recolor of blocks after victory check (#72) If the victory check is unsuccessfull, i.e., board is not solved, then add back the original colors of the blocks on the next message close #69 --- bot/exts/fun/mathdoku.py | 19 +++++-- bot/exts/fun/mathdoku_integration.py | 8 +++ tests/exts/fun/test_board_filled_handler.py | 6 +- tests/exts/fun/test_recolor_blocks.py | 61 +++++++++++++++++++++ 4 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 tests/exts/fun/test_recolor_blocks.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 1dca1a5697..c2df0b3d7f 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -226,25 +226,32 @@ def board_filled_handler(self) -> bool: The method calls the victory check and colors in the blocks that are not fufilled if any,\n and returns True or False if the board is solved. """ - if self.check_victory(): - return True - wrong_blocks = self._blocks_fufilled_check() + if isinstance(wrong_blocks, bool): + wrong_blocks = [] + for block in self.blocks: if block in wrong_blocks: block.color = (255, 0, 0) else: block.color = (100, 255, 100) - return False + return self.check_victory() def check_full_grid(self) -> bool: - "Helper that checks if a grid is completely filled" + """Helper that checks if a grid is completely filled""" for i in range(self.size): for cell in self.cells[i]: if cell.guess <= 0: return False return True + + def recolor_blocks(self) -> None: + """ + Method to recolor all blocks in their original color + """ + for block in self.blocks: + block.color = block.compute_color() def __getitem__(self, i: int) -> list[Cell]: """ @@ -317,7 +324,7 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", label = str(block.number) + " " + str(block.operation) x_start = (label_cell.column) * cellSize + margin + margin//2 y_start = (label_cell.row) * cellSize + margin + margin//2 - print("Label:" + label ) + #print("Label:" + label ) draw.text((x_start+4, y_start+2), str(label), fill="black", font=fontLable) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 9170331351..96d649ab81 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -69,6 +69,7 @@ MAGNIFYING_EMOJI = '๐Ÿ”' PARTY_EMOJI = "๐ŸŽ‰" log = get_logger(__name__) +has_filled_board_prev = False class Mathdoku(commands.Cog): @@ -138,6 +139,7 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m def predicate(self, message: discord.Message) -> bool: """Predicate checking the message typed for each turn.""" + global has_filled_board_prev if self.player_id == message.author.id: input_text = message.content.strip() @@ -150,6 +152,10 @@ def predicate(self, message: discord.Message) -> bool: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) return bool(match) + if has_filled_board_prev: + self.grids.recolor_blocks() + has_filled_board_prev = False + valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies full_grid = self.grids.check_full_grid() if (full_grid): @@ -165,6 +171,7 @@ def predicate(self, message: discord.Message) -> bool: def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> bool: """Predicate checking the reaction""" + global has_filled_board_prev if self.player_id == user.id and self.board.id == reaction.message.id: emoji = str(reaction.emoji) if emoji == MAGNIFYING_EMOJI: @@ -173,6 +180,7 @@ def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) file = discord.File(self.grids._generate_image(), filename="mathdoku.png") self.bot.loop.create_task(self.board.edit(content=None, attachments=[file])) + has_filled_board_prev = True if result: self.bot.loop.create_task(self.board.add_reaction(PARTY_EMOJI)) return result diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 63677ae3f2..948bb8ed69 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -4,8 +4,10 @@ def test_board_filled_handler(tmp_path: Path): - # Contract: The generate image function should when called generate an image file named - # "mathdoku.png" + """ + Contract: The board filled should color in the right and wrong blocks + and save the board to testdokuboardfilled2.png + """ filepath = "testdokuboardfilled2.png" diff --git a/tests/exts/fun/test_recolor_blocks.py b/tests/exts/fun/test_recolor_blocks.py new file mode 100644 index 0000000000..0c2d0e0583 --- /dev/null +++ b/tests/exts/fun/test_recolor_blocks.py @@ -0,0 +1,61 @@ +from bot.exts.fun.mathdoku_parser import create_grids +from pathlib import Path + + +def test_board_filled_handler(tmp_path: Path): + """ + Contract: The board should be recolored + """ + + filepath1 = "testdokuboardfilledcolor1.png" + filepath2 = "testdokuboardfilledcolor2.png" + filepath3 = "testdokuboardfilledcolor3.png" + + content = """\ +5x5:d5 +.KK "8:(d=5)" +FFFF5 +AAE1B +CCEGB +CDIGG +3DIHH + +A 7+ +B 2- +C 9+ +D 5+ +E 2/ +F 10+ +G 40x +H 2/ +I 2- + +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 +3 1 5 2 4 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + save_to_disk = False + grid._generate_image(outfile=filepath1, saveToFile=save_to_disk) + + for block in grid.blocks: + block.color = (255, 255, 255) + + grid._generate_image(outfile=filepath2, saveToFile=save_to_disk) + + grid.recolor_blocks() + + grid._generate_image(outfile=filepath3, saveToFile=save_to_disk) \ No newline at end of file From 388ddcb6ab8ffd94ba84ee4d7498ad51d839fa8b Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Thu, 26 Feb 2026 11:29:38 +0100 Subject: [PATCH 32/74] feat: add different colors for singleton blocks (#76) close #73 --- bot/exts/fun/mathdoku_parser.py | 2 +- tests/exts/fun/test_board_filled_handler.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index 39d82ab549..81e2293535 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -87,7 +87,7 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s for j, col in enumerate(row): cell = created_grid[i][j] if col.isdigit(): # has no block - block = Block("0", "", int(col), cell) + block = Block(str(i) + str(j), "", int(col), cell) cell.block = block block.cells.append(cell) created_grid.blocks.append(block) diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 948bb8ed69..00189005c5 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -52,6 +52,7 @@ def test_board_filled_handler(tmp_path: Path): grid.cells[4][4].guess = 2 grid.cells[0][0].guess = 1 + # Comment out the line below to see different colors for singletons assert grid.board_filled_handler() is False assert grid.check_victory() is False From e55c87feae2c303afea57c38d7470ddd27415fe5 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Thu, 26 Feb 2026 12:54:13 +0100 Subject: [PATCH 33/74] docs: Add functional requirements documentation (#82) #81 closes #81 --- report.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/report.md b/report.md index ebd8a64140..0c7664a670 100644 --- a/report.md +++ b/report.md @@ -32,15 +32,78 @@ The experience of selecting an issue in this new project was more satisfactory a For setting up tools and libraries (step 4), enumerate all dependencies you took care of and where you spent your time, if that time exceeds 30 minutes. -## Overview of issue(s) and work done. +## Overview of issue(s) and work done. -### Title: Kenken command #989 +### Title: Kenken command #989 ### URL: https://github.com/python-discord/sir-lancebot/issues/989 (link to the original issue, should it be the issue to the pullrequest or the fork instead?) ### Summary in one or two sentences Scope (functionality and code affected). -We created a Mathdoku game command for the Sir Lancebot Discord bot. We only created new files in the sir-lancebot repository and did not change any existing files. +We created a Mathdoku game command for the Sir Lancebot Discord bot. We only created new files in the sir-lancebot repository and did not change any existing files. ## Requirements for the new feature or requirements affected by functionality being refactored +- **FR-01 - Help Command Exposure:** +The bot shall expose a `help` entry under the command group `.Mathdoku` for running **mathdoku**. + +- **FR-02 - Start Game Command:** +The command `.md start`shall start a new valid **mathdoku** game session. + +- **FR-03 - Grid Size:** +The `.md start` command shall accept an optional grid size parameter. + +- **FR-04 - Independent Game Sessions:** +Each game session shall maintain an independent board state. + +- **FR-05 - Hint Message Publication:** +After starting a game, the bot publish a hint message and automatically attach a lightbulb emoji reaction to that message. + +- **FR-06 - Hint Cooldown Mechanism:** +There shall be a cooldown period of 180 seconds between consecutive hint requests. + +- **FR-07 - Board Representation:** +The board shall be internally represented as an indexable matrix structure, accessible by row and column coordinates. + +- **FR-08 - Cell Data Model:** +Each cell in the board shall store its row and column coordinates, associated block, current guessed value, and correct solution value. + +- **FR-09 - Board Parsing Validation:** +The board parser shall ignore any invalid configuration that violates the mathdoku rules. + +- **FR-10 - Block Data Model:** +Each block shall store a unique id, a mathematical operation, and a target result number. + +- **FR-11 - Input Request Handling:** +The game shall prompt the player to provide input moves during gameplay. The bot must clearly indicate when user input is expected. + +- **FR-12 - Leave command:** +The game shall provide a command allowing users to leave an active game session. + +- **FR-13 - Invalid Input Notification:** +The bot shall notify the user whenever an invalid input is detected. + +- **FR-14 - Inactivity Timeout:** +The game session shall automatically terminate after a defined period of user inactivity. + +- **FR-15 - Win Condition Validation:** +The game shall validate winning conditions by checking both the Latin square properly and correct block constraint satisfaction. + +- **FR-16 - Error Identification:** +The game shall identify incorrect cells or blocks. + +- **FR-17 - Visual Board Representation:** +The board shall be visually represented as an image showing grid structure and blocking coloring. + +- **FR-18 - Hint Reaction Trigger:** +The bot shall trigger the hint logic when a user reacts with the lightbulb emoji on the hint message. + +- **FR-19 - Hint Contect Logic:** +A hint shall return the first empty cell in teh board and reveal its correct value. + +- **FR-20 - Cooldown Feedback:** +The bot shall notify the user of the remaining cooldown time in seconds. + +- **FR-21 - No Available Hint Notification:** +The bot shall notify the user that no hints are available if all cells in the board are filled. + Optional (point 3): trace tests to requirements. ## Code changes From 7f0c95c6a15a045b5a51666f6664711c66bc0125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:22:29 +0100 Subject: [PATCH 34/74] refactor: Refactor the predicate functpredicate functions (#83) #74 create handlers for the reactions, the predicate functions should only return true or false closes #74 --- bot/exts/fun/mathdoku_integration.py | 104 ++++++++++++++------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 96d649ab81..3534426ec2 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -102,44 +102,70 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await ctx.send( "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." ) - - self.playing = True while self.playing == True: await self.input_number_on_board(ctx) await ctx.send("Game of Mathdoku is over!") + + async def input_number_on_board(self, ctx: commands.Context,) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" msg_task = asyncio.create_task(self.bot.wait_for("message", check=self.predicate, timeout=60.0)) react_task = asyncio.create_task(self.bot.wait_for("reaction_add", check=self.reaction_predicate, timeout=60.0)) - _, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when = asyncio.FIRST_COMPLETED) + done, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when = asyncio.FIRST_COMPLETED) for task in pending: task.cancel() try: - pass + finished = done.pop() + result = await finished - except TimeoutError: + except TimeoutError: # Timeout await ctx.send("You took too long. Game over!") self.playing = False return - if not self.playing: + if not self.playing: # takes care of the end message await ctx.send("The game has been ended") return - else: + + if finished is react_task: # A Reaction was posted + reaction, user = result + emoji = str(reaction.emoji) + if self.player_id != user.id: # reaction by wrong player + await self.board.remove_reaction(emoji, user) + if emoji == MAGNIFYING_EMOJI: + await self.magnifying_handler(ctx=ctx, user=user) + elif emoji == "hint": + pass + elif emoji == "rules": + pass + else: # any other emoji + await self.board.remove_reaction(emoji, user) + + else: # A message was posted + input_text = result.content.strip() + valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies + if not valid_match: + await result.add_reaction(CROSS_EMOJI) + return + + full_grid = self.grids.check_full_grid() + if (full_grid): + await self.board.add_reaction(MAGNIFYING_EMOJI) + + self.grids.recolor_blocks() file = discord.File(self.grids._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) return def predicate(self, message: discord.Message) -> bool: - """Predicate checking the message typed for each turn.""" - global has_filled_board_prev + """Predicate checking if the message matches a guess or the word end""" if self.player_id == message.author.id: input_text = message.content.strip() @@ -150,50 +176,32 @@ def predicate(self, message: discord.Message) -> bool: match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) if not match: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) - return bool(match) - - if has_filled_board_prev: - self.grids.recolor_blocks() - has_filled_board_prev = False - - valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies - full_grid = self.grids.check_full_grid() - if (full_grid): - self.bot.loop.create_task(self.board.add_reaction(MAGNIFYING_EMOJI)) - # might wanna change to another format - if not valid_match: - self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) - return valid_match + return False + return True else: return False - - def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> bool: - """Predicate checking the reaction""" - global has_filled_board_prev - if self.player_id == user.id and self.board.id == reaction.message.id: - emoji = str(reaction.emoji) - if emoji == MAGNIFYING_EMOJI: - if self.grids.check_full_grid(): - result = self.grids.board_filled_handler() - self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") - self.bot.loop.create_task(self.board.edit(content=None, attachments=[file])) - has_filled_board_prev = True - if result: - self.bot.loop.create_task(self.board.add_reaction(PARTY_EMOJI)) - return result - else: - self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) - return False - - else: - self.bot.loop.create_task(self.board.remove_reaction(emoji, user)) - return False + """Predicate checking if the reaction was on the correct message""" + if self.board.id == reaction.message.id: + return True - - + async def magnifying_handler(self, ctx, user): + if self.grids.check_full_grid(): + await self.board.remove_reaction(MAGNIFYING_EMOJI, user) + + result = self.grids.board_filled_handler() # check win and update img + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + await self.board.edit(content=None, attachments=[file]) + + if result: # WIN + await self.board.add_reaction(PARTY_EMOJI) + await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) + self.playing = False + return + else: + self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) + return async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" From 2782854d1d095fe5c17ff5c8cf1f1d1418f748ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:48:49 +0100 Subject: [PATCH 35/74] feat: add mathdoku hint interaction (#84) #47 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add mathdoku hint interaction with lightbulb emoji reaction and a 3 minute cooldown closes #47 Co-authored-by: Arnau Pelechano Garcรญa --- bot/exts/fun/mathdoku_integration.py | 34 ++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 3534426ec2..6b1e0d0921 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,7 +1,7 @@ import asyncio -from collections.abc import Iterator -from dataclasses import dataclass -from random import randint, random +import re +from copy import deepcopy +from random import randint import discord from discord.ext import commands @@ -68,6 +68,7 @@ CROSS_EMOJI = '\u274C' #"\u274e" MAGNIFYING_EMOJI = '๐Ÿ”' PARTY_EMOJI = "๐ŸŽ‰" +HINT_EMOJI = "๐Ÿ’ก" log = get_logger(__name__) has_filled_board_prev = False @@ -102,14 +103,14 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await ctx.send( "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." ) + + await self.board.add_reaction(HINT_EMOJI) + self.playing = True - while self.playing == True: + while self.playing is True: await self.input_number_on_board(ctx) - await ctx.send("Game of Mathdoku is over!") - - async def input_number_on_board(self, ctx: commands.Context,) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" @@ -141,8 +142,8 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m await self.board.remove_reaction(emoji, user) if emoji == MAGNIFYING_EMOJI: await self.magnifying_handler(ctx=ctx, user=user) - elif emoji == "hint": - pass + elif emoji == HINT_EMOJI: + await self.hint_handler(ctx=ctx, user=user) elif emoji == "rules": pass else: # any other emoji @@ -201,8 +202,21 @@ async def magnifying_handler(self, ctx, user): return else: self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) - return + async def hint_handler(self, ctx, user) -> None: + """Handle hint request via ๐Ÿ’ก reaction.""" + + await self.board.remove_reaction(HINT_EMOJI, user) + result = self.grids.hint() + + if result["type"] == "cooldown": + await ctx.send(f"Hint on cooldown. Try again in {result['remaining_seconds']}s.") + elif result["type"] == "all filled cells": + await ctx.send("No empty cells left.") + else: + await ctx.send(f"Hint: row {result['row'] + 1}, column {result['column'] + 1} should be {result['value']}.") + + async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" from .mathdoku_parser import create_grids From 5e2f4ee2f970b544efe09feb99311d7fe1c7f2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Fri, 27 Feb 2026 13:46:12 +0100 Subject: [PATCH 36/74] fix: prevent several players from starting games (#87) #79 when a game is already running, no new game should be able to be started closes #79 --- bot/exts/fun/mathdoku_integration.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 6b1e0d0921..281de683ae 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -92,6 +92,12 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: @mathdoku_group.command(name="start") async def start_command(self, ctx: commands.Context, size: int = 5) -> None: """Start a game of Mathdoku.""" + + if self.playing: + await ctx.send("Someone else is playing right now. Please wait your turn.") + return + self.playing = True + self.player_id = ctx.author.id await ctx.send("Game of Mathdoku has been started!") @@ -106,7 +112,6 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await self.board.add_reaction(HINT_EMOJI) - self.playing = True while self.playing is True: await self.input_number_on_board(ctx) await ctx.send("Game of Mathdoku is over!") From dfe8bde5e995fb443e9cb8c7cabc0676cc452f40 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:03:14 +0100 Subject: [PATCH 37/74] fix: hint solution fixed and changed the hint format (#89) #85 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closesย #85 --- bot/exts/fun/mathdoku.py | 83 ++++++++++++++-------------- bot/exts/fun/mathdoku_integration.py | 72 ++++++++++++------------ 2 files changed, 77 insertions(+), 78 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index c2df0b3d7f..06841b2cd5 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,6 +1,7 @@ +from datetime import datetime from io import BytesIO + from PIL import Image, ImageDraw, ImageFont -from datetime import datetime COLORS = [ (235, 59, 59), @@ -102,7 +103,7 @@ def guess(self): return self._guess @guess.setter - def guess(self, new_guess): + def guess(self, new_guess) -> None: self._guess = new_guess class Block: @@ -134,7 +135,7 @@ def __init__(self, size: int) -> None: tuple(Cell(col, row) for col in range(size)) for row in range(size) ) # 2D tupple for cells [row][col] - + self._last_hint_timestamp = None @property @@ -149,7 +150,7 @@ def __str__(self) -> str: print_str += str(self.cells[row][col].guess) + " " print_str += str(self.cells[row][col + 1].guess) + "]\n" return print_str - + def _latin_square_check(self) -> bool: """ Checks if the grid is filled correctly in terms of a latin square.\n @@ -167,12 +168,12 @@ def _latin_square_check(self) -> bool: return False return True - + def _blocks_fufilled_check(self) -> list[Block] | bool: """ Checks if all the blocks are filled correctly and meets the requirements. \n Returns the blocks that are wrong or True if all blocks meet the requirements. \n - Will return False if the input is invalid. + Will return False if the input is invalid. """ wrong_blocks = [] for block in self.blocks: @@ -201,10 +202,10 @@ def _blocks_fufilled_check(self) -> list[Block] | bool: else: result = block.number break - + if abs(result) != block.number: wrong_blocks.append(block) - + if len(wrong_blocks) == 0: return True return wrong_blocks @@ -212,20 +213,18 @@ def _blocks_fufilled_check(self) -> list[Block] | bool: def check_victory(self) -> bool: """ - Checks if the board is in a state where the player has won and will - return True or False + Checks if the board is in a state where the player has won and will + return True or False. """ - if self._latin_square_check() and isinstance(self._blocks_fufilled_check(), bool) and self._blocks_fufilled_check(): - return True - return False - + return bool(self._latin_square_check() and isinstance(self._blocks_fufilled_check(), bool) and self._blocks_fufilled_check()) + def board_filled_handler(self) -> bool: """ Handler for when board is filled.\n The method calls the victory check and colors in the blocks that are not fufilled if any,\n and returns True or False if the board is solved. - """ + """ wrong_blocks = self._blocks_fufilled_check() if isinstance(wrong_blocks, bool): wrong_blocks = [] @@ -237,19 +236,17 @@ def board_filled_handler(self) -> bool: block.color = (100, 255, 100) return self.check_victory() - + def check_full_grid(self) -> bool: - """Helper that checks if a grid is completely filled""" + """Helper that checks if a grid is completely filled.""" for i in range(self.size): for cell in self.cells[i]: if cell.guess <= 0: return False return True - + def recolor_blocks(self) -> None: - """ - Method to recolor all blocks in their original color - """ + """Method to recolor all blocks in their original color.""" for block in self.blocks: block.color = block.compute_color() @@ -262,7 +259,7 @@ def __getitem__(self, i: int) -> list[Cell]: return self.cells[i] def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", saveToFile = False) -> None: - """Print the Grid to """ + """Print the Grid to.""" fontLable = ImageFont.load_default(15) fontGuess = ImageFont.load_default(30) img = Image.new("RGB", (cellSize * len(self.cells) + 2*margin, cellSize * len(self.cells) + 2*margin), "white") @@ -271,19 +268,19 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", for i, row in enumerate(self.cells): for j, cell in enumerate(row): # 1) The block color - x_start = (cell.column) * cellSize + margin + margin//2 + x_start = (cell.column) * cellSize + margin + margin//2 y_start = (cell.row) * cellSize + margin + margin//2 - x_end = (cell.column) * cellSize + cellSize + margin + margin//2 + x_end = (cell.column) * cellSize + cellSize + margin + margin//2 y_end = (cell.row) * cellSize + cellSize + margin + margin//2 color = cell.block.color draw.rectangle((x_start, y_start, x_end, y_end),fill=color) - + # 2) the guess guess = cell.guess if (guess != 0): - draw.text((x_start+30, y_start+22), str(guess), + draw.text((x_start+30, y_start+22), str(guess), fill="black", font=fontGuess) - + # 3) the lines between the cells thin_line_width = 2 draw.line((x_start, y_start, x_start + cellSize, y_start), fill="black", width=thin_line_width) @@ -325,29 +322,29 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", x_start = (label_cell.column) * cellSize + margin + margin//2 y_start = (label_cell.row) * cellSize + margin + margin//2 #print("Label:" + label ) - draw.text((x_start+4, y_start+2), str(label), + draw.text((x_start+4, y_start+2), str(label), fill="black", font=fontLable) # 5) the x axis description A-... for i in range(self.size): text = chr(ord("A") + i) - draw.text((margin + 30 + i * cellSize + margin//2, 4), str(text), + draw.text((margin + 30 + i * cellSize + margin//2, 4), str(text), fill="black", font=fontGuess) - + # 6) the y axis description 1-... for j in range(self.size): text = str(j+1) - draw.text((13, margin + 22 + j * cellSize + margin//2), str(text), + draw.text((13, margin + 22 + j * cellSize + margin//2), str(text), fill="black", font=fontGuess) if (saveToFile): img.save(outfile) - + buffer = BytesIO() img.save(buffer, "PNG") buffer.seek(0) return buffer - + def _find_first_empty_cell(self): """Return the first empty cell (`guess == 0`) in row-major order, or `None` if all cells are filled.""" for row in self.cells: @@ -376,16 +373,18 @@ def hint(self, now: datetime | None = None): cell, _reason = found self._last_hint_timestamp = current_time + + coord = f"{chr(ord('A') + cell.column)}{cell.row + 1}" return { "type": "hint", - "row": cell.row, - "column": cell.column, - "value": cell.correct, + "guess": f"{coord} {cell.correct}", } - - def add_guess(self, guess): - """Takes the user guess and checks if its valid, if it is -> add to cell - A guess is in format A5 4, where A = column, 5 = row and 4 = guessed value""" + + def add_guess(self, guess) -> bool: + """ + Takes the user guess and checks if its valid, if it is -> add to cell + A guess is in format A5 4, where A = column, 5 = row and 4 = guessed value. + """ guess = guess.split() column = ord(guess[0][0].lower()) - 97 row = int(guess[0][1]) - 1 @@ -393,9 +392,9 @@ def add_guess(self, guess): if (column < 0 or row < 0 or value < 1): return False - + if (column >= self.size or row >= self.size or value > self.size): return False - + self.cells[row][column].guess = value return True diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 281de683ae..dbaf19f79e 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,21 +1,14 @@ import asyncio import re -from copy import deepcopy -from random import randint import discord from discord.ext import commands from pydis_core.utils.logging import get_logger from bot.bot import Bot -from bot.constants import Client -from bot.utils.converters import CoordinateConverter -from bot.utils.exceptions import UserNotPlayingError -# TODO remove this import when merging files -from bot.exts.fun.mathdoku import Grid, Block - -import re +# TODO remove this import when merging files +from bot.exts.fun.mathdoku import Block, Grid # These 2 commands make the API not work for some reason if uncommented # from .mathdoku_parser import create_grids @@ -40,9 +33,19 @@ testingGrid.blocks.append(testBlock_3) cell_one.guess = 1 -cell_three.guess = 3 +cell_three.guess = 3 cell_seven.guess = 2 +cell_one.correct = 1 +cell_two.correct = 2 +cell_three.correct = 3 +cell_four.correct = 3 +cell_five.correct = 1 +cell_six.correct = 2 +cell_seven.correct = 2 +cell_eight.correct = 3 +cell_nine.correct = 1 + testBlock_1.cells.append(cell_one) testBlock_1.cells.append(cell_two) testBlock_1.cells.append(cell_three) @@ -62,11 +65,11 @@ testBlock_3.cells.append(cell_five) testBlock_3.cells.append(cell_six) cell_five.block = testBlock_3 -cell_six.block = testBlock_3 +cell_six.block = testBlock_3 -CROSS_EMOJI = '\u274C' #"\u274e" -MAGNIFYING_EMOJI = '๐Ÿ”' +CROSS_EMOJI = "\u274C" #"\u274e" +MAGNIFYING_EMOJI = "๐Ÿ”" PARTY_EMOJI = "๐ŸŽ‰" HINT_EMOJI = "๐Ÿ’ก" log = get_logger(__name__) @@ -107,7 +110,7 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: self.board = await ctx.send(file=file) await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." + "Type the square and what number you want to input. Format it like this: A1 3\nType `end` to end game." ) await self.board.add_reaction(HINT_EMOJI) @@ -118,20 +121,19 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: async def input_number_on_board(self, ctx: commands.Context,) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" - msg_task = asyncio.create_task(self.bot.wait_for("message", check=self.predicate, timeout=60.0)) react_task = asyncio.create_task(self.bot.wait_for("reaction_add", check=self.reaction_predicate, timeout=60.0)) - + done, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when = asyncio.FIRST_COMPLETED) - + for task in pending: task.cancel() try: finished = done.pop() result = await finished - - except TimeoutError: # Timeout + + except TimeoutError: # Timeout await ctx.send("You took too long. Game over!") self.playing = False return @@ -139,7 +141,7 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m if not self.playing: # takes care of the end message await ctx.send("The game has been ended") return - + if finished is react_task: # A Reaction was posted reaction, user = result emoji = str(reaction.emoji) @@ -160,18 +162,18 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m if not valid_match: await result.add_reaction(CROSS_EMOJI) return - + full_grid = self.grids.check_full_grid() if (full_grid): await self.board.add_reaction(MAGNIFYING_EMOJI) - + self.grids.recolor_blocks() file = discord.File(self.grids._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) return def predicate(self, message: discord.Message) -> bool: - """Predicate checking if the message matches a guess or the word end""" + """Predicate checking if the message matches a guess or the word end.""" if self.player_id == message.author.id: input_text = message.content.strip() @@ -184,33 +186,31 @@ def predicate(self, message: discord.Message) -> bool: self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) return False return True - else: - return False + return False def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> bool: - """Predicate checking if the reaction was on the correct message""" + """Predicate checking if the reaction was on the correct message.""" if self.board.id == reaction.message.id: return True - - async def magnifying_handler(self, ctx, user): + return None + + async def magnifying_handler(self, ctx, user) -> None: if self.grids.check_full_grid(): await self.board.remove_reaction(MAGNIFYING_EMOJI, user) - + result = self.grids.board_filled_handler() # check win and update img file = discord.File(self.grids._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) - + if result: # WIN await self.board.add_reaction(PARTY_EMOJI) await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) self.playing = False return - else: - self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) + self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) async def hint_handler(self, ctx, user) -> None: """Handle hint request via ๐Ÿ’ก reaction.""" - await self.board.remove_reaction(HINT_EMOJI, user) result = self.grids.hint() @@ -219,9 +219,9 @@ async def hint_handler(self, ctx, user) -> None: elif result["type"] == "all filled cells": await ctx.send("No empty cells left.") else: - await ctx.send(f"Hint: row {result['row'] + 1}, column {result['column'] + 1} should be {result['value']}.") - - + await ctx.send(f"Hint: {result['guess']}") + + async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" from .mathdoku_parser import create_grids From 5d13498c1f60ddef5649ca7b7501b0b63c055158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:21:20 +0100 Subject: [PATCH 38/74] feat: re-send the board after 10 valid guesses (#90) #88 to not make the user scroll so much we delete the old board and send it again - this way it stays in the visible chat window closes #88 --- bot/exts/fun/mathdoku_integration.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index dbaf19f79e..255c86795c 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -85,6 +85,7 @@ def __init__(self, bot: Bot, grids: list): self.playing = False self.player_id = None self.board = None # The message that the board is posten on + self.guess_count = 0 @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -162,6 +163,17 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m if not valid_match: await result.add_reaction(CROSS_EMOJI) return + + self.guess_count += 1 + if self.guess_count % 10 == 0: # re-send the grid after 10 guesses so the user doesn't need to scroll + await self.board.delete() + self.grids.recolor_blocks() + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + self.board = await ctx.send(file=file) + await self.board.add_reaction(HINT_EMOJI) + await ctx.send( + "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." + ) full_grid = self.grids.check_full_grid() if (full_grid): From 45f248fa590d7cf5e363e3248b7a9acf0435b3d2 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Fri, 27 Feb 2026 14:30:55 +0100 Subject: [PATCH 39/74] feat: add better color dispersion and more vibrant colors (#91) #77 close #77 --- bot/exts/fun/mathdoku.py | 171 ++++++++++++++++--------------- bot/exts/fun/mathdoku_parser.py | 1 + tests/exts/fun/test_color_gen.py | 13 +++ 3 files changed, 102 insertions(+), 83 deletions(-) create mode 100644 tests/exts/fun/test_color_gen.py diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 06841b2cd5..01a7cebdd3 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -2,89 +2,90 @@ from io import BytesIO from PIL import Image, ImageDraw, ImageFont +from random import randint COLORS = [ - (235, 59, 59), - (199, 61, 50), - (235, 85, 59), - (199, 83, 50), - (235, 111, 59), - (199, 105, 50), - (235, 137, 59), - (199, 127, 50), - (235, 163, 59), - (199, 149, 50), - (235, 189, 59), - (199, 171, 50), - (235, 215, 59), - (199, 193, 50), - (228, 235, 59), - (182, 199, 50), - (202, 235, 59), - (160, 199, 50), - (176, 235, 59), - (138, 199, 50), - (150, 235, 59), - (116, 199, 50), - (124, 235, 59), - (94, 199, 50), - (98, 235, 59), - (72, 199, 50), - (72, 235, 59), - (50, 199, 50), - (59, 235, 72), - (50, 199, 72), - (59, 235, 98), - (50, 199, 94), - (59, 235, 124), - (50, 199, 116), - (59, 235, 150), - (50, 199, 138), - (59, 235, 176), - (50, 199, 160), - (59, 235, 202), - (50, 199, 182), - (59, 235, 228), - (50, 193, 199), - (59, 215, 235), - (50, 171, 199), - (59, 189, 235), - (50, 149, 199), - (59, 163, 235), - (50, 127, 199), - (59, 137, 235), - (50, 105, 199), - (59, 111, 235), - (50, 83, 199), - (59, 85, 235), - (50, 61, 199), - (59, 59, 235), - (61, 50, 199), - (85, 59, 235), - (83, 50, 199), - (111, 59, 235), - (105, 50, 199), - (137, 59, 235), - (127, 50, 199), - (163, 59, 235), - (149, 50, 199), - (189, 59, 235), - (171, 50, 199), - (215, 59, 235), - (193, 50, 199), - (235, 59, 228), - (199, 50, 182), - (235, 59, 202), - (199, 50, 160), - (235, 59, 176), - (199, 50, 138), - (235, 59, 150), - (199, 50, 116), - (235, 59, 124), - (199, 50, 94), - (235, 59, 98), - (199, 50, 72), - (235, 59, 72), + (255, 50, 50), + (255, 66, 50), + (255, 81, 50), + (255, 96, 50), + (255, 111, 50), + (255, 126, 50), + (255, 141, 50), + (255, 156, 50), + (255, 171, 50), + (255, 187, 50), + (255, 202, 50), + (255, 217, 50), + (255, 232, 50), + (255, 247, 50), + (247, 255, 50), + (232, 255, 50), + (217, 255, 50), + (202, 255, 50), + (187, 255, 50), + (171, 255, 50), + (156, 255, 50), + (141, 255, 50), + (126, 255, 50), + (111, 255, 50), + (96, 255, 50), + (81, 255, 50), + (66, 255, 50), + (50, 255, 50), + (50, 255, 66), + (50, 255, 81), + (50, 255, 96), + (50, 255, 111), + (50, 255, 126), + (50, 255, 141), + (50, 255, 156), + (50, 255, 171), + (50, 255, 186), + (50, 255, 202), + (50, 255, 217), + (50, 255, 232), + (50, 255, 247), + (50, 247, 255), + (50, 232, 255), + (50, 217, 255), + (50, 202, 255), + (50, 186, 255), + (50, 171, 255), + (50, 156, 255), + (50, 141, 255), + (50, 126, 255), + (50, 111, 255), + (50, 96, 255), + (50, 81, 255), + (50, 66, 255), + (50, 50, 255), + (66, 50, 255), + (81, 50, 255), + (96, 50, 255), + (111, 50, 255), + (126, 50, 255), + (141, 50, 255), + (156, 50, 255), + (171, 50, 255), + (187, 50, 255), + (202, 50, 255), + (217, 50, 255), + (232, 50, 255), + (247, 50, 255), + (255, 50, 247), + (255, 50, 232), + (255, 50, 217), + (255, 50, 202), + (255, 50, 187), + (255, 50, 171), + (255, 50, 156), + (255, 50, 141), + (255, 50, 126), + (255, 50, 111), + (255, 50, 96), + (255, 50, 81), + (255, 50, 66) ] @@ -108,6 +109,8 @@ def guess(self, new_guess) -> None: class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" + color_id = 0 + color_offset = randint(0, 80) def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> None: self.id = id @@ -115,12 +118,14 @@ def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> No self.operation = operation self.number = number self.label_cell = label_cell + self.color_id = Block.color_id self.color = self.compute_color() + + Block.color_id += 1 def compute_color(self) -> tuple[int, int, int]: """Computes the block's color.""" - c_a = ord(self.id[0])**2 - ord("A") - return COLORS[c_a % len(COLORS)] + return COLORS[(self.color_id * (len(COLORS) // (Block.color_id + 1)) + Block.color_offset) % len(COLORS)] class Grid: diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index 81e2293535..d6d94c4728 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -41,6 +41,7 @@ def create_grids(file_path: Path = FILE_PATH) -> list[Grid]: _read_solution(grid, solution_str) except IndexError: continue + grid.recolor_blocks() grids.append(grid) return grids diff --git a/tests/exts/fun/test_color_gen.py b/tests/exts/fun/test_color_gen.py new file mode 100644 index 0000000000..4821673080 --- /dev/null +++ b/tests/exts/fun/test_color_gen.py @@ -0,0 +1,13 @@ +import colorsys + + +def test_color_gen(): + """ + Generates the colors for the blocks + """ + n = 81 + for i in range(n): + hue = i / n + r, g, b = colorsys.hsv_to_rgb(hue, 0.8, 1.0) + #print((int(r*255), int(g*255), int(b*255)), end="") + #print(",") From 8fc2a1f0fae9076c24520f553656465b525e62c0 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:06:19 +0100 Subject: [PATCH 40/74] feat: add new boards of sizes 3-9 with 3 difficulties each (#95) #70 closes #70 --- bot/exts/fun/mathdoku.py | 77 +- bot/exts/fun/mathdoku_integration.py | 46 +- bot/exts/fun/mathdoku_parser.py | 16 +- bot/resources/fun/mathdoku_boards.txt | 17803 +++++++++++++++++- tests/exts/fun/test_board_filled_handler.py | 19 +- tests/exts/fun/test_mathdoku_hint.py | 10 +- tests/exts/fun/test_mathdoku_parser.py | 146 +- tests/exts/fun/test_recolor_blocks.py | 19 +- tests/exts/fun/test_victory_check.py | 30 +- 9 files changed, 17846 insertions(+), 320 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 01a7cebdd3..91daedc2e0 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -85,7 +85,7 @@ (255, 50, 111), (255, 50, 96), (255, 50, 81), - (255, 50, 66) + (255, 50, 66), ] @@ -107,8 +107,10 @@ def guess(self): def guess(self, new_guess) -> None: self._guess = new_guess + class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" + color_id = 0 color_offset = randint(0, 80) @@ -120,28 +122,29 @@ def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> No self.label_cell = label_cell self.color_id = Block.color_id self.color = self.compute_color() - + Block.color_id += 1 def compute_color(self) -> tuple[int, int, int]: """Computes the block's color.""" - return COLORS[(self.color_id * (len(COLORS) // (Block.color_id + 1)) + Block.color_offset) % len(COLORS)] + c_a = ord(self.id[0]) ** 2 - ord("A") + return COLORS[c_a % len(COLORS)] class Grid: """Represents the full game board, with all blocks and player guesses.""" - HINT_COOLDOWN_SECONDS = 180 # 3 minutes of hint cooldown. + HINT_COOLDOWN_SECONDS = 180 # 3 minutes of hint cooldown. - def __init__(self, size: int) -> None: + def __init__(self, size: int, difficulty: str | None = None) -> None: self.size = size self.blocks = [] self._cells = tuple( - tuple(Cell(col, row) for col in range(size)) - for row in range(size) - ) # 2D tupple for cells [row][col] + tuple(Cell(col, row) for col in range(size)) for row in range(size) + ) # 2D tupple for cells [row][col] self._last_hint_timestamp = None + self.difficulty = difficulty @property def cells(self): @@ -179,6 +182,7 @@ def _blocks_fufilled_check(self) -> list[Block] | bool: Checks if all the blocks are filled correctly and meets the requirements. \n Returns the blocks that are wrong or True if all blocks meet the requirements. \n Will return False if the input is invalid. + Will return False if the input is invalid. """ wrong_blocks = [] for block in self.blocks: @@ -215,14 +219,16 @@ def _blocks_fufilled_check(self) -> list[Block] | bool: return True return wrong_blocks - def check_victory(self) -> bool: """ Checks if the board is in a state where the player has won and will return True or False. """ - return bool(self._latin_square_check() and isinstance(self._blocks_fufilled_check(), bool) and self._blocks_fufilled_check()) - + return bool( + self._latin_square_check() + and isinstance(self._blocks_fufilled_check(), bool) + and self._blocks_fufilled_check() + ) def board_filled_handler(self) -> bool: """ @@ -263,28 +269,29 @@ def __getitem__(self, i: int) -> list[Cell]: """ return self.cells[i] - def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", saveToFile = False) -> None: + def _generate_image(self, cellSize=80, margin=30, outfile="mathdoku.png", saveToFile=False) -> None: """Print the Grid to.""" fontLable = ImageFont.load_default(15) fontGuess = ImageFont.load_default(30) - img = Image.new("RGB", (cellSize * len(self.cells) + 2*margin, cellSize * len(self.cells) + 2*margin), "white") + img = Image.new( + "RGB", (cellSize * len(self.cells) + 2 * margin, cellSize * len(self.cells) + 2 * margin), "white" + ) draw = ImageDraw.Draw(img) for i, row in enumerate(self.cells): for j, cell in enumerate(row): # 1) The block color - x_start = (cell.column) * cellSize + margin + margin//2 - y_start = (cell.row) * cellSize + margin + margin//2 - x_end = (cell.column) * cellSize + cellSize + margin + margin//2 - y_end = (cell.row) * cellSize + cellSize + margin + margin//2 + x_start = (cell.column) * cellSize + margin + margin // 2 + y_start = (cell.row) * cellSize + margin + margin // 2 + x_end = (cell.column) * cellSize + cellSize + margin + margin // 2 + y_end = (cell.row) * cellSize + cellSize + margin + margin // 2 color = cell.block.color - draw.rectangle((x_start, y_start, x_end, y_end),fill=color) + draw.rectangle((x_start, y_start, x_end, y_end), fill=color) # 2) the guess guess = cell.guess - if (guess != 0): - draw.text((x_start+30, y_start+22), str(guess), - fill="black", font=fontGuess) + if guess != 0: + draw.text((x_start + 30, y_start + 22), str(guess), fill="black", font=fontGuess) # 3) the lines between the cells thin_line_width = 2 @@ -311,7 +318,9 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", offset = 2 if self.cells[i - n_over][j].block.id != cell.block.id or self.cells[i - n_over][j] is cell: - draw.line((x_start - offset, y_start, x_end + offset, y_start), fill="black", width=thick_line_width) + draw.line( + (x_start - offset, y_start, x_end + offset, y_start), fill="black", width=thick_line_width + ) if self.cells[i + n_under][j].block.id != cell.block.id or self.cells[i + n_under][j] is cell: draw.line((x_start - offset, y_end, x_end + offset, y_end), fill="black", width=thick_line_width) if self.cells[i][j - n_left].block.id != cell.block.id or self.cells[i][j - n_left] is cell: @@ -319,30 +328,26 @@ def _generate_image(self, cellSize = 80, margin = 30, outfile = "mathdoku.png", if self.cells[i][j + n_right].block.id != cell.block.id or self.cells[i][j + n_right] is cell: draw.line((x_end, y_start, x_end, y_end), fill="black", width=thick_line_width) - for block in self.blocks: # 4) the lable of the block - in the top left corner of the lable cell label_cell = block.label_cell label = str(block.number) + " " + str(block.operation) - x_start = (label_cell.column) * cellSize + margin + margin//2 - y_start = (label_cell.row) * cellSize + margin + margin//2 - #print("Label:" + label ) - draw.text((x_start+4, y_start+2), str(label), - fill="black", font=fontLable) + x_start = (label_cell.column) * cellSize + margin + margin // 2 + y_start = (label_cell.row) * cellSize + margin + margin // 2 + # print("Label:" + label ) + draw.text((x_start + 4, y_start + 2), str(label), fill="black", font=fontLable) # 5) the x axis description A-... for i in range(self.size): text = chr(ord("A") + i) - draw.text((margin + 30 + i * cellSize + margin//2, 4), str(text), - fill="black", font=fontGuess) + draw.text((margin + 30 + i * cellSize + margin // 2, 4), str(text), fill="black", font=fontGuess) # 6) the y axis description 1-... for j in range(self.size): - text = str(j+1) - draw.text((13, margin + 22 + j * cellSize + margin//2), str(text), - fill="black", font=fontGuess) + text = str(j + 1) + draw.text((13, margin + 22 + j * cellSize + margin // 2), str(text), fill="black", font=fontGuess) - if (saveToFile): + if saveToFile: img.save(outfile) buffer = BytesIO() @@ -395,10 +400,10 @@ def add_guess(self, guess) -> bool: row = int(guess[0][1]) - 1 value = int(guess[1]) - if (column < 0 or row < 0 or value < 1): + if column < 0 or row < 0 or value < 1: return False - if (column >= self.size or row >= self.size or value > self.size): + if column >= self.size or row >= self.size or value > self.size: return False self.cells[row][column].guess = value diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 255c86795c..b7c00d2a85 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -68,7 +68,7 @@ cell_six.block = testBlock_3 -CROSS_EMOJI = "\u274C" #"\u274e" +CROSS_EMOJI = "\u274c" # "\u274e" MAGNIFYING_EMOJI = "๐Ÿ”" PARTY_EMOJI = "๐ŸŽ‰" HINT_EMOJI = "๐Ÿ’ก" @@ -79,12 +79,12 @@ class Mathdoku(commands.Cog): """Play a game of Mathdoku.""" - def __init__(self, bot: Bot, grids: list): + def __init__(self, bot: Bot, grids: dict): self.bot = bot - self.grids = grids # The game Grid + self.grids = grids # The game Grid self.playing = False self.player_id = None - self.board = None # The message that the board is posten on + self.board = None # The message that the board is posten on self.guess_count = 0 @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) @@ -96,7 +96,7 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: @mathdoku_group.command(name="start") async def start_command(self, ctx: commands.Context, size: int = 5) -> None: """Start a game of Mathdoku.""" - + if self.playing: await ctx.send("Someone else is playing right now. Please wait your turn.") return @@ -120,12 +120,15 @@ async def start_command(self, ctx: commands.Context, size: int = 5) -> None: await self.input_number_on_board(ctx) await ctx.send("Game of Mathdoku is over!") - async def input_number_on_board(self, ctx: commands.Context,) -> None: # None might need to be changed later + async def input_number_on_board( + self, + ctx: commands.Context, + ) -> None: # None might need to be changed later """Lets the player choose a square and input a number if it is valid.""" msg_task = asyncio.create_task(self.bot.wait_for("message", check=self.predicate, timeout=60.0)) react_task = asyncio.create_task(self.bot.wait_for("reaction_add", check=self.reaction_predicate, timeout=60.0)) - done, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when = asyncio.FIRST_COMPLETED) + done, pending = await asyncio.wait({msg_task, react_task}, timeout=60.0, return_when=asyncio.FIRST_COMPLETED) for task in pending: task.cancel() @@ -159,24 +162,25 @@ async def input_number_on_board(self, ctx: commands.Context,) -> None: # None m else: # A message was posted input_text = result.content.strip() - valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies + valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies if not valid_match: await result.add_reaction(CROSS_EMOJI) return - + self.guess_count += 1 - if self.guess_count % 10 == 0: # re-send the grid after 10 guesses so the user doesn't need to scroll + if self.guess_count % 10 == 0: # re-send the grid after 10 guesses so the user doesn't need to scroll await self.board.delete() self.grids.recolor_blocks() file = discord.File(self.grids._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) await self.board.add_reaction(HINT_EMOJI) await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" "Type `end` to end game." + "Type the square and what number you want to input. Format it like this: A1 3\n" + "Type `end` to end game." ) full_grid = self.grids.check_full_grid() - if (full_grid): + if full_grid: await self.board.add_reaction(MAGNIFYING_EMOJI) self.grids.recolor_blocks() @@ -208,17 +212,17 @@ def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> async def magnifying_handler(self, ctx, user) -> None: if self.grids.check_full_grid(): - await self.board.remove_reaction(MAGNIFYING_EMOJI, user) + await self.board.remove_reaction(MAGNIFYING_EMOJI, user) - result = self.grids.board_filled_handler() # check win and update img - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") - await self.board.edit(content=None, attachments=[file]) + result = self.grids.board_filled_handler() # check win and update img + file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + await self.board.edit(content=None, attachments=[file]) - if result: # WIN - await self.board.add_reaction(PARTY_EMOJI) - await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) - self.playing = False - return + if result: # WIN + await self.board.add_reaction(PARTY_EMOJI) + await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) + self.playing = False + return self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) async def hint_handler(self, ctx, user) -> None: diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index d6d94c4728..da0fe87326 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -28,13 +28,14 @@ def create_grids(file_path: Path = FILE_PATH) -> list[Grid]: Returns a list of succesfully parsed boards. """ matched_grid_strs = _search_for_grids_in_file(file_path) - grids = [] + grids = {} for m_board in matched_grid_strs: expected_size = int(m_board[0][0]) - grid = Grid(expected_size) - grid_str = m_board[1] - operation_str = m_board[2] - solution_str = m_board[3] + difficulty = m_board[1] + grid = Grid(expected_size, difficulty=difficulty) + grid_str = m_board[2] + operation_str = m_board[3] + solution_str = m_board[4] try: created_blocks = _create_cells_and_blocks(expected_size, grid, grid_str) _read_block_operations(created_blocks, operation_str) @@ -42,7 +43,7 @@ def create_grids(file_path: Path = FILE_PATH) -> list[Grid]: except IndexError: continue grid.recolor_blocks() - grids.append(grid) + grids.setdefault(expected_size, {}).setdefault(difficulty, []).append(grid) return grids @@ -58,8 +59,7 @@ def _search_for_grids_in_file(file_path: Path = FILE_PATH) -> list[tuple[str, st 4) proposed solution, e.g. 1 5 4 3 2. """ matched_boards = re.findall( - r"""(\d+x\d+:d\d)\r?\n - \.KK\ "\d+:\(d=\d+\)"\r?\n + r"""(\d+x\d+:d\d+)\ \((\w+)\)\r?\n ((?:[\d\w]+\r?\n)+) \r?\n ((?:\w\ \d+[\+\-x\/]\r?\n)+) diff --git a/bot/resources/fun/mathdoku_boards.txt b/bot/resources/fun/mathdoku_boards.txt index aaab280cc2..9c4efdd23e 100644 --- a/bot/resources/fun/mathdoku_boards.txt +++ b/bot/resources/fun/mathdoku_boards.txt @@ -1,244 +1,17643 @@ -5x5:d5 -.KK "1:(d=5)" -EAACC -EB3CG -EBF5G -E1FDD -FFFD5 +3x3:d2 (easy) +BCC +BAD +3AD + +A 2- +B 1- +C 1- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +AA3 +CBB +CDD + +A 3+ +B 4+ +C 6x +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +DDA +BCA +BC3 + +A 3+ +B 4+ +C 1- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +CCD +B1D +BAA + +A 5+ +B 2- +C 5+ +D 3+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +CCA +B1A +BDD + +A 3+ +B 4+ +C 1- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +CCB +A2B +ADD + +A 3+ +B 1- +C 4+ +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +DA3 +DAC +BBC + +A 6x +B 4+ +C 3+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +AAB +D3B +DCC + +A 3+ +B 4+ +C 3+ +D 5+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +2CA +BCA +BDD + +A 3+ +B 4+ +C 2- +D 6x + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +DDC +A3C +ABB + +A 6x +B 1- +C 4+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +DDC +A3C +ABB + +A 1- +B 3+ +C 2- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +1AA +CCD +BBD + +A 5+ +B 4+ +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +AAC +DBC +DB3 + +A 6x +B 1- +C 1- +D 4+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +DCC +D1A +BBA + +A 5+ +B 1- +C 2- +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +BB2 +DAA +DCC + +A 5+ +B 4+ +C 2- +D 1- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +BBD +A3D +ACC + +A 6x +B 3+ +C 1- +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +DDC +A1C +ABB + +A 4+ +B 6x +C 1- +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +DDC +AAC +2BB + +A 3+ +B 4+ +C 5+ +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +BB1 +DAA +DCC + +A 3+ +B 5+ +C 5+ +D 2- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +DDC +AAC +1BB + +A 2- +B 1- +C 3+ +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +CC3 +ADD +ABB + +A 1- +B 3+ +C 3+ +D 2- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +3AA +CCD +BBD + +A 3+ +B 5+ +C 3+ +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +CC2 +ABB +ADD + +A 3+ +B 1- +C 2- +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +DDC +AAC +3BB + +A 6x +B 3+ +C 2- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +CCD +AAD +3BB + +A 5+ +B 3+ +C 3+ +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +CAA +CBD +2BD + +A 1- +B 6x +C 4+ +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d2 (easy) +BBA +C1A +CDD + +A 1- +B 1- +C 4+ +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +BB3 +ACC +ADD + +A 5+ +B 3+ +C 2- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +BBC +A1C +ADD + +A 2- +B 5+ +C 3+ +D 6x + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +1AD +CAD +CBB + +A 5+ +B 3+ +C 1- +D 2- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +CCD +BBD +3AA + +A 3+ +B 5+ +C 1- +D 2- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d2 (easy) +AAC +DBC +DB3 + +A 1- +B 1- +C 3+ +D 4+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d2 (easy) +DDC +BBC +1AA + +A 6x +B 4+ +C 1- +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +DDA +CBA +CB2 + +A 2- +B 2- +C 5+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +2AA +CCB +DDB + +A 2- +B 5+ +C 2- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +1CC +BBD +AAD + +A 2- +B 5+ +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +AA3 +DCC +DBB + +A 1- +B 1- +C 2- +D 5+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +AB3 +ABC +DDC + +A 1- +B 6x +C 3+ +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +CDD +CBA +1BA + +A 6x +B 3+ +C 1- +D 2- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +ACC +ABD +3BD + +A 1- +B 2- +C 1- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +1BC +ABC +ADD + +A 5+ +B 5+ +C 2- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +DBB +DCC +AA3 + +A 1- +B 2- +C 3+ +D 6x + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +AAC +B2C +BDD + +A 2- +B 3+ +C 1- +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (medium) +3AA +BBC +DDC + +A 3+ +B 1- +C 2- +D 6x + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (medium) +AAC +B1C +BDD + +A 1- +B 4+ +C 1- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +2BA +CBA +CDD + +A 3+ +B 4+ +C 4+ +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +BBC +DDC +3AA + +A 3+ +B 3+ +C 2- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +AAC +BBC +3DD + +A 3+ +B 1- +C 4+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +AA3 +CDD +CBB + +A 1- +B 3+ +C 1- +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +ABB +A2D +CCD + +A 2- +B 3+ +C 1- +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (medium) +BB1 +DCC +DAA + +A 6x +B 1- +C 1- +D 4+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +AD3 +ADC +BBC + +A 1- +B 4+ +C 3+ +D 6x + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +1AA +BBD +CCD + +A 5+ +B 1- +C 4+ +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +CDD +CAB +3AB + +A 2- +B 3+ +C 3+ +D 6x + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +ACC +ABD +3BD + +A 1- +B 2- +C 5+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +3AA +BBD +CCD + +A 1- +B 3+ +C 1- +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (medium) +DDA +CBA +CB2 + +A 4+ +B 2- +C 5+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +AC1 +ACB +DDB + +A 5+ +B 6x +C 2- +D 3+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +1AC +BAC +BDD + +A 5+ +B 1- +C 2- +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +DDA +BBA +1CC + +A 3+ +B 4+ +C 6x +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +1AB +DAB +DCC + +A 5+ +B 4+ +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +DDB +CAB +CA2 + +A 4+ +B 4+ +C 5+ +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +1BD +ABD +ACC + +A 6x +B 6x +C 1- +D 2- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (medium) +DAA +DCC +BB3 + +A 2- +B 1- +C 1- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (medium) +DAA +DCB +2CB + +A 3+ +B 2- +C 1- +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (medium) +BCC +BDA +3DA + +A 3+ +B 3+ +C 1- +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +CB2 +CBA +DDA + +A 2- +B 1- +C 4+ +D 6x + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +AA3 +DCC +DBB + +A 1- +B 1- +C 4+ +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +1AA +DDC +BBC + +A 1- +B 4+ +C 3+ +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +AAC +DBC +DB2 + +A 3+ +B 2- +C 2- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +BD2 +BDC +AAC + +A 1- +B 2- +C 4+ +D 3+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +CCA +DBA +DB1 + +A 5+ +B 5+ +C 4+ +D 3+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +DDB +CAB +CA1 + +A 5+ +B 5+ +C 1- +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +BBD +C1D +CAA + +A 5+ +B 5+ +C 4+ +D 3+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +AA1 +CBB +CDD + +A 5+ +B 1- +C 2- +D 5+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +DDB +CAB +CA2 + +A 4+ +B 4+ +C 5+ +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +DCC +DAB +3AB + +A 4+ +B 1- +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +3AD +BAD +BCC + +A 3+ +B 1- +C 4+ +D 1- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +AAD +BBD +1CC + +A 1- +B 4+ +C 6x +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +AAC +DBC +DB1 + +A 2- +B 6x +C 1- +D 1- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +ACC +ADB +1DB + +A 1- +B 5+ +C 2- +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +DD2 +BCC +BAA + +A 2- +B 3+ +C 1- +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +1DB +CDB +CAA + +A 1- +B 2- +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +3BC +ABC +ADD + +A 1- +B 3+ +C 6x +D 2- + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +ADD +ABB +CC3 + +A 1- +B 1- +C 1- +D 2- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +DC1 +DCB +AAB + +A 1- +B 1- +C 4+ +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +AA2 +BCC +BDD + +A 2- +B 1- +C 6x +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +AAD +C1D +CBB + +A 1- +B 1- +C 4+ +D 1- + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +BA3 +BAD +CCD + +A 1- +B 3+ +C 2- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +1BB +AAC +DDC + +A 1- +B 1- +C 3+ +D 2- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +ADD +A1B +CCB + +A 5+ +B 1- +C 1- +D 4+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +DCC +DAA +BB1 + +A 1- +B 1- +C 3+ +D 4+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +BDD +BCC +AA2 + +A 2- +B 3+ +C 2- +D 5+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +CCA +DBA +DB3 + +A 1- +B 3+ +C 1- +D 4+ + +2 3 1 +3 1 2 +1 2 3 + +3x3:d3 (hard) +1CC +AAD +BBD + +A 5+ +B 4+ +C 6x +D 3+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +1BB +AAC +DDC + +A 5+ +B 6x +C 3+ +D 4+ + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +BDD +BCC +AA1 + +A 1- +B 4+ +C 6x +D 3+ + +3 1 2 +1 2 3 +2 3 1 + +3x3:d3 (hard) +BDD +B3C +AAC + +A 2- +B 3+ +C 1- +D 6x + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +1BA +DBA +DCC + +A 4+ +B 6x +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +3x3:d3 (hard) +CCA +D3A +DBB + +A 4+ +B 3+ +C 1- +D 1- + +1 2 3 +2 3 1 +3 1 2 + +4x4:d2 (easy) +4CCA +DDCA +D4EA +BBE1 + +A 24x +B 1- +C 9x +D 4+ +E 2/ + +4 3 1 2 +2 1 3 4 +1 4 2 3 +3 2 4 1 + +4x4:d3 (easy) +BBBD +1BDD +C1AA +CCA2 + +A 6+ +B 48x +C 11+ +D 12x + +2 3 4 1 +1 2 3 4 +4 1 2 3 +3 4 1 2 + +4x4:d4 (easy) +DDDB +DCCB +EEAB +2AAB + +A 8x +B 24x +C 7+ +D 6x +E 12x + +3 2 1 4 +1 4 3 2 +4 3 2 1 +2 1 4 3 + +4x4:d4 (easy) +3ABB +AAAD +CCDD +EEFF + +A 11+ +B 3+ +C 5+ +D 8+ +E 6x +F 5+ + +3 4 2 1 +1 2 4 3 +4 1 3 2 +2 3 1 4 + +4x4:d3 (easy) +AABB +DDB3 +3EBC +EEEC + +A 1- +B 9+ +C 3- +D 3- +E 12x + +4 3 1 2 +1 4 2 3 +3 2 4 1 +2 1 3 4 + +4x4:d3 (easy) +BEEA +BC1A +2CCA +DDC1 + +A 9+ +B 3- +C 36x +D 1- +E 2/ + +1 4 2 3 +4 3 1 2 +2 1 3 4 +3 2 4 1 + +4x4:d4 (easy) +CCEE +CAAA +BFFA +BBDD + +A 48x +B 12x +C 7+ +D 3+ +E 6x +F 6x + +4 1 2 3 +2 3 4 1 +1 2 3 4 +3 4 1 2 + +4x4:d3 (easy) +DD2C +ACCC +A3BB +EEB3 + +A 5+ +B 7+ +C 10+ +D 2- +E 8x + +3 1 2 4 +4 2 3 1 +1 3 4 2 +2 4 1 3 + +4x4:d3 (easy) +2CDD +CC2E +FFBE +AABE + +A 1- +B 4+ +C 8+ +D 7+ +E 8x +F 3- + +2 1 4 3 +4 3 2 1 +1 4 3 2 +3 2 1 4 + +4x4:d3 (easy) +F2CC +FFCA +1BBA +EEDD + +A 1- +B 2/ +C 6+ +D 1- +E 3+ +F 36x + +3 2 4 1 +4 3 1 2 +1 4 2 3 +2 1 3 4 + +4x4:d3 (easy) +FBB1 +FDCA +3DCA +EEEA + +A 9+ +B 12x +C 1- +D 5+ +E 6x +F 8x + +2 3 4 1 +4 1 2 3 +3 4 1 2 +1 2 3 4 + +4x4:d3 (easy) +DDFF +CCAF +EAAB +EE4B + +A 9x +B 12x +C 5+ +D 1- +E 4x +F 5+ + +3 4 2 1 +4 1 3 2 +2 3 1 4 +1 2 4 3 + +4x4:d3 (easy) +BCCE +B4CE +AAA1 +1DDD + +A 24x +B 1- +C 4+ +D 24x +E 12x + +3 1 2 4 +2 4 1 3 +4 2 3 1 +1 3 4 2 + +4x4:d3 (easy) +4BBA +EB1A +ECDD +CCCD + +A 2/ +B 7+ +C 11+ +D 6x +E 6x + +4 1 3 2 +2 3 1 4 +3 4 2 1 +1 2 4 3 + +4x4:d2 (easy) +C1BB +CAB2 +EAAD +EE3D + +A 11+ +B 6x +C 1- +D 5+ +E 4x + +4 1 2 3 +3 4 1 2 +2 3 4 1 +1 2 3 4 + +4x4:d3 (easy) +4BBB +AAAB +CCAD +CC3D + +A 11+ +B 18x +C 12x +D 5+ + +4 3 1 2 +1 4 2 3 +3 2 4 1 +2 1 3 4 + +4x4:d3 (easy) +AFFD +ABBD +4BCC +EE1C + +A 2- +B 5+ +C 9x +D 2/ +E 6+ +F 12x + +1 3 4 2 +3 1 2 4 +4 2 3 1 +2 4 1 3 + +4x4:d3 (easy) +EEBB +DCCB +DDAA +1FFA + +A 24x +B 4+ +C 1- +D 8x +E 1- +F 5+ + +3 4 1 2 +2 3 4 1 +4 1 2 3 +1 2 3 4 + +4x4:d4 (easy) +DCCC +D4BB +EE4F +EAAF + +A 1- +B 1- +C 6+ +D 12x +E 6x +F 5+ + +4 1 2 3 +3 4 1 2 +2 3 4 1 +1 2 3 4 + +4x4:d3 (easy) +A3CB +AACB +DEFF +DEEF + +A 8x +B 1- +C 3+ +D 1- +E 12x +F 7+ + +1 3 2 4 +4 2 1 3 +3 1 4 2 +2 4 3 1 + +4x4:d3 (easy) +DD4B +DCCB +GFFA +GEEA + +A 3- +B 1- +C 3+ +D 8+ +E 6x +F 7+ +G 1- + +3 1 4 2 +4 2 1 3 +2 4 3 1 +1 3 2 4 + +4x4:d3 (easy) +C4BB +CCB3 +4CAE +DDAE + +A 2- +B 7+ +C 6x +D 1- +E 8x + +3 4 2 1 +1 2 4 3 +4 1 3 2 +2 3 1 4 + +4x4:d3 (easy) +1AAA +BB2C +D2CC +DDD3 + +A 9+ +B 2- +C 12x +D 11+ + +1 3 4 2 +3 1 2 4 +4 2 3 1 +2 4 1 3 + +4x4:d4 (easy) +FFCC +DDAB +3AAB +GGEE + +A 8x +B 3+ +C 1- +D 1- +E 1- +F 1- +G 3- + +2 1 3 4 +4 3 1 2 +3 2 4 1 +1 4 2 3 + +4x4:d3 (easy) +AADD +AFFC +EBCC +EB2C + +A 5+ +B 3- +C 24x +D 1- +E 7+ +F 4+ + +1 2 4 3 +2 3 1 4 +4 1 3 2 +3 4 2 1 + +4x4:d3 (easy) +CCDD +4EEE +AABE +FFB4 + +A 5+ +B 4+ +C 1- +D 12x +E 12x +F 5+ + +2 1 4 3 +4 3 2 1 +1 4 3 2 +3 2 1 4 + +4x4:d4 (easy) +EECF +EDCF +BDAA +B1AA + +A 10+ +B 12x +C 1- +D 6+ +E 6+ +F 3- + +1 3 2 4 +2 4 3 1 +4 2 1 3 +3 1 4 2 + +4x4:d4 (easy) +CCFF +C4BB +DAAE +DDEE + +A 2- +B 5+ +C 12x +D 6+ +E 8+ +F 1- + +4 3 1 2 +1 4 2 3 +3 2 4 1 +2 1 3 4 + +4x4:d3 (easy) +2EEE +CADD +CA1D +C2BB + +A 1- +B 5+ +C 12x +D 12x +E 8+ + +2 1 3 4 +1 4 2 3 +4 3 1 2 +3 2 4 1 + +4x4:d3 (easy) +CCBB +CCBA +3DDA +EED1 + +A 2/ +B 8+ +C 8+ +D 9+ +E 1- + +4 1 2 3 +1 2 3 4 +3 4 1 2 +2 3 4 1 + +4x4:d3 (easy) +FCCD +FBCD +AB4D +A3EE + +A 1- +B 3+ +C 9+ +D 12x +E 3+ +F 3+ + +1 4 2 3 +2 1 3 4 +3 2 4 1 +4 3 1 2 + +4x4:d3 (easy) +3CCB +EEBB +E1DD +AAAD + +A 12x +B 6+ +C 2- +D 9+ +E 7+ + +3 2 4 1 +1 4 2 3 +2 1 3 4 +4 3 1 2 + +4x4:d3 (easy) +CC2F +2EDF +AED1 +AEBB + +A 3- +B 2/ +C 2- +D 2- +E 24x +F 1- + +3 1 2 4 +2 4 1 3 +4 2 3 1 +1 3 4 2 + +4x4:d5 (medium) +4DDD +FFCA +BCCA +B2EE + +A 5+ +B 2- +C 8x +D 6x +E 1- +F 1- + +4 1 3 2 +2 3 1 4 +3 4 2 1 +1 2 4 3 + +4x4:d5 (medium) +EEBB +DBBA +DDCA +2CCA + +A 6x +B 48x +C 7+ +D 8+ +E 1- + +3 2 1 4 +1 4 3 2 +4 3 2 1 +2 1 4 3 + +4x4:d5 (medium) +DB1E +DBBE +DABC +4ACC + +A 5+ +B 48x +C 6+ +D 6+ +E 7+ + +2 3 1 4 +1 2 4 3 +3 4 2 1 +4 1 3 2 + +4x4:d6 (medium) +BBEE +BBCC +AAAD +A1DD + +A 11+ +B 12+ +C 5+ +D 12x +E 1- + +4 3 1 2 +1 4 2 3 +3 2 4 1 +2 1 3 4 + +4x4:d5 (medium) +3BCC +ABBD +AEED +AEE3 + +A 8x +B 6+ +C 2- +D 1- +E 48x + +3 1 2 4 +4 2 3 1 +1 3 4 2 +2 4 1 3 + +4x4:d5 (medium) +1BBE +FEEE +FDDA +CC1A + +A 2- +B 1- +C 2- +D 1- +E 11+ +F 6x + +1 3 2 4 +3 1 4 2 +2 4 3 1 +4 2 1 3 + +4x4:d5 (medium) +BB4E +B4CE +DACC +DAC1 + +A 1- +B 6+ +C 24x +D 1- +E 1- + +1 3 4 2 +2 4 1 3 +3 1 2 4 +4 2 3 1 + +4x4:d5 (medium) +DDAA +ECCA +E4BB +1FFF + +A 9+ +B 1- +C 2- +D 5+ +E 5+ +F 24x + +4 1 3 2 +2 3 1 4 +3 4 2 1 +1 2 4 3 + +4x4:d5 (medium) +E4BB +EAFF +EAAD +CCDD + +A 8x +B 1- +C 12x +D 6x +E 6+ +F 5+ + +1 4 3 2 +3 2 1 4 +2 1 4 3 +4 3 2 1 + +4x4:d5 (medium) +BFFA +BBBA +E4CC +EEDD + +A 5+ +B 10+ +C 3+ +D 1- +E 12x +F 1- + +1 2 3 4 +2 3 4 1 +3 4 1 2 +4 1 2 3 + +4x4:d5 (medium) +DEE1 +DCEA +CCAA +CBBA + +A 48x +B 5+ +C 9+ +D 8x +E 12x + +2 4 3 1 +4 2 1 3 +1 3 2 4 +3 1 4 2 + +4x4:d5 (medium) +EEEE +BA2D +BACD +BBC2 + +A 5+ +B 10+ +C 4+ +D 7+ +E 10+ + +3 2 4 1 +1 4 2 3 +2 1 3 4 +4 3 1 2 + +4x4:d5 (medium) +BBB1 +BEEC +DAAC +D2AA + +A 10+ +B 24x +C 1- +D 1- +E 1- + +4 3 2 1 +1 4 3 2 +2 1 4 3 +3 2 1 4 + +4x4:d5 (medium) +AAC4 +ADCB +DDDB +4EEB + +A 5+ +B 6+ +C 2- +D 24x +E 5+ + +3 1 2 4 +1 3 4 2 +2 4 1 3 +4 2 3 1 + +4x4:d5 (medium) +4CCC +BCED +BBED +BAA4 + +A 5+ +B 7+ +C 10+ +D 1- +E 1- + +4 2 1 3 +2 4 3 1 +3 1 4 2 +1 3 2 4 + +4x4:d5 (medium) +DDDA +2DCA +ECCA +EBB4 + +A 6x +B 1- +C 12x +D 11+ +E 4+ + +4 2 1 3 +2 4 3 1 +3 1 4 2 +1 3 2 4 + +4x4:d6 (medium) +4DDA +BBDA +BECC +EECC + +A 1- +B 12x +C 48x +D 6x +E 12x + +4 1 3 2 +3 4 2 1 +1 2 4 3 +2 3 1 4 + +4x4:d6 (medium) +DFF1 +DDAA +DEEB +CC4B + +A 5+ +B 1- +C 3+ +D 24x +E 1- +F 6x + +4 3 2 1 +3 2 1 4 +1 4 3 2 +2 1 4 3 + +4x4:d5 (medium) +DDCC +EEAA +GBB1 +GGFF + +A 5+ +B 2- +C 1- +D 3- +E 5+ +F 7+ +G 6+ + +4 1 3 2 +2 3 1 4 +3 4 2 1 +1 2 4 3 + +4x4:d5 (medium) +EBBA +EECA +E1CD +1DDD + +A 2- +B 1- +C 1- +D 48x +E 13+ + +4 2 1 3 +2 4 3 1 +3 1 4 2 +1 3 2 4 + +4x4:d5 (medium) +BAA4 +BAFF +CDFE +CDDE + +A 7+ +B 5+ +C 1- +D 12x +E 3+ +F 8+ + +1 3 2 4 +4 2 1 3 +3 1 4 2 +2 4 3 1 + +4x4:d5 (medium) +GEEB +GDCB +ADCC +AAFF + +A 12x +B 5+ +C 12x +D 1- +E 5+ +F 2/ +G 3+ + +2 4 1 3 +1 3 4 2 +4 2 3 1 +3 1 2 4 + +4x4:d5 (medium) +CEEF +CE3F +BBDD +AADD + +A 5+ +B 1- +C 1- +D 12x +E 7+ +F 5+ + +3 2 4 1 +2 1 3 4 +4 3 1 2 +1 4 2 3 + +4x4:d5 (medium) +BEEE +BBCC +FFA4 +DDAA + +A 6x +B 8+ +C 5+ +D 12x +E 8x +F 1- + +3 2 4 1 +1 4 2 3 +2 1 3 4 +4 3 1 2 + +4x4:d5 (medium) +AAEE +4DDE +BBB2 +FFCC + +A 1- +B 12x +C 3- +D 5+ +E 12x +F 6x + +2 1 4 3 +4 3 2 1 +1 4 3 2 +3 2 1 4 + +4x4:d5 (medium) +AACC +AEEC +FFED +BBBD + +A 6+ +B 7+ +C 7+ +D 2- +E 24x +F 1- + +3 2 1 4 +1 4 3 2 +4 3 2 1 +2 1 4 3 + +4x4:d5 (medium) +AADC +ADDC +BBE1 +1EEE + +A 8+ +B 2- +C 1- +D 8x +E 72x + +3 1 4 2 +4 2 1 3 +2 4 3 1 +1 3 2 4 + +4x4:d5 (medium) +EA1D +EABD +C1BF +CCCF + +A 1- +B 2- +C 10+ +D 3+ +E 1- +F 1- + +3 4 1 2 +2 3 4 1 +4 1 2 3 +1 2 3 4 + +4x4:d5 (medium) +E2BB +ECFA +CCFA +CDDA + +A 9+ +B 3- +C 10+ +D 2- +E 1- +F 3+ + +3 2 4 1 +4 3 1 2 +1 4 2 3 +2 1 3 4 + +4x4:d5 (medium) +4AEE +AAEE +BDDC +BBBC + +A 12x +B 8+ +C 1- +D 5+ +E 8+ + +4 3 2 1 +1 4 3 2 +2 1 4 3 +3 2 1 4 + +4x4:d5 (medium) +CAEE +CAAE +DDBB +DDB2 + +A 8+ +B 10+ +C 1- +D 8+ +E 7+ + +3 4 2 1 +2 3 1 4 +1 2 4 3 +4 1 3 2 + +4x4:d5 (medium) +ECCA +E4CA +BBFF +1BDD + +A 5+ +B 7+ +C 6x +D 1- +E 5+ +F 1- + +2 3 1 4 +3 4 2 1 +4 1 3 2 +1 2 4 3 + +4x4:d5 (medium) +1DBB +CDDB +C1AE +CAAE + +A 24x +B 7+ +C 24x +D 8+ +E 5+ + +1 4 2 3 +4 3 1 2 +2 1 3 4 +3 2 4 1 + +4x4:d7 (hard) +BEE3 +BB3D +CCCD +AACD + +A 1- +B 8x +C 48x +D 8x +E 3+ + +4 1 2 3 +1 2 3 4 +3 4 1 2 +2 3 4 1 + +4x4:d7 (hard) +EC4A +ECCA +BBDF +DDDF + +A 1- +B 1- +C 12x +D 10+ +E 1- +F 12x + +2 3 4 1 +3 4 1 2 +1 2 3 4 +4 1 2 3 + +4x4:d8 (hard) +BECC +BEEA +BDDA +B4FF + +A 1- +B 10+ +C 1- +D 1- +E 12x +F 5+ + +4 3 2 1 +2 1 4 3 +3 2 1 4 +1 4 3 2 + +4x4:d7 (hard) +AAEE +FACC +FBBC +DDBB + +A 9+ +B 9+ +C 24x +D 1- +E 1- +F 5+ + +3 4 2 1 +1 2 4 3 +4 1 3 2 +2 3 1 4 + +4x4:d9 (hard) +EEAD +BAAD +BCCG +FFCG + +A 8+ +B 5+ +C 5+ +D 2- +E 1- +F 1- +G 2/ + +3 4 2 1 +1 2 4 3 +4 1 3 2 +2 3 1 4 + +4x4:d7 (hard) +EECF +BCCF +BA1D +3ADD + +A 5+ +B 2/ +C 9+ +D 9+ +E 2- +F 1- + +1 3 4 2 +4 2 3 1 +2 4 1 3 +3 1 2 4 + +4x4:d7 (hard) +BBCC +E4DD +EDDF +EAAF + +A 1- +B 2- +C 2/ +D 7+ +E 9+ +F 5+ + +1 3 4 2 +2 4 1 3 +3 1 2 4 +4 2 3 1 + +4x4:d7 (hard) +GEEB +GCCB +A3DF +AADF + +A 9+ +B 12x +C 2/ +D 5+ +E 2- +F 1- +G 1- + +2 1 3 4 +1 4 2 3 +4 3 1 2 +3 2 4 1 + +4x4:d7 (hard) +AEEE +ADDC +AD3C +FFBB + +A 6x +B 1- +C 1- +D 16x +E 7+ +F 1- + +3 2 1 4 +2 1 4 3 +1 4 3 2 +4 3 2 1 + +4x4:d8 (hard) +EE1B +EDBB +ADFF +AACC + +A 7+ +B 7+ +C 1- +D 2- +E 24x +F 12x + +3 2 1 4 +4 3 2 1 +2 1 4 3 +1 4 3 2 + +4x4:d7 (hard) +FFEB +AFEB +ADCC +DDC4 + +A 1- +B 1- +C 12x +D 6x +E 1- +F 16x + +4 1 2 3 +3 4 1 2 +2 3 4 1 +1 2 3 4 + +4x4:d7 (hard) +DEEE +DDE3 +AACF +BBCF + +A 5+ +B 1- +C 1- +D 6+ +E 11+ +F 3+ + +3 2 1 4 +2 1 4 3 +1 4 3 2 +4 3 2 1 + +4x4:d7 (hard) +CCCA +DDCA +DBBA +EEB4 + +A 6+ +B 6x +C 48x +D 9+ +E 3+ + +3 4 1 2 +2 3 4 1 +4 1 2 3 +1 2 3 4 + +4x4:d7 (hard) +EBDD +EB4D +AAAA +CCC1 + +A 10+ +B 3+ +C 9+ +D 12x +E 5+ + +3 2 1 4 +2 1 4 3 +1 4 3 2 +4 3 2 1 + +4x4:d8 (hard) +DEEC +DCCC +DABB +4AAB + +A 8x +B 6+ +C 48x +D 6x +E 5+ + +1 2 3 4 +2 3 4 1 +3 4 1 2 +4 1 2 3 + +4x4:d7 (hard) +A1BD +ABBD +CBFF +CCEE + +A 5+ +B 11+ +C 8+ +D 12x +E 5+ +F 1- + +4 1 2 3 +1 2 3 4 +3 4 1 2 +2 3 4 1 + +4x4:d7 (hard) +FDCC +FD2E +BBAE +BAAA + +A 24x +B 24x +C 2- +D 5+ +E 3- +F 5+ + +4 2 1 3 +1 3 2 4 +2 4 3 1 +3 1 4 2 + +4x4:d7 (hard) +FFDD +CBBG +CAEG +1AEE + +A 1- +B 1- +C 2/ +D 3+ +E 9+ +F 1- +G 2- + +3 4 1 2 +2 3 4 1 +4 1 2 3 +1 2 3 4 + +4x4:d7 (hard) +AAA3 +A4CC +EBBB +EDDB + +A 10+ +B 48x +C 1- +D 1- +E 1- + +4 1 2 3 +3 4 1 2 +2 3 4 1 +1 2 3 4 + +4x4:d7 (hard) +1CDD +CCDD +EBBA +EEAA + +A 12x +B 3- +C 7+ +D 48x +E 8+ + +1 2 3 4 +2 3 4 1 +3 4 1 2 +4 1 2 3 + +4x4:d7 (hard) +2DCC +DDDC +FABB +FAEE + +A 5+ +B 3+ +C 12x +D 24x +E 1- +F 1- + +2 3 1 4 +1 2 4 3 +3 4 2 1 +4 1 3 2 + +4x4:d7 (hard) +EEBB +E4BF +CDDF +CCAA + +A 1- +B 24x +C 8x +D 4+ +E 6+ +F 5+ + +1 2 4 3 +3 4 2 1 +2 3 1 4 +4 1 3 2 + +4x4:d7 (hard) +EAFF +EABC +EEBC +DDGG + +A 1- +B 5+ +C 1- +D 5+ +E 10+ +F 7+ +G 1- + +1 2 3 4 +2 3 4 1 +3 4 1 2 +4 1 2 3 + +4x4:d8 (hard) +CCAE +1AAE +DA3B +DDBB + +A 24x +B 7+ +C 2/ +D 12x +E 1- + +2 4 1 3 +1 3 4 2 +4 2 3 1 +3 1 2 4 + +4x4:d8 (hard) +DBAA +DBBA +DDEE +CC3E + +A 7+ +B 6+ +C 1- +D 48x +E 9+ + +3 2 4 1 +4 3 1 2 +1 4 2 3 +2 1 3 4 + +4x4:d7 (hard) +CAAF +CEFF +DEBB +D2GG + +A 5+ +B 1- +C 1- +D 3- +E 2- +F 7+ +G 1- + +3 4 1 2 +2 3 4 1 +4 1 2 3 +1 2 3 4 + +4x4:d7 (hard) +FDCC +FDDB +AAAB +3AEE + +A 24x +B 1- +C 12x +D 6+ +E 5+ +F 2/ + +2 1 4 3 +4 3 2 1 +1 4 3 2 +3 2 1 4 + +4x4:d8 (hard) +2BBE +ABDE +AADD +CCC1 + +A 7+ +B 6+ +C 9+ +D 8+ +E 1- + +2 4 1 3 +3 1 2 4 +1 3 4 2 +4 2 3 1 + +4x4:d8 (hard) +ADDE +AAEE +BBCE +3BCC + +A 16x +B 4x +C 9+ +D 1- +E 9+ + +4 3 2 1 +1 4 3 2 +2 1 4 3 +3 2 1 4 + +4x4:d8 (hard) +EECC +ECCB +D3BB +DDAA + +A 1- +B 6+ +C 96x +D 7+ +E 6+ + +1 2 4 3 +3 4 2 1 +2 3 1 4 +4 1 3 2 + +4x4:d8 (hard) +AADD +EECD +BBCD +BCC1 + +A 3+ +B 12x +C 24x +D 13+ +E 1- + +1 2 4 3 +2 3 1 4 +4 1 3 2 +3 4 2 1 + +4x4:d7 (hard) +AACF +3DCF +DDEB +EEEB + +A 3- +B 1- +C 1- +D 8x +E 10+ +F 1- + +4 1 3 2 +3 4 2 1 +1 2 4 3 +2 3 1 4 + +4x4:d7 (hard) +DBBC +D2AC +FAAE +FAEE + +A 36x +B 5+ +C 1- +D 1- +E 8x +F 1- + +3 1 4 2 +4 2 1 3 +2 4 3 1 +1 3 2 4 + +4x4:d7 (hard) +EFFC +EDCC +ADDB +ABBB + +A 8x +B 9+ +C 8x +D 8x +E 2- +F 1- + +1 4 3 2 +3 2 1 4 +2 1 4 3 +4 3 2 1 + +5x5:d4 (easy) +1DDCC +BFIIH +BFFIH +JEAAG +JEGGG + +A 2- +B 7+ +C 7+ +D 10x +E 9+ +F 5+ +G 20x +H 2- +I 10+ +J 1- + +1 2 5 3 4 +5 1 4 2 3 +2 3 1 4 5 +4 5 3 1 2 +3 4 2 5 1 + +5x5:d4 (easy) +HHAEI +DAAEI +D5JJC +1GGBC +FF5BC + +A 7+ +B 5+ +C 10+ +D 9+ +E 15x +F 1- +G 1- +H 1- +I 5+ +J 3+ + +2 3 4 5 1 +5 1 2 3 4 +4 5 1 2 3 +1 2 3 4 5 +3 4 5 1 2 + +5x5:d4 (easy) +GFC4H +GFCJH +KKCJB +EIIDB +EAADB + +A 1- +B 11+ +C 12+ +D 6+ +E 1- +F 3- +G 3- +H 2- +I 3+ +J 1- +K 6+ + +2 1 5 4 3 +5 4 3 2 1 +1 5 4 3 2 +3 2 1 5 4 +4 3 2 1 5 + +5x5:d5 (easy) +D3BBC +DDBAC +HFAAC +HFEEI +4GGII + +A 20x +B 9+ +C 6+ +D 10+ +E 3- +F 4- +G 1- +H 1- +I 60x + +5 3 2 4 1 +1 4 3 5 2 +2 5 4 1 3 +3 1 5 2 4 +4 2 1 3 5 + +5x5:d5 (easy) +CCDD1 +CHHDD +EE3AD +2FFAG +BBB1G + +A 1- +B 40x +C 24x +D 20+ +E 4- +F 3- +G 2- +H 2- + +3 2 5 4 1 +4 3 1 5 2 +1 5 3 2 4 +2 1 4 3 5 +5 4 2 1 3 + +5x5:d4 (easy) +BBCHH +EEC2F +EGADF +JGADF +JGAII + +A 8+ +B 4- +C 12x +D 1- +E 60x +F 8+ +G 6+ +H 1- +I 6+ +J 2- + +1 5 4 3 2 +5 4 3 2 1 +3 2 1 5 4 +2 1 5 4 3 +4 3 2 1 5 + +5x5:d5 (easy) +DDIGG +2IIGG +BBFFC +HH4EC +HAAE5 + +A 5+ +B 6+ +C 1- +D 1- +E 7+ +F 1- +G 9+ +H 6+ +I 10+ + +4 5 2 3 1 +2 3 5 1 4 +5 1 3 4 2 +1 2 4 5 3 +3 4 1 2 5 + +5x5:d3 (easy) +AB1EE +ABBB3 +GIDDF +GICFF +G2CHH + +A 1- +B 24x +C 9+ +D 6x +E 7+ +F 12+ +G 6x +H 5+ +I 6+ + +4 3 1 5 2 +5 4 2 1 3 +1 5 3 2 4 +2 1 4 3 5 +3 2 5 4 1 + +5x5:d3 (easy) +II3AH +B5AAH +BFDD5 +FFCCE +1GGGE + +A 24x +B 7+ +C 4- +D 1- +E 7+ +F 24x +G 11+ +H 3+ +I 6+ + +5 1 3 4 2 +4 5 2 3 1 +3 4 1 2 5 +2 3 5 1 4 +1 2 4 5 3 + +5x5:d4 (easy) +EE2BD +2EHBD +FFHDD +F5HAA +GGCCA + +A 32x +B 8+ +C 2- +D 30x +E 9+ +F 9x +G 3- +H 20x + +4 1 2 5 3 +2 4 5 3 1 +1 3 4 2 5 +3 5 1 4 2 +5 2 3 1 4 + +5x5:d4 (easy) +CCAA2 +3CHHI +JJFFI +BDFEG +BDDEG + +A 2- +B 3- +C 80x +D 5+ +E 2/ +F 60x +G 8+ +H 7+ +I 3- +J 3+ + +4 5 3 1 2 +3 4 2 5 1 +1 2 5 3 4 +5 1 4 2 3 +2 3 1 4 5 + +5x5:d4 (easy) +CCCBA +C3BBA +DD5BH +2GGGH +EEEFF + +A 8x +B 9+ +C 60x +D 8x +E 7+ +F 2- +G 12+ +H 4+ + +3 1 4 5 2 +5 3 1 2 4 +4 2 5 1 3 +2 5 3 4 1 +1 4 2 3 5 + +5x5:d4 (easy) +KKHDI +CCHDI +BBGF2 +BGGFJ +AAEEJ + +A 3- +B 10+ +C 4+ +D 1- +E 3- +F 4+ +G 20x +H 1- +I 6+ +J 1- +K 2/ + +4 2 3 5 1 +3 1 2 4 5 +5 3 4 1 2 +2 5 1 3 4 +1 4 5 2 3 + +5x5:d4 (easy) +GG5DI +JCADI +JCAFF +BBKKE +HHH4E + +A 2- +B 2- +C 15x +D 2- +E 15x +F 1- +G 3- +H 10x +I 2- +J 2- +K 2- + +4 1 5 3 2 +1 3 2 5 4 +3 5 4 2 1 +2 4 3 1 5 +5 2 1 4 3 + +5x5:d3 (easy) +EAAA2 +EI3DD +CIIHH +CBB5G +CFFFG + +A 20x +B 1- +C 7+ +D 3- +E 2- +F 12+ +G 2- +H 10x +I 24x + +3 5 1 4 2 +5 2 3 1 4 +1 3 4 2 5 +4 1 2 5 3 +2 4 5 3 1 + +5x5:d3 (easy) +IIBBA +DGBJA +DGEJ2 +D3EFC +HHFFC + +A 9+ +B 8+ +C 2- +D 11+ +E 6+ +F 16x +G 5+ +H 6+ +I 1- +J 8+ + +3 2 4 1 5 +2 1 3 5 4 +5 4 1 3 2 +4 3 5 2 1 +1 5 2 4 3 + +5x5:d3 (easy) +HDDDE +HFF1E +CFAB5 +CCABB +3CAGG + +A 10x +B 48x +C 9+ +D 7+ +E 5+ +F 45x +G 4- +H 9+ + +5 1 4 2 3 +4 5 3 1 2 +2 3 1 4 5 +1 2 5 3 4 +3 4 2 5 1 + +5x5:d4 (easy) +EE4GG +CIBBJ +CIIFJ +C4HFD +AAHFD + +A 5+ +B 1- +C 6+ +D 2- +E 10x +F 40x +G 2- +H 4+ +I 13+ +J 2/ + +5 2 4 3 1 +3 5 2 1 4 +1 3 5 4 2 +2 4 1 5 3 +4 1 3 2 5 + +5x5:d3 (easy) +IIACC +BBACG +DHHKG +D2FKE +JJFEE + +A 1- +B 4+ +C 25x +D 2- +E 18x +F 6+ +G 5+ +H 1- +I 2/ +J 3- +K 2/ + +2 4 3 1 5 +1 3 2 5 4 +3 5 4 2 1 +5 2 1 4 3 +4 1 5 3 2 + +5x5:d4 (easy) +IICBB +AACJJ +AEEEH +DFGHH +DFGKK + +A 6+ +B 12x +C 6+ +D 1- +E 12+ +F 1- +G 6x +H 8+ +I 3+ +J 20x +K 3+ + +2 1 5 4 3 +3 2 1 5 4 +1 5 4 3 2 +4 3 2 1 5 +5 4 3 2 1 + +5x5:d4 (easy) +CC1EE +CGI5E +GGIDB +3HHDB +FFAAB + +A 5+ +B 12x +C 7+ +D 8x +E 10+ +F 3- +G 60x +H 6+ +I 5+ + +4 2 1 3 5 +1 4 3 5 2 +5 3 2 4 1 +3 1 5 2 4 +2 5 4 1 3 + +5x5:d4 (easy) +DIIEE +DFFFA +GGGFA +C4BBB +CCHH1 + +A 1- +B 30x +C 8x +D 10x +E 3+ +F 13+ +G 6x +H 2- +I 1- + +5 3 4 1 2 +2 5 1 3 4 +3 1 2 4 5 +1 4 5 2 3 +4 2 3 5 1 + +5x5:d4 (easy) +5HHEE +DDIIF +CDD1F +C1AGB +JJAGB + +A 1- +B 1- +C 1- +D 96x +E 6x +F 6+ +G 20x +H 3- +I 7+ +J 4- + +5 4 1 3 2 +4 3 5 2 1 +3 2 4 1 5 +2 1 3 5 4 +1 5 2 4 3 + +5x5:d3 (easy) +GEEDD +GI2FH +GIFFH +GAABC +GJJBC + +A 6+ +B 2/ +C 10x +D 4+ +E 9+ +F 9+ +G 15+ +H 1- +I 3+ +J 1- + +2 4 5 3 1 +4 1 2 5 3 +5 2 3 1 4 +3 5 1 4 2 +1 3 4 2 5 + +5x5:d3 (easy) +JJBBE +CCFGE +KKFGE +AHIII +AHDDD + +A 6+ +B 10x +C 8+ +D 12x +E 6+ +F 6+ +G 7+ +H 6x +I 11+ +J 3- +K 2/ + +4 1 2 5 3 +3 5 1 4 2 +2 4 5 3 1 +1 3 4 2 5 +5 2 3 1 4 + +5x5:d3 (easy) +DDD1C +4HHCC +GBHAA +GBFFF +EEEII + +A 7+ +B 15x +C 12+ +D 30x +E 11+ +F 7+ +G 4+ +H 8x +I 2- + +5 2 3 1 4 +4 1 2 5 3 +1 3 4 2 5 +3 5 1 4 2 +2 4 5 3 1 + +5x5:d4 (easy) +HH5AF +H5AAF +DGGII +DBJJE +DBCCE + +A 11+ +B 1- +C 4- +D 60x +E 1- +F 5+ +G 1- +H 4+ +I 3+ +J 1- + +2 1 5 4 3 +1 5 4 3 2 +5 4 3 2 1 +4 3 2 1 5 +3 2 1 5 4 + +5x5:d4 (easy) +EED3B +GGDDB +3GADC +F1AAC +FF1AA + +A 17+ +B 2- +C 4+ +D 14+ +E 1- +F 10+ +G 80x + +1 2 5 3 4 +4 5 3 1 2 +3 4 2 5 1 +5 1 4 2 3 +2 3 1 4 5 + +5x5:d4 (easy) +BCCFA +BCFFA +HHIIE +GIIEE +GJJDD + +A 1- +B 6x +C 100x +D 5+ +E 75x +F 12x +G 9+ +H 4+ +I 16x +J 5+ + +2 4 5 3 1 +3 5 1 4 2 +1 3 4 2 5 +4 1 2 5 3 +5 2 3 1 4 + +5x5:d3 (easy) +HHCCC +BHGD2 +BGGDA +2EFFA +EE4FF + +A 7+ +B 4- +C 10x +D 12x +E 6x +F 75x +G 8+ +H 48x + +4 3 5 2 1 +5 4 1 3 2 +1 5 2 4 3 +2 1 3 5 4 +3 2 4 1 5 + +5x5:d4 (easy) +EH5DB +EHDDB +FIGGC +FIICC +5AAJJ + +A 2- +B 5+ +C 11+ +D 20x +E 3+ +F 12x +G 3+ +H 12x +I 15x +J 2- + +1 3 5 4 2 +2 4 1 5 3 +3 5 2 1 4 +4 1 3 2 5 +5 2 4 3 1 + +5x5:d4 (easy) +AAGII +DCGI5 +DC1FF +3HHBB +EEE3B + +A 2- +B 5+ +C 2/ +D 3- +E 20x +F 1- +G 6x +H 9+ +I 10+ + +1 3 2 5 4 +2 4 3 1 5 +5 2 1 4 3 +3 5 4 2 1 +4 1 5 3 2 + +5x5:d3 (easy) +IIDGG +AADG5 +HHBBF +4HBCF +EE4CF + +A 2/ +B 12+ +C 1- +D 2- +E 8+ +F 8x +G 12x +H 5+ +I 7+ + +5 2 1 4 3 +2 4 3 1 5 +1 3 2 5 4 +4 1 5 3 2 +3 5 4 2 1 + +5x5:d6 (medium) +CBEEE +CBHE1 +CDHAG +FDDAG +FFFII + +A 3- +B 2/ +C 8+ +D 12x +E 15+ +F 13+ +G 3- +H 1- +I 7+ + +1 4 5 2 3 +4 2 3 5 1 +3 1 2 4 5 +5 3 4 1 2 +2 5 1 3 4 + +5x5:d6 (medium) +IDDH2 +IAHHG +CAABG +C5EBG +CEEFF + +A 10+ +B 3- +C 7+ +D 5+ +E 9+ +F 8+ +G 12x +H 10x +I 8+ + +3 1 4 5 2 +5 3 1 2 4 +4 2 5 1 3 +2 5 3 4 1 +1 4 2 3 5 + +5x5:d6 (medium) +CCGGH +BCGEH +BJJEH +5FFDD +AAIID + +A 5+ +B 5+ +C 9+ +D 24x +E 1- +F 3+ +G 40x +H 10+ +I 2- +J 1- + +1 3 2 5 4 +3 5 4 2 1 +2 4 3 1 5 +5 2 1 4 3 +4 1 5 3 2 + +5x5:d6 (medium) +ICC4H +ICBBH +DDEEH +2AFFF +AAGGG + +A 8+ +B 2- +C 11+ +D 1- +E 5+ +F 60x +G 8+ +H 10+ +I 6+ + +1 5 2 4 3 +5 4 1 3 2 +3 2 4 1 5 +2 1 3 5 4 +4 3 5 2 1 + +5x5:d8 (medium) +IFFHH +IEEBH +GGJBH +KAJDC +KAADC + +A 9+ +B 6+ +C 10x +D 8x +E 1- +F 1- +G 3- +H 36x +I 7+ +J 2/ +K 2- + +2 4 5 3 1 +5 2 3 1 4 +4 1 2 5 3 +1 3 4 2 5 +3 5 1 4 2 + +5x5:d6 (medium) +HHCCC +HBCDD +BBFFI +3BEEI +GGAA5 + +A 4+ +B 12+ +C 24x +D 3- +E 3- +F 3- +G 2/ +H 9+ +I 7+ + +5 3 2 4 1 +1 4 3 5 2 +2 5 4 1 3 +3 1 5 2 4 +4 2 1 3 5 + +5x5:d6 (medium) +JJHH5 +BGHEC +BGFEC +IAFDC +IA5DD + +A 1- +B 1- +C 12x +D 8+ +E 5+ +F 6+ +G 6+ +H 8+ +I 5+ +J 3+ + +1 2 3 4 5 +4 5 1 2 3 +5 1 2 3 4 +2 3 4 5 1 +3 4 5 1 2 + +5x5:d6 (medium) +GGEHH +AGEHD +ABCCD +BB3II +BJJFF + +A 7+ +B 11+ +C 7+ +D 1- +E 3- +F 5+ +G 9+ +H 7+ +I 3- +J 5+ + +2 5 4 1 3 +4 2 1 3 5 +3 1 5 2 4 +1 4 3 5 2 +5 3 2 4 1 + +5x5:d6 (medium) +IEEA1 +IGGAA +DDGFC +DJBFC +1JBHH + +A 8+ +B 6x +C 2- +D 11+ +E 2- +F 2- +G 7+ +H 7+ +I 7+ +J 4- + +4 3 5 2 1 +3 2 4 1 5 +5 4 1 3 2 +2 1 3 5 4 +1 5 2 4 3 + +5x5:d6 (medium) +GHHF5 +GCCFF +GCC5F +GBEED +2BAAD + +A 3- +B 15x +C 13+ +D 2- +E 2/ +F 11+ +G 13+ +H 3+ + +4 2 1 3 5 +3 1 5 2 4 +1 4 3 5 2 +5 3 2 4 1 +2 5 4 1 3 + +5x5:d7 (medium) +BAFFE +BAJJE +HHGG3 +IIDGC +KKDGC + +A 5+ +B 7+ +C 3+ +D 7+ +E 1- +F 3+ +G 120x +H 1- +I 6+ +J 6+ +K 1- + +4 3 2 1 5 +3 2 1 5 4 +2 1 5 4 3 +1 5 4 3 2 +5 4 3 2 1 + +5x5:d6 (medium) +AFF4D +ABBDD +GCIID +GC5EH +1JJEH + +A 2- +B 1- +C 3- +D 30x +E 2- +F 3+ +G 2/ +H 6+ +I 2- +J 1- + +5 2 1 4 3 +3 5 4 2 1 +2 4 3 1 5 +4 1 5 3 2 +1 3 2 5 4 + +5x5:d7 (medium) +HHCCE +BAFCE +BAFFF +4GIIF +GGDD1 + +A 5+ +B 2- +C 7+ +D 2/ +E 7+ +F 20+ +G 10+ +H 3- +I 4+ + +2 5 4 1 3 +3 1 5 2 4 +1 4 3 5 2 +4 2 1 3 5 +5 3 2 4 1 + +5x5:d6 (medium) +2CCBI +HD1BI +HDAGE +FDAGE +FDJJE + +A 12x +B 1- +C 6+ +D 14+ +E 8+ +F 5+ +G 5+ +H 8+ +I 1- +J 3+ + +2 1 5 4 3 +3 2 1 5 4 +5 4 3 2 1 +1 5 4 3 2 +4 3 2 1 5 + +5x5:d6 (medium) +I4BBB +IGGDA +FJHDA +FJHCC +EEHHH + +A 12x +B 30x +C 3- +D 1- +E 8x +F 3- +G 6+ +H 15+ +I 4+ +J 2- + +1 4 3 5 2 +3 1 5 2 4 +2 5 4 1 3 +5 3 2 4 1 +4 2 1 3 5 + +5x5:d6 (medium) +5FFBE +CIBBE +CIIGG +HJDAA +HJDDD + +A 2- +B 9+ +C 3+ +D 12+ +E 4+ +F 1- +G 10x +H 12x +I 12+ +J 3+ + +5 3 2 4 1 +2 5 4 1 3 +1 4 3 5 2 +4 2 1 3 5 +3 1 5 2 4 + +5x5:d6 (medium) +C1GGI +CFF2I +ABFD3 +ABDDE +ABHHE + +A 7+ +B 10+ +C 2- +D 11+ +E 3- +F 9+ +G 1- +H 1- +I 3- + +5 1 3 4 2 +3 4 1 2 5 +1 2 4 5 3 +2 3 5 1 4 +4 5 2 3 1 + +5x5:d6 (medium) +ICCHH +ICFF3 +EEFAA +JBBBA +JGGDD + +A 8x +B 15x +C 7+ +D 6+ +E 2- +F 11+ +G 1- +H 1- +I 4+ +J 2/ + +3 1 2 4 5 +1 4 5 2 3 +5 3 4 1 2 +2 5 1 3 4 +4 2 3 5 1 + +5x5:d7 (medium) +EEECC +G5FFC +GDDF1 +IDBBH +IIAAH + +A 1- +B 1- +C 60x +D 24x +E 7+ +F 6x +G 2- +H 3- +I 6+ + +2 4 1 5 3 +3 5 2 1 4 +5 2 4 3 1 +1 3 5 4 2 +4 1 3 2 5 + +5x5:d6 (medium) +HHFCC +HFFE3 +G5FEB +GAAIB +DDAIB + +A 8+ +B 20x +C 1- +D 2/ +E 2- +F 12+ +G 2- +H 10+ +I 4- + +4 1 5 3 2 +5 2 1 4 3 +3 5 4 2 1 +1 3 2 5 4 +2 4 3 1 5 + +5x5:d6 (medium) +E2BBB +EIIFH +2AGFH +JAGFF +JCCDD + +A 2- +B 15x +C 4- +D 2/ +E 3- +F 11+ +G 2/ +H 1- +I 12x +J 8+ + +4 2 1 3 5 +1 4 3 5 2 +2 5 4 1 3 +5 3 2 4 1 +3 1 5 2 4 + +5x5:d6 (medium) +GGKKC +FFAAC +EJJDC +EBBDH +IIBDH + +A 1- +B 6+ +C 6+ +D 20x +E 2- +F 20x +G 6+ +H 9+ +I 1- +J 6+ +K 12x + +1 5 4 3 2 +5 4 3 2 1 +2 1 5 4 3 +4 3 2 1 5 +3 2 1 5 4 + +5x5:d6 (medium) +AAEHH +4CEEH +GCFFF +GCC5D +G5BBD + +A 3- +B 2- +C 24x +D 5+ +E 9+ +F 9+ +G 6x +H 9+ + +5 2 1 4 3 +4 1 5 3 2 +2 4 3 1 5 +1 3 2 5 4 +3 5 4 2 1 + +5x5:d6 (medium) +GGFFE +GFFAE +GCDA3 +3CDAB +HHD3B + +A 40x +B 1- +C 3- +D 8+ +E 1- +F 24x +G 13+ +H 3- + +5 3 4 1 2 +4 2 3 5 1 +1 4 5 2 3 +3 1 2 4 5 +2 5 1 3 4 + +5x5:d6 (medium) +BBDF2 +BEDFG +EED1G +JJCII +HHCAA + +A 3- +B 9+ +C 8+ +D 8x +E 20x +F 2- +G 2- +H 1- +I 6+ +J 4- + +4 3 1 5 2 +2 1 4 3 5 +5 4 2 1 3 +1 5 3 2 4 +3 2 5 4 1 + +5x5:d6 (medium) +2HHBI +ABBBI +ADDEC +GFFEC +GGFJJ + +A 2- +B 15+ +C 8+ +D 1- +E 2/ +F 8+ +G 20x +H 1- +I 3+ +J 12x + +2 3 4 5 1 +3 4 5 1 2 +1 2 3 4 5 +4 5 1 2 3 +5 1 2 3 4 + +5x5:d6 (medium) +CG4II +CGDD5 +JHHEE +JAAEB +FFFBB + +A 5+ +B 8x +C 2- +D 2- +E 11+ +F 9+ +G 7+ +H 1- +I 4+ +J 4- + +2 5 4 1 3 +4 2 1 3 5 +1 4 3 5 2 +5 3 2 4 1 +3 1 5 2 4 + +5x5:d6 (medium) +JCCCI +J3FFI +GGGDA +BEEDA +BBHH3 + +A 3- +B 8+ +C 30x +D 6+ +E 2- +F 6+ +G 8+ +H 1- +I 3- +J 2- + +4 5 2 3 1 +2 3 5 1 4 +3 4 1 2 5 +5 1 3 4 2 +1 2 4 5 3 + +5x5:d6 (medium) +AAFFF +EEEII +HGGDD +HCCBD +2CBBD + +A 4+ +B 12+ +C 10+ +D 30x +E 10+ +F 11+ +G 1- +H 12x +I 3- + +1 3 4 2 5 +5 2 3 1 4 +4 1 2 5 3 +3 5 1 4 2 +2 4 5 3 1 + +5x5:d6 (medium) +HEJJG +HEDFG +AADFF +BBBCI +CCCCI + +A 3- +B 10+ +C 11+ +D 1- +E 20x +F 12+ +G 1- +H 1- +I 1- +J 8+ + +2 4 5 3 1 +3 5 1 4 2 +4 1 2 5 3 +5 2 3 1 4 +1 3 4 2 5 + +5x5:d6 (medium) +5ABDD +EABBD +EEIIG +CF4HG +CFFHH + +A 5+ +B 8+ +C 2- +D 9+ +E 6x +F 9+ +G 6+ +H 30x +I 1- + +5 1 2 3 4 +3 4 5 1 2 +1 2 3 4 5 +2 3 4 5 1 +4 5 1 2 3 + +5x5:d6 (medium) +IA1HB +IAHHB +CCEFF +JDEFG +JDGGG + +A 6+ +B 12x +C 7+ +D 1- +E 2- +F 7+ +G 15+ +H 12x +I 1- +J 1- + +4 5 1 2 3 +5 1 2 3 4 +3 4 5 1 2 +1 2 3 4 5 +2 3 4 5 1 + +5x5:d6 (medium) +BBEEC +IIECC +FFHAG +JDHAG +JDHHG + +A 3+ +B 6+ +C 12+ +D 1- +E 24x +F 5+ +G 8+ +H 60x +I 3+ +J 20x + +1 5 2 4 3 +2 1 3 5 4 +3 2 4 1 5 +4 3 5 2 1 +5 4 1 3 2 + +5x5:d11 (hard) +DDIHH +IIIAA +GEIA1 +GEBBC +4FFFC + +A 8+ +B 1- +C 1- +D 3+ +E 2- +F 8+ +G 7+ +H 1- +I 19+ + +1 2 3 4 5 +3 4 5 1 2 +2 3 4 5 1 +5 1 2 3 4 +4 5 1 2 3 + +5x5:d9 (hard) +BBGGG +BCCII +AADD5 +HHHJJ +EE1FF + +A 2/ +B 9+ +C 1- +D 2- +E 7+ +F 1- +G 8x +H 20x +I 1- +J 1- + +3 5 4 2 1 +1 3 2 5 4 +2 4 3 1 5 +4 1 5 3 2 +5 2 1 4 3 + +5x5:d9 (hard) +AADDI +HHDII +HFEGG +FFE2B +1CCCB + +A 1- +B 1- +C 12+ +D 10+ +E 1- +F 12+ +G 4- +H 9+ +I 60x + +2 1 5 4 3 +3 2 1 5 4 +4 3 2 1 5 +5 4 3 2 1 +1 5 4 3 2 + +5x5:d9 (hard) +BDGGI +BDDII +5CDAA +CC5HE +FFHHE + +A 1- +B 6+ +C 8+ +D 12+ +E 2- +F 4+ +G 2- +H 8+ +I 8+ + +4 2 3 5 1 +2 5 1 3 4 +5 3 4 1 2 +1 4 5 2 3 +3 1 2 4 5 + +5x5:d9 (hard) +DDHGJ +F4HGJ +FIIKK +BBCCE +AAAEE + +A 8+ +B 6+ +C 5+ +D 5+ +E 40x +F 3- +G 3- +H 3- +I 5+ +J 2- +K 2- + +3 2 5 4 1 +5 4 2 1 3 +2 1 4 3 5 +1 5 3 2 4 +4 3 1 5 2 + +5x5:d9 (hard) +AABBI +HACBI +HCCGG +FFDEE +3DDDE + +A 7+ +B 12+ +C 10+ +D 8x +E 60x +F 7+ +G 3+ +H 1- +I 2- + +1 4 5 2 3 +4 2 3 5 1 +5 3 4 1 2 +2 5 1 3 4 +3 1 2 4 5 + +5x5:d9 (hard) +BBHHF +DBBFF +DDG5C +A5GEC +AA1EE + +A 10+ +B 12+ +C 5+ +D 6x +E 9+ +F 8+ +G 6+ +H 2- + +4 1 5 3 2 +2 4 3 1 5 +1 3 2 5 4 +3 5 4 2 1 +5 2 1 4 3 + +5x5:d10 (hard) +HHICF +GEICF +GEFFF +DD3BA +DDDBA + +A 2/ +B 8+ +C 2/ +D 40x +E 3- +F 60x +G 2- +H 7+ +I 3- + +4 3 5 2 1 +1 5 2 4 3 +3 2 4 1 5 +2 1 3 5 4 +5 4 1 3 2 + +5x5:d9 (hard) +HHBBD +H2JJD +CC4FF +AEEGG +AIIGG + +A 6+ +B 3- +C 3- +D 20x +E 5+ +F 2- +G 12+ +H 8+ +I 1- +J 2- + +3 1 5 2 4 +4 2 1 3 5 +2 5 4 1 3 +5 3 2 4 1 +1 4 3 5 2 + +5x5:d9 (hard) +2CCDD +BCCFF +B1EGF +HHEGF +HAAII + +A 7+ +B 1- +C 30x +D 1- +E 7+ +F 14+ +G 2/ +H 10+ +I 1- + +2 5 1 3 4 +4 2 3 5 1 +3 1 2 4 5 +1 4 5 2 3 +5 3 4 1 2 + +5x5:d9 (hard) +FICCG +FI1GG +BBAGE +BDA2E +2DHHH + +A 7+ +B 10+ +C 5+ +D 2- +E 2- +F 15x +G 14+ +H 60x +I 2- + +3 2 4 1 5 +5 4 1 3 2 +1 5 2 4 3 +4 3 5 2 1 +2 1 3 5 4 + +5x5:d9 (hard) +AAKGC +JAKGC +JFFHH +EEDDH +EIIBB + +A 9+ +B 2- +C 2/ +D 1- +E 11+ +F 7+ +G 7+ +H 8+ +I 5+ +J 12x +K 2- + +1 5 3 2 4 +4 3 1 5 2 +3 2 5 4 1 +5 4 2 1 3 +2 1 4 3 5 + +5x5:d10 (hard) +DDDGG +EEEEI +AACFI +HCCFF +HHBBF + +A 15x +B 3- +C 8+ +D 7+ +E 13+ +F 13+ +G 2- +H 10+ +I 3+ + +4 2 1 3 5 +1 4 3 5 2 +5 3 2 4 1 +3 1 5 2 4 +2 5 4 1 3 + +5x5:d9 (hard) +DHEEB +DHGFB +JJGFB +JCCI4 +AAAII + +A 8+ +B 8+ +C 5+ +D 1- +E 2/ +F 4+ +G 2- +H 9+ +I 12+ +J 6+ + +3 5 4 2 1 +2 4 3 1 5 +4 1 5 3 2 +1 3 2 5 4 +5 2 1 4 3 + +5x5:d10 (hard) +FFFHH +IEEBB +IAA1B +DGGCB +D3CCB + +A 20x +B 16+ +C 8+ +D 2- +E 3+ +F 8+ +G 6+ +H 3- +I 2- + +1 4 3 5 2 +4 2 1 3 5 +2 5 4 1 3 +3 1 5 2 4 +5 3 2 4 1 + +5x5:d9 (hard) +DHHB5 +DHGBB +3FGGC +EFFCC +EAAII + +A 1- +B 8+ +C 11+ +D 1- +E 3+ +F 8+ +G 20x +H 6x +I 4+ + +4 2 1 3 5 +5 3 2 4 1 +3 1 5 2 4 +1 4 3 5 2 +2 5 4 1 3 + +5x5:d10 (hard) +FF3GI +FEGGI +EEGCC +HHDCB +AADDB + +A 7+ +B 4- +C 12x +D 11+ +E 20x +F 8+ +G 30x +H 5+ +I 2/ + +2 1 3 5 4 +5 4 1 3 2 +1 5 2 4 3 +3 2 4 1 5 +4 3 5 2 1 + +5x5:d9 (hard) +AH1ID +AHGID +AHGC3 +ABGCE +4BFFE + +A 11+ +B 2- +C 1- +D 3- +E 3- +F 1- +G 12+ +H 8x +I 3- + +3 2 1 5 4 +5 4 3 2 1 +2 1 5 4 3 +1 5 4 3 2 +4 3 2 1 5 + +5x5:d9 (hard) +C5DDE +CGDDE +AGHFE +ABHFE +4BFFF + +A 4- +B 1- +C 5+ +D 60x +E 40x +F 13+ +G 1- +H 7+ + +3 5 1 4 2 +2 4 5 3 1 +1 3 4 2 5 +5 2 3 1 4 +4 1 2 5 3 + +5x5:d9 (hard) +IDDHH +IDAAC +GGJ5C +EEJF5 +3BBFF + +A 5+ +B 6+ +C 1- +D 10+ +E 2/ +F 9+ +G 3- +H 5+ +I 7+ +J 2- + +5 3 2 4 1 +2 5 4 1 3 +1 4 3 5 2 +4 2 1 3 5 +3 1 5 2 4 + +5x5:d10 (hard) +GGG3I +F3DDI +FBBHA +FFHHA +CCEE5 + +A 12x +B 2- +C 1- +D 3- +E 3- +F 40x +G 20x +H 11+ +I 1- + +5 4 1 3 2 +4 3 5 2 1 +2 1 3 5 4 +1 5 2 4 3 +3 2 4 1 5 + +5x5:d10 (hard) +DIIGG +DFFHH +B2FHH +BECCA +BEECA + +A 7+ +B 24x +C 8+ +D 6+ +E 10+ +F 6+ +G 3- +H 60x +I 1- + +1 4 3 5 2 +5 3 2 4 1 +4 2 1 3 5 +3 1 5 2 4 +2 5 4 1 3 + +5x5:d9 (hard) +DDDB4 +CCDBB +F1EEA +FHHHA +GGIII + +A 7+ +B 7+ +C 7+ +D 12+ +E 20x +F 4+ +G 2/ +H 9+ +I 9+ + +5 3 1 2 4 +2 5 3 4 1 +3 1 4 5 2 +1 4 2 3 5 +4 2 5 1 3 + +5x5:d9 (hard) +EEJJB +KFFFB +KAAHG +CCHHG +IIDDG + +A 3+ +B 2- +C 1- +D 1- +E 1- +F 9+ +G 11+ +H 8+ +I 6+ +J 9+ +K 2- + +2 1 5 4 3 +5 4 3 2 1 +3 2 1 5 4 +4 3 2 1 5 +1 5 4 3 2 + +5x5:d9 (hard) +FIIGG +FFE4D +JJECD +AHHCC +AHBBB + +A 5+ +B 10+ +C 6+ +D 5+ +E 2- +F 10+ +G 3- +H 40x +I 5+ +J 8+ + +3 1 4 5 2 +2 5 3 4 1 +5 3 1 2 4 +4 2 5 1 3 +1 4 2 3 5 + +5x5:d9 (hard) +AHHCC +ABBC2 +ADEEF +DDDGF +2DGGG + +A 8+ +B 1- +C 6+ +D 13+ +E 7+ +F 1- +G 13+ +H 6+ + +4 5 1 2 3 +3 4 5 1 2 +1 2 3 4 5 +5 1 2 3 4 +2 3 4 5 1 + +5x5:d12 (hard) +EIIAA +ECCDA +JJCDD +HBBFF +HHGGF + +A 15x +B 6+ +C 40x +D 11+ +E 5+ +F 24x +G 5+ +H 10+ +I 1- +J 4+ + +4 2 3 5 1 +1 4 5 2 3 +3 1 2 4 5 +2 5 1 3 4 +5 3 4 1 2 + +5x5:d9 (hard) +FFCCC +AFBBC +A1BEE +1DDEE +DDGGG + +A 2/ +B 9+ +C 10+ +D 150x +E 13+ +F 12+ +G 12x + +3 5 2 1 4 +2 4 1 5 3 +4 1 3 2 5 +1 3 5 4 2 +5 2 4 3 1 + +5x5:d9 (hard) +AADGG +HADBG +HF4BC +FFECC +F2EEC + +A 8+ +B 3+ +C 10+ +D 10x +E 9+ +F 14+ +G 60x +H 6+ + +3 1 2 4 5 +1 4 5 2 3 +5 3 4 1 2 +2 5 1 3 4 +4 2 3 5 1 + +5x5:d9 (hard) +DDD3E +ACDEE +ACHH2 +AABBG +FF5BG + +A 13+ +B 8+ +C 1- +D 11+ +E 20x +F 5+ +G 2- +H 3- + +2 5 1 3 4 +4 2 3 5 1 +5 3 4 1 2 +3 1 2 4 5 +1 4 5 2 3 + +5x5:d12 (hard) +GGFAA +GEFHH +GEEIB +DDCIB +D3CBB + +A 2- +B 12+ +C 15x +D 8+ +E 20x +F 2- +G 10+ +H 2- +I 2/ + +1 2 4 5 3 +4 5 2 3 1 +3 4 1 2 5 +5 1 3 4 2 +2 3 5 1 4 + +5x5:d10 (hard) +HHKE4 +CFKEI +CFBBI +JDDDG +JAAGG + +A 2/ +B 12x +C 1- +D 8+ +E 3- +F 6+ +G 11+ +H 2- +I 1- +J 5+ +K 3- + +5 3 1 2 4 +3 1 4 5 2 +2 5 3 4 1 +4 2 5 1 3 +1 4 2 3 5 + +5x5:d10 (hard) +ACHKK +ACHFF +ICHDF +IEEDB +GGJJB + +A 6+ +B 20x +C 9+ +D 6+ +E 7+ +F 9+ +G 3- +H 10+ +I 1- +J 5+ +K 2- + +5 2 4 3 1 +1 3 5 4 2 +2 4 1 5 3 +3 5 2 1 4 +4 1 3 2 5 + +5x5:d9 (hard) +EEAFB +EJAFB +GJIID +GGKKD +5CCHH + +A 7+ +B 1- +C 1- +D 9+ +E 8+ +F 4- +G 8+ +H 5+ +I 2- +J 2/ +K 3- + +2 5 4 1 3 +1 4 3 5 2 +4 2 1 3 5 +3 1 5 2 4 +5 3 2 4 1 + +6x6:d4 (easy) +BFF2II +BGFKAI +6GMKAL +HHMMCL +EHDDCL +EJJJ4L + +A 3+ +B 6+ +C 1- +D 3- +E 5+ +F 14+ +G 20x +H 24x +I 24x +J 10+ +K 5- +L 90x +M 11+ + +1 6 5 2 3 4 +5 4 3 6 1 2 +6 5 4 1 2 3 +4 3 2 5 6 1 +3 2 1 4 5 6 +2 1 6 3 4 5 + +6x6:d3 (easy) +JBIINN +JBIAAA +LLDMM5 +GLDCEE +G1KCHE +FFK6HH + +A 15x +B 7+ +C 3+ +D 2- +E 12+ +F 4- +G 1- +H 12+ +I 8x +J 11+ +K 2- +L 48x +M 4+ +N 4- + +5 3 1 4 2 6 +6 4 2 5 3 1 +4 2 6 3 1 5 +2 6 4 1 5 3 +3 1 5 2 6 4 +1 5 3 6 4 2 + +6x6:d4 (easy) +EDDABB +EMDAA6 +4MJJLC +KHH2LC +K5GGLF +KKIIFF + +A 10+ +B 2- +C 4- +D 12x +E 8+ +F 6+ +G 12x +H 1- +I 9+ +J 7+ +K 72x +L 90x +M 6x + +3 1 6 5 2 4 +5 3 2 1 4 6 +4 2 1 6 3 5 +6 4 3 2 5 1 +1 5 4 3 6 2 +2 6 5 4 1 3 + +6x6:d3 (easy) +EDDDD6 +EL4HGK +BLLHGK +BFAH3K +MFAHCI +MJJ3CI + +A 6+ +B 2- +C 3+ +D 40x +E 2- +F 24x +G 11+ +H 48x +I 1- +J 30x +K 8+ +L 18x +M 5- + +3 1 2 5 4 6 +5 3 4 1 6 2 +4 2 3 6 5 1 +2 6 1 4 3 5 +6 4 5 2 1 3 +1 5 6 3 2 4 + +6x6:d4 (easy) +KNNFFF +KDHHHF +KDACGG +IIACOB +MELLOB +MEJJJJ + +A 2- +B 3- +C 6+ +D 8x +E 4+ +F 72x +G 24x +H 6x +I 7+ +J 360x +K 14+ +L 3/ +M 8x +N 3- +O 5+ + +6 5 2 4 3 1 +5 4 1 3 2 6 +3 2 5 1 6 4 +1 6 3 5 4 2 +4 3 6 2 1 5 +2 1 4 6 5 3 + +6x6:d3 (easy) +EEFF4L +KKF1GL +AKBBGL +ANNMI3 +HCCMID +H6JJJD + +A 10+ +B 3/ +C 5- +D 20x +E 6x +F 9+ +G 30x +H 3+ +I 2- +J 30x +K 12+ +L 9+ +M 2/ +N 1- + +3 2 1 5 4 6 +5 4 3 1 6 2 +4 3 2 6 5 1 +6 5 4 2 1 3 +2 1 6 4 3 5 +1 6 5 3 2 4 + +6x6:d4 (easy) +AA3IIK +LLFF3K +OCCBB3 +OJN5MM +GJNEED +G4HHHD + +A 3- +B 5- +C 2- +D 1- +E 1- +F 3- +G 5- +H 11+ +I 1- +J 18x +K 8+ +L 7+ +M 3- +N 7+ +O 2- + +4 1 3 6 5 2 +2 5 1 4 3 6 +5 2 4 1 6 3 +3 6 2 5 4 1 +6 3 5 2 1 4 +1 4 6 3 2 5 + +6x6:d4 (easy) +IIKKMM +GNEE2C +GNFFFC +GAABBC +DD1LLL +JJJ6HH + +A 12x +B 1- +C 10+ +D 1- +E 4+ +F 24x +G 10+ +H 6+ +I 5- +J 9+ +K 8+ +L 15+ +M 2/ +N 30x + +6 1 5 3 4 2 +4 5 3 1 2 6 +5 6 4 2 3 1 +1 2 6 4 5 3 +2 3 1 5 6 4 +3 4 2 6 1 5 + +6x6:d5 (easy) +DDDKKC +MMHKEC +GGHKEN +4PPIIN +ALLJJB +AFFOOB + +A 4+ +B 4- +C 1- +D 30x +E 2- +F 1- +G 1- +H 2- +I 3+ +J 5- +K 13+ +L 1- +M 5- +N 2- +O 9+ +P 1- + +5 6 1 2 3 4 +6 1 2 3 4 5 +2 3 4 5 6 1 +4 5 6 1 2 3 +3 4 5 6 1 2 +1 2 3 4 5 6 + +6x6:d4 (easy) +AAFMME +AAFFFE +BJJ4KK +BCDGGK +2CDNII +LLDNHH + +A 24x +B 4- +C 3- +D 20x +E 20x +F 36x +G 3+ +H 3+ +I 7+ +J 30x +K 11+ +L 24x +M 30x +N 2- + +3 1 2 6 5 4 +4 2 3 1 6 5 +1 5 6 4 3 2 +5 3 4 2 1 6 +2 6 1 5 4 3 +6 4 5 3 2 1 + +6x6:d4 (easy) +6EEALL +GGEAMM +JJKKM2 +I3BKDD +IIB2DC +HHHFFC + +A 15x +B 5+ +C 10+ +D 30x +E 15+ +F 1- +G 1- +H 6x +I 13+ +J 7+ +K 10+ +L 3+ +M 36x + +6 5 4 3 2 1 +2 1 6 5 4 3 +1 6 5 4 3 2 +4 3 2 1 6 5 +5 4 3 2 1 6 +3 2 1 6 5 4 + +6x6:d4 (easy) +EEBGL2 +NNBGLI +FFKKOI +DDDDOH +2CCMJH +AAMMJJ + +A 20x +B 1- +C 4+ +D 24x +E 1- +F 3+ +G 12x +H 1- +I 7+ +J 24x +K 30x +L 3+ +M 36x +N 5- +O 2- + +5 6 4 3 1 2 +6 1 5 4 2 3 +1 2 6 5 3 4 +3 4 2 1 5 6 +2 3 1 6 4 5 +4 5 3 2 6 1 + +6x6:d5 (easy) +JBBKK6 +J1CEEI +OOCNNI +DDAAHI +LFFFHH +LFMMGG + +A 7+ +B 4- +C 12x +D 2- +E 24x +F 20+ +G 6+ +H 9+ +I 30x +J 2- +K 6+ +L 5+ +M 1- +N 4- +O 3/ + +3 5 1 2 4 6 +5 1 3 4 6 2 +6 2 4 5 1 3 +2 4 6 1 3 5 +1 3 5 6 2 4 +4 6 2 3 5 1 + +6x6:d3 (easy) +JJJLG2 +FIILGN +FIEPDN +OHEPDC +OHBAAC +KKB1MM + +A 5- +B 1- +C 6+ +D 2- +E 4- +F 5- +G 10+ +H 10+ +I 7+ +J 30x +K 2- +L 2- +M 8+ +N 7+ +O 2/ +P 2- + +5 1 6 3 4 2 +1 3 2 5 6 4 +6 2 1 4 5 3 +4 6 5 2 3 1 +2 4 3 6 1 5 +3 5 4 1 2 6 + +6x6:d3 (easy) +BMKKAL +BM6DAL +EEIDHC +E2IHHC +NNJJ6C +5GGFFF + +A 1- +B 1- +C 11+ +D 10x +E 8+ +F 8+ +G 3/ +H 36x +I 1- +J 2- +K 4- +L 5- +M 1- +N 1- + +2 3 5 1 4 6 +3 4 6 2 5 1 +6 1 3 5 2 4 +1 2 4 6 3 5 +4 5 1 3 6 2 +5 6 2 4 1 3 + +6x6:d4 (easy) +GNJJCC +GNN3AA +GLLOOE +BPKKHE +BPIIHD +FFMMMD + +A 5- +B 4- +C 30x +D 3- +E 1- +F 6x +G 13+ +H 4+ +I 12x +J 1- +K 30x +L 5+ +M 11+ +N 40x +O 3- +P 3/ + +3 4 1 2 5 6 +4 5 2 3 6 1 +6 1 4 5 2 3 +1 2 5 6 3 4 +5 6 3 4 1 2 +2 3 6 1 4 5 + +6x6:d4 (easy) +AALLBB +A2JGGH +MJJG5H +MCNIIH +ECNFDD +EKKFFF + +A 90x +B 2/ +C 1- +D 2- +E 7+ +F 19+ +G 7+ +H 14+ +I 7+ +J 16+ +K 4- +L 4+ +M 3- +N 3+ + +6 5 3 1 4 2 +3 2 6 4 1 5 +1 6 4 2 5 3 +4 3 1 5 2 6 +5 4 2 6 3 1 +2 1 5 3 6 4 + +6x6:d4 (easy) +EEE5NN +EG1AAA +GGKKII +4FFLLD +BHJJMD +BHCCMM + +A 60x +B 6+ +C 5+ +D 3- +E 8+ +F 5+ +G 144x +H 2- +I 2- +J 3- +K 3- +L 1- +M 24x +N 24x + +3 1 2 5 4 6 +2 6 1 4 3 5 +6 4 5 2 1 3 +4 2 3 6 5 1 +1 5 6 3 2 4 +5 3 4 1 6 2 + +6x6:d4 (easy) +BBLL4D +HKKLDD +H4AACF +III3CF +GG4JJJ +MMMEEJ + +A 5- +B 6x +C 6+ +D 10x +E 4- +F 2/ +G 1- +H 3- +I 13+ +J 270x +K 4+ +L 120x +M 10+ + +2 3 5 6 4 1 +6 1 3 4 2 5 +3 4 6 1 5 2 +5 6 2 3 1 4 +1 2 4 5 3 6 +4 5 1 2 6 3 + +6x6:d5 (easy) +AAKJCC +BBKJ1C +EEKKDC +LHHID4 +L2IIFM +GG1FFM + +A 10+ +B 2- +C 72x +D 2- +E 2- +F 72x +G 3/ +H 1- +I 30x +J 5- +K 15+ +L 3- +M 4- + +6 4 5 1 2 3 +5 3 4 6 1 2 +3 1 2 4 5 6 +1 5 6 2 3 4 +4 2 3 5 6 1 +2 6 1 3 4 5 + +6x6:d4 (easy) +KKEEEA +K4BBCA +1FFBCC +HHHGGC +JJLLLL +MMIIDD + +A 1- +B 6x +C 21+ +D 6+ +E 24x +F 10x +G 10x +H 12x +I 1- +J 2- +K 14+ +L 15+ +M 4- + +5 3 6 1 4 2 +6 4 1 2 5 3 +1 5 2 3 6 4 +3 1 4 5 2 6 +4 2 5 6 3 1 +2 6 3 4 1 5 + +6x6:d6 (easy) +LLAGGK +BBAIIK +NEEFIK +NCHFDD +6CHMJJ +OO1MJJ + +A 1- +B 12x +C 1- +D 1- +E 2- +F 4+ +G 4- +H 30x +I 9+ +J 18+ +K 12x +L 1- +M 2- +N 4- +O 5+ + +4 5 3 6 2 1 +3 4 2 5 1 6 +5 6 4 1 3 2 +1 2 6 3 5 4 +6 1 5 2 4 3 +2 3 1 4 6 5 + +6x6:d4 (easy) +JNNCCK +JAGFKK +DAGFBB +D3HMMB +4HHMII +LLEEI3 + +A 4- +B 11+ +C 3- +D 30x +E 24x +F 6+ +G 4+ +H 7+ +I 9+ +J 4+ +K 8+ +L 7+ +M 60x +N 20x + +1 4 5 3 6 2 +3 6 1 5 2 4 +5 2 3 1 4 6 +6 3 4 2 5 1 +4 1 2 6 3 5 +2 5 6 4 1 3 + +6x6:d4 (easy) +4KKKBL +JGGBBL +JAADII +JE4DIH +JEC1FH +EECFFH + +A 3/ +B 72x +C 11+ +D 10+ +E 24x +F 12x +G 6+ +H 60x +I 7+ +J 16+ +K 9+ +L 3/ + +4 1 3 5 6 2 +2 5 1 3 4 6 +3 6 2 4 5 1 +5 2 4 6 1 3 +6 3 5 1 2 4 +1 4 6 2 3 5 + +6x6:d5 (easy) +AAAOOH +FDDGGH +FLLCCM +B4LIPM +BNNIPE +KKNJJE + +A 72x +B 6+ +C 1- +D 15x +E 5- +F 3/ +G 7+ +H 1- +I 10+ +J 1- +K 3- +L 6x +M 6x +N 60x +O 3+ +P 2- + +3 6 4 2 1 5 +2 5 3 1 6 4 +6 3 1 5 4 2 +1 4 2 6 5 3 +5 2 6 4 3 1 +4 1 5 3 2 6 + +6x6:d5 (easy) +CCLLLF +HHII5F +BJIPPF +BJKNAA +DGKNEO +DGMMEO + +A 6+ +B 10x +C 4- +D 12x +E 1- +F 24x +G 3+ +H 2- +I 4+ +J 2/ +K 1- +L 11+ +M 1- +N 12x +O 7+ +P 24x + +1 5 3 2 6 4 +6 4 2 1 5 3 +5 3 1 6 4 2 +2 6 4 3 1 5 +3 1 5 4 2 6 +4 2 6 5 3 1 + +6x6:d5 (easy) +JJEE1C +NFFBCC +NF6BMM +AGLLDD +AGIIDO +HHHKKO + +A 1- +B 9+ +C 40x +D 120x +E 2- +F 12+ +G 3+ +H 20x +I 8+ +J 9+ +K 6x +L 4+ +M 4+ +N 5+ +O 3- + +6 3 4 2 1 5 +3 6 1 5 4 2 +2 5 6 4 3 1 +5 2 3 1 6 4 +4 1 2 6 5 3 +1 4 5 3 2 6 + +6x6:d6 (easy) +DDBB3M +DAAIIM +DGGFF1 +GGEKKK +LGEEJH +LCCCJH + +A 2/ +B 6+ +C 7+ +D 10+ +E 15x +F 2- +G 21+ +H 5+ +I 2/ +J 1- +K 12x +L 20x +M 1- + +2 4 1 5 3 6 +1 3 6 4 2 5 +3 5 2 6 4 1 +6 2 5 3 1 4 +4 6 3 1 5 2 +5 1 4 2 6 3 + +6x6:d4 (easy) +KKJFFH +KJJJHH +B2DIIG +BEDI3G +EE6CLL +2AACCC + +A 2- +B 2- +C 18+ +D 2- +E 120x +F 8+ +G 10+ +H 6x +I 25x +J 15+ +K 72x +L 1- + +4 3 5 2 6 1 +6 5 1 4 2 3 +3 2 4 1 5 6 +1 6 2 5 3 4 +5 4 6 3 1 2 +2 1 3 6 4 5 + +6x6:d4 (easy) +CCOOJJ +CILGAA +DILGHK +DEB5HK +FEBHHN +FFMM5N + +A 2- +B 10+ +C 13+ +D 3+ +E 2- +F 16+ +G 2/ +H 18x +I 2- +J 2/ +K 7+ +L 2- +M 1- +N 2- +O 5- + +3 5 1 6 4 2 +5 1 3 2 6 4 +1 3 5 4 2 6 +2 4 6 5 3 1 +6 2 4 3 1 5 +4 6 2 1 5 3 + +6x6:d4 (easy) +KBBFF5 +KD3FMI +DDCCMI +EHH3JI +ELGAJJ +ELGAAJ + +A 180x +B 2- +C 3+ +D 10+ +E 10x +F 8x +G 20x +H 3/ +I 9+ +J 12+ +K 18x +L 1- +M 15x + +3 4 6 1 2 5 +6 1 3 4 5 2 +4 5 1 2 3 6 +5 6 2 3 4 1 +2 3 5 6 1 4 +1 2 4 5 6 3 + +6x6:d4 (easy) +FF6AMM +DHHAII +D4BBLL +JJCEE3 +NNC5EE +GGCKKK + +A 2/ +B 4+ +C 60x +D 1- +E 11+ +F 4+ +G 4- +H 7+ +I 7+ +J 6+ +K 12x +L 1- +M 1- +N 10+ + +1 3 6 2 4 5 +3 5 2 4 6 1 +2 4 1 3 5 6 +5 1 4 6 2 3 +4 6 3 5 1 2 +6 2 5 1 3 4 + +6x6:d4 (easy) +1LLIIB +HA2MIB +HACMGG +JJCE6G +DNCEFF +DNCKK1 + +A 7+ +B 3- +C 14+ +D 3/ +E 3+ +F 4- +G 32x +H 12x +I 10+ +J 3- +K 12x +L 1- +M 30x +N 8+ + +1 4 5 3 2 6 +4 1 2 6 5 3 +3 6 1 5 4 2 +5 2 3 1 6 4 +6 3 4 2 1 5 +2 5 6 4 3 1 + +6x6:d7 (medium) +MMBBE1 +IILLEE +1PPDDN +CCJJKN +FFHOKG +AAHO6G + +A 1- +B 2- +C 1- +D 1- +E 13+ +F 1- +G 5+ +H 1- +I 7+ +J 2- +K 3+ +L 2/ +M 1- +N 2- +O 1- +P 3- + +2 3 6 4 5 1 +6 1 4 2 3 5 +1 2 5 3 4 6 +5 6 3 1 2 4 +4 5 2 6 1 3 +3 4 1 5 6 2 + +6x6:d7 (medium) +BAGKKK +BAGMKC +DAMMHC +DJJHHN +II6EFN +LLLEFF + +A 12+ +B 2- +C 2- +D 1- +E 2- +F 14+ +G 4- +H 10x +I 15x +J 5+ +K 15+ +L 10x +M 24x +N 3/ + +4 2 5 6 3 1 +6 4 1 2 5 3 +2 6 3 4 1 5 +3 1 4 5 2 6 +5 3 6 1 4 2 +1 5 2 3 6 4 + +6x6:d7 (medium) +LGGGKK +L1JFIN +DJJFIN +DJEECH +DB5CCH +DBAAMM + +A 1- +B 18x +C 12+ +D 72x +E 3+ +F 1- +G 11+ +H 2- +I 5- +J 14+ +K 1- +L 7+ +M 1- +N 11+ + +5 4 6 1 3 2 +2 1 3 4 6 5 +3 2 4 5 1 6 +6 5 1 2 4 3 +4 3 5 6 2 1 +1 6 2 3 5 4 + +6x6:d7 (medium) +FFFFKC +EEEFKC +LJHHK3 +LJJ2II +6AAGGM +DD4BBM + +A 1- +B 1- +C 3/ +D 1- +E 12+ +F 16+ +G 1- +H 7+ +I 1- +J 12+ +K 8+ +L 9+ +M 4- + +1 2 3 4 5 6 +3 4 5 6 1 2 +4 5 6 1 2 3 +5 6 1 2 3 4 +6 1 2 3 4 5 +2 3 4 5 6 1 + +6x6:d7 (medium) +DKBBGL +DKNNGL +DIIFAA +EEEFA4 +MMHHAJ +CC5HJJ + +A 150x +B 9+ +C 5+ +D 15x +E 18x +F 1- +G 2- +H 72x +I 2/ +J 11+ +K 3/ +L 2- +M 10x +N 7+ + +3 6 4 5 2 1 +5 2 6 1 4 3 +1 4 2 3 6 5 +6 3 1 2 5 4 +2 5 3 4 1 6 +4 1 5 6 3 2 + +6x6:d8 (medium) +LFHHB1 +LF2JBB +FFCJGG +DDCEEG +KDC6EA +KDIIAA + +A 48x +B 9+ +C 12+ +D 16+ +E 6+ +F 24x +G 15+ +H 2/ +I 5+ +J 3- +K 6+ +L 2- + +4 2 6 3 5 1 +6 4 2 5 1 3 +3 1 5 2 4 6 +2 6 4 1 3 5 +1 5 3 6 2 4 +5 3 1 4 6 2 + +6x6:d7 (medium) +FJJGGC +FNAOOC +FNA6DD +LLAAKK +BEEMMK +BBIIHH + +A 13+ +B 8+ +C 2/ +D 2- +E 2- +F 15+ +G 1- +H 2- +I 4+ +J 8+ +K 11+ +L 3- +M 5+ +N 3+ +O 1- + +6 3 5 1 2 4 +4 1 3 5 6 2 +5 2 4 6 1 3 +3 6 2 4 5 1 +1 4 6 2 3 5 +2 5 1 3 4 6 + +6x6:d7 (medium) +DDII3H +DGKKCH +3GMKCF +EEMKCF +BJJAAF +BBJ1LL + +A 3- +B 30x +C 9+ +D 40x +E 1- +F 14+ +G 3+ +H 3- +I 4- +J 13+ +K 17+ +L 6+ +M 7+ + +5 4 2 6 3 1 +2 1 5 3 6 4 +3 2 6 4 1 5 +4 3 1 5 2 6 +1 6 4 2 5 3 +6 5 3 1 4 2 + +6x6:d8 (medium) +MMMDJJ +KEHDOO +KEHHLL +IIHCCG +BBFAAG +NNF1AG + +A 60x +B 1- +C 1- +D 1- +E 5+ +F 2- +G 24x +H 240x +I 30x +J 2- +K 9+ +L 4- +M 12x +N 20x +O 2/ + +1 2 6 4 5 3 +6 1 5 3 4 2 +3 4 2 6 1 5 +5 6 4 2 3 1 +2 3 1 5 6 4 +4 5 3 1 2 6 + +6x6:d7 (medium) +LJJ2CC +LJFFEE +LDMMB1 +DDGHBB +N6GHKA +NIIKKA + +A 1- +B 60x +C 10+ +D 20x +E 2- +F 7+ +G 1- +H 1- +I 4- +J 6+ +K 9+ +L 120x +M 1- +N 1- + +5 3 1 2 4 6 +4 2 6 1 3 5 +6 4 2 3 5 1 +1 5 3 4 6 2 +2 6 4 5 1 3 +3 1 5 6 2 4 + +6x6:d7 (medium) +2BAAKK +BBCEKK +JNCEEE +JNHHMG +LLDMMG +FFDII6 + +A 2/ +B 8+ +C 4- +D 6x +E 240x +F 1- +G 1- +H 3- +I 6+ +J 2/ +K 240x +L 1- +M 36x +N 3- + +2 1 6 3 5 4 +1 6 5 2 4 3 +3 2 1 4 6 5 +6 5 4 1 3 2 +5 4 3 6 2 1 +4 3 2 5 1 6 + +6x6:d7 (medium) +6AAHKK +CFIHEK +CFIME4 +GJIMEE +GJJ2BL +DDNNBL + +A 4- +B 10+ +C 2- +D 1- +E 30x +F 3/ +G 1- +H 3- +I 48x +J 12+ +K 11+ +L 4- +M 2- +N 2/ + +6 5 1 4 2 3 +3 2 4 1 5 6 +1 6 2 5 3 4 +5 4 6 3 1 2 +4 3 5 2 6 1 +2 1 3 6 4 5 + +6x6:d7 (medium) +GJJJ2I +GAAAAI +EEFFBB +KEHFLL +K4HHHD +1CCMMD + +A 14+ +B 4- +C 1- +D 3/ +E 6+ +F 14+ +G 1- +H 60x +I 12x +J 18x +K 9+ +L 2- +M 1- + +5 6 1 3 2 4 +4 5 6 2 1 3 +2 3 4 6 5 1 +6 1 2 4 3 5 +3 4 5 1 6 2 +1 2 3 5 4 6 + +6x6:d7 (medium) +BMDDGG +BMID1E +BCILLE +KC2AJH +KFAAJH +KFFFFH + +A 10+ +B 9+ +C 10x +D 13+ +E 4- +F 15+ +G 3/ +H 9+ +I 2- +J 2- +K 30x +L 2/ +M 7+ + +3 1 4 5 2 6 +2 6 3 4 1 5 +4 2 5 6 3 1 +1 5 2 3 6 4 +5 3 6 1 4 2 +6 4 1 2 5 3 + +6x6:d8 (medium) +FF4GJC +F3GGJC +FHHHJJ +4EHHBB +DEAAKI +DDA1KI + +A 12x +B 11+ +C 5- +D 11+ +E 6+ +F 60x +G 12+ +H 20+ +I 7+ +J 12x +K 1- + +5 2 4 3 1 6 +6 3 5 4 2 1 +1 4 6 5 3 2 +4 1 3 2 6 5 +2 5 1 6 4 3 +3 6 2 1 5 4 + +6x6:d7 (medium) +J2NNIB +JJAAIB +CCAE1D +CFFEHD +LGMMHH +LGGKKK + +A 18x +B 4- +C 14+ +D 1- +E 1- +F 3- +G 10x +H 60x +I 2- +J 12+ +K 13+ +L 2- +M 2/ +N 2- + +4 2 5 3 6 1 +2 6 3 1 4 5 +5 3 6 4 1 2 +6 4 1 5 2 3 +3 1 4 2 5 6 +1 5 2 6 3 4 + +6x6:d9 (medium) +CC2JJB +CLLLFB +MGGLF5 +M2GIIH +DAAKNH +DEEKNH + +A 1- +B 5+ +C 12+ +D 2/ +E 7+ +F 2/ +G 12x +H 11+ +I 2- +J 4- +K 2- +L 19+ +M 6+ +N 8+ + +6 3 2 5 1 4 +3 6 5 2 4 1 +1 4 3 6 2 5 +5 2 1 4 6 3 +2 5 4 1 3 6 +4 1 6 3 5 2 + +6x6:d7 (medium) +JJJH1E +IIJHEE +CLLHBB +CF2HKB +1FGAKM +DDGAAM + +A 30x +B 18x +C 20x +D 7+ +E 12x +F 8+ +G 1- +H 120x +I 7+ +J 180x +K 2- +L 4- +M 1- + +2 3 5 4 1 6 +3 4 6 5 2 1 +4 5 1 6 3 2 +5 6 2 1 4 3 +1 2 4 3 6 5 +6 1 3 2 5 4 + +6x6:d7 (medium) +ABBLII +A6ELGG +KKELFF +CCC3FN +MHHDDN +MH3DJJ + +A 1- +B 1- +C 11+ +D 8x +E 3- +F 12+ +G 6x +H 60x +I 1- +J 1- +K 2- +L 13+ +M 2/ +N 5- + +5 1 2 6 4 3 +4 6 1 5 3 2 +1 3 4 2 6 5 +2 4 5 3 1 6 +3 5 6 4 2 1 +6 2 3 1 5 4 + +6x6:d7 (medium) +ILCJJM +ILCF5M +6DFFHH +AD5FHE +ADBBHE +KKBGG4 + +A 1- +B 12+ +C 1- +D 9+ +E 1- +F 30x +G 3- +H 36x +I 1- +J 3- +K 6+ +L 7+ +M 1- + +2 6 3 1 4 5 +3 1 4 2 5 6 +6 4 1 5 2 3 +4 2 5 3 6 1 +5 3 6 4 1 2 +1 5 2 6 3 4 + +6x6:d7 (medium) +CCNKHH +LCNKGH +LEAAGF +EEIIGF +JJJDDD +MMBBB6 + +A 9+ +B 24x +C 72x +D 48x +E 12+ +F 2- +G 14+ +H 10+ +I 5- +J 9+ +K 1- +L 3- +M 4- +N 1- + +6 4 2 3 1 5 +5 3 1 2 6 4 +2 6 4 5 3 1 +4 2 6 1 5 3 +3 1 5 6 4 2 +1 5 3 4 2 6 + +6x6:d8 (medium) +N1EDDM +NKEIIM +GKHHJM +GBBBJF +CC6BFF +CLLAA3 + +A 1- +B 30x +C 12x +D 1- +E 1- +F 10+ +G 2/ +H 4+ +I 1- +J 4- +K 1- +L 6+ +M 12x +N 1- + +6 1 3 5 4 2 +5 6 2 4 3 1 +4 5 1 3 2 6 +2 3 5 1 6 4 +3 4 6 2 1 5 +1 2 4 6 5 3 + +6x6:d8 (medium) +DDDAAA +DBCC3I +KBJJII +KFOHMM +NFOHEE +NLLLGG + +A 10+ +B 2/ +C 1- +D 40x +E 20x +F 3- +G 12x +H 5+ +I 60x +J 3+ +K 2- +L 30x +M 1- +N 1- +O 9+ + +5 4 2 3 1 6 +1 6 4 5 3 2 +4 3 1 2 6 5 +6 5 3 4 2 1 +3 2 6 1 5 4 +2 1 5 6 4 3 + +6x6:d7 (medium) +BB5EEE +BAALEH +JKKL3H +JDKLII +GDF4CC +G1FMMC + +A 7+ +B 24x +C 16+ +D 7+ +E 72x +F 5+ +G 7+ +H 5+ +I 1- +J 1- +K 36x +L 8+ +M 1- + +3 4 5 6 1 2 +2 3 4 5 6 1 +5 6 1 2 3 4 +4 5 6 1 2 3 +1 2 3 4 5 6 +6 1 2 3 4 5 + +6x6:d7 (medium) +GGHHHH +GGFFH6 +AAA5EE +KK3DJJ +BBDD4J +4IIDCC + +A 24x +B 1- +C 7+ +D 14+ +E 1- +F 3+ +G 10+ +H 23+ +I 3- +J 8+ +K 30x + +1 2 5 6 3 4 +3 4 1 2 5 6 +6 1 4 5 2 3 +5 6 3 4 1 2 +2 3 6 1 4 5 +4 5 2 3 6 1 + +6x6:d8 (medium) +NFIAAL +NFII1L +KHHIDD +KEEEMM +GOO2CB +GGJJCB + +A 12x +B 1- +C 1- +D 1- +E 24x +F 9+ +G 36x +H 1- +I 15+ +J 4- +K 1- +L 6x +M 5- +N 5- +O 5- + +1 5 4 6 2 3 +6 4 3 5 1 2 +4 2 1 3 5 6 +5 3 2 4 6 1 +3 1 6 2 4 5 +2 6 5 1 3 4 + +6x6:d7 (medium) +KKEEII +DDDDII +5BNNFF +MB5JJF +MHHCCL +GGHAAL + +A 30x +B 5+ +C 5+ +D 15+ +E 12x +F 72x +G 4+ +H 24x +I 40x +J 7+ +K 12x +L 10x +M 8x +N 1- + +6 2 3 4 5 1 +3 5 6 1 2 4 +5 1 2 3 4 6 +2 4 5 6 1 3 +4 6 1 2 3 5 +1 3 4 5 6 2 + +6x6:d9 (medium) +AEEKKK +ABBICC +AGFIII +GGF6LL +6HHJDD +HHJJJ5 + +A 40x +B 1- +C 1- +D 1- +E 6x +F 10x +G 6+ +H 16+ +I 15+ +J 40x +K 24x +L 1- + +5 2 3 4 6 1 +2 5 6 1 3 4 +4 1 2 3 5 6 +1 4 5 6 2 3 +6 3 4 5 1 2 +3 6 1 2 4 5 + +6x6:d8 (medium) +3DDHHK +CBDHKK +CBLLIM +GGL5IM +G4JFAA +EEJFFF + +A 7+ +B 2- +C 4- +D 15+ +E 4- +F 60x +G 48x +H 8+ +I 12x +J 2- +K 12+ +L 9+ +M 6x + +3 5 6 4 2 1 +1 3 4 2 6 5 +5 1 2 6 4 3 +4 6 1 5 3 2 +2 4 5 3 1 6 +6 2 3 1 5 4 + +6x6:d7 (medium) +LFAA3C +LFBBEC +GGMEE1 +GGMEJJ +IIKKDJ +HH6DDJ + +A 4- +B 2/ +C 2/ +D 7+ +E 144x +F 5+ +G 360x +H 4+ +I 3/ +J 200x +K 2- +L 10x +M 1- + +2 4 1 5 3 6 +5 1 4 2 6 3 +3 5 2 6 4 1 +4 6 3 1 5 2 +6 2 5 3 1 4 +1 3 6 4 2 5 + +6x6:d7 (medium) +JAAIII +JFFCCC +JBKCMM +BBKCGG +HHEEDL +2HHHDL + +A 10x +B 36x +C 1200x +D 2- +E 1- +F 5+ +G 5+ +H 360x +I 72x +J 9+ +K 1- +L 6+ +M 3+ + +1 2 5 6 3 4 +3 4 1 2 5 6 +5 6 3 4 1 2 +6 1 4 5 2 3 +4 5 2 3 6 1 +2 3 6 1 4 5 + +6x6:d9 (medium) +GGGGAA +3CCFAA +II1FLL +EDD4HH +EJDDD1 +EJBBKK + +A 240x +B 9+ +C 12x +D 19+ +E 12+ +F 1- +G 14+ +H 1- +I 1- +J 2- +K 1- +L 30x + +2 1 5 6 3 4 +3 2 6 1 4 5 +4 3 1 2 5 6 +6 5 3 4 1 2 +5 4 2 3 6 1 +1 6 4 5 2 3 + +6x6:d7 (medium) +JJJJCL +BHHCCL +B4IINE +DKKANE +DD2AGG +DFFAMM + +A 9+ +B 1- +C 13+ +D 16+ +E 8+ +F 1- +G 2- +H 7+ +I 6+ +J 11+ +K 5- +L 20x +M 2- +N 2- + +1 2 3 5 6 4 +2 3 4 6 1 5 +3 4 5 1 2 6 +5 6 1 3 4 2 +6 1 2 4 5 3 +4 5 6 2 3 1 + +6x6:d10 (hard) +HH5JJE +IHJJEE +ICCDE5 +IKADDD +FKADBB +FKLLGG + +A 4+ +B 1- +C 8x +D 20+ +E 15+ +F 1- +G 7+ +H 24x +I 10+ +J 120x +K 12+ +L 1- + +2 3 5 4 1 6 +3 4 6 5 2 1 +1 2 4 3 6 5 +6 1 3 2 5 4 +4 5 1 6 3 2 +5 6 2 1 4 3 + +6x6:d11 (hard) +D4FFLN +DOOFLN +IJHGGE +IJHAEE +CJBAKK +CBBMMK + +A 3- +B 9+ +C 1- +D 7+ +E 11+ +F 60x +G 1- +H 3+ +I 5+ +J 10+ +K 12+ +L 1- +M 7+ +N 1- +O 30x + +6 4 5 3 2 1 +1 5 6 4 3 2 +3 1 2 6 5 4 +2 6 1 5 4 3 +5 3 4 2 1 6 +4 2 3 1 6 5 + +6x6:d10 (hard) +ABBELL +ABEECC +AII2GG +D1FKKG +DDFHMJ +6NNHMJ + +A 7+ +B 9+ +C 7+ +D 11+ +E 11+ +F 2/ +G 60x +H 7+ +I 1- +J 1- +K 2- +L 1- +M 1- +N 1- + +2 6 1 3 5 4 +4 2 3 5 1 6 +1 5 6 2 4 3 +3 1 2 4 6 5 +5 3 4 6 2 1 +6 4 5 1 3 2 + +6x6:d10 (hard) +4KKHHG +MEIICG +MEBBC4 +F1DBJA +FND2JA +FNDLLA + +A 8+ +B 90x +C 7+ +D 24x +E 1- +F 90x +G 2/ +H 2- +I 2- +J 2- +K 3- +L 8+ +M 1- +N 1- + +4 2 5 1 3 6 +1 5 2 4 6 3 +2 6 3 5 1 4 +3 1 4 6 2 5 +5 3 6 2 4 1 +6 4 1 3 5 2 + +6x6:d10 (hard) +GGJJCC +GIIJJB +A2KFNB +AEKFNB +LEKDHH +LDDDMM + +A 5- +B 15+ +C 3- +D 10+ +E 9+ +F 11+ +G 10+ +H 10x +I 1- +J 12+ +K 8+ +L 1- +M 3- +N 1- + +3 5 6 2 4 1 +2 4 5 1 3 6 +6 2 3 5 1 4 +1 3 4 6 2 5 +4 6 1 3 5 2 +5 1 2 4 6 3 + +6x6:d11 (hard) +JJ4FFC +LLIKGC +LIIKGC +B4AKEE +BHAMMD +HHHMDD + +A 3+ +B 1- +C 9+ +D 40x +E 18x +F 2- +G 1- +H 15+ +I 13+ +J 3+ +K 9+ +L 36x +M 8+ + +2 1 4 5 3 6 +4 3 6 1 5 2 +3 2 5 6 4 1 +5 4 1 2 6 3 +6 5 2 3 1 4 +1 6 3 4 2 5 + +6x6:d10 (hard) +KO6LLH +KOBBHH +CCGGND +CAEEND +CAFFJ2 +MMIIJJ + +A 2- +B 7+ +C 16+ +D 5- +E 1- +F 1- +G 5+ +H 60x +I 9+ +J 10+ +K 1- +L 7+ +M 4- +N 9+ +O 1- + +3 1 6 5 2 4 +4 2 1 6 3 5 +6 4 3 2 5 1 +5 3 2 1 4 6 +1 5 4 3 6 2 +2 6 5 4 1 3 + +6x6:d12 (hard) +LHHMMG +LDBBBG +DDD4JJ +KEEFF1 +KCCCII +AAANNI + +A 6x +B 10+ +C 12+ +D 14+ +E 20x +F 1- +G 1- +H 7+ +I 48x +J 1- +K 8+ +L 1- +M 1- +N 30x + +5 3 4 2 1 6 +4 2 3 1 6 5 +1 5 6 4 3 2 +6 4 5 3 2 1 +2 6 1 5 4 3 +3 1 2 6 5 4 + +6x6:d10 (hard) +NNBKK2 +IIBKEL +JJGGEL +5OOHMC +DFHHMC +DFFAAC + +A 6+ +B 30x +C 120x +D 7+ +E 2- +F 12x +G 5+ +H 10+ +I 3- +J 7+ +K 30x +L 2- +M 1- +N 5+ +O 2/ + +1 4 6 3 5 2 +6 3 5 2 4 1 +2 5 1 4 6 3 +5 2 4 1 3 6 +4 1 3 6 2 5 +3 6 2 5 1 4 + +6x6:d10 (hard) +ECCLLJ +EE6HLJ +D2HHFF +DDIHKF +6BIKKK +BBGGAA + +A 3- +B 60x +C 20x +D 12+ +E 5+ +F 10+ +G 3/ +H 60x +I 2- +J 12x +K 12+ +L 30x + +1 5 4 2 3 6 +3 1 6 4 5 2 +4 2 1 5 6 3 +2 6 5 3 4 1 +6 4 3 1 2 5 +5 3 2 6 1 4 + +6x6:d10 (hard) +MMJJJ1 +AAFFEE +IIIFBK +II5DBK +CLLDDK +CHHGGK + +A 5- +B 3+ +C 4- +D 12x +E 8+ +F 12+ +G 1- +H 5+ +I 16+ +J 36x +K 240x +L 7+ +M 1- + +4 5 6 2 3 1 +6 1 2 4 5 3 +2 3 4 6 1 5 +3 4 5 1 2 6 +5 6 1 3 4 2 +1 2 3 5 6 4 + +6x6:d12 (hard) +FAAHHH +FLDDIK +5LGGIK +MEGBNN +MEEBBB +JJCCC5 + +A 6+ +B 180x +C 9+ +D 7+ +E 9+ +F 8+ +G 30x +H 9+ +I 3- +J 7+ +K 3- +L 3- +M 1- +N 1- + +6 5 1 2 3 4 +2 1 3 4 5 6 +5 4 6 1 2 3 +4 3 5 6 1 2 +3 2 4 5 6 1 +1 6 2 3 4 5 + +6x6:d11 (hard) +HJFAAA +HJFLLA +C1FDDD +CCEEKD +CMMGKI +BBBG2I + +A 14+ +B 30x +C 180x +D 180x +E 2/ +F 36x +G 2- +H 1- +I 24x +J 1- +K 5+ +L 7+ +M 1- + +4 3 2 6 5 1 +5 4 3 1 6 2 +2 1 6 4 3 5 +6 5 4 2 1 3 +3 2 1 5 4 6 +1 6 5 3 2 4 + +6x6:d12 (hard) +AAAMHH +AK4MGH +AKIIGJ +FFIEJJ +BDDEJC +BLL2JC + +A 96x +B 1- +C 1- +D 2/ +E 3- +F 2- +G 1- +H 15x +I 12+ +J 19+ +K 1- +L 4+ +M 6+ + +4 2 6 1 5 3 +2 6 4 5 3 1 +1 5 3 4 2 6 +3 1 5 6 4 2 +6 4 2 3 1 5 +5 3 1 2 6 4 + +6x6:d11 (hard) +KKKBG4 +5MMBGD +CC2BGD +FHHJLL +FHJJAA +EEEJII + +A 1- +B 13+ +C 2- +D 6+ +E 12+ +F 2- +G 13+ +H 24x +I 1- +J 13+ +K 9+ +L 7+ +M 7+ + +2 6 1 3 5 4 +5 3 4 6 2 1 +3 1 2 4 6 5 +4 2 3 5 1 6 +6 4 5 1 3 2 +1 5 6 2 4 3 + +6x6:d10 (hard) +DMHHJJ +DM3IOE +GNNIOE +G5FFOA +CCBBLA +KKLLL2 + +A 1- +B 7+ +C 1- +D 1- +E 1- +F 3+ +G 7+ +H 9+ +I 1- +J 7+ +K 1- +L 14+ +M 1- +N 3/ +O 60x + +3 2 4 5 6 1 +2 1 3 4 5 6 +1 6 2 3 4 5 +6 5 1 2 3 4 +5 4 6 1 2 3 +4 3 5 6 1 2 + +6x6:d10 (hard) +K2EEEN +KKAEEN +MMAILL +BBHIJJ +CCHIGD +FFF5GD + +A 2- +B 1- +C 5+ +D 2/ +E 21+ +F 11+ +G 8+ +H 4- +I 6+ +J 7+ +K 12+ +L 11+ +M 1- +N 4+ + +1 2 5 6 4 3 +5 6 3 4 2 1 +3 4 1 2 6 5 +4 5 2 3 1 6 +2 3 6 1 5 4 +6 1 4 5 3 2 + +6x6:d10 (hard) +2LLDDJ +ECC6JJ +ECIFF6 +EIIKKB +AGGG2B +A2HHHB + +A 10+ +B 20x +C 8+ +D 7+ +E 15x +F 1- +G 10+ +H 12+ +I 60x +J 30x +K 1- +L 9+ + +2 4 5 1 6 3 +1 3 4 6 5 2 +5 1 2 4 3 6 +3 5 6 2 1 4 +4 6 1 3 2 5 +6 2 3 5 4 1 + +6x6:d10 (hard) +MMII5K +CGGDDK +CFFHDO +NNHHAO +JJHLAA +3BBLEE + +A 9+ +B 6+ +C 6+ +D 12x +E 4- +F 4+ +G 2- +H 60x +I 2- +J 1- +K 2/ +L 2/ +M 5- +N 1- +O 3- + +1 6 2 4 5 3 +4 3 5 1 2 6 +2 1 3 5 6 4 +5 4 6 2 3 1 +6 5 1 3 4 2 +3 2 4 6 1 5 + +6x6:d10 (hard) +A5EENN +ACMMHI +FCGDHI +FJGDII +JJBBOK +LL5BOK + +A 8+ +B 9+ +C 4+ +D 1- +E 3+ +F 1- +G 7+ +H 3/ +I 11+ +J 11+ +K 2- +L 1- +M 1- +N 1- +O 6+ + +6 5 2 1 4 3 +2 1 4 3 6 5 +4 3 6 5 2 1 +5 4 1 6 3 2 +1 6 3 2 5 4 +3 2 5 4 1 6 + +6x6:d10 (hard) +1KKIIG +DEEFFG +DCC5LH +DD1BLH +JJBBA3 +J1AAAA + +A 16+ +B 12x +C 12x +D 16+ +E 1- +F 7+ +G 4- +H 5+ +I 20x +J 15+ +K 6x +L 2/ + +1 2 3 4 5 6 +3 4 5 6 1 2 +2 3 4 5 6 1 +5 6 1 2 3 4 +4 5 6 1 2 3 +6 1 2 3 4 5 + +6x6:d10 (hard) +GGOJJE +M6OEEE +MCCBBN +5KKDFN +LAKDFI +LAHHFI + +A 1- +B 1- +C 1- +D 1- +E 13+ +F 30x +G 2- +H 3/ +I 1- +J 6x +K 10+ +L 1- +M 3+ +N 12x +O 6+ + +6 4 5 3 2 1 +2 6 1 5 4 3 +1 5 6 4 3 2 +5 3 4 2 1 6 +4 2 3 1 6 5 +3 1 2 6 5 4 + +6x6:d10 (hard) +NNAFMM +H3AFFK +HJCFIK +HJC5II +BBDDGE +BLLGGE + +A 2- +B 8+ +C 4- +D 3/ +E 1- +F 144x +G 20x +H 13+ +I 12x +J 24x +K 1- +L 6x +M 1- +N 4- + +1 5 6 4 2 3 +5 3 4 2 6 1 +6 4 5 3 1 2 +2 6 1 5 3 4 +3 1 2 6 4 5 +4 2 3 1 5 6 + +6x6:d10 (hard) +JJLLAA +JCCAAG +BHHH1G +BHIHGG +EEI2FF +DDIKK6 + +A 144x +B 1- +C 3- +D 8x +E 4+ +F 1- +G 36x +H 23+ +I 12x +J 60x +K 2- +L 6+ + +6 2 5 1 3 4 +5 1 4 6 2 3 +4 6 3 5 1 2 +3 5 2 4 6 1 +1 3 6 2 4 5 +2 4 1 3 5 6 + +6x6:d10 (hard) +GHHFFF +GHKKE5 +IIKEEN +JJMBBN +JDMLAA +DD4LCC + +A 1- +B 8+ +C 1- +D 36x +E 8+ +F 9+ +G 4+ +H 11+ +I 1- +J 30x +K 15+ +L 3- +M 1- +N 6+ + +3 4 5 2 6 1 +1 2 3 6 4 5 +4 5 6 3 1 2 +6 1 2 5 3 4 +5 6 1 4 2 3 +2 3 4 1 5 6 + +6x6:d10 (hard) +2IIIIH +DFFLIH +DGFLCH +DGGJCE +DAAJJE +DKK5BB + +A 9+ +B 5- +C 2- +D 360x +E 10x +F 13+ +G 10x +H 8+ +I 17+ +J 12x +K 5+ +L 3/ + +2 6 1 3 5 4 +5 3 4 6 2 1 +1 5 6 2 4 3 +3 1 2 4 6 5 +6 4 5 1 3 2 +4 2 3 5 1 6 + +6x6:d11 (hard) +DDEEE5 +3HKMMM +GHKFII +GCBFIA +GCBL2A +GCLLJJ + +A 5+ +B 1- +C 24x +D 4- +E 8+ +F 12x +G 16+ +H 6+ +I 8+ +J 7+ +K 8+ +L 15x +M 120x + +2 6 1 3 4 5 +3 1 2 4 5 6 +1 5 6 2 3 4 +5 3 4 6 1 2 +6 4 5 1 2 3 +4 2 3 5 6 1 + +6x6:d12 (hard) +FLLIBB +FFIIBB +DJAAHH +DJGAHH +EJGCCK +E1GGCK + +A 30x +B 12x +C 30x +D 7+ +E 1- +F 40x +G 11+ +H 72x +I 60x +J 120x +K 9+ +L 3- + +5 3 6 4 2 1 +4 2 5 3 1 6 +6 4 1 5 3 2 +1 5 2 6 4 3 +2 6 3 1 5 4 +3 1 4 2 6 5 + +6x6:d10 (hard) +JJBBBD +JFKBDD +FFK2DC +FAEEEC +FALEHH +GGLIII + +A 3- +B 15+ +C 20x +D 15+ +E 10+ +F 108x +G 9+ +H 4- +I 18x +J 9+ +K 1- +L 3/ + +4 3 1 5 6 2 +2 1 5 3 4 6 +1 6 4 2 3 5 +6 5 3 1 2 4 +3 2 6 4 5 1 +5 4 2 6 1 3 + +6x6:d10 (hard) +N4JJDD +NKKDDH +AAALDH +EIILL5 +EEGFBB +CCGFMM + +A 30x +B 2- +C 3/ +D 11+ +E 8+ +F 9+ +G 1- +H 4- +I 6x +J 3- +K 1- +L 10+ +M 15x +N 1- + +6 4 5 2 3 1 +5 3 4 1 2 6 +1 5 6 3 4 2 +4 2 3 6 1 5 +3 1 2 5 6 4 +2 6 1 4 5 3 + +6x6:d12 (hard) +IIMMAA +I2CCBA +GDEEBF +GDHHLF +GHHHLL +KKK4JJ + +A 13+ +B 1- +C 5- +D 7+ +E 30x +F 3+ +G 11+ +H 18+ +I 60x +J 4- +K 9+ +L 15x +M 3+ + +5 3 1 2 6 4 +4 2 6 1 5 3 +3 1 5 6 4 2 +2 6 4 5 3 1 +6 4 2 3 1 5 +1 5 3 4 2 6 + +6x6:d10 (hard) +MMALLJ +BEAALJ +BEA1HH +GGGIHN +FDKI6N +FDKKCC + +A 20+ +B 1- +C 1- +D 3/ +E 1- +F 2- +G 30x +H 8+ +I 3- +J 12x +K 24x +L 10+ +M 3+ +N 3- + +2 1 3 4 5 6 +4 3 5 6 1 2 +5 4 6 1 2 3 +6 5 1 2 3 4 +3 2 4 5 6 1 +1 6 2 3 4 5 + +6x6:d10 (hard) +5JGAAB +EJGIIB +EL3IIB +KLCM3B +KKCMHD +NNFFHD + +A 5- +B 36x +C 6+ +D 20x +E 1- +F 2/ +G 2- +H 1- +I 20+ +J 3/ +K 11+ +L 6+ +M 2/ +N 3- + +5 2 4 1 6 3 +3 6 2 5 4 1 +4 1 3 6 5 2 +2 5 1 4 3 6 +6 3 5 2 1 4 +1 4 6 3 2 5 + +6x6:d11 (hard) +II4CCJ +1BHHJJ +KBHFLL +KKDFFE +G2DDFE +GGGGAA + +A 10+ +B 1- +C 3+ +D 120x +E 7+ +F 90x +G 16+ +H 9+ +I 2/ +J 13+ +K 24x +L 2- + +3 6 4 2 1 5 +1 4 2 6 5 3 +6 3 1 5 4 2 +4 1 5 3 2 6 +5 2 6 4 3 1 +2 5 3 1 6 4 + +7x7:d4 (easy) +1FFFFCC +JJJOOO7 +LII6DDP +LGGGEBP +LL2AEBM +HNQA2KM +HNQAKKM + +A 60x +B 15x +C 7+ +D 4+ +E 2- +F 504x +G 12+ +H 11+ +I 7+ +J 9+ +K 14+ +L 336x +M 6x +N 8+ +O 12+ +P 24x +Q 7+ + +1 3 6 7 4 2 5 +3 5 1 2 6 4 7 +7 2 5 6 3 1 4 +2 4 7 1 5 3 6 +4 6 2 3 7 5 1 +6 1 4 5 2 7 3 +5 7 3 4 1 6 2 + +7x7:d4 (easy) +MMQQCCD +MHHLLLD +SOHLEE2 +SOJNAII +5JJNARB +KKFPPRB +KKF4GGG + +A 10+ +B 1- +C 5- +D 3- +E 2- +F 12+ +G 10+ +H 32x +I 3- +J 7+ +K 15+ +L 12+ +M 10+ +N 9+ +O 30x +P 2/ +Q 1- +R 3+ +S 8+ + +3 1 6 5 2 7 4 +6 4 2 1 5 3 7 +1 6 4 3 7 5 2 +7 5 3 2 6 4 1 +5 3 1 7 4 2 6 +4 2 7 6 3 1 5 +2 7 5 4 1 6 3 + +7x7:d4 (easy) +2OIICAP +OO7QCAP +G5JQKDP +GGJQKDD +HMMMEEE +HFFBSLL +RRFBSNN + +A 21x +B 1- +C 3/ +D 84x +E 14x +F 10+ +G 7+ +H 2/ +I 1- +J 9+ +K 6+ +L 1- +M 120x +N 1- +O 90x +P 8+ +Q 28x +R 8+ +S 12x + +2 3 4 5 6 7 1 +5 6 7 1 2 3 4 +4 5 6 7 1 2 3 +1 2 3 4 5 6 7 +3 4 5 6 7 1 2 +6 7 1 2 3 4 5 +7 1 2 3 4 5 6 + +7x7:d4 (easy) +SSMMHFI +OGEEHFI +OGQEHFJ +ODQ4PPJ +BDLRAKK +BLLRACK +7TTNNCC + +A 5- +B 2- +C 10+ +D 4- +E 15+ +F 16+ +G 3- +H 14+ +I 20x +J 5- +K 56x +L 9+ +M 4+ +N 1- +O 15x +P 6+ +Q 6+ +R 12+ +S 8+ +T 24x + +2 6 1 3 4 7 5 +1 5 7 2 3 6 4 +5 2 4 6 7 3 1 +3 7 2 4 5 1 6 +6 3 5 7 1 4 2 +4 1 3 5 6 2 7 +7 4 6 1 2 5 3 + +7x7:d4 (easy) +QQ5LLLR +KKLLGGR +DKCC5MM +D2BSSAA +NJBFFFH +NJPPIFH +NEEEIOO + +A 7+ +B 6- +C 2/ +D 2- +E 11+ +F 288x +G 10x +H 21x +I 4+ +J 6+ +K 12+ +L 23+ +M 3+ +N 70x +O 1- +P 3- +Q 18x +R 7+ +S 2- + +3 6 5 2 4 7 1 +1 4 3 7 2 5 6 +4 7 6 3 5 1 2 +6 2 1 5 7 3 4 +5 1 7 4 6 2 3 +2 5 4 1 3 6 7 +7 3 2 6 1 4 5 + +7x7:d5 (easy) +LLRRD1C +SGIIDNC +SGMMNN3 +JOAAKHH +JO6PKQH +E2BPFQH +EBBBFFF + +A 12x +B 360x +C 6+ +D 2- +E 1- +F 19+ +G 1- +H 60x +I 8+ +J 21x +K 3/ +L 42x +M 3+ +N 17+ +O 3- +P 2- +Q 2- +R 6x +S 1- + +6 7 2 3 5 1 4 +4 5 7 1 3 6 2 +5 6 1 2 4 7 3 +7 1 3 4 6 2 5 +3 4 6 7 2 5 1 +1 2 4 5 7 3 6 +2 3 5 6 1 4 7 + +7x7:d6 (easy) +1QNSGKK +FQNSGKC +FJHBBRC +FJHLLRC +AODE3MM +AODEEPP +TTIIII6 + +A 3- +B 6+ +C 30x +D 4- +E 18+ +F 13+ +G 4- +H 12x +I 120x +J 11+ +K 13+ +L 5+ +M 2/ +N 3- +O 3- +P 5- +Q 3- +R 4- +S 2/ +T 6- + +1 2 4 3 6 5 7 +4 5 7 6 2 1 3 +3 4 6 5 1 7 2 +6 7 2 1 4 3 5 +5 6 1 7 3 2 4 +2 3 5 4 7 6 1 +7 1 3 2 5 4 6 + +7x7:d5 (easy) +MMEDDRR +MBEOJJ5 +MBBOGGG +IBHH3KG +IQQNNKG +ICFFAAL +ICCPPLL + +A 5+ +B 21+ +C 90x +D 4+ +E 2- +F 3/ +G 144x +H 4- +I 17+ +J 2/ +K 11+ +L 9+ +M 80x +N 5- +O 3- +P 35x +Q 4- +R 2- + +4 2 6 3 1 5 7 +2 7 4 1 6 3 5 +5 3 7 4 2 6 1 +6 4 1 5 3 7 2 +3 1 5 2 7 4 6 +7 5 2 6 4 1 3 +1 6 3 7 5 2 4 + +7x7:d5 (easy) +JJJGGSS +HHPP7II +FFPNBBC +RRENK6C +1EEKKLL +ADDMMLT +ADOOQQT + +A 15x +B 1- +C 3- +D 14x +E 140x +F 28x +G 4+ +H 2/ +I 5+ +J 13+ +K 14+ +L 16+ +M 10+ +N 2/ +O 12x +P 30x +Q 20x +R 3- +S 3- +T 4+ + +2 6 5 1 3 4 7 +6 3 2 5 7 1 4 +7 4 3 6 1 2 5 +4 1 7 3 5 6 2 +1 5 4 7 2 3 6 +5 2 1 4 6 7 3 +3 7 6 2 4 5 1 + +7x7:d4 (easy) +AR6OFFI +AROO5JI +GGLLHJJ +G6MMHQQ +NPPMMKK +NECCCDD +EEEBB6D + +A 21x +B 6x +C 35x +D 24x +E 19+ +F 6+ +G 210x +H 1- +I 28x +J 6+ +K 8+ +L 10x +M 18+ +N 1- +O 36x +P 2- +Q 5- +R 3- + +3 4 6 2 1 5 7 +7 1 3 6 5 2 4 +6 7 2 5 4 1 3 +5 6 1 4 3 7 2 +1 2 4 7 6 3 5 +2 3 5 1 7 4 6 +4 5 7 3 2 6 1 + +7x7:d4 (easy) +AANNCC6 +ADNFFHB +PDLF2HB +PLLLKHH +2MGGKHO +MMG4KEO +MJJIIEE + +A 12+ +B 8+ +C 2- +D 12x +E 10+ +F 126x +G 90x +H 420x +I 5+ +J 2- +K 210x +L 48x +M 147x +N 70x +O 9+ +P 6+ + +4 2 7 5 1 3 6 +6 4 2 7 3 5 1 +5 3 1 6 2 4 7 +1 6 4 2 5 7 3 +2 7 5 3 6 1 4 +3 1 6 4 7 2 5 +7 5 3 1 4 6 2 + +7x7:d5 (easy) +HHHIL3N +RR2ILDN +CKKQGDN +CKQQGMM +CKEEPJJ +3FFPPAA +OOBBAAA + +A 13+ +B 2/ +C 8x +D 2- +E 11+ +F 1- +G 2- +H 8+ +I 20x +J 4- +K 210x +L 8+ +M 4- +N 18+ +O 28x +P 30x +Q 9+ +R 3- + +5 2 1 4 7 3 6 +6 3 2 5 1 4 7 +4 1 7 3 6 2 5 +2 6 5 1 4 7 3 +1 5 4 7 3 6 2 +3 7 6 2 5 1 4 +7 4 3 6 2 5 1 + +7x7:d5 (easy) +QQGGMM2 +ABGGRRO +ABB6CCO +PPPNNCO +KPJLN3D +KEJLIID +KEHH1FF + +A 6- +B 14+ +C 8+ +D 13+ +E 5+ +F 4- +G 16+ +H 10x +I 9+ +J 11+ +K 11+ +L 3+ +M 1- +N 72x +O 10+ +P 20+ +Q 8+ +R 1- + +5 3 1 4 7 6 2 +1 6 4 7 3 2 5 +7 5 3 6 2 1 4 +4 2 7 3 6 5 1 +2 7 5 1 4 3 6 +3 1 6 2 5 4 7 +6 4 2 5 1 7 3 + +7x7:d5 (easy) +N3QHGFF +NNQHGMM +IIEEKKO +SIDDJJO +SIDAA4R +LPPPABR +LPCCBB4 + +A 126x +B 15x +C 1- +D 5+ +E 1- +F 3- +G 3- +H 4- +I 80x +J 2- +K 5- +L 7+ +M 4- +N 126x +O 2/ +P 15+ +Q 3- +R 8+ +S 6- + +6 3 7 1 4 2 5 +3 7 4 5 1 6 2 +4 1 5 6 2 7 3 +7 4 1 2 5 3 6 +1 5 2 3 6 4 7 +2 6 3 4 7 5 1 +5 2 6 7 3 1 4 + +7x7:d5 (easy) +BOSCCRR +BOSE3FM +NNIEEFM +2GIDDQQ +KGPPHHH +KJJAAA2 +KTT2LLL + +A 13+ +B 20x +C 2- +D 2- +E 12+ +F 12x +G 13+ +H 13+ +I 2- +J 1- +K 18x +L 14+ +M 10+ +N 3- +O 1- +P 5+ +Q 20x +R 8+ +S 1- +T 30x + +5 2 3 6 4 7 1 +4 1 2 5 3 6 7 +7 4 5 1 6 2 3 +2 6 7 3 1 4 5 +3 7 1 4 2 5 6 +6 3 4 7 5 1 2 +1 5 6 2 7 3 4 + +7x7:d4 (easy) +QQGHHCC +OGGK4AA +OOKKPA6 +RRDDPPP +ILLNNNP +IMMMBBE +IFFJJ6E + +A 20x +B 8+ +C 5+ +D 1- +E 2- +F 2/ +G 21x +H 2/ +I 14x +J 6+ +K 16+ +L 15x +M 72x +N 15+ +O 126x +P 168x +Q 10x +R 3- + +5 2 7 6 3 4 1 +6 3 1 7 4 5 2 +3 7 5 4 1 2 6 +4 1 6 5 2 3 7 +1 5 3 2 6 7 4 +2 6 4 3 7 1 5 +7 4 2 1 5 6 3 + +7x7:d6 (easy) +5GGHCED +JTTHCED +JOOLLLK +JFFPPPK +RMMAAAK +R7SBNAQ +IISBN7Q + +A 360x +B 4- +C 5- +D 2- +E 2/ +F 7+ +G 2/ +H 2- +I 7+ +J 12+ +K 21x +L 12+ +M 21x +N 6+ +O 3/ +P 42x +Q 3- +R 8+ +S 3- +T 6+ + +5 6 3 1 7 2 4 +7 1 5 3 2 4 6 +1 2 6 4 3 5 7 +4 5 2 7 6 1 3 +2 3 7 5 4 6 1 +6 7 4 2 1 3 5 +3 4 1 6 5 7 2 + +7x7:d5 (easy) +DDAAIFF +4GHIIIF +GGHJJMM +N2KKOOR +NCC2OLR +NQEBBLL +NQEPPL5 + +A 1- +B 5+ +C 8+ +D 1- +E 5- +F 14x +G 10+ +H 2- +I 42x +J 4- +K 1- +L 12+ +M 13+ +N 210x +O 17+ +P 18x +Q 7+ +R 24x + +3 4 5 6 2 7 1 +4 5 6 7 3 1 2 +2 3 4 5 1 6 7 +1 2 3 4 7 5 6 +6 7 1 2 5 3 4 +5 6 7 1 4 2 3 +7 1 2 3 6 4 5 + +7x7:d5 (easy) +4SSDDDQ +MHHFJDQ +MI4FJRE +IIOKKRE +CCOGKBP +NNAGBBP +NNALLL6 + +A 5- +B 14+ +C 1- +D 12+ +E 2- +F 1- +G 13+ +H 35x +I 42x +J 8+ +K 11+ +L 35x +M 30x +N 10+ +O 7+ +P 1- +Q 5- +R 2/ +S 15x + +4 5 3 2 6 1 7 +6 7 5 4 1 3 2 +5 6 4 3 7 2 1 +7 1 6 5 2 4 3 +2 3 1 7 4 6 5 +1 2 7 6 3 5 4 +3 4 2 1 5 7 6 + +7x7:d5 (easy) +BBLQAPP +RRLQAKI +HHGGOKI +HH7COOJ +1CCCFJJ +SMMMFNN +SDDEEN6 + +A 5- +B 2- +C 20+ +D 35x +E 3- +F 8+ +G 7+ +H 16+ +I 1- +J 10x +K 42x +L 4+ +M 12x +N 84x +O 12+ +P 3- +Q 10+ +R 15x +S 1- + +7 5 3 6 2 1 4 +5 3 1 4 7 6 2 +6 4 2 5 1 7 3 +4 2 7 3 6 5 1 +1 6 4 7 3 2 5 +3 1 6 2 5 4 7 +2 7 5 1 4 3 6 + +7x7:d6 (easy) +OOO5KQJ +M5BBKQJ +MLLBEE5 +ML3AAIH +NNPP1IH +1DCCGIH +DDFFGGG + +A 6+ +B 12+ +C 9+ +D 72x +E 7+ +F 2- +G 14+ +H 16+ +I 60x +J 5+ +K 3/ +L 7+ +M 12+ +N 35x +O 14+ +P 2- +Q 4- + +6 1 7 5 2 3 4 +3 5 4 2 6 7 1 +7 2 1 6 3 4 5 +2 4 3 1 5 6 7 +5 7 6 4 1 2 3 +1 3 2 7 4 5 6 +4 6 5 3 7 1 2 + +7x7:d5 (easy) +RRHNNQQ +ORHNLQE +OAFFLLE +BAGGDDE +BPGIIMJ +BPK1MMJ +PPKSSCC + +A 1- +B 48x +C 5- +D 1- +E 13+ +F 3+ +G 24x +H 30x +I 12+ +J 9+ +K 4- +L 11+ +M 8+ +N 42x +O 4+ +P 13+ +Q 24x +R 14+ +S 10+ + +7 3 5 6 1 2 4 +1 4 6 7 2 3 5 +3 6 1 2 4 5 7 +4 7 2 3 5 6 1 +6 2 4 5 7 1 3 +2 5 7 1 3 4 6 +5 1 3 4 6 7 2 + +7x7:d6 (easy) +PP3EECC +MGG1DDO +MMQBJDO +KKQBJLO +AKIBNL6 +AAIINLL +HHHINFF + +A 14+ +B 9+ +C 3+ +D 11+ +E 13+ +F 6- +G 11+ +H 9+ +I 140x +J 1- +K 5+ +L 252x +M 15+ +N 12+ +O 12+ +P 20x +Q 1- + +5 4 3 6 7 2 1 +7 6 5 1 2 4 3 +1 7 6 2 3 5 4 +2 1 7 3 4 6 5 +3 2 1 4 5 7 6 +6 5 4 7 1 3 2 +4 3 2 5 6 1 7 + +7x7:d4 (easy) +JJGGGHH +LLAAEOO +CMREEO4 +CMRKKSS +PMFKDII +PQFND6B +4QNNNBB + +A 28x +B 12+ +C 2- +D 7+ +E 18+ +F 1- +G 21x +H 9+ +I 6- +J 8+ +K 6+ +L 3- +M 15x +N 60x +O 6+ +P 1- +Q 3- +R 6+ +S 1- + +6 2 3 7 1 4 5 +3 6 7 4 5 1 2 +5 1 2 6 7 3 4 +7 3 4 1 2 5 6 +2 5 6 3 4 7 1 +1 4 5 2 3 6 7 +4 7 1 5 6 2 3 + +7x7:d5 (easy) +PPBB6JG +APBEEJG +AA3CCJJ +A6MFQQN +5OMFFQN +KODFIIN +KODHHLL + +A 147x +B 60x +C 10x +D 5- +E 3- +F 14+ +G 1- +H 10+ +I 3+ +J 168x +K 24x +L 2/ +M 9+ +N 11+ +O 12+ +P 5+ +Q 12+ + +2 1 4 3 6 7 5 +3 2 5 4 7 1 6 +1 7 3 2 5 6 4 +7 6 2 1 4 5 3 +5 4 7 6 2 3 1 +4 3 6 5 1 2 7 +6 5 1 7 3 4 2 + +7x7:d4 (easy) +RRFFHCQ +MM4JHCQ +B2JJPCK +BBBEPAK +LODEAAK +LODGIN5 +SSDGINN + +A 13+ +B 19+ +C 15x +D 21x +E 12+ +F 2- +G 1- +H 7+ +I 20x +J 13+ +K 12x +L 5+ +M 35x +N 252x +O 2/ +P 1- +Q 14x +R 2- +S 2- + +3 5 2 4 6 1 7 +5 7 4 6 1 3 2 +7 2 6 1 3 5 4 +6 1 5 7 2 4 3 +4 6 3 5 7 2 1 +1 3 7 2 4 6 5 +2 4 1 3 5 7 6 + +7x7:d5 (easy) +MM1HAKJ +OSPHAKJ +OSP1AQQ +IIGG5QD +5LLNNND +F3BNECR +FBBBECR + +A 14+ +B 420x +C 12x +D 3+ +E 3+ +F 6- +G 6x +H 3- +I 2- +J 18x +K 7+ +L 28x +M 2- +N 16+ +O 2/ +P 10x +Q 20+ +R 9+ +S 4- + +2 4 1 7 3 5 6 +6 1 5 4 7 2 3 +3 5 2 1 4 6 7 +4 6 3 2 5 7 1 +5 7 4 3 6 1 2 +1 3 7 6 2 4 5 +7 2 6 5 1 3 4 + +7x7:d7 (easy) +AAQQGGG +ANKK6EE +NNDDPPP +RLMIIH6 +RLMSOHH +JFFSOCC +JJ3BBCC + +A 8x +B 3- +C 15+ +D 1- +E 4- +F 3- +G 14+ +H 12x +I 7+ +J 126x +K 3+ +L 7+ +M 8x +N 10+ +O 2- +P 10+ +Q 30x +R 2- +S 2- + +1 2 5 6 3 4 7 +4 5 1 2 6 7 3 +2 3 6 7 4 5 1 +7 1 4 5 2 3 6 +5 6 2 3 7 1 4 +3 4 7 1 5 6 2 +6 7 3 4 1 2 5 + +7x7:d6 (easy) +1CMRROH +ACMMBOH +AJEEBOH +AJJKIIH +G4JKINN +GLLPPNQ +FFFFDDQ + +A 168x +B 3- +C 3- +D 42x +E 6+ +F 60x +G 10x +H 630x +I 36x +J 42x +K 28x +L 1- +M 9+ +N 7+ +O 84x +P 6x +Q 6+ +R 1- + +1 3 2 6 5 4 7 +4 6 5 2 1 7 3 +7 2 1 5 4 3 6 +6 1 7 4 3 2 5 +2 4 3 7 6 5 1 +5 7 6 3 2 1 4 +3 5 4 1 7 6 2 + +7x7:d4 (easy) +HHQQ1GG +BHLLIEE +BB4IINN +OBFFFN5 +OAA5DKK +OAPPDDK +CCMMJJJ + +A 8+ +B 20+ +C 1- +D 20x +E 20x +F 84x +G 1- +H 14+ +I 14x +J 12x +K 294x +L 18x +M 21x +N 11+ +O 6x +P 3/ +Q 3- + +6 7 5 2 1 4 3 +7 1 6 3 2 5 4 +5 6 4 1 7 3 2 +1 2 7 4 3 6 5 +2 3 1 5 4 7 6 +3 4 2 6 5 1 7 +4 5 3 7 6 2 1 + +7x7:d6 (easy) +OGGGGKC +OO7AKKC +BEEA6MM +BBEANQM +B3RRNQL +IJJHHFL +IDDPPFF + +A 9+ +B 120x +C 1- +D 3- +E 9+ +F 60x +G 12+ +H 2- +I 6x +J 21x +K 6+ +L 3- +M 17+ +N 21x +O 168x +P 2- +Q 3- +R 8+ + +7 5 1 4 2 3 6 +6 4 7 3 1 2 5 +4 2 5 1 6 7 3 +1 6 2 5 3 4 7 +5 3 6 2 7 1 4 +2 7 3 6 4 5 1 +3 1 4 7 5 6 2 + +7x7:d4 (easy) +IMKKGRR +IMHNGJJ +MMHN4QL +F1BBBQL +FEEOO7L +TT6SSDC +PPAAADC + +A 15+ +B 210x +C 8+ +D 4+ +E 2/ +F 4+ +G 1- +H 14x +I 11+ +J 2- +K 1- +L 7+ +M 19+ +N 4+ +O 1- +P 9+ +Q 4- +R 35x +S 6- +T 6+ + +6 4 1 2 3 5 7 +5 3 7 1 2 4 6 +7 5 2 3 4 6 1 +3 1 5 6 7 2 4 +1 6 3 4 5 7 2 +4 2 6 7 1 3 5 +2 7 4 5 6 1 3 + +7x7:d5 (easy) +FPPP3BB +F3LLCQQ +MSDLCCC +MSD2GOO +EETGGKO +IITNNKH +RRJJAAH + +A 2- +B 3+ +C 14x +D 6+ +E 8+ +F 2- +G 90x +H 2/ +I 11+ +J 1- +K 2/ +L 120x +M 2/ +N 3- +O 12+ +P 18+ +Q 1- +R 1- +S 3- +T 14x + +4 5 6 7 3 1 2 +2 3 4 5 1 6 7 +3 4 5 6 2 7 1 +6 7 1 2 5 3 4 +7 1 2 3 6 4 5 +5 6 7 1 4 2 3 +1 2 3 4 7 5 6 + +7x7:d8 (medium) +MMMDDNR +MCC3DNR +FLLHKII +FO3HKPA +BOOKKPA +BGGQEJJ +BBGQEJ5 + +A 7+ +B 36x +C 1- +D 8+ +E 2- +F 2- +G 10+ +H 9+ +I 4+ +J 105x +K 20+ +L 1- +M 420x +N 5- +O 14x +P 24x +Q 24x +R 2/ + +5 3 4 1 6 7 2 +7 5 6 3 1 2 4 +6 4 5 2 7 1 3 +4 2 3 7 5 6 1 +2 7 1 5 3 4 6 +3 1 2 6 4 5 7 +1 6 7 4 2 3 5 + +7x7:d9 (medium) +HHMJBB1 +PPMJRRF +PCGLLEF +3CGNEEQ +I3SNAAQ +IIS1OOD +IKKK5OD + +A 5- +B 6+ +C 5- +D 2- +E 36x +F 4- +G 2- +H 30x +I 19+ +J 42x +K 10+ +L 2/ +M 1- +N 1- +O 15+ +P 20x +Q 1- +R 2- +S 3- + +6 5 3 7 2 4 1 +5 4 2 6 1 3 7 +1 7 5 2 4 6 3 +3 2 7 4 6 1 5 +4 3 1 5 7 2 6 +7 6 4 1 3 5 2 +2 1 6 3 5 7 4 + +7x7:d9 (medium) +DDDALHR +DDQALHR +KQQASEF +KNOOSEF +JNICMMF +JIICPFF +GGBBPP7 + +A 24x +B 3- +C 11+ +D 5040x +E 3- +F 21+ +G 8+ +H 8+ +I 10+ +J 1- +K 5+ +L 1- +M 3- +N 1- +O 10+ +P 14+ +Q 18+ +R 3+ +S 6+ + +7 5 6 3 4 1 2 +6 4 5 2 3 7 1 +1 6 7 4 5 2 3 +4 2 3 7 1 5 6 +3 1 2 6 7 4 5 +2 7 1 5 6 3 4 +5 3 4 1 2 6 7 + +7x7:d9 (medium) +PPEQQQF +UEEEBBF +UNNDDBT +KII3MMT +KAAGCCO +SSAGJLO +HHRRJL2 + +A 13+ +B 11+ +C 1- +D 6+ +E 168x +F 2- +G 4- +H 11+ +I 2- +J 5- +K 4- +L 12+ +M 1- +N 9+ +O 3- +P 1- +Q 8+ +R 7+ +S 7+ +T 5- +U 1- + +7 6 4 5 2 1 3 +2 1 6 7 4 3 5 +3 2 7 1 5 4 6 +5 4 2 3 7 6 1 +1 7 5 6 3 2 4 +4 3 1 2 6 5 7 +6 5 3 4 1 7 2 + +7x7:d8 (medium) +IIINNNA +OO3JJAA +GHHDSMM +GPPDS7B +CPLDEEB +CCLLFEE +RRKKFQQ + +A 12+ +B 7+ +C 9+ +D 15+ +E 60x +F 28x +G 1- +H 14x +I 12+ +J 3/ +K 8+ +L 12+ +M 6+ +N 15x +O 35x +P 42x +Q 4- +R 2- +S 1- + +6 4 2 1 5 3 7 +7 5 3 2 6 4 1 +4 2 7 6 3 1 5 +3 1 6 5 2 7 4 +2 7 5 4 1 6 3 +1 6 4 3 7 5 2 +5 3 1 7 4 2 6 + +7x7:d9 (medium) +RDDF4II +RLLFQQQ +MSSFOOH +MCEEJJH +KCTTBPP +KCNABPP +5NNAGGG + +A 3- +B 2- +C 105x +D 1- +E 2- +F 14+ +G 126x +H 5+ +I 4- +J 6+ +K 2- +L 5+ +M 8+ +N 12x +O 4- +P 19+ +Q 12+ +R 2/ +S 9+ +T 4+ + +3 6 7 2 4 1 5 +6 2 3 5 7 4 1 +1 4 5 7 2 6 3 +7 3 4 6 1 5 2 +4 7 1 3 5 2 6 +2 5 6 1 3 7 4 +5 1 2 4 6 3 7 + +7x7:d9 (medium) +1FFQQNN +PLL2CGN +PAAMCGN +PI1MHHJ +BIKKDDJ +BIKKSER +B3OOSER + +A 3- +B 15+ +C 7+ +D 3- +E 8+ +F 3/ +G 6- +H 3/ +I 12+ +J 4- +K 16+ +L 3- +M 8+ +N 20+ +O 1- +P 60x +Q 2- +R 2- +S 7+ + +1 2 6 5 7 4 3 +5 6 3 2 4 1 7 +4 5 2 1 3 7 6 +3 4 1 7 2 6 5 +6 7 4 3 5 2 1 +7 1 5 4 6 3 2 +2 3 7 6 1 5 4 + +7x7:d9 (medium) +DDII2AF +D2EQQAF +DDEEM4F +LLL2MKN +LLPBBKN +OCPGJJ7 +OCGGGHH + +A 4- +B 2- +C 3- +D 27+ +E 10x +F 10+ +G 17+ +H 1- +I 6- +J 11+ +K 6+ +L 18+ +M 7+ +N 8+ +O 10x +P 2- +Q 2- + +6 5 1 7 2 3 4 +3 2 5 4 6 7 1 +7 6 2 1 3 4 5 +1 7 3 2 4 5 6 +4 3 6 5 7 1 2 +2 1 4 3 5 6 7 +5 4 7 6 1 2 3 + +7x7:d8 (medium) +LLDDD2M +PP6DCCM +PEI5NCM +EEIINJJ +OEIAHBJ +OKKAHBB +OGGFFFF + +A 1- +B 60x +C 12+ +D 42x +E 14+ +F 168x +G 7+ +H 1- +I 12+ +J 14+ +K 2- +L 1- +M 12+ +N 2/ +O 35x +P 72x + +4 5 7 3 1 2 6 +3 4 6 2 7 1 5 +6 7 2 5 3 4 1 +2 3 5 1 6 7 4 +1 2 4 7 5 6 3 +7 1 3 6 4 5 2 +5 6 1 4 2 3 7 + +7x7:d8 (medium) +EIQHH3P +EIQQNDP +B4CKNDD +BBCKKOA +LLJJ7OA +LMMSSOF +RRGGGFF + +A 2/ +B 14+ +C 2- +D 13+ +E 4- +F 8+ +G 16+ +H 3- +I 2/ +J 1- +K 56x +L 15x +M 1- +N 1- +O 12+ +P 11+ +Q 10x +R 3- +S 10x + +2 6 5 4 1 3 7 +6 3 2 1 5 7 4 +7 4 3 2 6 1 5 +5 2 1 7 4 6 3 +1 5 4 3 7 2 6 +3 7 6 5 2 4 1 +4 1 7 6 3 5 2 + +7x7:d10 (medium) +PPPCCJA +DNNNJJA +DQMLJ1A +EQMLIF2 +EOOIIFB +EOO2HHB +2KKGGGB + +A 11+ +B 15+ +C 5- +D 9+ +E 10+ +F 4- +G 10+ +H 2- +I 11+ +J 13+ +K 3- +L 12+ +M 5- +N 126x +O 90x +P 14+ +Q 2/ + +7 5 2 1 6 4 3 +5 3 7 6 4 2 1 +4 2 6 5 3 1 7 +6 4 1 7 5 3 2 +3 1 5 4 2 7 6 +1 6 3 2 7 5 4 +2 7 4 3 1 6 5 + +7x7:d8 (medium) +PENNAAQ +PEFFAAQ +I3GFOHL +IIGGOHL +3BBBBLL +MMMCDJJ +MKKCDJ6 + +A 336x +B 17+ +C 5- +D 2- +E 4- +F 11+ +G 90x +H 5+ +I 18+ +J 60x +K 11+ +L 15+ +M 60x +N 4+ +O 8+ +P 3- +Q 10+ + +2 5 1 3 4 6 7 +5 1 4 6 7 2 3 +7 3 6 1 2 4 5 +4 7 3 5 6 1 2 +3 6 2 4 5 7 1 +6 2 5 7 1 3 4 +1 4 7 2 3 5 6 + +7x7:d10 (medium) +N2HEEUU +NCHREPM +QCGRBPM +QLGFBBS +QL4FDDS +IIIAAJK +TTOO4JK + +A 3- +B 13+ +C 4+ +D 4- +E 21x +F 5- +G 1- +H 1- +I 8+ +J 7+ +K 14x +L 2- +M 10+ +N 1- +O 2- +P 2- +Q 40x +R 2/ +S 4+ +T 9+ +U 1- + +6 2 1 3 7 4 5 +7 3 2 4 1 5 6 +5 1 7 2 6 3 4 +4 7 6 1 5 2 3 +2 5 4 6 3 7 1 +1 4 3 5 2 6 7 +3 6 5 7 4 1 2 + +7x7:d8 (medium) +CCJJJBB +CCLFFHE +AALFFHE +QALL7KK +QM3PGGK +OMNPDDI +OONNDII + +A 60x +B 15x +C 840x +D 13+ +E 1- +F 42x +G 11+ +H 3+ +I 12+ +J 8x +K 42x +L 840x +M 4+ +N 10+ +O 12+ +P 2- +Q 1- + +7 6 1 2 4 3 5 +5 4 6 7 2 1 3 +6 5 7 1 3 2 4 +3 2 4 5 7 6 1 +2 1 3 4 6 5 7 +4 3 5 6 1 7 2 +1 7 2 3 5 4 6 + +7x7:d8 (medium) +DB5MMPP +DBBM5FK +DHEEEFK +HHEGGQA +NHH5JQA +NIIOJ7A +5CCOOLL + +A 18x +B 12x +C 1- +D 11+ +E 15+ +F 2- +G 1- +H 22+ +I 9+ +J 5+ +K 12+ +L 12x +M 36x +N 8+ +O 10+ +P 3- +Q 2- + +7 6 5 3 2 1 4 +3 2 1 6 5 4 7 +1 7 6 4 3 2 5 +4 3 2 7 6 5 1 +2 1 7 5 4 3 6 +6 5 4 2 1 7 3 +5 4 3 1 7 6 2 + +7x7:d8 (medium) +AA3RRMM +AAARIIJ +NNOOSGJ +L4PSSGD +LCPEEQD +FCKK1QQ +FBBBHHH + +A 80x +B 14+ +C 30x +D 1- +E 9+ +F 3/ +G 3- +H 8+ +I 3- +J 1- +K 21x +L 8+ +M 8+ +N 4- +O 3- +P 1- +Q 60x +R 120x +S 9+ + +5 2 3 6 4 7 1 +4 1 2 5 3 6 7 +3 7 1 4 2 5 6 +7 4 5 1 6 2 3 +1 5 6 2 7 3 4 +2 6 7 3 1 4 5 +6 3 4 7 5 1 2 + +7x7:d8 (medium) +4JTOIII +BJTOIGA +BJNQQGA +CCN1MSS +PLLHMEF +PKLHEEF +PK6DDRR + +A 8+ +B 30x +C 1- +D 12+ +E 126x +F 6x +G 1- +H 11+ +I 17+ +J 210x +K 4+ +L 11+ +M 2- +N 4- +O 1- +P 10+ +Q 2/ +R 5+ +S 7+ +T 3+ + +4 5 1 2 7 3 6 +5 6 2 3 1 4 7 +6 7 3 4 2 5 1 +3 4 7 1 6 2 5 +1 2 5 6 4 7 3 +7 1 4 5 3 6 2 +2 3 6 7 5 1 4 + +7x7:d9 (medium) +MMHQ1RR +SSHQKKK +GGEEDAA +GEE7DIN +LLCCOIN +P5FFOJT +PPBBOJT + +A 5+ +B 4- +C 4- +D 9+ +E 40x +F 5+ +G 14+ +H 9+ +I 3/ +J 4- +K 10+ +L 3- +M 11+ +N 3- +O 14+ +P 10+ +Q 3/ +R 10x +S 21x +T 11+ + +4 7 3 6 1 5 2 +7 3 6 2 4 1 5 +3 6 2 5 7 4 1 +5 1 4 7 2 6 3 +1 4 7 3 5 2 6 +2 5 1 4 6 3 7 +6 2 5 1 3 7 4 + +7x7:d8 (medium) +PPPHH3Q +IIKKHGQ +SSCKGGG +MFC6ARR +MFJJALL +6OOBBDD +EEOB1NN + +A 12x +B 15+ +C 3- +D 1- +E 9+ +F 1- +G 20+ +H 9+ +I 2- +J 6+ +K 13+ +L 1- +M 1- +N 20x +O 13+ +P 16+ +Q 10x +R 8+ +S 7+ + +7 5 4 1 6 3 2 +3 1 7 4 2 6 5 +1 6 5 2 7 4 3 +5 3 2 6 4 1 7 +4 2 1 5 3 7 6 +6 4 3 7 5 2 1 +2 7 6 3 1 5 4 + +7x7:d8 (medium) +LLLQQGG +LJSSOGA +3JMMOOA +F7KKONN +FPPDDEB +CPIIIEB +CRRHHBB + +A 1- +B 14+ +C 2- +D 28x +E 3/ +F 1- +G 21x +H 3- +I 11+ +J 1- +K 10+ +L 240x +M 5- +N 10x +O 21+ +P 20x +Q 3- +R 18x +S 5- + +6 5 2 4 1 3 7 +4 3 7 2 6 1 5 +3 2 6 1 5 7 4 +1 7 4 6 3 5 2 +2 1 5 7 4 6 3 +5 4 1 3 7 2 6 +7 6 3 5 2 4 1 + +7x7:d10 (medium) +AACBB5N +JCCGGKN +JCCIKKH +J2IIHHH +6EEIFFH +LEOD2FF +LLODMMM + +A 9+ +B 4+ +C 20+ +D 1- +E 11+ +F 18+ +G 2/ +H 210x +I 18+ +J 60x +K 13+ +L 13+ +M 12x +N 42x +O 6x + +2 7 4 1 3 5 6 +3 1 5 2 4 6 7 +5 3 7 4 6 1 2 +4 2 6 3 5 7 1 +6 4 1 5 7 2 3 +1 6 3 7 2 4 5 +7 5 2 6 1 3 4 + +7x7:d8 (medium) +PPJQLLC +7PJQQCC +HHKMMTF +IIKKOTF +RRKAOBS +GDDABBS +GG2EENN + +A 4- +B 56x +C 12x +D 4- +E 2- +F 4- +G 13+ +H 2- +I 8x +J 11+ +K 504x +L 9+ +M 6+ +N 2- +O 4- +P 6+ +Q 15+ +R 7+ +S 12x +T 9+ + +1 3 6 5 7 2 4 +7 2 5 4 6 1 3 +5 7 3 2 4 6 1 +2 4 7 6 1 3 5 +6 1 4 3 5 7 2 +3 5 1 7 2 4 6 +4 6 2 1 3 5 7 + +7x7:d8 (medium) +KK7BOOO +KKBBCC6 +DQQBCGL +DQI3JGL +EEIJJNN +PEHFMRR +PPHFMAA + +A 1- +B 40x +C 10+ +D 5- +E 36x +F 13+ +G 10+ +H 1- +I 30x +J 13+ +K 15+ +L 35x +M 8+ +N 6- +O 9+ +P 100x +Q 12+ +R 1- + +3 4 7 5 6 1 2 +7 1 4 2 3 5 6 +6 7 3 1 2 4 5 +1 2 5 3 4 6 7 +2 3 6 4 5 7 1 +5 6 2 7 1 3 4 +4 5 1 6 7 2 3 + +7x7:d8 (medium) +MPP6DAA +MPEEDNR +3GGGKNR +FJJGKKL +FHJSSLL +QHOOCCB +QHIICBB + +A 10x +B 42x +C 120x +D 1- +E 2- +F 2/ +G 19+ +H 14+ +I 2- +J 140x +K 6x +L 12+ +M 6- +N 3- +O 4+ +P 9+ +Q 30x +R 11+ +S 5+ + +1 3 4 6 7 2 5 +7 2 3 5 6 1 4 +3 5 6 1 2 4 7 +2 4 5 7 1 3 6 +4 6 7 2 3 5 1 +5 7 1 3 4 6 2 +6 1 2 4 5 7 3 + +7x7:d8 (medium) +QQGGIMM +C3RRIDM +CAAAADD +CNEHH5L +NNEEHLL +JJFOOBB +PPFF6KK + +A 18+ +B 4- +C 14x +D 126x +E 14+ +F 13+ +G 2- +H 9+ +I 35x +J 1- +K 4- +L 8x +M 48x +N 14+ +O 1- +P 7+ +Q 9+ +R 5- + +4 5 3 1 7 2 6 +2 3 1 6 5 7 4 +1 2 7 5 4 6 3 +7 1 6 4 3 5 2 +6 7 5 3 2 4 1 +5 6 4 2 1 3 7 +3 4 2 7 6 1 5 + +7x7:d9 (medium) +OOONNKK +EEAANII +EELLFF5 +QGGLL5C +QMG5PCC +MMHJPDD +4HHJBBD + +A 14x +B 8+ +C 8x +D 14+ +E 18x +F 1- +G 17+ +H 13+ +I 1- +J 7+ +K 1- +L 13+ +M 12+ +N 15+ +O 35x +P 1- +Q 3/ + +7 5 1 6 4 3 2 +1 6 2 7 5 4 3 +3 1 4 2 7 6 5 +2 7 3 1 6 5 4 +6 4 7 5 3 2 1 +5 3 6 4 2 1 7 +4 2 5 3 1 7 6 + +7x7:d8 (medium) +KKDC4BB +LDDCFF1 +LPD1OSA +PPMMOSA +GRJMEQQ +GRJMEHH +NNJII1H + +A 1- +B 1- +C 2/ +D 19+ +E 8+ +F 5- +G 2- +H 16+ +I 1- +J 12x +K 3+ +L 2- +M 17+ +N 1- +O 1- +P 17+ +Q 42x +R 5- +S 7+ + +2 1 7 3 4 6 5 +5 4 3 6 7 2 1 +7 6 5 1 2 4 3 +6 5 4 7 1 3 2 +3 2 1 4 5 7 6 +1 7 6 2 3 5 4 +4 3 2 5 6 1 7 + +7x7:d9 (medium) +JE1GMSS +JETGMOO +2DTAKOB +PDDAKIB +PLLHHII +NNHH1CC +RRFFQQC + +A 3- +B 6+ +C 17+ +D 40x +E 5- +F 3- +G 12+ +H 18+ +I 7+ +J 2- +K 4- +L 9+ +M 10+ +N 4- +O 48x +P 2- +Q 3- +R 5+ +S 9+ +T 10+ + +3 6 1 5 4 7 2 +5 1 3 7 6 2 4 +2 5 7 4 3 6 1 +6 2 4 1 7 3 5 +4 7 2 6 5 1 3 +7 3 5 2 1 4 6 +1 4 6 3 2 5 7 + +7x7:d9 (medium) +RRAA4FF +JJ2MMFG +SEEKOOG +SITKOCP +BITCCCP +BBLLHDD +NNUUHQQ + +A 2- +B 10+ +C 13+ +D 6+ +E 1- +F 10+ +G 1- +H 8+ +I 1- +J 6- +K 4- +L 2- +M 1- +N 1- +O 12+ +P 12x +Q 8+ +R 1- +S 3- +T 1- +U 12+ + +6 7 1 3 4 2 5 +7 1 2 4 5 3 6 +1 2 3 5 6 4 7 +4 5 6 1 2 7 3 +5 6 7 2 3 1 4 +2 3 4 6 7 5 1 +3 4 5 7 1 6 2 + +7x7:d9 (medium) +PPBBRRN +PBBKKJN +DDEE4JQ +AEEFIJQ +ACHFIIM +ACHSSLM +GG2OOLM + +A 11+ +B 13+ +C 3/ +D 9+ +E 216x +F 5- +G 8+ +H 4- +I 11+ +J 8+ +K 1- +L 1- +M 12+ +N 3- +O 1- +P 105x +Q 10x +R 3/ +S 11+ + +7 5 4 1 2 6 3 +3 1 7 4 5 2 6 +2 7 6 3 4 1 5 +6 4 3 7 1 5 2 +1 6 5 2 3 7 4 +4 2 1 5 6 3 7 +5 3 2 6 7 4 1 + +7x7:d8 (medium) +ELLNNRA +EEGGGRA +EJJ5CCO +JJ1HHCO +JIIIBBD +PFFFMMD +PKKKMQQ + +A 8+ +B 1- +C 10+ +D 2- +E 56x +F 16+ +G 13+ +H 4- +I 10x +J 2016x +K 20x +L 9+ +M 10+ +N 7+ +O 15x +P 8+ +Q 3/ +R 20x + +1 6 3 2 5 4 7 +2 7 4 3 6 5 1 +4 2 6 5 1 7 3 +6 4 1 7 3 2 5 +7 5 2 1 4 3 6 +5 3 7 6 2 1 4 +3 1 5 4 7 6 2 + +7x7:d8 (medium) +II1CCCM +R4ESSPM +ROEAAPP +DOOF2BN +DGJFLBN +GGJKL2N +HHHKQQQ + +A 4- +B 6- +C 14+ +D 1- +E 1- +F 1- +G 12+ +H 42x +I 3/ +J 2- +K 7+ +L 2- +M 12+ +N 24x +O 210x +P 30x +Q 120x +R 1- +S 3/ + +6 2 1 7 4 3 5 +1 4 3 2 6 5 7 +2 5 4 3 7 6 1 +4 7 6 5 2 1 3 +3 6 5 4 1 7 2 +5 1 7 6 3 2 4 +7 3 2 1 5 4 6 + +7x7:d8 (medium) +GGIIIK2 +RRILKKK +NFFL5JJ +NBBCOOA +BBPCOAA +D7PMHQE +DD5MHQE + +A 90x +B 120x +C 3+ +D 12+ +E 3- +F 5- +G 5+ +H 3/ +I 19+ +J 28x +K 17+ +L 9+ +M 11+ +N 5- +O 9+ +P 14x +Q 6+ +R 3- + +4 1 3 5 7 6 2 +5 2 4 6 1 7 3 +2 6 1 3 5 4 7 +7 4 6 1 3 2 5 +1 5 7 2 4 3 6 +3 7 2 4 6 5 1 +6 3 5 7 2 1 4 + +7x7:d11 (hard) +OEMQQLP +OEMKKLP +B7CKGJJ +BBCFGG4 +AACFH4I +AD2FHHI +DDDNNNI + +A 18x +B 14+ +C 14+ +D 96x +E 1- +F 13+ +G 7+ +H 35x +I 105x +J 3- +K 12+ +L 1- +M 8+ +N 12+ +O 9+ +P 3+ +Q 18x + +4 2 7 3 6 5 1 +5 3 1 4 7 6 2 +2 7 5 1 4 3 6 +7 5 3 6 2 1 4 +3 1 6 2 5 4 7 +6 4 2 5 1 7 3 +1 6 4 7 3 2 5 + +7x7:d13 (hard) +QQHMEEE +CCHMDDL +BKRRDLL +BKIIIIL +1KJJAAA +N1FOOAP +NFFOGGP + +A 17+ +B 1- +C 2- +D 10x +E 8+ +F 12+ +G 6- +H 2- +I 84x +J 1- +K 11+ +L 432x +M 2- +N 9+ +O 14+ +P 3- +Q 14x +R 6- + +2 7 5 6 3 4 1 +7 5 3 4 1 2 6 +4 2 7 1 5 6 3 +5 3 1 2 6 7 4 +1 6 4 5 2 3 7 +3 1 6 7 4 5 2 +6 4 2 3 7 1 5 + +7x7:d11 (hard) +BRRNNGD +BJNNGGD +4JCCGPH +L6IIKPH +LAAAKPF +SS5MQEF +OOOMQEF + +A 14+ +B 7+ +C 4- +D 2/ +E 11+ +F 90x +G 18+ +H 8+ +I 4- +J 7+ +K 3- +L 3- +M 3- +N 15+ +O 8+ +P 12x +Q 4- +R 28x +S 8+ + +6 7 4 1 5 3 2 +1 2 6 3 7 5 4 +4 5 2 6 3 1 7 +5 6 3 7 4 2 1 +2 3 7 4 1 6 5 +7 1 5 2 6 4 3 +3 4 1 5 2 7 6 + +7x7:d13 (hard) +DLBPEER +DLBPGGR +O6BCCQI +ONNCAQI +HHMCA2I +FKMC4JJ +FKKSSJJ + +A 4- +B 13+ +C 630x +D 13+ +E 1- +F 6+ +G 6- +H 5+ +I 14+ +J 15+ +K 105x +L 1- +M 8+ +N 5+ +O 8+ +P 1- +Q 10+ +R 7+ +S 3/ + +7 3 6 5 2 1 4 +6 2 5 4 1 7 3 +3 6 2 1 5 4 7 +5 1 4 3 7 6 2 +1 4 7 6 3 2 5 +2 5 1 7 4 3 6 +4 7 3 2 6 5 1 + +7x7:d11 (hard) +M6OOFFJ +MRBOLLJ +MRBBKC5 +MGGKKCN +EHHAAAN +EHHIQQN +PP7IIDD + +A 30x +B 42x +C 1- +D 1- +E 1- +F 5+ +G 3- +H 140x +I 11+ +J 10+ +K 6+ +L 3- +M 60x +N 12x +O 10+ +P 1- +Q 2/ +R 2/ + +5 6 3 2 4 1 7 +1 2 6 5 7 4 3 +3 4 1 7 2 6 5 +4 5 2 1 3 7 6 +6 7 4 3 5 2 1 +7 1 5 4 6 3 2 +2 3 7 6 1 5 4 + +7x7:d11 (hard) +GG7KABB +2LLKAAB +DDRRRPF +QQJJPPF +QMMEEOC +QISSNOC +QIHHNO7 + +A 16+ +B 8+ +C 10+ +D 8+ +E 1- +F 1- +G 11+ +H 3- +I 9+ +J 4- +K 5+ +L 1- +M 5+ +N 1- +O 13+ +P 48x +Q 21+ +R 60x +S 3- + +5 6 7 4 3 2 1 +2 3 4 1 7 6 5 +7 1 2 6 5 4 3 +6 7 1 5 4 3 2 +1 2 3 7 6 5 4 +3 4 5 2 1 7 6 +4 5 6 3 2 1 7 + +7x7:d12 (hard) +RRLSSJJ +GGLLB3J +PDLLBII +PDDKKN3 +EMMFFNN +ECMOAHH +CCOOAQQ + +A 13+ +B 2/ +C 10+ +D 168x +E 28x +F 10+ +G 7+ +H 4- +I 3- +J 17+ +K 4- +L 210x +M 9+ +N 14+ +O 10+ +P 6x +Q 12x +R 2/ +S 3- + +6 3 1 2 5 4 7 +5 2 7 1 4 3 6 +3 7 5 6 2 1 4 +2 6 4 5 1 7 3 +4 1 6 7 3 2 5 +7 4 2 3 6 5 1 +1 5 3 4 7 6 2 + +7x7:d12 (hard) +GDD7FFI +GPHSNJI +RPHSNJL +RQEANKL +OQEA7KL +OBEATTM +BBEECCM + +A 14+ +B 90x +C 1- +D 24x +E 280x +F 3+ +G 2- +H 2- +I 8+ +J 8+ +K 2- +L 14+ +M 6+ +N 15+ +O 12x +P 5- +Q 9+ +R 1- +S 2/ +T 8+ + +5 4 6 7 2 1 3 +7 6 1 2 4 3 5 +2 1 3 4 6 5 7 +1 7 2 3 5 4 6 +3 2 4 5 7 6 1 +4 3 5 6 1 7 2 +6 5 7 1 3 2 4 + +7x7:d12 (hard) +6QQHFRR +OQHHFEA +O4CCFEA +MKKGDE2 +MKBGDDD +IJBSPPL +IJBSNNL + +A 10+ +B 16+ +C 7+ +D 120x +E 35x +F 16+ +G 1- +H 11+ +I 6- +J 1- +K 12+ +L 1- +M 1- +N 2/ +O 7+ +P 4+ +Q 21x +R 2- +S 1- + +6 1 3 5 7 2 4 +5 7 2 4 6 1 3 +2 4 6 1 3 5 7 +4 6 1 3 5 7 2 +3 5 7 2 4 6 1 +7 2 4 6 1 3 5 +1 3 5 7 2 4 6 + +7x7:d13 (hard) +PHHMMRR +P3CCMII +EKKNN7I +EELLNDD +EQQBGGD +FFJBGGO +AAJB6GO + +A 2- +B 10+ +C 12+ +D 35x +E 17+ +F 2- +G 16+ +H 5- +I 36x +J 6- +K 2- +L 2- +M 9+ +N 9+ +O 1- +P 4- +Q 2- +R 2/ + +5 7 2 4 1 3 6 +1 3 5 7 4 6 2 +2 4 6 1 5 7 3 +7 2 4 6 3 5 1 +6 1 3 5 2 4 7 +4 6 1 3 7 2 5 +3 5 7 2 6 1 4 + +7x7:d11 (hard) +LTTBBDD +LPCSKK4 +GPCSAMM +GG4AAQQ +RHJAFFQ +RHJ4OOE +RNNIIOE + +A 13+ +B 2/ +C 3/ +D 9+ +E 3- +F 30x +G 15+ +H 10+ +I 4- +J 2- +K 1- +L 5+ +M 2- +N 4- +O 12+ +P 10x +Q 18x +R 15+ +S 4- +T 6+ + +4 1 5 6 3 2 7 +1 5 2 3 7 6 4 +5 2 6 7 4 3 1 +3 7 4 5 2 1 6 +7 4 1 2 6 5 3 +2 6 3 4 1 7 5 +6 3 7 1 5 4 2 + +7x7:d13 (hard) +OJJGGPP +OJMENNP +OMMEDDI +H5MCQQI +HBLCAAK +HBLLRRK +5BSSFFF + +A 1- +B 12+ +C 8+ +D 1- +E 2- +F 12x +G 8+ +H 21x +I 1- +J 24x +K 3+ +L 18+ +M 56x +N 6- +O 12+ +P 14+ +Q 13+ +R 1- +S 1- + +6 1 4 5 3 2 7 +4 6 2 3 1 7 5 +2 4 7 1 6 5 3 +3 5 1 2 7 6 4 +7 2 5 6 4 3 1 +1 3 6 7 5 4 2 +5 7 3 4 2 1 6 + +7x7:d11 (hard) +BBF5MME +B2FNNKE +IIIQDKK +GGGQDCC +PPJA3RC +LJJAARH +LLOOOHH + +A 8+ +B 8+ +C 48x +D 3/ +E 5- +F 2- +G 12+ +H 13+ +I 10+ +J 14+ +K 15+ +L 12+ +M 5- +N 2- +O 24x +P 1- +Q 3- +R 15x + +3 4 6 5 7 2 1 +1 2 4 3 5 7 6 +2 3 5 4 6 1 7 +5 6 1 7 2 4 3 +6 7 2 1 3 5 4 +4 5 7 6 1 3 2 +7 1 3 2 4 6 5 + +7x7:d11 (hard) +CHHHFPA +COGGFPA +COOFFE5 +MKK1DEE +MQQDDRR +2JIBNNL +JJIBBNL + +A 6x +B 72x +C 21x +D 7+ +E 15+ +F 210x +G 2- +H 16+ +I 6+ +J 13+ +K 4- +L 3- +M 1- +N 17+ +O 36x +P 1- +Q 3- +R 42x + +1 4 7 5 6 2 3 +7 3 6 4 5 1 2 +3 6 2 7 1 4 5 +4 7 3 1 2 5 6 +5 1 4 2 3 6 7 +2 5 1 6 7 3 4 +6 2 5 3 4 7 1 + +7x7:d13 (hard) +FFOOEQQ +CFTTELL +CFKKMLA +UJJRMLA +UBBRNNG +4IDDSSG +IIPPHH1 + +A 1- +B 8+ +C 4- +D 7+ +E 2- +F 280x +G 1- +H 10x +I 18x +J 42x +K 12x +L 19+ +M 1- +N 3/ +O 4- +P 3- +Q 4- +R 1- +S 4- +T 2/ +U 1- + +7 4 5 1 3 6 2 +5 2 3 6 1 4 7 +1 5 6 2 4 7 3 +2 6 7 3 5 1 4 +3 7 1 4 6 2 5 +4 1 2 5 7 3 6 +6 3 4 7 2 5 1 + +7x7:d11 (hard) +AABBKOO +JJCIKOM +RCCIIMM +RF4NSEH +LFFNSEH +LQQ5GGH +L6DDDPP + +A 2/ +B 2- +C 12x +D 9+ +E 7+ +F 10+ +G 7+ +H 48x +I 14+ +J 2- +K 3/ +L 11+ +M 15x +N 9+ +O 15+ +P 9+ +Q 6- +R 4- +S 2- + +2 4 3 1 6 7 5 +5 7 6 4 2 3 1 +7 2 1 6 4 5 3 +3 5 4 2 7 1 6 +1 3 2 7 5 6 4 +6 1 7 5 3 4 2 +4 6 5 3 1 2 7 + +7x7:d14 (hard) +HHFKCCC +PFFKAAA +PLLLJJ1 +BBD3JJN +BDDG6JN +BBQGOOE +MMQGIIE + +A 14+ +B 252x +C 12+ +D 140x +E 1- +F 6+ +G 35x +H 20x +I 2- +J 840x +K 8+ +L 13+ +M 1- +N 9+ +O 2- +P 9+ +Q 1- + +4 5 1 6 7 2 3 +7 1 4 2 3 5 6 +2 3 6 4 5 7 1 +1 2 5 3 4 6 7 +3 4 7 5 6 1 2 +6 7 3 1 2 4 5 +5 6 2 7 1 3 4 + +7x7:d11 (hard) +PLLMHHH +P6FMRRH +AGFFFKH +AGGG2KJ +NN6BBJJ +DDDEEQC +OOOIIQC + +A 3- +B 3- +C 1- +D 6x +E 3- +F 240x +G 18+ +H 15+ +I 6+ +J 24x +K 10+ +L 1- +M 18x +N 2- +O 9+ +P 3- +Q 1- +R 8+ + +7 2 1 6 3 4 5 +4 6 5 3 7 1 2 +3 5 4 2 6 7 1 +6 1 7 5 2 3 4 +5 7 6 4 1 2 3 +1 3 2 7 4 5 6 +2 4 3 1 5 6 7 + +7x7:d13 (hard) +OOK6III +SSKEIPP +DMMECCB +DAAJ1BB +NNJJJG6 +RFFHQGL +RFHHQLL + +A 2/ +B 90x +C 2- +D 14x +E 3- +F 84x +G 2- +H 6+ +I 20+ +J 17+ +K 8+ +L 11+ +M 24x +N 2- +O 2- +P 3- +Q 1- +R 9+ +S 7+ + +1 3 5 6 2 7 4 +6 1 3 4 7 5 2 +2 4 6 7 3 1 5 +7 2 4 5 1 6 3 +3 5 7 1 4 2 6 +5 7 2 3 6 4 1 +4 6 1 2 5 3 7 + +7x7:d11 (hard) +SLLIIFF +SPIIHHF +BPNIEAA +BPNGEEE +JD6GQEO +JDCCQOO +2KKMMRR + +A 3- +B 5+ +C 3+ +D 2- +E 21+ +F 10+ +G 2- +H 8+ +I 12+ +J 8+ +K 6+ +L 9+ +M 2- +N 11+ +O 10+ +P 15+ +Q 12+ +R 4- +S 42x + +7 6 3 4 2 1 5 +6 5 2 3 1 7 4 +4 3 7 1 6 5 2 +1 7 4 5 3 2 6 +3 2 6 7 5 4 1 +5 4 1 2 7 6 3 +2 1 5 6 4 3 7 + +7x7:d11 (hard) +PPPIF7E +1PIIFOE +MMKKHOO +GG1HHDD +LLQQNN7 +JJCC1BB +JAAAABB + +A 18+ +B 12+ +C 35x +D 9+ +E 1- +F 1- +G 10+ +H 72x +I 14x +J 13+ +K 2- +L 2/ +M 3- +N 6+ +O 9+ +P 120x +Q 2/ + +2 5 3 1 4 7 6 +1 4 2 7 3 6 5 +4 7 5 3 6 2 1 +7 3 1 6 2 5 4 +3 6 4 2 5 1 7 +6 2 7 5 1 4 3 +5 1 6 4 7 3 2 + +7x7:d11 (hard) +2AAHHHH +JJKK7PP +GGNNEE2 +GGOOMMR +FSSLIIR +FC7LQQD +FCCBB4D + +A 1- +B 5- +C 30x +D 2- +E 2- +F 14+ +G 21+ +H 126x +I 2- +J 3- +K 2- +L 1- +M 5- +N 5- +O 2/ +P 4- +Q 2- +R 1- +S 3+ + +2 5 4 6 1 3 7 +1 4 3 5 7 2 6 +4 7 6 1 3 5 2 +7 3 2 4 6 1 5 +6 2 1 3 5 7 4 +5 1 7 2 4 6 3 +3 6 5 7 2 4 1 + +7x7:d11 (hard) +RRLLNND +KGGNNDD +K2IIPDC +EEM4PJC +MMMM4JB +AAOOOHB +QQFFHH1 + +A 4+ +B 12+ +C 1- +D 12+ +E 7+ +F 1- +G 3- +H 16+ +I 1- +J 8+ +K 35x +L 6- +M 16+ +N 17+ +O 15+ +P 8+ +Q 10+ +R 2- + +2 4 1 7 3 5 6 +5 7 4 3 6 1 2 +7 2 6 5 1 3 4 +6 1 5 4 7 2 3 +3 5 2 1 4 6 7 +1 3 7 6 2 4 5 +4 6 3 2 5 7 1 + +7x7:d11 (hard) +MMMBBPP +RMNNBFF +RJLNBKK +HJLOOOI +HCCSEEI +GGASDDD +QQAATT4 + +A 56x +B 15+ +C 1- +D 18x +E 2- +F 4- +G 35x +H 1- +I 1- +J 3- +K 7+ +L 4- +M 19+ +N 11+ +O 120x +P 3- +Q 2- +R 4- +S 3/ +T 7+ + +1 6 5 3 2 4 7 +2 7 6 4 3 5 1 +6 4 3 1 7 2 5 +3 1 7 5 4 6 2 +4 2 1 6 5 7 3 +7 5 4 2 1 3 6 +5 3 2 7 6 1 4 + +7x7:d14 (hard) +6CCCPLL +GGGJPEL +MGAJ1EI +MQAJHEI +KQBHHEO +KBBDDDO +FFNNDD7 + +A 11+ +B 15x +C 10+ +D 23+ +E 24x +F 9+ +G 11+ +H 120x +I 3- +J 14+ +K 3- +L 12+ +M 4- +N 1- +O 9+ +P 28x +Q 42x + +6 5 3 2 4 7 1 +2 1 6 5 7 3 4 +3 2 7 6 1 4 5 +7 6 4 3 5 1 2 +1 7 5 4 6 2 3 +4 3 1 7 2 5 6 +5 4 2 1 3 6 7 + +7x7:d11 (hard) +OOHN7BM +AOHNBBM +ALCCC3M +ALJJQQG +SKJFFIG +SKRDIIP +3KRDEEP + +A 11+ +B 12x +C 40x +D 4- +E 6- +F 7+ +G 3/ +H 2- +I 40x +J 12+ +K 14+ +L 4+ +M 105x +N 2- +O 12+ +P 5+ +Q 1- +R 18x +S 35x + +2 4 5 1 7 6 3 +4 6 7 3 2 1 5 +6 1 2 5 4 3 7 +1 3 4 7 6 5 2 +5 7 1 4 3 2 6 +7 2 3 6 5 4 1 +3 5 6 2 1 7 4 + +7x7:d12 (hard) +DDIIMMA +DOIFFMA +OOKKJ7A +HHPGJB4 +HPPGGBB +CCCEQQQ +NNNE2LL + +A 42x +B 9+ +C 35x +D 11+ +E 1- +F 1- +G 16+ +H 90x +I 72x +J 7+ +K 4- +L 8+ +M 13+ +N 168x +O 12x +P 42x +Q 13+ + +5 4 2 6 7 1 3 +2 1 6 3 4 5 7 +4 3 1 5 6 7 2 +6 5 3 7 1 2 4 +3 2 7 4 5 6 1 +1 7 5 2 3 4 6 +7 6 4 1 2 3 5 + +7x7:d11 (hard) +HHHKK6L +HMMIBCL +QQMIBC5 +EDDIFPP +EEDFFGG +E6ANNJJ +OOANNJJ + +A 9+ +B 7+ +C 6+ +D 60x +E 12+ +F 19+ +G 3- +H 56x +I 6+ +J 105x +K 15x +L 5- +M 15+ +N 16+ +O 4- +P 9+ +Q 4- + +4 7 1 5 3 6 2 +2 5 6 3 1 4 7 +7 3 4 1 6 2 5 +1 4 5 2 7 3 6 +6 2 3 7 5 1 4 +3 6 7 4 2 5 1 +5 1 2 6 4 7 3 + +7x7:d12 (hard) +HHBBBBQ +KHNGFFQ +KNNGFIM +KEEAAIM +OOEAA1C +JDDLLLC +J6DLPPC + +A 72x +B 19+ +C 15+ +D 10+ +E 105x +F 84x +G 3- +H 48x +I 2- +J 12x +K 13+ +L 105x +M 5- +N 10+ +O 7+ +P 2- +Q 4+ + +6 2 4 3 7 5 1 +1 4 6 5 2 7 3 +5 1 3 2 6 4 7 +7 3 5 4 1 6 2 +2 5 7 6 3 1 4 +4 7 2 1 5 3 6 +3 6 1 7 4 2 5 + +7x7:d11 (hard) +OSSHHJJ +OILLHJQ +KIIN3JQ +KBBNDDG +AFPPCGG +AFP1CEE +RRMMEEE + +A 5- +B 3- +C 6+ +D 3- +E 240x +F 5- +G 16+ +H 140x +I 15+ +J 126x +K 5- +L 7+ +M 2- +N 5- +O 8+ +P 11+ +Q 3- +R 1- +S 3/ + +3 2 6 4 5 1 7 +5 4 1 6 7 3 2 +1 7 4 2 3 6 5 +6 5 2 7 1 4 3 +2 1 5 3 4 7 6 +7 6 3 1 2 5 4 +4 3 7 5 6 2 1 + +7x7:d12 (hard) +CCBPDNJ +HHBPDNJ +HIII6NJ +OI4EEEK +OOMMMKK +GAAAQQL +GGFFFFL + +A 14+ +B 4- +C 12+ +D 4- +E 30x +F 13+ +G 72x +H 40x +I 12+ +J 13+ +K 12+ +L 3- +M 14+ +N 15+ +O 10+ +P 3- +Q 1- + +7 5 3 4 1 2 6 +4 2 7 1 5 6 3 +5 3 1 2 6 7 4 +1 6 4 5 2 3 7 +2 7 5 6 3 4 1 +3 1 6 7 4 5 2 +6 4 2 3 7 1 5 + +7x7:d11 (hard) +G1CCPPS +GDABP3S +RDAB1LL +RHHBMF4 +EEQJMFO +NQQJKOO +NIIKKKO + +A 1- +B 10+ +C 1- +D 1- +E 4- +F 2- +G 11+ +H 5- +I 2- +J 1- +K 15+ +L 5- +M 2- +N 3- +O 16+ +P 84x +Q 42x +R 2/ +S 6+ + +4 1 3 2 6 7 5 +7 4 6 5 2 3 1 +6 3 5 4 1 2 7 +3 7 2 1 5 6 4 +1 5 7 6 3 4 2 +2 6 1 7 4 5 3 +5 2 4 3 7 1 6 + +7x7:d13 (hard) +GBBBNNN +GGEENFF +PG6EAAF +P1OOMMQ +IIOHMJQ +CIDHLJK +CIDLLKK + +A 1- +B 11+ +C 7+ +D 2- +E 10+ +F 20x +G 882x +H 4- +I 80x +J 3- +K 84x +L 15+ +M 36x +N 17+ +O 15+ +P 20x +Q 3- + +7 3 2 6 4 5 1 +3 6 5 2 7 1 4 +4 7 6 3 1 2 5 +5 1 7 4 2 3 6 +2 5 4 1 6 7 3 +6 2 1 5 3 4 7 +1 4 3 7 5 6 2 + +7x7:d11 (hard) +HIIDDM7 +HLIADMM +HLNA7JM +QQNGJJP +KNNGGPP +KBBOGCE +FFOOOCE + +A 6+ +B 4- +C 13+ +D 120x +E 1- +F 5- +G 28x +H 30x +I 8+ +J 13+ +K 1- +L 4- +M 11+ +N 840x +O 72x +P 9+ +Q 2- + +3 1 4 6 5 2 7 +2 7 3 5 4 1 6 +5 3 6 1 7 4 2 +4 2 5 7 6 3 1 +6 4 7 2 1 5 3 +7 5 1 3 2 6 4 +1 6 2 4 3 7 5 + +8x8:d4 (easy) +EPCCDD2B +EPGGA2BB +XPG8AFUU +XRGNQFFJ +MRRNQTFJ +M5KOTTHY +VVKOOLHY +IIIWWLSS + +A 9+ +B 20+ +C 6+ +D 2- +E 4+ +F 432x +G 588x +H 2- +I 11+ +J 4+ +K 4/ +L 2- +M 15+ +N 2- +O 11+ +P 21+ +Q 4/ +R 8x +S 5- +T 24x +U 7+ +V 2/ +W 14x +X 7+ +Y 2/ + +3 8 5 1 6 4 2 7 +1 6 3 7 4 2 8 5 +2 7 4 8 5 3 1 6 +5 2 7 3 8 6 4 1 +7 4 1 5 2 8 6 3 +8 5 2 6 3 1 7 4 +6 3 8 4 1 7 5 2 +4 1 6 2 7 5 3 8 + +8x8:d6 (easy) +UHHR7VVF +UTTRREEF +UGGMMXJJ +U4KKKXAB +COO4QQAB +CSSSQYYP +WISZZNDP +WILLLNDP + +A 15x +B 6- +C 10x +D 11+ +E 3- +F 1- +G 1- +H 6x +I 40x +J 3/ +K 10x +L 36x +M 1- +N 9+ +O 13+ +P 15+ +Q 17+ +R 64x +S 11+ +T 1- +U 24+ +V 5+ +W 3- +X 5- +Y 5- +Z 1- + +6 2 3 8 7 4 1 5 +7 3 4 1 8 5 2 6 +3 7 8 5 4 1 6 2 +8 4 5 2 1 6 3 7 +2 6 7 4 3 8 5 1 +5 1 2 7 6 3 8 4 +4 8 1 6 5 2 7 3 +1 5 6 3 2 7 4 8 + +8x8:d6 (easy) +RROOO7NH +7RGTTTNH +IRGBEE4C +IF3BMMMC +JFSAADDD +J7SLAUUU +WPXLVKKU +WPXLVVQQ + +A 84x +B 3- +C 5+ +D 18x +E 13+ +F 14+ +G 4/ +H 5+ +I 3- +J 1- +K 2- +L 48x +M 10+ +N 11+ +O 24x +P 3+ +Q 9+ +R 18+ +S 1- +T 13+ +U 640x +V 120x +W 12x +X 13+ + +6 4 1 3 8 7 5 2 +7 5 2 4 1 8 6 3 +5 3 8 2 7 6 4 1 +8 6 3 5 2 1 7 4 +2 8 5 7 4 3 1 6 +1 7 4 6 3 2 8 5 +4 2 7 1 6 5 3 8 +3 1 6 8 5 4 2 7 + +8x8:d4 (easy) +I7PPPOMM +ISSVVOKK +4EBBBCCC +EEFFDDD3 +UUFQ1NNR +LUQQAN1R +LJJ5ATTT +LLHHA8GG + +A 20+ +B 14+ +C 56x +D 11+ +E 16+ +F 56x +G 1- +H 2/ +I 1- +J 3- +K 30x +L 90x +M 20x +N 9+ +O 1- +P 13+ +Q 16+ +R 6- +S 5- +T 6+ +U 105x +V 5+ + +1 7 2 8 3 6 5 4 +2 8 3 1 4 7 6 5 +4 2 5 3 6 1 8 7 +8 6 1 7 2 5 4 3 +7 5 8 6 1 4 3 2 +5 3 6 4 7 2 1 8 +6 4 7 5 8 3 2 1 +3 1 4 2 5 8 7 6 + +8x8:d4 (easy) +NNXOWWDD +YRXOJKKA +YRQGJBBA +TTQGJCZS +VVPPMCZS +FVEEMM3H +F2UUULaH +IIII1LaH + +A 4/ +B 1- +C 6- +D 6+ +E 14+ +F 6- +G 4+ +H 105x +I 19+ +J 16+ +K 7+ +L 48x +M 40x +N 3- +O 56x +P 12+ +Q 2- +R 12+ +S 2- +T 5- +U 72x +V 96x +W 28x +X 1- +Y 6+ +Z 4/ +a 6- + +3 6 2 8 7 4 5 1 +2 5 1 7 6 3 4 8 +4 7 3 1 8 5 6 2 +6 1 5 3 2 7 8 4 +8 3 7 5 4 1 2 6 +1 4 8 6 5 2 3 7 +7 2 6 4 3 8 1 5 +5 8 4 2 1 6 7 3 + +8x8:d4 (easy) +PPaSS5WW +P5aEGQJJ +PNNEGQUU +8YYIIKKU +VHCTTOMM +VHC6OORM +BDXFFZRA +BDXLLZAA + +A 10+ +B 1- +C 3- +D 3+ +E 15x +F 3- +G 10+ +H 5- +I 5- +J 1- +K 15x +L 3- +M 11+ +N 3- +O 42x +P 36x +Q 2- +R 3+ +S 5+ +T 5- +U 48x +V 3- +W 56x +X 13+ +Y 5+ +Z 7- +a 6x + +2 6 3 4 1 5 7 8 +1 5 2 3 8 4 6 7 +3 7 4 5 2 6 8 1 +8 4 1 2 7 3 5 6 +7 3 8 1 6 2 4 5 +4 8 5 6 3 7 1 2 +5 1 6 7 4 8 2 3 +6 2 7 8 5 1 3 4 + +8x8:d6 (easy) +RGGGGNNT +RRBBONTT +DRBOOUKK +DD4PMUUJ +QQQPMCCJ +VFEILLAJ +VFEISSAH +VFEEWWHH + +A 20x +B 14+ +C 3/ +D 56x +E 896x +F 13+ +G 42x +H 56x +I 1- +J 14+ +K 11+ +L 48x +M 1- +N 14+ +O 160x +P 5+ +Q 14+ +R 17+ +S 6- +T 90x +U 13+ +V 10+ +W 2- + +6 7 3 1 2 4 8 5 +4 5 1 7 8 2 6 3 +1 2 6 4 5 7 3 8 +7 8 4 2 3 5 1 6 +8 1 5 3 4 6 2 7 +2 3 7 5 6 8 4 1 +3 4 8 6 7 1 5 2 +5 6 2 8 1 3 7 4 + +8x8:d8 (easy) +CCFFWWW8 +BBBFWWRT +NULEHGRT +NULEHGJJ +NUXXXP1M +OO2KKPSM +IDA7YYSZ +IDAQQVVZ + +A 2- +B 84x +C 3+ +D 8x +E 7+ +F 240x +G 11+ +H 3- +I 8+ +J 14x +K 1- +L 3- +M 11+ +N 48x +O 1- +P 3+ +Q 1- +R 2- +S 4- +T 2/ +U 280x +V 1- +W 504x +X 60x +Y 3- +Z 4+ + +2 1 5 6 7 4 3 8 +4 3 7 8 1 6 5 2 +6 5 1 2 3 8 7 4 +1 8 4 5 6 3 2 7 +8 7 3 4 5 2 1 6 +7 6 2 3 4 1 8 5 +3 2 6 7 8 5 4 1 +5 4 8 1 2 7 6 3 + +8x8:d4 (easy) +HHHLLLUU +TTQAACPY +XIQ8ACPY +XIMMKBBR +NNFKK3ER +JJFGOOE3 +JSGGODWW +7SVVVDDD + +A 210x +B 8+ +C 24x +D 20+ +E 7+ +F 3- +G 14x +H 72x +I 3- +J 70x +K 11+ +L 16+ +M 15x +N 6- +O 15+ +P 2- +Q 6- +R 10+ +S 3- +T 2- +U 7+ +V 72x +W 24x +X 2/ +Y 6- + +4 6 3 1 8 7 5 2 +1 3 8 6 5 4 2 7 +3 5 2 8 7 6 4 1 +6 8 5 3 2 1 7 4 +8 2 7 5 4 3 1 6 +5 7 4 2 1 8 6 3 +2 4 1 7 6 5 3 8 +7 1 6 4 3 2 8 5 + +8x8:d6 (easy) +LCCC5BBB +LXXQQBHH +RNNQMMVV +R6AAWWEE +YFUUDPKE +YFFGDPK1 +IIZGJTaO +SSZ5JTaO + +A 16x +B 22+ +C 11+ +D 14+ +E 11+ +F 12+ +G 32x +H 24x +I 12x +J 9+ +K 8+ +L 3/ +M 1- +N 12+ +O 3- +P 2- +Q 9+ +R 1- +S 9+ +T 5- +U 24x +V 2- +W 7+ +X 8+ +Y 8+ +Z 2/ +a 3- + +6 7 1 3 5 4 2 8 +2 3 5 7 1 8 6 4 +4 5 7 1 3 2 8 6 +5 6 8 2 4 3 1 7 +1 2 4 6 8 7 5 3 +7 8 2 4 6 5 3 1 +3 4 6 8 2 1 7 5 +8 1 3 5 7 6 4 2 + +8x8:d7 (easy) +UJGG2QQV +UJKKKCQV +4WKKICEE +RWNNICYA +RPP2SSYA +XHHMMFFL +XDZTTOLL +DDZBBOOO + +A 1- +B 8+ +C 15+ +D 240x +E 4- +F 6- +G 2/ +H 24x +I 3- +J 3- +K 21+ +L 13+ +M 2- +N 4- +O 256x +P 5- +Q 10+ +R 7- +S 20x +T 4+ +U 3- +V 7+ +W 3- +X 9+ +Y 15+ +Z 2- + +6 7 4 8 2 3 5 1 +3 4 1 5 7 8 2 6 +4 5 2 6 8 1 3 7 +1 2 7 3 5 6 8 4 +8 1 6 2 4 5 7 3 +2 3 8 4 6 7 1 5 +7 8 5 1 3 4 6 2 +5 6 3 7 1 2 4 8 + +8x8:d6 (easy) +KKUUP7VV +GMMUPNNL +GXXRRNNL +QQOFREAA +5QOFEEAD +TJJFCCHD +TT3WWHHS +BBIIISSS + +A 32x +B 1- +C 10+ +D 2- +E 9+ +F 280x +G 7- +H 12+ +I 17+ +J 28x +K 1- +L 1- +M 7+ +N 360x +O 1- +P 7- +Q 252x +R 14+ +S 15+ +T 168x +U 72x +V 3- +W 7+ +X 5+ + +2 3 6 4 1 7 5 8 +1 2 5 3 8 6 4 7 +8 1 4 2 7 5 3 6 +6 7 2 8 5 3 1 4 +5 6 1 7 4 2 8 3 +3 4 7 5 2 8 6 1 +7 8 3 1 6 4 2 5 +4 5 8 6 3 1 7 2 + +8x8:d7 (easy) +HXLMMPSS +HXLLMP2V +IIILJJVV +I1TEAQQQ +CCTEANNO +FFDYYBBO +WWD2RBUU +GGRRRKK2 + +A 3+ +B 9+ +C 56x +D 8+ +E 7+ +F 1- +G 30x +H 1- +I 15+ +J 21x +K 1- +L 23+ +M 15+ +N 1- +O 3- +P 9+ +Q 18+ +R 24+ +S 8+ +T 1- +U 2- +V 384x +W 1- +X 1- +Y 2- + +2 3 5 6 4 8 1 7 +3 4 6 7 5 1 2 8 +1 2 4 5 3 7 8 6 +8 1 3 4 2 6 7 5 +7 8 2 3 1 5 6 4 +4 5 7 8 6 2 3 1 +6 7 1 2 8 4 5 3 +5 6 8 1 7 3 4 2 + +8x8:d5 (easy) +CCXXOOLL +FRTTAAJL +FRPTMMJJ +8RPPGGEE +QDDKKVI7 +QDNN3VII +QSSHHH6I +WWSBB5UU + +A 15+ +B 28x +C 5- +D 9+ +E 12x +F 6- +G 1- +H 12+ +I 22+ +J 30x +K 7+ +L 168x +M 1- +N 1- +O 3+ +P 4+ +Q 40x +R 120x +S 224x +T 48x +U 3+ +V 12x +W 9+ +X 20x + +3 8 5 4 1 2 7 6 +1 6 3 2 7 8 5 4 +7 4 1 8 5 6 3 2 +8 5 2 1 6 7 4 3 +4 1 6 5 2 3 8 7 +5 2 7 6 3 4 1 8 +2 7 4 3 8 1 6 5 +6 3 8 7 4 5 2 1 + +8x8:d4 (easy) +HLUU7TCC +HLAAATFC +1LIIEEFV +QQQXXGGV +Q3BBKKMM +PPRYN8MM +WJRYNSZZ +WJ5OOSDD + +A 13+ +B 24x +C 90x +D 56x +E 3- +F 9+ +G 4+ +H 4/ +I 4/ +J 3+ +K 5- +L 336x +M 16x +N 5- +O 2- +P 2- +Q 25+ +R 28x +S 1- +T 6+ +U 4+ +V 8x +W 1- +X 2- +Y 3- +Z 1- + +2 8 3 1 7 4 6 5 +8 6 1 7 5 2 4 3 +1 7 2 8 6 3 5 4 +7 5 8 6 4 1 3 2 +5 3 6 4 2 7 1 8 +6 4 7 5 3 8 2 1 +3 1 4 2 8 5 7 6 +4 2 5 3 1 6 8 7 + +8x8:d5 (easy) +WKTMM1QQ +WKTNNNQB +RRT6UXXB +OOPDUIJJ +LLPDUIVJ +C5FFEEVA +CHHFYYVA +GGHHSSS3 + +A 6- +B 4- +C 24x +D 4+ +E 2/ +F 105x +G 13+ +H 16+ +I 2- +J 120x +K 10+ +L 1- +M 1- +N 42x +O 3+ +P 35x +Q 168x +R 1- +S 8+ +T 96x +U 13+ +V 21x +W 4+ +X 5- +Y 8+ + +3 2 8 4 5 1 6 7 +1 8 6 2 3 7 4 5 +5 4 2 6 7 3 8 1 +2 1 7 3 4 8 5 6 +8 7 5 1 2 6 3 4 +6 5 3 7 8 4 1 2 +4 3 1 5 6 2 7 8 +7 6 4 8 1 5 2 3 + +8x8:d5 (easy) +WWCDDSSS +GICDVEE1 +GINVVAFH +ONNB6AFH +O7NBKQQL +OUUUKQLL +PMM7TYJX +PPRRTYJX + +A 6- +B 6+ +C 2- +D 7+ +E 14+ +F 12+ +G 11+ +H 6- +I 1- +J 1- +K 10x +L 112x +M 9+ +N 144x +O 15+ +P 11+ +Q 10+ +R 13+ +S 13+ +T 7+ +U 16x +V 168x +W 1- +X 1- +Y 1- + +7 6 5 4 1 2 8 3 +5 4 3 2 7 8 6 1 +6 5 4 3 8 1 7 2 +4 3 2 1 6 7 5 8 +8 7 6 5 2 3 1 4 +3 2 1 8 5 6 4 7 +2 1 8 7 4 5 3 6 +1 8 7 6 3 4 2 5 + +8x8:d5 (easy) +EBZZTRG3 +EBIITRGA +EFFJJR8A +EXX1NNAA +WHHYYLLV +W5SSPQMV +COOUPQMV +CKKUU5DD + +A 19+ +B 2- +C 1- +D 4/ +E 15+ +F 8+ +G 15x +H 2- +I 3- +J 10x +K 2- +L 7+ +M 5+ +N 2/ +O 4/ +P 7+ +Q 28x +R 17+ +S 9+ +T 4/ +U 14+ +V 56x +W 6- +X 14+ +Y 5- +Z 3- + +1 6 4 7 2 8 5 3 +7 4 2 5 8 6 3 1 +4 1 7 2 5 3 8 6 +3 8 6 1 4 2 7 5 +2 7 5 8 3 1 6 4 +8 5 3 6 1 7 4 2 +5 2 8 3 6 4 1 7 +6 3 1 4 7 5 2 8 + +8x8:d7 (easy) +RRLLSX8O +QWWGSXJO +QAAGGGJ5 +EMMMK2HH +E5FMKBVV +TFFPPBBD +TCUP2IID +TCUUNNID + +A 4/ +B 11+ +C 3- +D 13+ +E 8+ +F 105x +G 16+ +H 1- +I 144x +J 8x +K 2- +L 1- +M 14+ +N 2- +O 2- +P 18+ +Q 2- +R 4- +S 8+ +T 16+ +U 10+ +V 56x +W 2- +X 12+ + +2 6 4 3 7 5 8 1 +4 8 6 5 1 7 2 3 +6 2 8 7 3 1 4 5 +7 3 1 8 4 2 5 6 +1 5 3 2 6 4 7 8 +3 7 5 4 8 6 1 2 +5 1 7 6 2 8 3 4 +8 4 2 1 5 3 6 7 + +8x8:d7 (easy) +GR3XXNNN +GRPPBBBU +5VVPCCUU +FQOIICAA +FQOWWHHY +TMEEL4KY +TMM8LDKS +ZZJJDDKS + +A 30x +B 280x +C 6x +D 16+ +E 12+ +F 4+ +G 8x +H 3- +I 4- +J 6- +K 6+ +L 1- +M 11+ +N 15+ +O 6- +P 10+ +Q 2- +R 2- +S 3+ +T 1- +U 19+ +V 3- +W 12x +X 6+ +Y 5- +Z 4- + +4 8 3 5 1 2 7 6 +2 6 1 3 7 8 5 4 +5 1 4 6 2 3 8 7 +3 7 2 4 8 1 6 5 +1 5 8 2 6 7 4 3 +6 2 5 7 3 4 1 8 +7 3 6 8 4 5 2 1 +8 4 7 1 5 6 3 2 + +8x8:d6 (easy) +JVSSAWWH +JVVSAF6H +GQCCBFFT +GQOOBLZT +YPDOBLZK +YPDIBRRK +NPIIIMEE +NUU3MMXX + +A 1- +B 24x +C 3- +D 5+ +E 11+ +F 10+ +G 1- +H 6x +I 384x +J 1- +K 1- +L 8+ +M 14+ +N 5- +O 12+ +P 210x +Q 7- +R 5+ +S 140x +T 56x +U 8+ +V 96x +W 40x +X 3- +Y 9+ +Z 16x + +1 3 7 4 6 8 5 2 +2 4 8 5 7 1 6 3 +7 1 5 2 4 6 3 8 +6 8 4 1 3 5 2 7 +4 6 2 7 1 3 8 5 +5 7 3 8 2 4 1 6 +3 5 1 6 8 2 7 4 +8 2 6 3 5 7 4 1 + +8x8:d8 (easy) +DWWGGAUU +DMMJJA3U +8QXPCYYN +LQXPCSSN +LV8OOESN +VVIIFESN +KKTIFRHB +KTTTRRHB + +A 7- +B 5- +C 20x +D 6x +E 1- +F 9+ +G 5- +H 3- +I 84x +J 5- +K 12+ +L 5- +M 28x +N 16+ +O 3- +P 15+ +Q 1- +R 10+ +S 22+ +T 144x +U 15+ +V 30x +W 3- +X 6x +Y 7+ + +3 8 5 2 7 1 4 6 +2 7 4 1 6 8 3 5 +8 5 2 7 4 6 1 3 +1 6 3 8 5 7 2 4 +6 3 8 5 2 4 7 1 +5 2 7 4 1 3 6 8 +4 1 6 3 8 2 5 7 +7 4 1 6 3 5 8 2 + +8x8:d6 (easy) +QQMMODD6 +QQMCODJJ +VCCCRRXX +VC4KKTTP +NEU5GHHP +NEUIGHAA +NELI6FSW +7LLBBFSW + +A 9+ +B 14+ +C 24+ +D 10+ +E 36x +F 2- +G 9+ +H 56x +I 2/ +J 56x +K 2- +L 12+ +M 60x +N 30x +O 1- +P 6+ +Q 16+ +R 5- +S 4+ +T 14+ +U 5- +V 4/ +W 4/ +X 1- + +3 8 5 2 4 1 7 6 +4 1 6 3 5 2 8 7 +8 5 2 7 1 6 4 3 +2 7 4 1 3 8 6 5 +6 3 8 5 7 4 2 1 +1 6 3 8 2 7 5 4 +5 2 7 4 6 3 1 8 +7 4 1 6 8 5 3 2 + +8x8:d6 (easy) +TTAAWRXX +TYDIWRMM +BYDIHH4b +BBGGGHOb +BBaVLLOP +KSaVFFUP +KSQENNU1 +CCQEJJZZ + +A 2/ +B 14+ +C 2- +D 5- +E 6+ +F 3+ +G 19+ +H 15+ +I 11+ +J 1- +K 14+ +L 1- +M 7- +N 1- +O 2- +P 11+ +Q 10+ +R 6- +S 2- +T 105x +U 4/ +V 11+ +W 4+ +X 1- +Y 3- +Z 1- +a 3- +b 6x + +5 3 4 2 1 8 7 6 +7 5 6 4 3 2 1 8 +2 8 1 7 6 5 4 3 +1 7 8 6 5 4 3 2 +3 1 2 8 7 6 5 4 +6 4 5 3 2 1 8 7 +8 6 7 5 4 3 2 1 +4 2 3 1 8 7 6 5 + +8x8:d7 (easy) +KKKUC1RR +AWKUCC4R +AWDDDNNN +5PPLVFFN +GG8LVOSI +BMMHHOSI +BMQ4TESJ +BQQTTEEJ + +A 4- +B 15+ +C 60x +D 10+ +E 35x +F 2- +G 24x +H 3- +I 5- +J 2- +K 19+ +L 5+ +M 16+ +N 20+ +O 11+ +P 6- +Q 8+ +R 18+ +S 14+ +T 10+ +U 7- +V 56x +W 6+ + +2 4 6 8 5 1 3 7 +3 5 7 1 6 2 4 8 +7 1 3 5 2 6 8 4 +5 7 1 3 8 4 6 2 +4 6 8 2 7 3 5 1 +1 3 5 7 4 8 2 6 +6 8 2 4 1 5 7 3 +8 2 4 6 3 7 1 5 + +8x8:d5 (easy) +VVNN7DBB +LLWWCDDB +5PPQCTOO +JPXQETOR +JFXEETAR +FFIIAAAR +GGIHMM4R +SSSHUUKK + +A 540x +B 16+ +C 1- +D 80x +E 40x +F 196x +G 40x +H 10+ +I 11+ +J 5+ +K 48x +L 2/ +M 13+ +N 2/ +O 49x +P 16+ +Q 2- +R 12+ +S 20x +T 8x +U 1- +V 7+ +W 8+ +X 12x + +1 6 2 4 7 8 5 3 +6 3 7 1 4 5 2 8 +5 2 6 8 3 4 1 7 +3 8 4 6 1 2 7 5 +2 7 3 5 8 1 6 4 +7 4 8 2 5 6 3 1 +8 5 1 3 6 7 4 2 +4 1 5 7 2 3 8 6 + +8x8:d7 (easy) +SSQQIIIT +SMMDWG5T +4FDDWGBB +JF7EOLLB +JJEEOUH7 +J7KKUUHP +RXNNA1HP +RXCCAVVV + +A 4- +B 11+ +C 3- +D 24x +E 80x +F 1- +G 14x +H 24x +I 56x +J 18+ +K 10x +L 11+ +M 5- +N 5- +O 24x +P 2/ +Q 18x +R 3/ +S 56x +T 8+ +U 18x +V 19+ +W 3- +X 4- + +1 8 3 6 2 4 7 5 +7 6 1 4 8 2 5 3 +4 3 6 1 5 7 2 8 +5 4 7 2 6 8 3 1 +3 2 5 8 4 6 1 7 +8 7 2 5 1 3 6 4 +6 5 8 3 7 1 4 2 +2 1 4 7 3 5 8 6 + +8x8:d6 (easy) +GS5BBJTT +GSM2BJOT +QEMCPJOO +QECCPJDL +XAUUUDDL +XAIIFFF5 +HHNVKKRY +WWNV8KRY + +A 7+ +B 8+ +C 13+ +D 18+ +E 2- +F 6x +G 9+ +H 1- +I 2/ +J 19+ +K 210x +L 4/ +M 3- +N 6- +O 40x +P 8+ +Q 2- +R 8+ +S 15+ +T 17+ +U 105x +V 2- +W 20x +X 14x +Y 4+ + +8 7 5 1 3 2 4 6 +1 8 6 2 4 3 5 7 +6 5 3 7 1 8 2 4 +4 3 1 5 7 6 8 2 +2 1 7 3 5 4 6 8 +7 6 4 8 2 1 3 5 +3 2 8 4 6 5 7 1 +5 4 2 6 8 7 1 3 + +8x8:d6 (easy) +7BNWQQKK +BBNWQMLK +BTTF4MLA +GGTFCCEA +DD5UUCEA +RRHHUP4S +VVOHPPJS +VOOIIPJS + +A 28x +B 36x +C 144x +D 15+ +E 3- +F 35x +G 20x +H 17+ +I 1- +J 5- +K 90x +L 9+ +M 11+ +N 3- +O 48x +P 17+ +Q 48x +R 30x +S 80x +T 12+ +U 10+ +V 12x +W 5+ + +7 6 4 1 8 2 5 3 +2 1 7 4 3 5 8 6 +3 2 8 5 4 6 1 7 +5 4 2 7 6 8 3 1 +8 7 5 2 1 3 6 4 +6 5 3 8 7 1 4 2 +4 3 1 6 5 7 2 8 +1 8 6 3 2 4 7 5 + +8x8:d5 (easy) +O7GNNYUU +OGGNHYQQ +RRKBHCC2 +WLKBHMMF +WLLIIIPF +SAAAXTPP +SJJ3XTDV +SJ5EEDDV + +A 24x +B 6- +C 8+ +D 112x +E 12x +F 3- +G 9+ +H 336x +I 120x +J 120x +K 4/ +L 168x +M 35x +N 17+ +O 3- +P 13+ +Q 7+ +R 5+ +S 16+ +T 7- +U 7- +V 1- +W 3- +X 1- +Y 2/ + +2 7 6 5 4 3 1 8 +5 2 1 8 7 6 4 3 +4 1 8 7 6 5 3 2 +6 3 2 1 8 7 5 4 +3 8 7 6 5 4 2 1 +7 4 3 2 1 8 6 5 +8 5 4 3 2 1 7 6 +1 6 5 4 3 2 8 7 + +8x8:d7 (easy) +TTTKMEE2 +GG5KMMRR +FFJJWIYY +UPP8WINN +UCXXDDZZ +aCCCDLLS +aAAHHQSS +7VVBBQOO + +A 9+ +B 3- +C 30x +D 96x +E 2- +F 5- +G 5+ +H 5- +I 5+ +J 1- +K 5- +L 35x +M 36x +N 1- +O 1- +P 42x +Q 2- +R 1- +S 11+ +T 224x +U 6x +V 6x +W 3- +X 1- +Y 1- +Z 7- +a 30x + +4 7 8 1 6 5 3 2 +1 4 5 6 3 2 8 7 +8 3 4 5 2 1 7 6 +3 6 7 8 5 4 2 1 +2 5 6 7 4 3 1 8 +6 1 2 3 8 7 5 4 +5 8 1 2 7 6 4 3 +7 2 3 4 1 8 6 5 + +8x8:d7 (easy) +QQ2RRMOO +KQE6IMXX +KEEIIIDD +V7WWLLTT +VAAG2LJH +6UUGLLJH +NNCGFFSS +NCCPPPBB + +A 7+ +B 1- +C 280x +D 1- +E 18x +F 3- +G 12+ +H 8+ +I 560x +J 6- +K 8+ +L 3360x +M 7- +N 30x +O 1- +P 7+ +Q 17+ +R 1- +S 7- +T 11+ +U 24x +V 8x +W 5+ +X 1- + +8 5 2 7 6 1 4 3 +7 4 1 6 5 8 3 2 +1 6 3 8 7 2 5 4 +2 7 4 1 8 3 6 5 +4 1 6 3 2 5 8 7 +6 3 8 5 4 7 2 1 +5 2 7 4 3 6 1 8 +3 8 5 2 1 4 7 6 + +8x8:d5 (easy) +H5AATTKK +HSSDDTLK +HCCEEELL +UNNVVOOL +UNPPVFFF +4BBGQQXX +MRRGIJJ7 +MMRRIJWW + +A 24x +B 1- +C 3+ +D 12+ +E 18+ +F 10x +G 4- +H 42x +I 4+ +J 48x +K 336x +L 18+ +M 96x +N 16+ +O 4- +P 48x +Q 30x +R 19+ +S 6x +T 6x +U 2- +V 12+ +W 7- +X 1- + +1 5 4 6 2 3 7 8 +7 3 2 4 8 1 5 6 +6 2 1 3 7 8 4 5 +5 1 8 2 6 7 3 4 +3 7 6 8 4 5 1 2 +4 8 7 1 5 6 2 3 +8 4 3 5 1 2 6 7 +2 6 5 7 3 4 8 1 + +8x8:d9 (medium) +WDDDVVQQ +WRDUCIIB +FRMUC7IB +FFMU5EKK +GGGNNEE1 +8TTXXHHS +JJJPLOOS +AA8PLLOS + +A 1- +B 3- +C 5- +D 23+ +E 56x +F 18+ +G 13+ +H 3+ +I 13+ +J 40x +K 16x +L 15+ +M 2- +N 1- +O 10+ +P 5- +Q 10+ +R 5+ +S 84x +T 11+ +U 56x +V 4- +W 6x +X 1- + +3 2 7 8 1 5 4 6 +2 1 6 7 8 4 3 5 +5 4 1 2 3 7 6 8 +7 6 3 4 5 1 8 2 +6 5 2 3 4 8 7 1 +8 7 4 5 6 2 1 3 +1 8 5 6 7 3 2 4 +4 3 8 1 2 6 5 7 + +8x8:d10 (medium) +IIIHHRR3 +AAVHDDWW +LLVOOOMM +TKKKOJ1M +TTKBBJSS +NPEEQ7FF +NPCEQGUF +CCCC1GUF + +A 3- +B 12+ +C 2880x +D 7+ +E 8+ +F 160x +G 2/ +H 64x +I 14+ +J 48x +K 22+ +L 28x +M 11+ +N 3- +O 16+ +P 5- +Q 3- +R 3- +S 9+ +T 7+ +U 2- +V 8+ +W 11+ + +1 6 7 4 2 5 8 3 +5 2 3 8 6 1 4 7 +7 4 5 2 8 3 6 1 +2 7 8 5 3 6 1 4 +4 1 2 7 5 8 3 6 +3 8 1 6 4 7 2 5 +6 3 4 1 7 2 5 8 +8 5 6 3 1 4 7 2 + +8x8:d9 (medium) +TTMMCCSS +DDMMCCJ8 +ILL5BBJV +IGLBBB4V +RGGOFFUN +RG8OHHUN +AAPPEEEE +A4KKKEQQ + +A 10+ +B 17+ +C 20+ +D 35x +E 504x +F 24x +G 144x +H 3- +I 2- +J 2/ +K 14+ +L 14+ +M 10+ +N 2/ +O 24x +P 6- +Q 3- +R 8+ +S 14x +T 10+ +U 2- +V 2- + +4 6 3 1 8 5 2 7 +5 7 4 2 1 6 3 8 +8 2 7 5 4 1 6 3 +6 8 5 3 2 7 4 1 +7 1 6 4 3 8 5 2 +1 3 8 6 5 2 7 4 +3 5 2 8 7 4 1 6 +2 4 1 7 6 3 8 5 + +8x8:d9 (medium) +KTTJJJD8 +KTMMORDI +1EEAORRI +NNEAO5BW +SS1LUUBW +SSHLFFBQ +PPHV6GGQ +PPHVCCXX + +A 2/ +B 12+ +C 4+ +D 1- +E 21+ +F 6+ +G 5+ +H 16+ +I 3- +J 14+ +K 1- +L 6+ +M 21x +N 2- +O 70x +P 560x +Q 2- +R 19+ +S 384x +T 9+ +U 2- +V 5- +W 1- +X 3/ + +5 3 2 6 1 7 4 8 +6 4 3 7 2 8 5 1 +1 7 6 2 5 3 8 4 +3 1 8 4 7 5 2 6 +4 2 1 5 8 6 3 7 +8 6 5 1 4 2 7 3 +2 8 7 3 6 4 1 5 +7 5 4 8 3 1 6 2 + +8x8:d10 (medium) +UGHHQQ5M +UG5HHWIM +XJJJSWIC +XEESSDIC +LPRFODDC +LPRFOKKK +NNVAABT5 +5NVVVBTT + +A 9+ +B 2- +C 8x +D 42x +E 4- +F 5- +G 6- +H 11+ +I 56x +J 15+ +K 18+ +L 5- +M 14+ +N 17+ +O 5- +P 3- +Q 2- +R 3- +S 120x +T 9+ +U 16x +V 23+ +W 2- +X 1- + +8 7 3 1 2 4 5 6 +2 1 5 3 4 6 7 8 +4 3 7 5 6 8 1 2 +3 2 6 4 5 7 8 1 +6 5 1 7 8 2 3 4 +1 8 4 2 3 5 6 7 +7 6 2 8 1 3 4 5 +5 4 8 6 7 1 2 3 + +8x8:d9 (medium) +WMMSSFII +W7YYFFIU +NNJJXLLU +AOOOXBL5 +AOKKKBHQ +VO1DDBHQ +VRGG7TTE +7RGPPCCE + +A 5+ +B 17+ +C 2/ +D 12x +E 2- +F 6x +G 16+ +H 2/ +I 20+ +J 3- +K 15+ +L 10+ +M 3+ +N 5- +O 3360x +P 1- +Q 3- +R 2/ +S 9+ +T 4- +U 12x +V 6+ +W 2- +X 5- +Y 11+ + +6 1 2 5 4 3 7 8 +4 7 8 3 2 1 5 6 +8 3 4 7 6 5 1 2 +3 6 7 2 1 8 4 5 +2 5 6 1 8 7 3 4 +5 8 1 4 3 2 6 7 +1 4 5 8 7 6 2 3 +7 2 3 6 5 4 8 1 + +8x8:d9 (medium) +DDKQQLU3 +J1KQXLUS +JJAQXLFS +JAANNNFF +BBVVIIOO +CTEE8PWW +CTTHPPGG +CRRHH8MM + +A 14+ +B 14+ +C 24x +D 2- +E 10+ +F 96x +G 2- +H 13+ +I 15x +J 224x +K 14+ +L 24x +M 2- +N 11+ +O 2/ +P 14+ +Q 160x +R 6x +S 13+ +T 8+ +U 4+ +V 6- +W 6- +X 28x + +5 7 6 8 2 4 1 3 +7 1 8 2 4 6 3 5 +2 4 3 5 7 1 6 8 +4 6 5 7 1 3 8 2 +6 8 7 1 3 5 2 4 +3 5 4 6 8 2 7 1 +8 2 1 3 5 7 4 6 +1 3 2 4 6 8 5 7 + +8x8:d9 (medium) +TCCR3YPP +TTCRJYHH +BBAAJJHK +BBVFFUUK +OBVLLLGG +OQQNIIS3 +OWWNNISE +MMDDNXXE + +A 4- +B 22+ +C 42x +D 4- +E 3- +F 11+ +G 12+ +H 12x +I 210x +J 224x +K 30x +L 30x +M 1- +N 48x +O 12+ +P 4- +Q 3- +R 13+ +S 56x +T 64x +U 3+ +V 6- +W 7+ +X 6x +Y 9+ + +8 7 2 6 3 4 5 1 +1 8 3 7 4 5 6 2 +4 3 6 2 7 8 1 5 +5 4 7 3 8 1 2 6 +7 6 1 5 2 3 4 8 +2 1 4 8 5 6 7 3 +3 2 5 1 6 7 8 4 +6 5 8 4 1 2 3 7 + +8x8:d10 (medium) +UJJB6DNH +UJWBDDNH +IIWBTDNQ +MMMTTDYQ +GS3AAAYO +GSLLVV8O +RFFCCCKK +R3EEPPXX + +A 11+ +B 15+ +C 24x +D 2520x +E 4/ +F 11+ +G 1- +H 1- +I 1- +J 16+ +K 7- +L 2- +M 6x +N 9+ +O 1- +P 8+ +Q 5+ +R 11+ +S 1- +T 280x +U 3+ +V 2- +W 2- +X 11+ +Y 2- + +1 8 7 5 6 4 3 2 +2 1 8 6 7 5 4 3 +8 7 6 4 5 3 2 1 +3 2 1 7 8 6 5 4 +5 4 3 1 2 8 7 6 +6 5 4 2 3 1 8 7 +7 6 5 3 4 2 1 8 +4 3 2 8 1 7 6 5 + +8x8:d11 (medium) +TTP4SSWH +TEPPSSWH +TECGGOAA +JDCGGOA8 +JDBQIIVV +J6BQNNRR +LLBQMNRK +6UUUMFFK + +A 56x +B 21+ +C 20x +D 2- +E 2- +F 2/ +G 1260x +H 13+ +I 12+ +J 9+ +K 15x +L 20x +M 4/ +N 14+ +O 1- +P 10+ +Q 11+ +R 24x +S 24x +T 896x +U 11+ +V 1- +W 1- + +7 8 2 4 3 1 5 6 +8 1 3 5 4 2 6 7 +2 3 5 7 6 4 8 1 +1 2 4 6 5 3 7 8 +3 4 6 8 7 5 1 2 +5 6 8 2 1 7 3 4 +4 5 7 1 8 6 2 3 +6 7 1 3 2 8 4 5 + +8x8:d9 (medium) +7WWQQQKK +UUOOL1CI +GAAELCCI +GJEEEC1N +SJJFFVVN +SSMMFRTN +X4PHHRTD +XPP3BBBD + +A 2- +B 280x +C 504x +D 6- +E 21+ +F 8+ +G 5- +H 7+ +I 5- +J 14+ +K 3/ +L 5- +M 42x +N 11+ +O 1- +P 12+ +Q 17+ +R 3- +S 15x +T 24x +U 12+ +V 2- +W 4+ +X 10+ + +7 3 1 8 5 4 2 6 +4 8 6 5 2 1 7 3 +1 5 3 2 7 6 4 8 +6 2 8 7 4 3 1 5 +3 7 5 4 1 8 6 2 +5 1 7 6 3 2 8 4 +8 4 2 1 6 5 3 7 +2 6 4 3 8 7 5 1 + +8x8:d9 (medium) +VKMPP3UU +VKMMFFAA +DDDDFNN3 +1QQIINSS +XQJLLNGG +X5JBWCGO +HYRBWCCO +HYREETTT + +A 1- +B 4- +C 12+ +D 840x +E 5- +F 10+ +G 16x +H 30x +I 2/ +J 12x +K 1- +L 3- +M 11+ +N 22+ +O 4- +P 5+ +Q 12+ +R 1- +S 56x +T 8+ +U 11+ +V 56x +W 4- +X 1- +Y 7- + +7 2 8 1 4 3 5 6 +8 3 1 2 5 4 6 7 +4 7 5 6 1 8 2 3 +1 4 2 3 6 5 7 8 +3 6 4 5 8 7 1 2 +2 5 3 4 7 6 8 1 +6 1 7 8 3 2 4 5 +5 8 6 7 2 1 3 4 + +8x8:d9 (medium) +BYYIKJUU +BSHIKJJ1 +3SHPPPDD +TMMEERRR +TZZ6QFXX +OO3AQFLL +VGGAQWWN +V3CCCNNN + +A 3+ +B 3- +C 15+ +D 1- +E 2- +F 6+ +G 6- +H 2/ +I 5- +J 17+ +K 3- +L 1- +M 10+ +N 17+ +O 5- +P 10+ +Q 96x +R 48x +S 4- +T 1- +U 1- +V 3- +W 24x +X 7+ +Y 6- +Z 12+ + +4 7 1 8 2 3 5 6 +7 2 4 3 5 6 8 1 +3 6 8 7 1 2 4 5 +1 4 6 5 7 8 2 3 +2 5 7 6 8 1 3 4 +6 1 3 2 4 5 7 8 +5 8 2 1 3 4 6 7 +8 3 5 4 6 7 1 2 + +8x8:d10 (medium) +OOUUDTTT +LLLUDCNM +SS2GVCNM +BBBGVC6M +HHK6AREE +FIKKARRE +FIIPJJJ2 +WWPP3QQQ + +A 6- +B 14+ +C 17+ +D 5- +E 16+ +F 5- +G 5- +H 5- +I 17+ +J 16+ +K 12+ +L 56x +M 11+ +N 4+ +O 3- +P 42x +Q 15+ +R 14x +S 40x +T 16+ +U 8+ +V 11+ +W 5+ + +7 4 1 2 6 5 8 3 +2 7 4 5 1 8 3 6 +8 5 2 3 7 6 1 4 +5 2 7 8 4 3 6 1 +3 8 5 6 2 1 4 7 +1 6 3 4 8 7 2 5 +6 3 8 1 5 4 7 2 +4 1 6 7 3 2 5 8 + +8x8:d10 (medium) +YYPKWWNA +SMPKFFNA +SMBZTTQC +DMBZLLQC +DMJJ8IIC +DEVVH8OO +EEEEHRXO +UU7GGRXO + +A 24x +B 6x +C 120x +D 12+ +E 18+ +F 28x +G 2/ +H 2- +I 5+ +J 4- +K 3- +L 3- +M 672x +N 5- +O 17+ +P 40x +Q 1- +R 7+ +S 24x +T 5+ +U 9+ +V 3/ +W 5- +X 13+ +Y 30x +Z 1- + +6 5 8 4 7 2 1 3 +3 2 5 1 4 7 6 8 +8 7 2 6 1 4 3 5 +1 8 3 7 2 5 4 6 +7 6 1 5 8 3 2 4 +4 3 6 2 5 8 7 1 +2 1 4 8 3 6 5 7 +5 4 7 3 6 1 8 2 + +8x8:d11 (medium) +8IIISJJJ +OOXXSCCP +MMLHHPPP +R5LHHHAP +RBB2VVAD +WWFFNQ3D +EETFNQGG +EETTKKUU + +A 3- +B 7- +C 8+ +D 20x +E 14+ +F 17+ +G 1- +H 31+ +I 6x +J 18+ +K 13+ +L 1- +M 12x +N 3+ +O 5+ +P 256x +Q 1- +R 11+ +S 2- +T 14+ +U 42x +V 3- +W 13+ +X 9+ + +8 1 2 3 4 7 5 6 +2 3 4 5 6 1 7 8 +3 4 5 6 7 2 8 1 +4 5 6 7 8 3 1 2 +7 8 1 2 3 6 4 5 +6 7 8 1 2 5 3 4 +5 6 7 8 1 4 2 3 +1 2 3 4 5 8 6 7 + +8x8:d9 (medium) +2DDDBKKT +AAADB1KT +AALLLRRM +EEU5IIIM +FFUPPPPN +VHHOOJ5N +VCCSSJGG +QQCC1JJJ + +A 28+ +B 15x +C 384x +D 13+ +E 1- +F 1- +G 3+ +H 9+ +I 11+ +J 3360x +K 144x +L 12+ +M 48x +N 2/ +O 3+ +P 1008x +Q 11+ +R 5- +S 1- +T 35x +U 8+ +V 3- + +2 1 6 4 5 3 8 7 +8 7 4 2 3 1 6 5 +1 8 5 3 4 2 7 6 +3 2 7 5 6 4 1 8 +5 4 1 7 8 6 3 2 +7 6 3 1 2 8 5 4 +4 3 8 6 7 5 2 1 +6 5 2 8 1 7 4 3 + +8x8:d10 (medium) +7RVVFFUU +RR3AAAAB +LRIID7BB +L1IIDOOO +LMMKKGGX +EJSHHTTX +EJSSWQPP +CCNNWQQ1 + +A 14+ +B 16+ +C 4- +D 56x +E 1- +F 9+ +G 5- +H 7+ +I 14+ +J 1- +K 2/ +L 12+ +M 1- +N 12+ +O 13+ +P 1- +Q 96x +R 192x +S 336x +T 10x +U 30x +V 2/ +W 1- +X 24x + +7 3 2 4 1 8 5 6 +8 4 3 5 2 1 6 7 +6 2 1 3 8 7 4 5 +5 1 8 2 7 6 3 4 +1 5 4 6 3 2 7 8 +4 8 7 1 6 5 2 3 +3 7 6 8 5 4 1 2 +2 6 5 7 4 3 8 1 + +8x8:d11 (medium) +MMPOLLXX +WMPO4SSQ +WEPFFDDQ +CEUHHH3Q +CCUUBAAJ +VRRRB5AJ +VVKNNGTY +IIK2NGTY + +A 13+ +B 8+ +C 56x +D 9+ +E 6+ +F 14+ +G 5+ +H 48x +I 8+ +J 2- +K 11+ +L 4- +M 336x +N 20+ +O 1- +P 30x +Q 14+ +R 96x +S 40x +T 7+ +U 8+ +V 16+ +W 4+ +X 3- +Y 9+ + +8 6 2 5 3 7 4 1 +1 7 3 6 4 8 5 2 +3 1 5 8 6 2 7 4 +7 5 1 4 2 6 3 8 +4 2 6 1 7 3 8 5 +6 4 8 3 1 5 2 7 +2 8 4 7 5 1 6 3 +5 3 7 2 8 4 1 6 + +8x8:d9 (medium) +2LLTTCCJ +OOGDIC5J +XXGDIIKJ +RRG6UKKB +HEEAUMKB +HHEAMMMB +VPPWFQQB +VNNWFSSS + +A 2- +B 14+ +C 49x +D 5+ +E 96x +F 2/ +G 14+ +H 96x +I 36x +J 17+ +K 15+ +L 2/ +M 12+ +N 4- +O 14+ +P 4- +Q 24x +R 2- +S 19+ +T 2- +U 15+ +V 8+ +W 4/ +X 4+ + +2 8 4 3 5 1 7 6 +8 6 2 1 3 7 5 4 +3 1 5 4 6 2 8 7 +5 3 7 6 8 4 2 1 +4 2 6 5 7 3 1 8 +6 4 8 7 1 5 3 2 +7 5 1 8 2 6 4 3 +1 7 3 2 4 8 6 5 + +8x8:d10 (medium) +IIRNLYYU +XXRNLSSU +TTRRHSBB +P1FRHHDB +PFFQKKDD +GGQQK2CC +OWWJVEEE +OMMJVAA5 + +A 5- +B 24x +C 42x +D 8x +E 17+ +F 17+ +G 5+ +H 14+ +I 3- +J 1- +K 16+ +L 5+ +M 1- +N 11+ +O 15+ +P 10+ +Q 14+ +R 27+ +S 15+ +T 3- +U 9+ +V 1- +W 5+ +X 10x +Y 2/ + +5 8 1 7 4 6 3 2 +2 5 6 4 1 3 8 7 +3 6 7 5 2 4 1 8 +6 1 2 8 5 7 4 3 +4 7 8 6 3 5 2 1 +1 4 5 3 8 2 7 6 +7 2 3 1 6 8 5 4 +8 3 4 2 7 1 6 5 + +8x8:d11 (medium) +QJSSLCCC +QJJLLOOV +HRJ5MOKV +HRDDMMK6 +5IIIFBKT +GPPPFB2T +GEEEUUAA +GGGEENNN + +A 2- +B 6- +C 14+ +D 14x +E 26+ +F 12+ +G 21+ +H 6- +I 144x +J 17+ +K 18+ +L 48x +M 15x +N 10+ +O 84x +P 24x +Q 4/ +R 2/ +S 5- +T 4/ +U 14+ +V 20x + +2 5 3 8 4 6 1 7 +8 3 1 6 2 4 7 5 +7 2 8 5 1 3 6 4 +1 4 2 7 3 5 8 6 +5 8 6 3 7 1 4 2 +3 6 4 1 5 7 2 8 +4 7 5 2 6 8 3 1 +6 1 7 4 8 2 5 3 + +8x8:d10 (medium) +DMMMKKNN +DMQWWHN7 +XXQPHHOO +RR1PPPPV +ISJLLLGV +ISJBEEGG +TTJBECCU +T2AAFFFU + +A 2- +B 1- +C 13+ +D 40x +E 40x +F 24x +G 144x +H 10+ +I 2- +J 120x +K 11+ +L 13+ +M 13+ +N 20x +O 5+ +P 25+ +Q 16x +R 2- +S 6- +T 14+ +U 2- +V 40x +W 10+ +X 2- + +8 6 3 1 7 4 5 2 +5 3 8 6 4 1 2 7 +7 5 2 8 6 3 4 1 +6 4 1 7 5 2 3 8 +3 1 6 4 2 7 8 5 +1 7 4 2 8 5 6 3 +2 8 5 3 1 6 7 4 +4 2 7 5 3 8 1 6 + +8x8:d10 (medium) +CC7BBBFQ +UHIENNFQ +UHIENVVQ +KKKTTJJL +GGTTPP3L +MGG6AADD +MGRR4XXS +MMRWWOOS + +A 6- +B 30x +C 5- +D 2/ +E 9+ +F 1- +G 15+ +H 56x +I 1- +J 3- +K 72x +L 6- +M 20+ +N 7+ +O 5- +P 2- +Q 9+ +R 24x +S 1- +T 22+ +U 20x +V 18x +W 20x +X 7+ + +6 1 7 2 3 5 8 4 +5 8 6 1 2 4 7 3 +4 7 5 8 1 3 6 2 +3 6 4 7 8 2 5 1 +1 4 2 5 6 8 3 7 +2 5 3 6 7 1 4 8 +7 2 8 3 4 6 1 5 +8 3 1 4 5 7 2 6 + +8x8:d9 (medium) +LLO4GGXX +VVORG2QQ +BBORIII7 +1BMUUINN +CMMKUJAS +CTEKFJAS +TTEDFFPY +HHHDWWPY + +A 1- +B 15+ +C 20x +D 1- +E 24x +F 11+ +G 60x +H 10+ +I 17+ +J 9+ +K 1- +L 14x +M 14+ +N 11+ +O 16+ +P 9+ +Q 40x +R 8+ +S 7+ +T 144x +U 84x +V 7+ +W 2- +X 7+ +Y 3+ + +7 2 8 4 5 3 1 6 +6 1 7 3 4 2 8 5 +8 3 1 5 6 4 2 7 +1 4 2 6 7 5 3 8 +4 7 5 1 2 8 6 3 +5 8 6 2 3 1 7 4 +3 6 4 8 1 7 5 2 +2 5 3 7 8 6 4 1 + +8x8:d10 (medium) +5DPPWWAA +VDPPIIIK +VBBB6HHK +NLLGGR8K +NQLGGRJU +SQ7GOXJU +S7EMOXFF +CCEMM2TT + +A 5- +B 24x +C 13+ +D 12x +E 30x +F 3- +G 22+ +H 2- +I 56x +J 2/ +K 120x +L 7+ +M 12+ +N 2/ +O 1- +P 16+ +Q 13+ +R 5- +S 9+ +T 5+ +U 11+ +V 2- +W 9+ +X 12x + +5 4 3 6 1 8 2 7 +4 3 2 5 8 7 1 6 +2 1 8 3 6 5 7 4 +3 2 1 4 7 6 8 5 +6 5 4 7 2 1 3 8 +1 8 7 2 5 4 6 3 +8 7 6 1 4 3 5 2 +7 6 5 8 3 2 4 1 + +8x8:d9 (medium) +RRD4FFFQ +RRDJJTTQ +7NDOOTMQ +NNEOGHMM +VNEOGHCC +V8LLLBBC +IUKKAAPP +IU3KSSWW + +A 4- +B 5+ +C 12x +D 15+ +E 3- +F 60x +G 5- +H 3- +I 1- +J 2- +K 15+ +L 13+ +M 72x +N 112x +O 384x +P 6- +Q 280x +R 10+ +S 4- +T 168x +U 30x +V 1- +W 16x + +1 3 8 4 2 6 5 7 +2 4 1 5 3 7 6 8 +7 1 6 2 8 4 3 5 +8 2 7 3 1 5 4 6 +5 7 4 8 6 2 1 3 +6 8 5 1 7 3 2 4 +3 5 2 6 4 8 7 1 +4 6 3 7 5 1 8 2 + +8x8:d10 (medium) +LLHHMIEE +LLPHMI5E +CCPP4TKK +CC2NOTXX +VBBNOSGG +VBQWWSDU +JQQQQRDU +J1FFRRAA + +A 1- +B 15+ +C 18+ +D 4+ +E 120x +F 3- +G 1- +H 17+ +I 1- +J 12x +K 15+ +L 448x +M 3+ +N 15x +O 14+ +P 10+ +Q 24+ +R 96x +S 42x +T 6+ +U 6- +V 9+ +W 3- +X 1- + +1 8 4 7 2 3 6 5 +8 7 3 6 1 2 5 4 +3 2 6 1 4 5 8 7 +7 6 2 5 8 1 4 3 +5 4 8 3 6 7 2 1 +4 3 7 2 5 6 1 8 +6 5 1 4 7 8 3 2 +2 1 5 8 3 4 7 6 + +8x8:d10 (medium) +CCHGGPKK +TJHDDPPK +TJFF2PPV +YYLLOONV +MIIBOONV +MBBBXXSS +ZZRQQAEE +UURWWAAE + +A 140x +B 20+ +C 1- +D 1- +E 17+ +F 1- +G 3+ +H 2- +I 2/ +J 3- +K 12+ +L 1- +M 15x +N 5+ +O 60x +P 2520x +Q 7+ +R 3- +S 6- +T 7+ +U 1- +V 40x +W 7- +X 12+ +Y 1- +Z 1- + +8 7 3 2 1 5 6 4 +6 5 1 8 7 3 4 2 +1 8 4 3 2 6 7 5 +4 3 7 6 5 1 2 8 +5 4 8 7 6 2 3 1 +3 2 6 5 4 8 1 7 +2 1 5 4 3 7 8 6 +7 6 2 1 8 4 5 3 + +8x8:d11 (medium) +AIOEEEQQ +AIOOOEQW +MMNNO8TW +GGCCLLTW +UUUHXKKD +RRRHXBSD +1JJPPBSS +FFJPVV2S + +A 20x +B 9+ +C 10x +D 4- +E 26+ +F 2- +G 2- +H 2/ +I 1- +J 21+ +K 2- +L 4+ +M 2- +N 5+ +O 168x +P 12+ +Q 12x +R 13+ +S 1400x +T 1- +U 112x +V 2- +W 15+ +X 4- + +5 3 2 7 8 6 4 1 +4 2 1 6 7 5 3 8 +7 5 4 1 2 8 6 3 +8 6 5 2 3 1 7 4 +2 8 7 4 5 3 1 6 +6 4 3 8 1 7 5 2 +1 7 6 3 4 2 8 5 +3 1 8 5 6 4 2 7 + +8x8:d10 (medium) +7IILCCCC +PPILQFDD +VVMUQFTT +AMMUU3OY +AHJJ1OOY +GHBJNEKK +GGBRNEXX +WWRRNSS6 + +A 7+ +B 1- +C 40x +D 1- +E 1- +F 3- +G 11+ +H 35x +I 17+ +J 30x +K 1- +L 7+ +M 8+ +N 16+ +O 144x +P 6x +Q 3- +R 56x +S 10x +T 5+ +U 16+ +V 1- +W 9+ +X 7- +Y 3- + +7 8 3 6 2 1 4 5 +2 3 6 1 5 4 7 8 +5 6 1 4 8 7 2 3 +1 2 5 8 4 3 6 7 +6 7 2 5 1 8 3 4 +4 5 8 3 7 6 1 2 +3 4 7 2 6 5 8 1 +8 1 4 7 3 2 5 6 + +8x8:d9 (medium) +FFIII7AA +PPIMTBBE +RRUMTTBE +5HUUKKEE +HHDDKKKJ +WW5LLNNJ +CCVV7QQO +SSS1GGGO + +A 9+ +B 8+ +C 3- +D 12x +E 112x +F 5- +G 13+ +H 28x +I 17+ +J 48x +K 480x +L 18x +M 3- +N 14x +O 1- +P 5- +Q 2/ +R 2/ +S 224x +T 40x +U 20+ +V 3+ +W 5+ + +1 6 2 3 8 7 4 5 +3 8 4 5 2 1 6 7 +6 3 7 8 5 4 1 2 +5 2 6 7 4 3 8 1 +2 7 3 4 1 8 5 6 +4 1 5 6 3 2 7 8 +8 5 1 2 7 6 3 4 +7 4 8 1 6 5 2 3 + +8x8:d10 (medium) +LL2XAFJG +TTTXAFJG +RCCUAQJG +RKKUQQDD +IENMMMS7 +IEN7SSSW +HHOVBBWW +H7OVYYPP + +A 7+ +B 5- +C 2- +D 5+ +E 6+ +F 3- +G 13+ +H 15+ +I 4+ +J 42x +K 2- +L 1- +M 40x +N 10+ +O 8+ +P 3- +Q 14+ +R 40x +S 19+ +T 10+ +U 2/ +V 2- +W 16+ +X 13+ +Y 5- + +7 8 2 5 1 4 6 3 +2 3 5 8 4 7 1 6 +8 1 3 6 2 5 7 4 +5 6 8 3 7 2 4 1 +3 4 6 1 5 8 2 7 +1 2 4 7 3 6 8 5 +4 5 7 2 6 1 3 8 +6 7 1 4 8 3 5 2 + +8x8:d12 (hard) +XGT6CZZA +XGTTC1AA +SSIIMQQQ +EEHBMNNF +OOHBRRYF +UUUU3PYV +LJJJJPKV +LJDDWWK5 + +A 11+ +B 3- +C 2- +D 5- +E 2- +F 3- +G 6- +H 7+ +I 11+ +J 21+ +K 7+ +L 2/ +M 8+ +N 16x +O 2- +P 2- +Q 12+ +R 3- +S 35x +T 16+ +U 70x +V 10+ +W 24x +X 2/ +Y 1- +Z 35x + +2 8 3 6 4 7 5 1 +4 2 5 8 6 1 7 3 +7 5 8 3 1 4 2 6 +5 3 6 1 7 2 8 4 +8 6 1 4 2 5 3 7 +1 7 2 5 3 6 4 8 +3 1 4 7 5 8 6 2 +6 4 7 2 8 3 1 5 + +8x8:d14 (hard) +TTFL3CDU +ZTFLLCDU +Z7GGVVDE +HQQNNEEE +HSSXJJBB +KKSXRRRB +WPYIIIAA +WPYOOMM5 + +A 2- +B 10+ +C 1- +D 15+ +E 48x +F 3+ +G 3- +H 3- +I 112x +J 1- +K 8+ +L 168x +M 1- +N 11+ +O 3- +P 24x +Q 1- +R 21+ +S 9+ +T 16x +U 15+ +V 1- +W 2- +X 7- +Y 2- +Z 2- + +2 8 1 6 3 4 5 7 +3 1 2 7 4 5 6 8 +1 7 8 5 2 3 4 6 +7 5 6 3 8 1 2 4 +4 2 3 8 5 6 7 1 +5 3 4 1 6 7 8 2 +6 4 5 2 7 8 1 3 +8 6 7 4 1 2 3 5 + +8x8:d15 (hard) +PVLLCCRR +PVLDIIIJ +N5DDTTIJ +NGODDKSS +3GOMMKKK +HUOMAEQQ +HU6BAEQF +HHBBAE8F + +A 8+ +B 280x +C 5- +D 19+ +E 17+ +F 1- +G 3- +H 168x +I 252x +J 5- +K 240x +L 72x +M 13+ +N 9+ +O 14x +P 1- +Q 28x +R 9+ +S 3- +T 3- +U 3- +V 7- + +5 1 4 6 8 3 7 2 +4 8 3 5 7 2 6 1 +1 5 8 2 4 7 3 6 +8 4 7 1 3 6 2 5 +3 7 2 4 6 1 5 8 +2 6 1 3 5 8 4 7 +7 3 6 8 2 5 1 4 +6 2 5 7 1 4 8 3 + +8x8:d13 (hard) +LCCF5RMM +L4FFPRHH +LUN1PRAV +KUNTTGAV +KUJJGG7B +IIDEEOBB +IDDQEOXX +WWDQQSS4 + +A 1- +B 24x +C 6- +D 14+ +E 48x +F 13+ +G 8x +H 8+ +I 70x +J 3- +K 9+ +L 10+ +M 10+ +N 1- +O 3- +P 10+ +Q 18+ +R 17+ +S 3- +T 1- +U 21+ +V 15+ +W 5- +X 2- + +6 1 7 4 5 3 8 2 +1 4 2 7 8 6 3 5 +3 6 4 1 2 8 5 7 +4 7 5 2 3 1 6 8 +5 8 6 3 4 2 7 1 +2 5 3 8 1 7 4 6 +7 2 8 5 6 4 1 3 +8 3 1 6 7 5 2 4 + +8x8:d13 (hard) +SSSIIXXE +LUUNNRRE +LUNNJYFK +CCCWJYFK +PPPWQTFO +DPMMQT3O +D5GVBAAH +DGGVBHHH + +A 8+ +B 3- +C 17+ +D 126x +E 1- +F 32x +G 15+ +H 48x +I 4- +J 7+ +K 24x +L 6+ +M 2- +N 96x +O 9+ +P 14+ +Q 40x +R 12+ +S 8x +T 7+ +U 168x +V 11+ +W 6+ +X 2- +Y 3- + +2 4 1 7 3 8 6 5 +1 3 8 6 2 7 5 4 +5 7 4 2 6 3 1 8 +8 2 7 5 1 6 4 3 +4 6 3 1 5 2 8 7 +7 1 6 4 8 5 3 2 +3 5 2 8 4 1 7 6 +6 8 5 3 7 4 2 1 + +8x8:d14 (hard) +RPPMMDDD +RPJWLGGG +TTJWLLHN +XXSQQLHN +BBS1OUUN +VVEIOUAA +CCEIIK8A +CC6IKKFF + +A 18+ +B 1- +C 10+ +D 6x +E 7+ +F 10+ +G 16+ +H 1- +I 420x +J 6+ +K 35x +L 21+ +M 2- +N 9+ +O 5- +P 105x +Q 4+ +R 2/ +S 16x +T 42x +U 13+ +V 9+ +W 4- +X 1- + +4 5 7 8 6 2 3 1 +2 3 5 6 4 8 1 7 +6 7 1 2 8 4 5 3 +7 8 2 3 1 5 6 4 +5 6 8 1 7 3 4 2 +8 1 3 4 2 6 7 5 +1 2 4 5 3 7 8 6 +3 4 6 7 5 1 2 8 + +8x8:d12 (hard) +SSSQQQKI +MAAF8CKI +MVAFCCEI +RVPPEEEE +ROOPNU1B +WWJ2NULB +HJJJJDLB +HTTGGDDB + +A 12+ +B 210x +C 18+ +D 6x +E 1440x +F 7- +G 7+ +H 2- +I 15+ +J 27+ +K 10+ +L 32x +M 3+ +N 1- +O 2- +P 13+ +Q 40x +R 2/ +S 42x +T 2- +U 56x +V 1- +W 1- + +6 7 1 5 4 2 3 8 +2 3 5 1 8 6 7 4 +1 2 4 8 7 5 6 3 +8 1 3 7 6 4 5 2 +4 5 7 3 2 8 1 6 +3 4 6 2 1 7 8 5 +7 8 2 6 5 3 4 1 +5 6 8 4 3 1 2 7 + +8x8:d13 (hard) +SVVJOM5L +SCCJOMFL +IQQ5OOFF +IQWWAAFT +HHDDRNTT +H5RRRNKK +HEBBGGPK +EEUUUPP6 + +A 21x +B 3- +C 1- +D 5- +E 24x +F 15+ +G 4- +H 14+ +I 20x +J 6- +K 56x +L 2- +M 24x +N 3- +O 18+ +P 112x +Q 23+ +R 72x +S 48x +T 48x +U 10+ +V 1- +W 5- + +6 1 2 7 8 4 5 3 +8 3 4 1 2 6 7 5 +4 7 8 5 6 2 3 1 +5 8 1 6 7 3 4 2 +7 2 3 8 1 5 6 4 +2 5 6 3 4 8 1 7 +3 6 7 4 5 1 2 8 +1 4 5 2 3 7 8 6 + +8x8:d13 (hard) +OOJJ4ITT +RRYYIIIT +3QQQUUGG +CCPHUMEE +XXPHLMM3 +BBNHLKKK +SSNDDFVA +SSNWWFVA + +A 6- +B 28x +C 2/ +D 30x +E 13+ +F 2- +G 3- +H 13+ +I 25+ +J 7+ +K 12+ +L 9+ +M 12+ +N 12+ +O 5- +P 35x +Q 19+ +R 7+ +S 22+ +T 15+ +U 8+ +V 2/ +W 12x +X 5+ +Y 5- + +1 6 2 5 4 7 3 8 +5 2 6 1 8 3 7 4 +3 8 4 7 6 1 5 2 +6 3 7 2 1 4 8 5 +4 1 5 8 7 2 6 3 +7 4 8 3 2 5 1 6 +2 7 3 6 5 8 4 1 +8 5 1 4 3 6 2 7 + +8x8:d12 (hard) +7QPEJIII +QQPEJSSS +DQ4OJSAA +DUUOVN1G +LCC2VNNG +LCCBBKT3 +LFFF6KTH +LRRRRMMH + +A 1- +B 5+ +C 1680x +D 10x +E 11+ +F 20x +G 1- +H 2- +I 13+ +J 10+ +K 3- +L 72x +M 3- +N 15+ +O 11+ +P 3+ +Q 20+ +R 168x +S 17+ +T 5- +U 1- +V 1- + +7 8 1 5 2 6 3 4 +8 1 2 6 3 7 4 5 +2 3 4 8 5 1 6 7 +5 6 7 3 8 4 1 2 +4 5 6 2 7 3 8 1 +6 7 8 4 1 5 2 3 +3 4 5 1 6 2 7 8 +1 2 3 7 4 8 5 6 + +8x8:d13 (hard) +1VAEERRM +FVASSSCM +FHOOOUCM +HHTTXUKK +DDLLXG7K +JJ4QXGNN +WJQQPPYB +WWIIP8YB + +A 56x +B 5+ +C 3+ +D 4- +E 12x +F 1- +G 8+ +H 13+ +I 7+ +J 16+ +K 11+ +L 2/ +M 21+ +N 2- +O 12+ +P 30x +Q 14x +R 10+ +S 12+ +T 24x +U 10+ +V 11+ +W 14+ +X 17+ +Y 3- + +1 5 7 4 3 2 8 6 +2 6 8 5 4 3 1 7 +3 7 1 6 5 4 2 8 +5 1 3 8 7 6 4 2 +8 4 6 3 2 1 7 5 +6 2 4 1 8 7 5 3 +4 8 2 7 6 5 3 1 +7 3 5 2 1 8 6 4 + +8x8:d15 (hard) +V1SSJJXL +VBBH3RXL +8QBHPRTT +QQQAPO4T +GGFAEOMM +KGFEEWWI +KKUUNNII +KKYYDDCC + +A 1- +B 9+ +C 1- +D 3+ +E 245x +F 2- +G 15+ +H 1- +I 11+ +J 42x +K 23+ +L 5- +M 1- +N 9+ +O 7- +P 16x +Q 21+ +R 1- +S 3- +T 210x +U 2/ +V 3- +W 7+ +X 5- +Y 11+ + +4 1 5 8 6 7 2 3 +1 6 2 5 3 4 7 8 +8 5 1 4 2 3 6 7 +6 3 7 2 8 1 4 5 +5 2 6 1 7 8 3 4 +3 8 4 7 5 6 1 2 +2 7 3 6 4 5 8 1 +7 4 8 3 1 2 5 6 + +8x8:d12 (hard) +VGGDDNNN +VOKKDQ7B +COWK8QMB +CEWJJJMU +PEESJ4HU +PP6SSHHA +FXLLIIRA +FXLTTIRR + +A 10+ +B 6+ +C 8+ +D 14+ +E 11+ +F 5+ +G 2- +H 12+ +I 42x +J 14+ +K 24x +L 12+ +M 4- +N 15+ +O 2- +P 20+ +Q 12+ +R 144x +S 7+ +T 4/ +U 9+ +V 11+ +W 21x +X 11+ + +3 6 4 7 1 8 2 5 +8 3 1 4 6 5 7 2 +2 5 3 6 8 7 1 4 +6 1 7 2 4 3 5 8 +7 2 8 3 5 4 6 1 +5 8 6 1 3 2 4 7 +1 4 2 5 7 6 8 3 +4 7 5 8 2 1 3 6 + +8x8:d13 (hard) +FQQNBCCW +FQQNBBCW +JJUU2PPP +VDYYLLL6 +VDGGAK6T +M3SSAKIT +MRXXEIII +RROOE7HH + +A 6- +B 18x +C 17+ +D 1- +E 20x +F 7+ +G 1- +H 1- +I 17+ +J 5- +K 2/ +L 120x +M 24x +N 3- +O 1- +P 20+ +Q 22+ +R 12+ +S 9+ +T 12+ +U 1- +V 1- +W 5+ +X 13+ +Y 3+ + +2 7 5 4 3 6 8 1 +5 2 8 7 6 1 3 4 +1 6 4 3 2 5 7 8 +7 4 2 1 8 3 5 6 +8 5 3 2 1 4 6 7 +6 3 1 8 7 2 4 5 +4 1 7 6 5 8 2 3 +3 8 6 5 4 7 1 2 + +8x8:d12 (hard) +DDPPPGXX +8FFTEGBB +V2FTEYYB +VNNTAUCC +OOKTAUIC +MOK4QQIW +MSKJJHWW +SSSLLHRR + +A 1- +B 90x +C 16x +D 3/ +E 4/ +F 28x +G 6- +H 2- +I 8+ +J 1- +K 14+ +L 1- +M 8+ +N 40x +O 15+ +P 12+ +Q 5- +R 5+ +S 15+ +T 126x +U 1- +V 2- +W 120x +X 56x +Y 1- + +2 6 3 5 4 1 8 7 +8 4 1 3 2 7 6 5 +6 2 7 1 8 5 4 3 +4 8 5 7 6 3 2 1 +3 7 4 6 5 2 1 8 +1 5 2 4 3 8 7 6 +7 3 8 2 1 6 5 4 +5 1 6 8 7 4 3 2 + +8x8:d15 (hard) +WCC7RRXX +WGMOO5II +DGMMUUI8 +DBBTJUAA +HKKTJPAA +H4TTJPVV +QQ5SSFVN +LLLEEFNN + +A 20+ +B 5- +C 7+ +D 2- +E 8+ +F 1- +G 1- +H 8+ +I 16+ +J 21+ +K 5- +L 224x +M 8+ +N 6x +O 2/ +P 2- +Q 7- +R 3- +S 2- +T 12+ +U 7+ +V 15+ +W 5- +X 7+ + +2 1 6 7 5 8 3 4 +7 6 3 4 2 5 8 1 +6 5 2 3 1 4 7 8 +4 3 8 1 7 2 5 6 +3 2 7 8 6 1 4 5 +5 4 1 2 8 3 6 7 +1 8 5 6 4 7 2 3 +8 7 4 5 3 6 1 2 + +8x8:d13 (hard) +FFFWHUUB +FEEWHU5B +OTTIINNB +OOYQI7MM +AAYQ3VPP +SSJDDVRG +XJJKKLRG +XCCLLLRG + +A 7+ +B 12x +C 4+ +D 9+ +E 6- +F 210x +G 18+ +H 3- +I 112x +J 40x +K 1- +L 120x +M 1- +N 6+ +O 120x +P 9+ +Q 3+ +R 15+ +S 28x +T 10+ +U 13+ +V 2- +W 2/ +X 9+ +Y 1- + +2 5 3 6 7 4 8 1 +7 2 8 3 4 1 5 6 +3 6 4 7 8 5 1 2 +5 8 6 1 2 7 3 4 +6 1 7 2 3 8 4 5 +4 7 5 8 1 6 2 3 +1 4 2 5 6 3 7 8 +8 3 1 4 5 2 6 7 + +8x8:d13 (hard) +VVVLOOO5 +DDVLWKKS +UXRRWWWS +UXGTTPPP +QXG1YHNM +Q7FEYHNM +QJFEIIAA +JJCC6IBB + +A 8+ +B 2- +C 2- +D 2/ +E 10+ +F 14+ +G 4- +H 3- +I 128x +J 15x +K 7+ +L 5- +M 2/ +N 5+ +O 13+ +P 48x +Q 16+ +R 8+ +S 2/ +T 4- +U 24x +V 15+ +W 17+ +X 96x +Y 7+ + +7 1 2 8 4 6 3 5 +2 4 5 3 7 1 6 8 +6 8 1 7 3 5 2 4 +4 6 7 5 1 3 8 2 +8 2 3 1 5 7 4 6 +5 7 8 6 2 4 1 3 +3 5 6 4 8 2 7 1 +1 3 4 2 6 8 5 7 + +8x8:d11 (hard) +UGOO7QNN +UGFFFQSN +IIBBYYS4 +R4WDDHHE +RRWKK8HE +LXAAKKHH +LXATTCCV +PPMMTJJV + +A 11+ +B 1- +C 1- +D 1- +E 4+ +F 60x +G 2- +H 18+ +I 30x +J 20x +K 17+ +L 6- +M 1- +N 18+ +O 1- +P 1- +Q 8+ +R 10+ +S 4- +T 36x +U 6+ +V 42x +W 24x +X 2- +Y 4+ + +4 3 5 6 7 1 8 2 +2 1 3 4 5 7 6 8 +6 5 7 8 1 3 2 4 +5 4 6 7 8 2 1 3 +3 2 4 5 6 8 7 1 +7 6 8 1 2 4 3 5 +1 8 2 3 4 6 5 7 +8 7 1 2 3 5 4 6 + +8x8:d8 (hard) +QQQ7PFTT +IIUPPFFT +W5UPDDML +WJURR3ML +BJOOVVMC +BKKK1NEC +SSAXXNEE +YYAGGHHH + +A 5+ +B 2- +C 2- +D 1- +E 14+ +F 16+ +G 5- +H 14+ +I 24x +J 3- +K 60x +L 1- +M 14+ +N 3/ +O 5+ +P 144x +Q 48x +R 3- +S 14x +T 30x +U 84x +V 1- +W 15+ +X 13+ +Y 3- + +1 6 8 7 4 5 2 3 +3 8 2 1 6 7 4 5 +8 5 7 6 3 4 1 2 +7 4 6 5 2 3 8 1 +4 1 3 2 7 8 5 6 +6 3 5 4 1 2 7 8 +2 7 1 8 5 6 3 4 +5 2 4 3 8 1 6 7 + +8x8:d7 (hard) +5AAATTJJ +RF5KKTGG +RFDDNNLL +RUUOWHHH +PPUOWHMM +CCCQ6XXM +SCVQBBBM +SSV4IIEE + +A 18+ +B 16+ +C 36x +D 2/ +E 9+ +F 11+ +G 11+ +H 22+ +I 2- +J 5- +K 7- +L 2/ +M 48x +N 8+ +O 10x +P 5- +Q 42x +R 9+ +S 15+ +T 8+ +U 14+ +V 6- +W 5+ +X 3- + +5 8 7 3 2 4 1 6 +3 6 5 1 8 2 7 4 +2 5 4 8 7 1 6 3 +4 7 6 2 1 3 8 5 +7 2 1 5 4 6 3 8 +1 4 3 7 6 8 5 2 +8 3 2 6 5 7 4 1 +6 1 8 4 3 5 2 7 + +8x8:d7 (hard) +OOHUURLL +BOHD3RWW +BOHDAKQQ +7VVVAKNN +X5CCAFFF +XEEEIZZ5 +JGGYIITM +JSSYPPTM + +A 12+ +B 3- +C 48x +D 5- +E 13+ +F 56x +G 7+ +H 10+ +I 12+ +J 8x +K 13+ +L 5- +M 2- +N 5- +O 23+ +P 7- +Q 5+ +R 1- +S 11+ +T 8+ +U 3- +V 8x +W 5- +X 4+ +Y 2- +Z 4/ + +6 8 3 1 4 5 7 2 +5 7 2 8 3 4 6 1 +8 2 5 3 6 7 1 4 +7 1 4 2 5 6 8 3 +3 5 8 6 1 2 4 7 +1 3 6 4 7 8 2 5 +4 6 1 7 2 3 5 8 +2 4 7 5 8 1 3 6 + +8x8:d7 (hard) +MSSPPIIV +MWWL8IQV +8CLLGEQQ +FC5LGEED +FTKKGRED +HTOKGRRR +HJOOBBNN +HJUUAA5N A 1- -B 6+ -C 12x +B 13+ +C 11+ +D 7- +E 22+ +F 1- +G 120x +H 14+ +I 12+ +J 5+ +K 72x +L 56x +M 1- +N 36x +O 280x +P 13+ +Q 168x +R 14+ +S 1- +T 1- +U 11+ +V 9+ +W 1- + +5 2 1 6 7 8 3 4 +6 3 2 7 8 1 4 5 +8 5 4 1 2 3 6 7 +1 6 5 2 3 4 7 8 +2 7 6 3 4 5 8 1 +3 8 7 4 5 6 1 2 +4 1 8 5 6 7 2 3 +7 4 3 8 1 2 5 6 + +8x8:d8 (hard) +LFFF7TTG +L2UMKEEG +VDUMKJJJ +VDIQQJ4H +VIIWWRRH +OOSSWPYY +XOCCCPAA +XBB4CPNN + +A 5+ +B 24x +C 840x D 8+ -E 30x -F 15+ -G 3- +E 11+ +F 60x +G 11+ +H 1- +I 19+ +J 150x +K 5+ +L 5+ +M 11+ +N 7+ +O 72x +P 17+ +Q 6x +R 12x +S 6+ +T 9+ +U 3- +V 21+ +W 9+ +X 3- +Y 14x -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 +4 5 2 6 7 1 8 3 +1 2 7 3 4 6 5 8 +6 7 4 8 1 3 2 5 +8 1 6 2 3 5 4 7 +7 8 5 1 2 4 3 6 +3 4 1 5 6 8 7 2 +5 6 3 7 8 2 1 4 +2 3 8 4 5 7 6 1 -5x5:d5 -.KK "2:(d=5)" -FFI2D -3GICD -EGCCA -EBJHA -EBJHH +8x8:d5 (hard) +UUQQQAEE +3RRCXAOE +SS4CXPOO +GGFF3PPH +JJNNNWWH +KKTTI3MM +VKTBIIIM +VDDBILL8 -A 2- +A 3/ +B 3- +C 7+ +D 2/ +E 15+ +F 1- +G 1- +H 1- +I 23+ +J 1- +K 448x +L 14x +M 120x +N 84x +O 15+ +P 20x +Q 13+ +R 3- +S 3+ +T 6+ +U 13+ +V 5+ +W 24x +X 56x + +7 6 1 8 4 2 5 3 +3 2 5 4 8 6 1 7 +2 1 4 3 7 5 8 6 +6 5 8 7 3 1 4 2 +5 4 7 6 2 8 3 1 +8 7 2 1 5 3 6 4 +1 8 3 2 6 4 7 5 +4 3 6 5 1 7 2 8 + +8x8:d6 (hard) +NNPPYYHH +GGPOOEAU +WWWOQEAU +RRD7QMAU +RRDSSMJJ +KKBZZFJ2 +VVBT2FXI +2LLTCCXI + +A 7+ +B 24x +C 5+ +D 5- +E 24x +F 2- +G 56x +H 56x +I 2- +J 90x +K 13+ +L 5- +M 11+ +N 1- +O 16x +P 30x +Q 1- +R 12+ +S 12x +T 15x +U 24x +V 9+ +W 14+ +X 35x +Y 3- +Z 9+ + +3 4 1 6 5 2 8 7 +7 8 5 2 1 6 4 3 +5 6 3 8 7 4 2 1 +4 5 2 7 6 3 1 8 +1 2 7 4 3 8 6 5 +6 7 4 1 8 5 3 2 +8 1 6 3 2 7 5 4 +2 3 8 5 4 1 7 6 + +8x8:d8 (hard) +AAGGGJJP +VVYMRRRP +OOYM2LLE +UUSSTXXE +UUKKTHH2 +QQWKT1HC +NNWWBDHC +NIIBBDFF + +A 1- B 12x -C 12x +C 40x +D 11+ +E 21x +F 12+ +G 21+ +H 72x +I 3/ +J 3- +K 20+ +L 11+ +M 24x +N 392x +O 11+ +P 7+ +Q 1- +R 14+ +S 1- +T 14+ +U 12+ +V 9+ +W 11+ +X 5- +Y 3- + +4 3 7 6 8 2 5 1 +1 8 4 3 5 7 2 6 +6 5 1 8 2 4 7 3 +2 1 5 4 6 8 3 7 +5 4 8 7 1 3 6 2 +3 2 6 5 7 1 4 8 +8 7 3 2 4 6 1 5 +7 6 2 1 3 5 8 4 + +8x8:d6 (hard) +GGGTYEEE +GDDTYEUB +PPHHCCUB +ZOOOKCU5 +ZVRRKKQQ +FVI3WWNN +FIIJJ8SL +XXAAMMSL + +A 2- +B 3- +C 42x +D 3- +E 20+ +F 3/ +G 18+ +H 35x +I 10+ +J 21x +K 8x +L 2/ +M 3- +N 1- +O 17+ +P 4/ +Q 1- +R 4/ +S 10x +T 3+ +U 15+ +V 13+ +W 11+ +X 8+ +Y 30x +Z 2- + +4 6 3 1 5 2 7 8 +5 7 4 2 6 3 8 1 +8 2 7 5 1 6 3 4 +1 3 8 6 2 7 4 5 +3 5 2 8 4 1 6 7 +6 8 5 3 7 4 1 2 +2 4 1 7 3 8 5 6 +7 1 6 4 8 5 2 3 + +8x8:d7 (hard) +SSIYYHH8 +USIGCC5D +UA4GGVVD +UAWWEEBB +U7MMLBBK +FNNLLTTK +FNJJRTQQ +OOPPR7XX + +A 2/ +B 19+ +C 5+ +D 3- +E 3- +F 6+ +G 40x +H 4- +I 8+ +J 1- +K 3+ +L 18+ +M 12x +N 24x +O 9+ +P 1- +Q 9+ +R 5+ +S 60x +T 256x +U 18+ +V 4- +W 7- +X 1- +Y 12x + +4 5 1 2 6 3 7 8 +2 3 7 8 4 1 5 6 +7 8 4 5 1 6 2 3 +3 4 8 1 5 2 6 7 +6 7 3 4 8 5 1 2 +5 6 2 3 7 4 8 1 +1 2 6 7 3 8 4 5 +8 1 5 6 2 7 3 4 + +8x8:d5 (hard) +DDWMMXXE +IIWNNOOE +UUGGKbFV +6GGZKbFV +aCCZHSFL +aBYPHS6L +QBYP6JJT +Q8AARRTT + +A 42x +B 20x +C 2- +D 5- +E 3- +F 56x +G 11+ +H 1- +I 11+ +J 13+ +K 2- +L 7- +M 5+ +N 7+ +O 11+ +P 12x +Q 4- +R 2/ +S 1- +T 11+ +U 24x +V 10+ +W 3- +X 2/ +Y 6x +Z 13+ +a 6x +b 35x + +7 2 8 1 4 6 3 5 +4 7 5 6 1 3 8 2 +8 3 1 2 5 7 4 6 +6 1 7 8 3 5 2 4 +3 6 4 5 8 2 7 1 +2 5 3 4 7 1 6 8 +1 4 2 3 6 8 5 7 +5 8 6 7 2 4 1 3 + +8x8:d7 (hard) +VVCTOHH4 +QVCTOUUG +QKK5DDUG +QPPJ8MMB +ENNJXX4B +ELAAIIBB +SLAWWIYY +SSAFFFRR + +A 126x +B 1680x +C 7+ +D 12x +E 3- +F 24x +G 5- +H 15x +I 7+ +J 7- +K 1- +L 3- +M 35x +N 6x +O 7+ +P 12x +Q 11+ +R 9+ +S 210x +T 14x +U 15+ +V 13+ +W 11+ +X 1- +Y 1- + +8 1 2 7 6 5 3 4 +3 4 5 2 1 8 6 7 +6 7 8 5 4 3 1 2 +2 3 4 1 8 7 5 6 +1 2 3 8 7 6 4 5 +4 5 6 3 2 1 7 8 +7 8 1 6 5 4 2 3 +5 6 7 4 3 2 8 1 + +8x8:d5 (hard) +NVVPPP1T +NNEPFFAT +NEE4JFAT +HHBBJFGG +WHOOLLGC +WQQR4LIC +KKMRSSI1 +UUMMSDDD + +A 5- +B 4- +C 1- +D 15+ +E 20+ +F 756x +G 12+ +H 15+ +I 3/ +J 3+ +K 10+ +L 12+ +M 19+ +N 120x +O 3/ +P 56x +Q 5+ +R 4- +S 18+ +T 112x +U 4+ +V 1- +W 15+ + +3 5 6 2 7 4 1 8 +2 4 5 1 6 3 8 7 +5 7 8 4 1 6 3 2 +6 8 1 5 2 7 4 3 +7 1 2 6 3 8 5 4 +8 2 3 7 4 1 6 5 +4 6 7 3 8 5 2 1 +1 3 4 8 5 2 7 6 + +8x8:d7 (hard) +CC4JGSPP +Y2RJGSSQ +YRRKDXNQ +2OLKDXNM +IOL3FFNM +IAWE1FBB +UAWEVBBT +UUHHVBTT + +A 48x +B 336x +C 1- D 1- -E 8+ -F 3- +E 2- +F 448x G 3- -H 11+ -I 5+ -J 6+ +H 1- +I 20x +J 13+ +K 1- +L 6- +M 10+ +N 56x +O 8+ +P 5- +Q 15x +R 12+ +S 11+ +T 8+ +U 16+ +V 7+ +W 8+ +X 1- +Y 2- -4 1 3 2 5 -3 5 2 1 4 -5 2 4 3 1 -2 4 1 5 3 -1 3 5 4 2 +6 7 4 5 2 1 3 8 +1 2 7 8 5 4 6 3 +3 4 1 2 7 6 8 5 +2 3 8 1 6 5 7 4 +4 5 2 3 8 7 1 6 +5 6 3 4 1 8 2 7 +7 8 5 6 3 2 4 1 +8 1 6 7 4 3 5 2 -5x5:d5 -.KK "3:(d=5)" -ICAAG -ICCGG -BBCE4 -F1DEH -FFD5H +8x8:d6 (hard) +RRRRPPB6 +HHRCC4BT +HJEFCC5T +HJEFDAAT +QMMMDIAO +QMM3LIOO +SSNLLGGO +VVNNUUKK -A 1- +A 6x B 1- -C 16+ -D 1- -E 3- -F 8+ -G 15x -H 5+ -I 1- +C 9+ +D 56x +E 12+ +F 2- +G 6- +H 336x +I 2- +J 6- +K 1- +L 15+ +M 960x +N 16+ +O 24+ +P 9+ +Q 4+ +R 384x +S 7+ +T 40x +U 3+ +V 40x -3 4 1 2 5 -4 5 2 3 1 -2 3 5 1 4 -5 1 3 4 2 -1 2 4 5 3 +8 3 1 2 4 5 7 6 +7 2 8 1 3 4 6 5 +6 1 7 8 2 3 5 4 +4 7 5 6 8 1 3 2 +3 6 4 5 7 8 2 1 +1 4 2 3 5 6 8 7 +2 5 3 4 6 7 1 8 +5 8 6 7 1 2 4 3 -5x5:d7 -.KK "4:(d=7)" -DDIIC -DHIBC -AHHBJ -A4GGJ -FFEE4 +9x9:d5 (easy) +YYggRRKK9 +IIOO3VLLT +XGbbDVeBT +XGEEDJeBT +ff5NDJJZZ +MMNNQHHhh +PWFQQAcCC +PWF8QAcUU +aaaSSS5dd -A 3+ -B 7+ -C 6+ -D 10+ +A 1- +B 7- +C 8+ +D 96x +E 5- +F 1- +G 2/ +H 3+ +I 32x +J 22+ +K 21x +L 6+ +M 14+ +N 11+ +O 3/ +P 3+ +Q 324x +R 3- +S 168x +T 21x +U 2/ +V 12+ +W 11+ +X 16+ +Y 5- +Z 24x +a 12+ +b 4- +c 1- +d 11+ +e 24x +f 4- +g 2/ +h 2- + +6 1 8 4 5 2 3 7 9 +4 8 6 2 3 9 1 5 7 +7 2 9 5 6 3 4 8 1 +9 4 2 7 8 5 6 1 3 +3 7 5 1 2 8 9 4 6 +5 9 7 3 4 1 2 6 8 +2 6 4 9 1 7 8 3 5 +1 5 3 8 9 6 7 2 4 +8 3 1 6 7 4 5 9 2 + +9x9:d5 (easy) +SA6MVVJJJ +SAAM3VVOO +WWWFRaIO7 +WLCFRaINN +5LCPPQQGB +LLUYY9GGB +ZDUYEEX5B +ZDDKKHXXX +ZTTTKHHHX + +A 392x +B 18+ +C 6- +D 13+ E 1- -F 2- -G 6+ -H 10+ -I 9+ -J 6x +F 3+ +G 84x +H 48x +I 1- +J 20x +K 63x +L 648x +M 72x +N 2/ +O 40x +P 28x +Q 9+ +R 1- +S 8- +T 120x +U 4+ +V 15+ +W 54x +X 27+ +Y 120x +Z 19+ +a 30x -4 1 3 2 5 -5 2 4 3 1 -1 3 5 4 2 -2 4 1 5 3 -3 5 2 1 4 +9 7 6 8 2 3 5 1 4 +1 8 7 9 3 4 6 2 5 +3 1 9 2 5 6 8 4 7 +2 9 8 1 4 5 7 3 6 +5 3 2 4 7 8 1 6 9 +6 4 3 5 8 9 2 7 1 +4 2 1 3 6 7 9 5 8 +8 6 5 7 1 2 4 9 3 +7 5 4 6 9 1 3 8 2 -5x5:d6 -.KK "5:(d=6)" -FFADI -BJADI -BJEEE -GCCCC -G4CHH +9x9:d5 (easy) +JJee3FFbK +JJAAA6IbK +XXZZGGICL +X2ZMOOICL +URRMMOO8L +UccSVVdYY +aTHSVVdWW +aTHQEEBB2 +aNNQQDDPP -A 3- -B 20x +A 16x +B 6+ +C 45x +D 8- +E 5- +F 1- +G 2/ +H 42x +I 252x +J 21+ +K 1- +L 16+ +M 20+ +N 4/ +O 16+ +P 1- +Q 160x +R 3- +S 45x +T 1- +U 1- +V 30x +W 5+ +X 15+ +Y 56x +Z 15x +a 168x +b 12x +c 6- +d 3/ +e 11+ + +1 6 9 2 3 7 8 4 5 +9 5 8 1 2 6 7 3 4 +2 7 1 3 4 8 9 5 6 +6 2 5 7 8 3 4 9 1 +5 1 4 6 7 2 3 8 9 +4 9 3 5 6 1 2 7 8 +8 4 7 9 1 5 6 2 3 +7 3 6 8 9 4 5 1 2 +3 8 2 4 5 9 1 6 7 + +9x9:d8 (easy) +XK2MPbbJJ +XKIMPPOO4 +XKIM6RQQC +LLLccRR7C +3DDEGVRFd +HHDEGVZFd +aHTee1ZZS +a1TTWWBBS +AANNUUYYY + +A 4- +B 5- +C 3- +D 21+ +E 4- +F 3/ +G 5- +H 64x +I 5- +J 1- +K 16+ +L 18+ +M 19+ +N 5- +O 14+ +P 16+ +Q 4- +R 32x +S 1- +T 15+ +U 2/ +V 21x +W 13+ +X 14x +Y 42x +Z 19+ +a 1- +b 40x +c 2- +d 6+ +e 15x + +1 6 2 7 9 5 8 4 3 +2 7 3 8 1 6 9 5 4 +7 3 8 4 6 2 5 1 9 +4 9 5 1 3 8 2 7 6 +3 8 4 9 2 7 1 6 5 +8 4 9 5 7 3 6 2 1 +6 2 7 3 5 1 4 9 8 +5 1 6 2 4 9 3 8 7 +9 5 1 6 8 4 7 3 2 + +9x9:d5 (easy) +IIEEHHOOd +IUZBBBc2d +IUZZJJcTT +1XAAJJWWT +XXFFSSSPP +MMMQYKPPb +CC3QYKRRb +Gaa1YYRVN +GDDLLVVVN + +A 10+ +B 432x +C 2- +D 6x +E 7+ +F 14+ +G 13+ +H 2- +I 15+ +J 15+ +K 13+ +L 13+ +M 22+ +N 6- +O 17+ +P 22+ +Q 6- +R 8x +S 64x +T 15+ +U 14+ +V 108x +W 11+ +X 24x +Y 441x +Z 16+ +a 1- +b 27x +c 4- +d 8+ + +4 2 1 6 5 3 9 8 7 +7 5 4 9 8 6 3 2 1 +2 9 8 4 3 1 7 6 5 +1 8 7 3 2 9 6 5 4 +3 1 9 5 4 2 8 7 6 +9 7 6 2 1 8 5 4 3 +6 4 3 8 7 5 2 1 9 +8 6 5 1 9 7 4 3 2 +5 3 2 7 6 4 1 9 8 + +9x9:d5 (easy) +AAKJJ8ZZZ +AAKK4UUDR +YYPBeeIDR +Y3PBQQIVF +EETTOOVVF +ccT1ONNaa +WWGGGbM4a +2HXXCbMLS +HHddCC4LS + +A 20+ +B 56x C 180x -D 2/ -E 15x -F 2- -G 3+ -H 7+ -I 3- -J 6x +D 18x +E 2- +F 1- +G 144x +H 18+ +I 1- +J 35x +K 7+ +L 1- +M 4- +N 2- +O 20+ +P 1- +Q 8- +R 40x +S 10+ +T 270x +U 15x +V 14x +W 2- +X 1- +Y 120x +Z 17+ +a 210x +b 21x +c 48x +d 1- +e 1- -3 1 5 2 4 -5 3 2 4 1 -4 2 1 3 5 -2 5 4 1 3 -1 4 3 5 2 +3 1 4 5 7 8 6 9 2 +9 7 1 2 4 5 3 6 8 +6 4 7 8 1 2 9 3 5 +5 3 6 7 9 1 8 2 4 +4 2 5 6 8 9 7 1 3 +8 6 9 1 3 4 2 5 7 +7 5 8 9 2 3 1 4 6 +2 9 3 4 6 7 5 8 1 +1 8 2 3 5 6 4 7 9 -5x5:d5 -.KK "6:(d=5)" -CHGJJ -CHGFE -DKFFE -DKBII -2BBAA +9x9:d5 (easy) +cM6LLLaff +cMM3GGaCC +EEbbPPBB2 +IEOOYPPZZ +I5QQYJDZX +1NNdYJDeX +RRNdWHHeS +TTUUW6FVS +KKKKAAFVS -A 6+ -B 8+ +A 36x +B 6+ +C 3- +D 13+ +E 126x +F 2/ +G 5- +H 8+ +I 2/ +J 6- +K 21+ +L 15+ +M 192x +N 8+ +O 4- +P 1152x +Q 14x +R 72x +S 12+ +T 8- +U 4- +V 2- +W 1- +X 3- +Y 72x +Z 21x +a 2- +b 36x +c 8+ +d 2- +e 2- +f 4/ + +3 4 6 1 5 9 7 2 8 +5 6 8 3 7 2 9 4 1 +6 7 9 4 8 3 1 5 2 +2 3 5 9 4 8 6 1 7 +4 5 7 2 6 1 8 3 9 +1 2 4 8 3 7 5 9 6 +8 9 2 6 1 5 3 7 4 +9 1 3 7 2 6 4 8 5 +7 8 1 5 9 4 2 6 3 + +9x9:d7 (easy) +1OOJJJETT +COXX3JEZZ +CNXXUUVVV +INNQFFFS1 +I8NQBPPSS +DDMQBBYYS +MMMLLLLRR +KKM2HHaaW +KAAbb4GGW + +A 72x +B 21+ +C 7- +D 11+ +E 12x +F 11+ +G 9+ +H 2- +I 7+ +J 18+ +K 30x +L 26+ +M 21+ +N 24+ +O 15+ +P 3- +Q 21+ +R 1- +S 108x +T 1- +U 7- +V 15+ +W 1- +X 525x +Y 6+ +Z 1- +a 24x +b 7+ + +1 5 4 6 2 9 3 7 8 +2 6 5 7 3 1 4 8 9 +9 4 3 5 1 8 2 6 7 +3 7 6 8 4 2 5 9 1 +4 8 7 9 5 3 6 1 2 +8 3 2 4 9 7 1 5 6 +7 2 1 3 8 6 9 4 5 +6 1 9 2 7 5 8 3 4 +5 9 8 1 6 4 7 2 3 + +9x9:d5 (easy) +ffIIgaBBB +PELLgaUU3 +PEbMMDdYY +hhbTODdGG +VVTTOW2QQ +NVJTFWK8X +N9JeFKKHX +SS4eZCcHX +RRAAZCccc + +A 4- +B 8x C 1- -D 6+ -E 5+ -F 9+ -G 1- -H 6+ -I 12x -J 5+ -K 1- +D 21x +E 5- +F 11+ +G 7- +H 3- +I 24x +J 4- +K 42x +L 9+ +M 54x +N 9+ +O 3- +P 2/ +Q 2- +R 7- +S 2- +T 23+ +U 8- +V 18+ +W 7+ +X 54x +Y 35x +Z 1- +a 72x +b 3/ +c 25+ +d 32x +e 13+ +f 12+ +g 30x +h 2- -4 1 5 3 2 -3 5 4 2 1 -1 3 2 5 4 -5 2 1 4 3 -2 4 3 1 5 +5 7 8 3 6 9 1 2 4 +4 6 7 2 5 8 9 1 3 +8 1 2 6 9 3 4 5 7 +3 5 6 1 4 7 8 9 2 +6 8 9 4 7 1 2 3 5 +2 4 5 9 3 6 7 8 1 +7 9 1 5 8 2 3 4 6 +1 3 4 8 2 5 6 7 9 +9 2 3 7 1 4 5 6 8 -5x5:d5 -.KK "7:(d=5)" -HHEG5 -DHEGJ -DDAAJ -FIIIC -FFBBC +9x9:d7 (easy) +ZOXLLLLJJ +ZOXLMUU2J +BOddMaUPP +B9KKKaNNP +bVVHHQQND +bSE4CGYND +SSECCGYeD +FFWccTAeI +RRWW6TAAI -A 6+ +A 21+ B 3- -C 7+ -D 12+ -E 15x +C 126x +D 135x +E 2/ +F 12x +G 4- +H 2- +I 2- +J 56x +K 11+ +L 270x +M 2- +N 60x +O 19+ +P 144x +Q 14+ +R 3+ +S 42x +T 18x +U 12+ +V 3+ +W 16+ +X 54x +Y 24x +Z 28x +a 3- +b 3- +c 7- +d 21x +e 1- + +4 5 6 2 9 3 1 8 7 +7 8 9 5 3 6 4 2 1 +5 6 7 3 1 4 2 9 8 +8 9 1 6 4 7 5 3 2 +9 1 2 7 5 8 6 4 3 +6 7 8 4 2 5 3 1 9 +2 3 4 9 7 1 8 6 5 +3 4 5 1 8 2 9 7 6 +1 2 3 8 6 9 7 5 4 + +9x9:d6 (easy) +ccUUUJJSS +IIAM5bbTT +4FAMMPPaO +WFARRCCaO +WBRRDNNKK +WBYdDDNZZ +EEYdDVVZQ +EX8LLGHQQ +EXXXGGHQ9 + +A 15x +B 5+ +C 12x +D 21+ +E 84x +F 14+ +G 15+ +H 6- +I 5- +J 4- +K 1- +L 3+ +M 336x +N 35x +O 5- +P 7+ +Q 1200x +R 324x +S 1- +T 10+ +U 168x +V 24x +W 22+ +X 180x +Y 42x +Z 11+ +a 9+ +b 12+ +c 11+ +d 10+ + +3 8 4 7 6 9 5 1 2 +2 7 3 6 5 8 4 9 1 +4 9 5 8 7 1 6 2 3 +9 5 1 4 3 6 2 7 8 +8 4 9 3 2 5 1 6 7 +5 1 6 9 8 2 7 3 4 +6 2 7 1 9 3 8 4 5 +7 3 8 2 1 4 9 5 6 +1 6 2 5 4 7 3 8 9 + +9x9:d6 (easy) +cLWW7DDYY +cLHHJJJGG +VPHXTTJOO +VPXXMMMU6 +IIQQQbbUN +IAAFFFBBN +I5AKKKBaa +SS5EZKCCR +SEEEZK7RR + +A 11+ +B 23+ +C 3+ +D 45x +E 324x +F 24x +G 2- +H 19+ +I 12+ +J 23+ +K 1152x +L 7- +M 24x +N 5- +O 2- +P 8x +Q 16+ +R 13+ +S 20+ +T 4+ +U 20x +V 18x +W 2- +X 245x +Y 2- +Z 3- +a 16+ +b 13+ +c 14+ + +6 8 4 2 7 5 9 1 3 +8 1 6 4 9 7 2 3 5 +2 4 9 7 3 1 5 6 8 +9 2 7 5 1 8 3 4 6 +1 3 8 6 2 9 4 5 7 +5 7 3 1 6 4 8 9 2 +3 5 1 8 4 2 6 7 9 +7 9 5 3 8 6 1 2 4 +4 6 2 9 5 3 7 8 1 + +9x9:d6 (easy) +4QQZfTJBe +OWWZfTJBe +OODDITJ5U +CCEII9aaU +EEENHMML4 +dPcNHVKLL +dPc8FVKYA +RRRXFVYYA +bb1XGGYSS + +A 27x +B 8- +C 1- +D 24x +E 12+ +F 3- +G 1- +H 24x +I 8+ +J 18+ +K 3+ +L 96x +M 8+ +N 63x +O 168x +P 1- +Q 6- +R 16+ +S 9+ +T 15+ +U 7- +V 8+ +W 6- +X 15x +Y 25+ +Z 3+ +a 10+ +b 1- +c 1- +d 8- +e 11+ +f 1- + +4 3 9 2 8 7 5 1 6 +3 2 8 1 7 6 4 9 5 +8 7 4 6 3 2 9 5 1 +6 5 2 4 1 9 7 3 8 +2 1 7 9 6 5 3 8 4 +9 8 5 7 4 3 1 6 2 +1 9 6 8 5 4 2 7 3 +7 6 3 5 2 1 8 4 9 +5 4 1 3 9 8 6 2 7 + +9x9:d6 (easy) +bTcMUUQZZ +bTcMR6QQQ +2TKRReeIa +XKK2ONNIa +X5KKOYYYS +HEEEAPPLS +HGBJAVVLD +HGBJdd2DD +WWWFFCC4D + +A 7- +B 10+ +C 5- +D 19+ +E 15+ F 8+ -G 4+ -H 7+ -I 10+ -J 3+ +G 3- +H 40x +I 4- +J 27x +K 16+ +L 2- +M 56x +N 36x +O 9+ +P 21x +Q 54x +R 90x +S 72x +T 24+ +U 1- +V 4- +W 108x +X 63x +Y 84x +Z 3+ +a 5- +b 7+ +c 13+ +d 1- +e 2/ -2 4 3 1 5 -4 1 5 3 2 -3 5 4 2 1 -1 3 2 5 4 -5 2 1 4 3 +3 8 6 7 4 5 9 1 2 +4 9 7 8 5 6 1 2 3 +2 7 5 6 3 4 8 9 1 +7 3 1 2 8 9 4 5 6 +9 5 3 4 1 2 6 7 8 +1 6 4 5 2 3 7 8 9 +8 4 2 3 9 1 5 6 7 +5 1 8 9 6 7 2 3 4 +6 2 9 1 7 8 3 4 5 -5x5:d5 -.KK "8:(d=5)" -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH +9x9:d6 (easy) +aIIIDDDcc +aaI4DBBOO +LLWWePPRJ +AZZWeVVRJ +ATffEECGN +KTHH5ECGN +KdQHXXXSg +KdQbb4SSg +FFMMUUYY1 -A 7+ +A 10+ B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ +C 45x +D 16+ +E 10+ +F 2- +G 5- +H 14+ +I 23+ +J 2- +K 15x +L 18x +M 12x +N 5- +O 3+ +P 2- +Q 7- +R 24x +S 14+ +T 32x +U 4/ +V 8+ +W 504x +X 216x +Y 3- +Z 1- +a 23+ +b 1- +c 6x +d 2- +e 14+ +f 1- +g 14+ + +9 7 6 5 4 1 8 2 3 +8 6 5 4 3 9 7 1 2 +2 9 8 7 6 3 1 4 5 +4 2 1 9 8 5 3 6 7 +6 4 3 2 1 7 5 8 9 +1 8 7 6 5 2 9 3 4 +5 3 2 1 9 6 4 7 8 +3 1 9 8 7 4 2 5 6 +7 5 4 3 2 8 6 9 1 + +9x9:d5 (easy) +2AAeWPPMM +DASeWUNZZ +DDSQQUNNE +bbFFCCCEE +TTFICXGGE +TOaIXXGKY +cOaLL6RKY +cOBLVVRRJ +OOBddHH2J + +A 405x +B 7- +C 35x +D 19+ +E 23+ +F 11+ +G 60x +H 3/ +I 2/ +J 48x +K 9+ +L 56x +M 10+ +N 21+ +O 72x +P 5+ +Q 7- +R 18+ +S 12x +T 252x +U 1- +V 7- +W 5- +X 144x +Y 15x +Z 14x +a 2- +b 4/ +c 4- +d 2- +e 7+ + +2 5 9 6 8 1 4 3 7 +6 9 4 1 3 5 8 7 2 +5 8 3 9 2 4 7 6 1 +8 2 6 3 5 7 1 9 4 +4 7 2 8 1 3 6 5 9 +9 3 7 4 6 8 2 1 5 +7 1 5 2 4 6 9 8 3 +3 6 1 7 9 2 5 4 8 +1 4 8 5 7 9 3 2 6 + +9x9:d4 (easy) +ccHHYMMM6 +AAO3YbbNB +2FOObbRNB +XFITTTRLB +XXICQGGLL +VddCQ5aPP +VJ2DDWaUP +JJZKKWaUU +eeZKSS3EE + +A 1- +B 10x +C 36x +D 9+ +E 6+ +F 5- +G 6- +H 6- I 2- +J 14+ +K 15x +L 22+ +M 160x +N 12+ +O 324x +P 11+ +Q 3/ +R 36x +S 42x +T 24x +U 21+ +V 5- +W 18x +X 140x +Y 45x +Z 32x +a 96x +b 20+ +c 1- +d 1- +e 8- -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 +3 2 1 7 9 8 5 4 6 +8 7 6 3 5 4 1 9 2 +2 1 9 6 8 7 4 3 5 +7 6 5 2 4 3 9 8 1 +5 4 3 9 2 1 7 6 8 +9 8 7 4 6 5 2 1 3 +4 3 2 8 1 9 6 5 7 +6 5 4 1 3 2 8 7 9 +1 9 8 5 7 6 3 2 4 -5x5:d5 -.KK "9:(d=5)" -EGGGG -EIIAD -ECIAD -1CFFJ -HHBBJ +9x9:d7 (easy) +bWCNNNOOO +bWCNZIYYd +UW7FZIIYd +USSFDMRR6 +UTSDDMBaa +UTJJLLBBV +UHHGG8BQV +AEKKPP2Qc +AEKK5XXXc -A 5+ -B 2- -C 1- -D 8+ -E 11+ -F 7+ -G 30x -H 2- -I 12x -J 5+ +A 16+ +B 1120x +C 15x +D 32x +E 6+ +F 7- +G 12+ +H 5- +I 7+ +J 24x +K 23+ +L 5+ +M 16+ +N 420x +O 19+ +P 1- +Q 2- +R 1- +S 16+ +T 2- +U 19+ +V 8+ +W 24x +X 36x +Y 15+ +Z 3/ +a 14+ +b 10+ +c 8+ +d 36x -4 1 5 3 2 -2 4 3 1 5 -5 2 1 4 3 -1 3 2 5 4 -3 5 4 2 1 +6 1 5 7 4 3 8 9 2 +4 8 3 5 2 1 6 7 9 +8 3 7 9 6 5 1 2 4 +1 5 9 2 8 7 3 4 6 +3 7 2 4 1 9 5 6 8 +5 9 4 6 3 2 7 8 1 +2 6 1 3 9 8 4 5 7 +9 4 8 1 7 6 2 3 5 +7 2 6 8 5 4 9 1 3 -5x5:d5 -.KK "10:(d=5)" -FFBBC -HFAGC -HHAGE -1IIGE -IIDDE +9x9:d5 (easy) +MMMM2LLYC +SJJEX5PYC +SSVEXXPYY +ASVEHHPDB +Aaa2FFPDB +3KKKFFIIT +QQ3NNGGbT +OQUUUGbbR +O7ZZZGWWR -A 8x -B 8+ -C 3+ -D 2- -E 60x -F 9+ -G 8x -H 9+ -I 200x +A 30x +B 1- +C 7+ +D 5+ +E 10+ +F 280x +G 1296x +H 63x +I 10+ +J 11+ +K 64x +L 28x +M 360x +N 3- +O 4- +P 20+ +Q 6x +R 14x +S 18+ +T 11+ +U 270x +V 48x +W 1- +X 14+ +Y 168x +Z 36x +a 2- +b 16x -4 2 3 5 1 -5 3 4 1 2 -3 1 2 4 5 -1 4 5 2 3 -2 5 1 3 4 +9 8 1 5 2 4 7 6 3 +1 9 2 6 3 5 8 7 4 +7 6 8 3 9 2 5 4 1 +5 4 6 1 7 9 3 2 8 +6 5 7 2 8 1 4 3 9 +3 2 4 8 5 7 1 9 6 +2 1 3 7 4 6 9 8 5 +4 3 5 9 6 8 2 1 7 +8 7 9 4 1 3 6 5 2 + +9x9:d6 (easy) +TTbbVCKKF +eMbNVCK7F +eMWNVRRd9 +QEWBLLRdJ +QEfBaaDDJ +YOfGSaIIZ +YOOGS6hhZ +gPPA6HHUU +gP7AXXccc + +A 14+ +B 2- +C 4- +D 54x +E 2- +F 5- +G 6x +H 5- +I 5+ +J 15x +K 108x +L 10+ +M 1- +N 1- +O 288x +P 17+ +Q 2/ +R 168x +S 8- +T 35x +U 3- +V 13+ +W 4- +X 36x +Y 13+ +Z 1- +a 11+ +b 9+ +c 15+ +d 3/ +e 72x +f 11+ +g 2- +h 3- + +5 7 2 1 8 4 9 3 6 +9 2 6 5 3 8 4 7 1 +8 1 5 4 2 7 3 6 9 +4 6 1 9 7 3 8 2 5 +2 4 8 7 5 1 6 9 3 +6 8 3 2 9 5 1 4 7 +7 9 4 3 1 6 2 5 8 +3 5 9 8 6 2 7 1 4 +1 3 7 6 4 9 5 8 2 diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 00189005c5..6cfdcd5a2e 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -1,19 +1,17 @@ -from bot.exts.fun.mathdoku import Grid, Block -from bot.exts.fun.mathdoku_parser import create_grids from pathlib import Path +from bot.exts.fun.mathdoku_parser import create_grids + -def test_board_filled_handler(tmp_path: Path): +def test_board_filled_handler(tmp_path: Path) -> None: """ Contract: The board filled should color in the right and wrong blocks - and save the board to testdokuboardfilled2.png + and save the board to testdokuboardfilled2.png. """ - filepath = "testdokuboardfilled2.png" - + content = """\ -5x5:d5 -.KK "8:(d=5)" +5x5:d5 (easy) FFFF5 AAE1B CCEGB @@ -41,13 +39,12 @@ def test_board_filled_handler(tmp_path: Path): grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - grid.cells[4][4].guess = 2 grid.cells[0][0].guess = 1 @@ -62,4 +59,4 @@ def test_board_filled_handler(tmp_path: Path): assert grid._blocks_fufilled_check()[0].id == "F" assert grid._blocks_fufilled_check()[1].id == "H" - grid._generate_image(outfile=filepath, saveToFile=False) \ No newline at end of file + grid._generate_image(outfile=filepath, saveToFile=False) diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py index 000e2d8da0..1cbe53c963 100644 --- a/tests/exts/fun/test_mathdoku_hint.py +++ b/tests/exts/fun/test_mathdoku_hint.py @@ -7,8 +7,7 @@ def _load_5x5_grid(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" content = """\ -5x5:d5 -.KK "8:(d=5)" +5x5:d5 (easy) FFFF5 AAE1B CCEGB @@ -35,10 +34,11 @@ def _load_5x5_grid(tmp_path: Path) -> None: grids_file.write_text(content, encoding="utf-8") grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] return grid + def test_hint_find_first_empty_cell(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) @@ -49,6 +49,7 @@ def test_hint_find_first_empty_cell(tmp_path: Path) -> None: assert result["column"] == 0 assert result["value"] == 4 + def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) @@ -63,6 +64,7 @@ def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: assert result["column"] == 3 assert result["value"] == 3 + def test_hint_cooldown(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) t0 = datetime(2026, 2, 25, 14, 0, 0) @@ -73,6 +75,7 @@ def test_hint_cooldown(tmp_path: Path) -> None: assert result["type"] == "cooldown" assert result["remaining_seconds"] == 150 + def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) t0 = datetime(2026, 2, 25, 14, 0, 0) @@ -83,6 +86,7 @@ def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: assert first_hint["type"] == "hint" assert second_hint["type"] == "hint" + def test_hint_all_cells_filled(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py index 4afc46a7a1..7464c6973a 100644 --- a/tests/exts/fun/test_mathdoku_parser.py +++ b/tests/exts/fun/test_mathdoku_parser.py @@ -3,10 +3,8 @@ from bot.exts.fun.mathdoku import Block from bot.exts.fun.mathdoku_parser import create_grids - -VALID_CONTENT = """\ -5x5:d5 -.KK "1:(d=5)" +VALID_5x5 = """\ +5x5:d5 (easy) EAACC EB3CG EBF5G @@ -28,16 +26,101 @@ 4 3 2 1 5 """ +VALID_9x9 = """\ +9x9:d6 (easy) +TTbbVCKKF +eMbNVCK7F +eMWNVRRd9 +QEWBLLRdJ +QEfBaaDDJ +YOfGSaIIZ +YOOGS6hhZ +gPPA6HHUU +gP7AXXccc + +A 14+ +B 2- +C 4- +D 54x +E 2- +F 5- +G 6x +H 5- +I 5+ +J 15x +K 108x +L 10+ +M 1- +N 1- +O 288x +P 17+ +Q 2/ +R 168x +S 8- +T 35x +U 3- +V 13+ +W 4- +X 36x +Y 13+ +Z 1- +a 11+ +b 9+ +c 15+ +d 3/ +e 72x +f 11+ +g 2- +h 3- + +5 7 2 1 8 4 9 3 6 +9 2 6 5 3 8 4 7 1 +8 1 5 4 2 7 3 6 9 +4 6 1 9 7 3 8 2 5 +2 4 8 7 5 1 6 9 3 +6 8 3 2 9 5 1 4 7 +7 9 4 3 1 6 2 5 8 +3 5 9 8 6 2 7 1 4 +1 3 7 6 4 9 5 8 2 +""" + +VALID_5x5_DOUBLE_DIGIT_DIFFICULTY = """\ +5x5:d12 (hard) +EIIAA +ECCDA +JJCDD +HBBFF +HHGGF + +A 15x +B 6+ +C 40x +D 11+ +E 5+ +F 24x +G 5+ +H 10+ +I 1- +J 4+ + +4 2 3 5 1 +1 4 5 2 3 +3 1 2 4 5 +2 5 1 3 4 +5 3 4 1 2 +""" + def test_load_valid_5x5_grid(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_CONTENT, encoding="utf-8") + grids_file.write_text(VALID_5x5, encoding="utf-8") grids = create_grids(file_path=grids_file) assert len(grids) == 1 - grid = grids[0] + grid = grids[5]["easy"][0] + assert grid.difficulty == "easy" assert grid.size == 5 assert len(grid.blocks) == 11 assert grid.cells[0][0].correct == 1 @@ -52,8 +135,7 @@ def test_load_valid_5x5_grid(tmp_path: Path) -> None: def test_load_invalid_5x5_grid(tmp_path: Path) -> None: """Creates a temp file with an invalid grid in it and attempts to load a grid from that file.""" content = """\ -5x5:d5 -.KK "1:(d=5)" +5x5:d5 (easy) EAACC EB3C EBF5G @@ -85,10 +167,10 @@ def test_load_invalid_5x5_grid(tmp_path: Path) -> None: def test_load_valid_5x5_grid_and_check_singletons_have_blocks(tmp_path: Path) -> None: """Loads a grid and checks that singleton cells have blocks that have correct numbers.""" grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_CONTENT, encoding="utf-8") + grids_file.write_text(VALID_5x5, encoding="utf-8") grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] assert type(grid[2][3].block) is Block assert type(grid[2][3].block.number) is int @@ -96,11 +178,51 @@ def test_load_valid_5x5_grid_and_check_singletons_have_blocks(tmp_path: Path) -> def test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells(tmp_path: Path) -> None: """Loads a grid and checks that singleton cells have blocks that contain a list with only them.""" grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_CONTENT, encoding="utf-8") + grids_file.write_text(VALID_5x5, encoding="utf-8") grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] cell = grid[2][3] block = cell.block assert len(block.cells) == 1 assert block.cells[0] is cell + + +def test_load_valid_5x5_with_double_digit_difficulty(tmp_path: Path) -> None: + """Loads a grid with a double digit difficulty and checks that it was loaded.""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(VALID_5x5_DOUBLE_DIGIT_DIFFICULTY, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + assert len(grids) == 1 + grid = grids[5]["hard"][0] + assert grid.difficulty == "hard" + assert grid.size == 5 + assert len(grid.blocks) == 10 + assert grid.cells[0][1].correct == 2 + assert grid.cells[0][1].block.id == "I" + assert grid.cells[0][1].block.operation == "-" + assert grid.cells[0][1].block.number == 1 + + assert grid.cells[4][3].correct == 1 + + +def test_load_valid_9x9(tmp_path: Path) -> None: + """Loads a 9x9 grid and checks that it was loaded correctly.""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(VALID_9x9, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + assert len(grids) == 1 + grid = grids[9]["easy"][0] + assert grid.difficulty == "easy" + assert grid.size == 9 + assert len(grid.blocks) == 39 + assert grid.cells[0][1].correct == 7 + assert grid.cells[0][1].block.id == "T" + assert grid.cells[0][1].block.operation == "x" + assert grid.cells[0][1].block.number == 35 + + assert grid.cells[4][3].correct == 7 diff --git a/tests/exts/fun/test_recolor_blocks.py b/tests/exts/fun/test_recolor_blocks.py index 0c2d0e0583..b2dc7b19f6 100644 --- a/tests/exts/fun/test_recolor_blocks.py +++ b/tests/exts/fun/test_recolor_blocks.py @@ -1,19 +1,16 @@ -from bot.exts.fun.mathdoku_parser import create_grids from pathlib import Path +from bot.exts.fun.mathdoku_parser import create_grids -def test_board_filled_handler(tmp_path: Path): - """ - Contract: The board should be recolored - """ +def test_board_filled_handler(tmp_path: Path) -> None: + """Contract: The board should be recolored.""" filepath1 = "testdokuboardfilledcolor1.png" filepath2 = "testdokuboardfilledcolor2.png" filepath3 = "testdokuboardfilledcolor3.png" - + content = """\ -5x5:d5 -.KK "8:(d=5)" +5x5:d5 (easy) FFFF5 AAE1B CCEGB @@ -41,13 +38,13 @@ def test_board_filled_handler(tmp_path: Path): grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - + save_to_disk = False grid._generate_image(outfile=filepath1, saveToFile=save_to_disk) @@ -58,4 +55,4 @@ def test_board_filled_handler(tmp_path: Path): grid.recolor_blocks() - grid._generate_image(outfile=filepath3, saveToFile=save_to_disk) \ No newline at end of file + grid._generate_image(outfile=filepath3, saveToFile=save_to_disk) diff --git a/tests/exts/fun/test_victory_check.py b/tests/exts/fun/test_victory_check.py index e3562f218b..6094e28a13 100644 --- a/tests/exts/fun/test_victory_check.py +++ b/tests/exts/fun/test_victory_check.py @@ -2,11 +2,11 @@ from bot.exts.fun.mathdoku_parser import create_grids + def test_victory_check_won(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" content = """\ -5x5:d5 -.KK "1:(d=5)" +5x5:d5 (easy) EAACC EB3CG EBF5G @@ -32,20 +32,20 @@ def test_victory_check_won(tmp_path: Path) -> None: grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - + assert grid.check_victory() + def test_victory_check_lost(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" content = """\ -5x5:d5 -.KK "1:(d=5)" +5x5:d5 (easy) EAACC EB3CG EBF5G @@ -71,13 +71,13 @@ def test_victory_check_lost(tmp_path: Path) -> None: grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - + grid.cells[0][1].guess = 4 assert grid.check_victory() is False @@ -90,8 +90,7 @@ def test_victory_check_lost(tmp_path: Path) -> None: def test_victory_check_won_with_division(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" content = """\ -5x5:d5 -.KK "8:(d=5)" +5x5:d5 (easy) FFFF5 AAE1B CCEGB @@ -119,20 +118,20 @@ def test_victory_check_won_with_division(tmp_path: Path) -> None: grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - + assert grid.check_victory() + def test_victory_check_lost_with_division(tmp_path: Path) -> None: """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" content = """\ -5x5:d5 -.KK "8:(d=5)" +5x5:d5 (easy) FFFF5 AAE1B CCEGB @@ -160,13 +159,12 @@ def test_victory_check_lost_with_division(tmp_path: Path) -> None: grids = create_grids(file_path=grids_file) - grid = grids[0] + grid = grids[5]["easy"][0] # Set guess to possible solution for row in grid.cells: for cell in row: cell.guess = cell.correct - grid.cells[4][4].guess = 2 From 873ed46857165f221b116deade3b0fbe7c79c7a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Fri, 27 Feb 2026 15:17:27 +0100 Subject: [PATCH 41/74] feat: delete user guess (#96) #94 when possible, delete a users guess - if not we stick to default behaviour and resend the message every 10 guesses closes #94 --- bot/exts/fun/mathdoku_integration.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index b7c00d2a85..e9549e390d 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -85,7 +85,7 @@ def __init__(self, bot: Bot, grids: dict): self.playing = False self.player_id = None self.board = None # The message that the board is posten on - self.guess_count = 0 + self.guess_count = 1 @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -167,7 +167,11 @@ async def input_number_on_board( await result.add_reaction(CROSS_EMOJI) return - self.guess_count += 1 + try: + await result.delete() + except Exception: + self.guess_count += 1 + if self.guess_count % 10 == 0: # re-send the grid after 10 guesses so the user doesn't need to scroll await self.board.delete() self.grids.recolor_blocks() From b37689a6143bac4fbeaf1500cb9c06ce11d6cd9f Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:34:16 +0100 Subject: [PATCH 42/74] fix: color variables are now instance variable in grid (#98) #92 The problem was that when creating mutiple grids the block class variables were ever increasing. These class variables was made then instance variables for the grid instead. Now the board is not just one color. close #92 --- bot/exts/fun/mathdoku.py | 18 ++++++++---------- bot/exts/fun/mathdoku_integration.py | 7 ++++--- bot/exts/fun/mathdoku_parser.py | 4 ++-- tests/exts/fun/test_mathdoku.py | 8 +++++--- tests/exts/fun/test_mathdoku_hint.py | 6 ++---- .../exts/fun/test_mathdoku_image_generation.py | 6 +++--- tests/exts/fun/test_valid_guess.py | 6 +++--- 7 files changed, 27 insertions(+), 28 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 91daedc2e0..b2472952ea 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -110,25 +110,21 @@ def guess(self, new_guess) -> None: class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" - - color_id = 0 - color_offset = randint(0, 80) - - def __init__(self, id: str, operation: str, number: int, label_cell: Cell) -> None: + def __init__(self, id: str, operation: str, number: int, label_cell: Cell, grid: "Grid") -> None: self.id = id self.cells = [] self.operation = operation self.number = number self.label_cell = label_cell - self.color_id = Block.color_id + self.grid = grid + self.color_id = grid.current_color_id + grid.current_color_id += 1 self.color = self.compute_color() - - Block.color_id += 1 + def compute_color(self) -> tuple[int, int, int]: """Computes the block's color.""" - c_a = ord(self.id[0]) ** 2 - ord("A") - return COLORS[c_a % len(COLORS)] + return COLORS[(self.color_id * (len(COLORS) // (self.grid.current_color_id)) + self.grid.color_offset) % len(COLORS)] class Grid: @@ -145,6 +141,8 @@ def __init__(self, size: int, difficulty: str | None = None) -> None: self._last_hint_timestamp = None self.difficulty = difficulty + self.color_offset = randint(0, 80) + self.current_color_id = 0 @property def cells(self): diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index e9549e390d..929aa8dd50 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -25,9 +25,9 @@ cell_seven = testingGrid.cells[2][0] cell_eight = testingGrid.cells[2][1] cell_nine = testingGrid.cells[2][2] -testBlock_1 = Block("A", "+", 6, cell_one) -testBlock_2 = Block("B", "+", 9, cell_four) -testBlock_3 = Block("C", "+", 3, cell_five) +testBlock_1 = Block("A", "+", 6, cell_one, testingGrid) +testBlock_2 = Block("B", "+", 9, cell_four, testingGrid) +testBlock_3 = Block("C", "+", 3, cell_five, testingGrid) testingGrid.blocks.append(testBlock_1) testingGrid.blocks.append(testBlock_2) testingGrid.blocks.append(testBlock_3) @@ -67,6 +67,7 @@ cell_five.block = testBlock_3 cell_six.block = testBlock_3 +testingGrid.recolor_blocks() CROSS_EMOJI = "\u274c" # "\u274e" MAGNIFYING_EMOJI = "๐Ÿ”" diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index da0fe87326..0def98f5b0 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -88,13 +88,13 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s for j, col in enumerate(row): cell = created_grid[i][j] if col.isdigit(): # has no block - block = Block(str(i) + str(j), "", int(col), cell) + block = Block(str(i) + str(j), "", int(col), cell, created_grid) cell.block = block block.cells.append(cell) created_grid.blocks.append(block) continue if col not in seen_blocks: - block = Block(col, None, None, cell) + block = Block(col, None, None, cell, created_grid) seen_blocks[col] = block created_grid.blocks.append(block) else: diff --git a/tests/exts/fun/test_mathdoku.py b/tests/exts/fun/test_mathdoku.py index eb398b26d1..ef051bb50f 100644 --- a/tests/exts/fun/test_mathdoku.py +++ b/tests/exts/fun/test_mathdoku.py @@ -1,15 +1,17 @@ -from bot.exts.fun.mathdoku import Block +from bot.exts.fun.mathdoku import Block, Grid def test_block_with_id_gets_color() -> None: """Tests that a block with an id of A gets a color.""" - block = Block("A", None, None, None) + testGrid = Grid(3) + block = Block("A", None, None, None, testGrid) assert type(block.color) is tuple assert len(block.color) == 3 def test_block_with_unexpected_id_gets_color() -> None: """Tests that a block with an unexpected id still gets a color.""" - block = Block("9ZZZ", None, None, None) + testGrid = Grid(3) + block = Block("9ZZZ", None, None, None, testGrid) assert type(block.color) is tuple assert len(block.color) == 3 diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py index 1cbe53c963..d95d6df806 100644 --- a/tests/exts/fun/test_mathdoku_hint.py +++ b/tests/exts/fun/test_mathdoku_hint.py @@ -38,8 +38,7 @@ def _load_5x5_grid(tmp_path: Path) -> None: return grid - -def test_hint_find_first_empty_cell(tmp_path: Path) -> None: +def _test_hint_find_first_empty_cell(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) @@ -49,8 +48,7 @@ def test_hint_find_first_empty_cell(tmp_path: Path) -> None: assert result["column"] == 0 assert result["value"] == 4 - -def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: +def _test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) grid.cells[0][0].guess = 4 diff --git a/tests/exts/fun/test_mathdoku_image_generation.py b/tests/exts/fun/test_mathdoku_image_generation.py index 6db43bba65..c2b91f0c21 100644 --- a/tests/exts/fun/test_mathdoku_image_generation.py +++ b/tests/exts/fun/test_mathdoku_image_generation.py @@ -22,9 +22,9 @@ def test_image_generation(): cell_seven = testingGrid.cells[2][0] cell_eight = testingGrid.cells[2][1] cell_nine = testingGrid.cells[2][2] - testBlock_1 = Block("A", "+", 3, cell_one) - testBlock_2 = Block("B", "/", 30, cell_four) - testBlock_3 = Block("C", "-", 300, cell_five) + testBlock_1 = Block("A", "+", 3, cell_one, testingGrid) + testBlock_2 = Block("B", "/", 30, cell_four, testingGrid) + testBlock_3 = Block("C", "-", 300, cell_five, testingGrid) testingGrid.blocks.append(testBlock_1) testingGrid.blocks.append(testBlock_2) testingGrid.blocks.append(testBlock_3) diff --git a/tests/exts/fun/test_valid_guess.py b/tests/exts/fun/test_valid_guess.py index 38569d3a94..faffad845b 100644 --- a/tests/exts/fun/test_valid_guess.py +++ b/tests/exts/fun/test_valid_guess.py @@ -17,9 +17,9 @@ def test_addguess(): cell_seven = testingGrid.cells[2][0] cell_eight = testingGrid.cells[2][1] cell_nine = testingGrid.cells[2][2] - testBlock_1 = Block("A", "+", 3, cell_one) - testBlock_2 = Block("B", "/", 30, cell_four) - testBlock_3 = Block("C", "-", 300, cell_five) + testBlock_1 = Block("A", "+", 3, cell_one, testingGrid) + testBlock_2 = Block("B", "/", 30, cell_four, testingGrid) + testBlock_3 = Block("C", "-", 300, cell_five, testingGrid) testingGrid.blocks.append(testBlock_1) testingGrid.blocks.append(testBlock_2) testingGrid.blocks.append(testBlock_3) From 889e4f985eb1a3fe792bf91f5f47c3f0e1a4df87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Fri, 27 Feb 2026 16:38:22 +0100 Subject: [PATCH 43/74] feat: load an actual grid (#99) #86 Pick difficulty and size by specifiying them in the command Randomly a grid with that size and difficulty is selected closes #86 --- bot/exts/fun/mathdoku_integration.py | 149 +++++++++++---------------- 1 file changed, 58 insertions(+), 91 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 929aa8dd50..dfe20061b6 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,80 +1,21 @@ import asyncio import re +from random import choice + import discord from discord.ext import commands from pydis_core.utils.logging import get_logger -from bot.bot import Bot +from copy import deepcopy -# TODO remove this import when merging files -from bot.exts.fun.mathdoku import Block, Grid - -# These 2 commands make the API not work for some reason if uncommented -# from .mathdoku_parser import create_grids -# grids = create_grids() - -# TODO remove this testing Grid -testingGrid = Grid(3) -cell_one = testingGrid.cells[0][0] -cell_two = testingGrid.cells[0][1] -cell_three = testingGrid.cells[0][2] -cell_four = testingGrid.cells[1][0] -cell_five = testingGrid.cells[1][1] -cell_six = testingGrid.cells[1][2] -cell_seven = testingGrid.cells[2][0] -cell_eight = testingGrid.cells[2][1] -cell_nine = testingGrid.cells[2][2] -testBlock_1 = Block("A", "+", 6, cell_one, testingGrid) -testBlock_2 = Block("B", "+", 9, cell_four, testingGrid) -testBlock_3 = Block("C", "+", 3, cell_five, testingGrid) -testingGrid.blocks.append(testBlock_1) -testingGrid.blocks.append(testBlock_2) -testingGrid.blocks.append(testBlock_3) - -cell_one.guess = 1 -cell_three.guess = 3 -cell_seven.guess = 2 - -cell_one.correct = 1 -cell_two.correct = 2 -cell_three.correct = 3 -cell_four.correct = 3 -cell_five.correct = 1 -cell_six.correct = 2 -cell_seven.correct = 2 -cell_eight.correct = 3 -cell_nine.correct = 1 - -testBlock_1.cells.append(cell_one) -testBlock_1.cells.append(cell_two) -testBlock_1.cells.append(cell_three) -cell_one.block = testBlock_1 -cell_two.block = testBlock_1 -cell_three.block = testBlock_1 - -testBlock_2.cells.append(cell_four) -testBlock_2.cells.append(cell_seven) -testBlock_2.cells.append(cell_eight) -testBlock_2.cells.append(cell_nine) -cell_four.block = testBlock_2 -cell_seven.block = testBlock_2 -cell_eight.block = testBlock_2 -cell_nine.block = testBlock_2 - -testBlock_3.cells.append(cell_five) -testBlock_3.cells.append(cell_six) -cell_five.block = testBlock_3 -cell_six.block = testBlock_3 - -testingGrid.recolor_blocks() +from bot.bot import Bot CROSS_EMOJI = "\u274c" # "\u274e" MAGNIFYING_EMOJI = "๐Ÿ”" PARTY_EMOJI = "๐ŸŽ‰" HINT_EMOJI = "๐Ÿ’ก" log = get_logger(__name__) -has_filled_board_prev = False class Mathdoku(commands.Cog): @@ -82,11 +23,12 @@ class Mathdoku(commands.Cog): def __init__(self, bot: Bot, grids: dict): self.bot = bot - self.grids = grids # The game Grid + self.grids = grids # All possible game grids + self.grid = None # the currently active game grid self.playing = False self.player_id = None self.board = None # The message that the board is posten on - self.guess_count = 1 + self.guess_count = 0 @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -95,20 +37,38 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: await self.bot.invoke_help_command(ctx) @mathdoku_group.command(name="start") - async def start_command(self, ctx: commands.Context, size: int = 5) -> None: - """Start a game of Mathdoku.""" + async def start_command(self, ctx: commands.Context, size: int = 5, difficulty = "medium") -> None: + """Start a game of Mathdoku + size = the board size. Pick from 3-9 + difficulty = easy, medium or hard + """ + + size = int(size) + difficulty = str(difficulty).lower() if self.playing: await ctx.send("Someone else is playing right now. Please wait your turn.") return - self.playing = True + if size not in [3,4,5,6,7,8,9]: + await ctx.send("Please give a valid size between 3 and 9") + return + + if difficulty not in ["easy", "medium", "hard"]: + await ctx.send("Please give a valid difficulty: easy, medium or hard") + return + + grids_available = self.grids[size][difficulty] + if len(grids_available) < 1: + await ctx.send("Couldn't find any boards for size: " + size + " and difficulty: " + difficulty + ". Sorry :/") + return + + self.playing = True self.player_id = ctx.author.id + self.grid = deepcopy(choice(grids_available)) # get a random grid from the available ones for this size / difficulty await ctx.send("Game of Mathdoku has been started!") - # TODO Create an actual Grid: - self.grids = testingGrid - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + file = discord.File(self.grid._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) await ctx.send( @@ -142,6 +102,9 @@ async def input_number_on_board( await ctx.send("You took too long. Game over!") self.playing = False return + + except Exception: + return if not self.playing: # takes care of the end message await ctx.send("The game has been ended") @@ -163,7 +126,7 @@ async def input_number_on_board( else: # A message was posted input_text = result.content.strip() - valid_match = self.grids.add_guess(input_text) # checks if its a valid guess and applies + valid_match = self.grid.add_guess(input_text) # checks if its a valid guess and applies if not valid_match: await result.add_reaction(CROSS_EMOJI) return @@ -173,23 +136,16 @@ async def input_number_on_board( except Exception: self.guess_count += 1 - if self.guess_count % 10 == 0: # re-send the grid after 10 guesses so the user doesn't need to scroll - await self.board.delete() - self.grids.recolor_blocks() - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") - self.board = await ctx.send(file=file) - await self.board.add_reaction(HINT_EMOJI) - await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" - "Type `end` to end game." - ) - - full_grid = self.grids.check_full_grid() + if self.guess_count > 10: # re-send the grid after 10 guesses so the user doesn't need to scroll + await self.resent_message(ctx=ctx) + self.guess_count = 0 + + full_grid = self.grid.check_full_grid() if full_grid: await self.board.add_reaction(MAGNIFYING_EMOJI) - self.grids.recolor_blocks() - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + self.grid.recolor_blocks() + file = discord.File(self.grid._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) return @@ -204,6 +160,7 @@ def predicate(self, message: discord.Message) -> bool: match = re.fullmatch(r"[A-Ja-j](10|[1-9])\s+[1-9]", input_text) if not match: + self.guess_count += 1 self.bot.loop.create_task(message.add_reaction(CROSS_EMOJI)) return False return True @@ -216,13 +173,12 @@ def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> return None async def magnifying_handler(self, ctx, user) -> None: - if self.grids.check_full_grid(): + if self.grid.check_full_grid(): await self.board.remove_reaction(MAGNIFYING_EMOJI, user) - result = self.grids.board_filled_handler() # check win and update img - file = discord.File(self.grids._generate_image(), filename="mathdoku.png") + result = self.grid.board_filled_handler() # check win and update img + file = discord.File(self.grid._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) - if result: # WIN await self.board.add_reaction(PARTY_EMOJI) await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) @@ -233,7 +189,7 @@ async def magnifying_handler(self, ctx, user) -> None: async def hint_handler(self, ctx, user) -> None: """Handle hint request via ๐Ÿ’ก reaction.""" await self.board.remove_reaction(HINT_EMOJI, user) - result = self.grids.hint() + result = self.grid.hint() if result["type"] == "cooldown": await ctx.send(f"Hint on cooldown. Try again in {result['remaining_seconds']}s.") @@ -243,6 +199,17 @@ async def hint_handler(self, ctx, user) -> None: await ctx.send(f"Hint: {result['guess']}") + async def resent_message(self, ctx): + await self.board.delete() + self.grid.recolor_blocks() + file = discord.File(self.grid._generate_image(), filename="mathdoku.png") + self.board = await ctx.send(file=file) + await self.board.add_reaction(HINT_EMOJI) + await ctx.send( + "Type the square and what number you want to input. Format it like this: A1 3\n" + "Type `end` to end game." + ) + async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" from .mathdoku_parser import create_grids From 05d8cce741b9060d72c10af37dba26a1c2b3a40d Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Fri, 27 Feb 2026 15:45:49 +0100 Subject: [PATCH 44/74] prelim rule command without deleting rule message --- bot/exts/fun/mathdoku_integration.py | 40 +++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index dfe20061b6..d5be53d522 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -15,6 +15,32 @@ MAGNIFYING_EMOJI = "๐Ÿ”" PARTY_EMOJI = "๐ŸŽ‰" HINT_EMOJI = "๐Ÿ’ก" +RULE_EMOJI = '๐Ÿ“•' +mathdoku_rules = """Rules for Playing Mathdoku + +The numbers you can enter on the board depend on the size of the board you selected. If you choose an n ร— n +board, you may use the numbers 1, 2, โ€ฆ, n. For example, on a 3 ร— 3 board you can use the numbers 1, 2, and 3. + +The board is divided into cages, which are groups of squares outlined with a thick border. In the top-left +corner of each cage, a target number and a mathematical operation (+, โˆ’, ร—, รท) are shown. The numbers you +place in that cage must combine (using the indicated operation) to produce the target number. The operation +may be applied in any order. + +Also note the following rules: + +1. A number may appear at most once in each row and at most once in each column. +2. A cage with one square must contain the target number shown in its corner. +3. A number may be repeated within a cage, as long as the repeats are not in the same row or column. + +The emojis attached to the board have different functions as well: + +๐Ÿ” Check: This emoji only appears when the board is full. When pressed, it checks whether the board's conditions are fulfilled. If they are, the board turns green and a victory message is shown. If not, the cages that are not fulfilled turn red. + +๐Ÿ’ก Hint: This emoji provides a helpful hint to assist the player in solving the puzzle. It has a cooldown of 3 minutes. + +๐Ÿ“• Rules: This emoji displays the rules of the Mathdoku game. +""" + log = get_logger(__name__) @@ -75,6 +101,7 @@ async def start_command(self, ctx: commands.Context, size: int = 5, difficulty = "Type the square and what number you want to input. Format it like this: A1 3\nType `end` to end game." ) + await self.board.add_reaction(RULE_EMOJI) await self.board.add_reaction(HINT_EMOJI) while self.playing is True: @@ -119,8 +146,8 @@ async def input_number_on_board( await self.magnifying_handler(ctx=ctx, user=user) elif emoji == HINT_EMOJI: await self.hint_handler(ctx=ctx, user=user) - elif emoji == "rules": - pass + elif emoji == RULE_EMOJI: + await self.rules_handler(ctx=ctx, user=user) else: # any other emoji await self.board.remove_reaction(emoji, user) @@ -179,12 +206,13 @@ async def magnifying_handler(self, ctx, user) -> None: result = self.grid.board_filled_handler() # check win and update img file = discord.File(self.grid._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) - if result: # WIN + + if result: # WIN await self.board.add_reaction(PARTY_EMOJI) await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) self.playing = False return - self.bot.loop.create_task(self.board.remove_reaction(MAGNIFYING_EMOJI, user)) + await self.board.remove_reaction(MAGNIFYING_EMOJI, user) async def hint_handler(self, ctx, user) -> None: """Handle hint request via ๐Ÿ’ก reaction.""" @@ -198,6 +226,10 @@ async def hint_handler(self, ctx, user) -> None: else: await ctx.send(f"Hint: {result['guess']}") + async def rules_handler(self, ctx, user) -> None: + """Handle rules request via ๐Ÿ“• reaction.""" + await self.board.remove_reaction(RULE_EMOJI, user) + await ctx.send(mathdoku_rules) async def resent_message(self, ctx): await self.board.delete() From b65abac0fb6953463365e6abb6cf4d9452a01b9f Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Fri, 27 Feb 2026 17:02:11 +0100 Subject: [PATCH 45/74] feat: add rules command closes #65 --- bot/exts/fun/mathdoku_integration.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index d5be53d522..8df72b5174 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -55,6 +55,8 @@ def __init__(self, bot: Bot, grids: dict): self.player_id = None self.board = None # The message that the board is posten on self.guess_count = 0 + self.rules_msg_exists = False + self.rules_msg = None @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: @@ -142,6 +144,10 @@ async def input_number_on_board( emoji = str(reaction.emoji) if self.player_id != user.id: # reaction by wrong player await self.board.remove_reaction(emoji, user) + if self.rules_msg_exists: + await self.rules_msg.delete() + self.rules_msg_exists = False + self.rules_msg = None if emoji == MAGNIFYING_EMOJI: await self.magnifying_handler(ctx=ctx, user=user) elif emoji == HINT_EMOJI: @@ -157,7 +163,10 @@ async def input_number_on_board( if not valid_match: await result.add_reaction(CROSS_EMOJI) return - + if self.rules_msg_exists: + await self.rules_msg.delete() + self.rules_msg_exists = False + self.rules_msg = None try: await result.delete() except Exception: @@ -229,7 +238,8 @@ async def hint_handler(self, ctx, user) -> None: async def rules_handler(self, ctx, user) -> None: """Handle rules request via ๐Ÿ“• reaction.""" await self.board.remove_reaction(RULE_EMOJI, user) - await ctx.send(mathdoku_rules) + self.rules_msg = await ctx.send(mathdoku_rules) + self.rules_msg_exists = True async def resent_message(self, ctx): await self.board.delete() From 2de91e9060d5ba6da99f1511b779c3d9dbe61314 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:24:22 +0100 Subject: [PATCH 46/74] fix: latin_square_check also checks for columns now (#104) close #100 --- bot/exts/fun/mathdoku.py | 20 ++++++++++++++++---- tests/exts/fun/test_board_filled_handler.py | 6 ++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index b2472952ea..165dce98cc 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -162,16 +162,28 @@ def _latin_square_check(self) -> bool: Checks if the grid is filled correctly in terms of a latin square.\n I.e all numbers in the range per colum and row exist. """ - check_structure = [[False for col in range(self.size)] for row in range(self.size)] + check_row_structure = [[False for col in range(self.size)] for row in range(self.size)] + check_col_structure = [[False for col in range(self.size)] for row in range(self.size)] + # Fills the check structure for row in range(self.size): for col in range(self.size): guess = self.cells[row][col].guess if guess == 0: return False - check_structure[row][guess - 1] = True - if all(check_structure[row]) is False: - return False + check_row_structure[row][guess - 1] = True + check_col_structure[guess - 1][col] = True + + #print("\n------------------") + #print(check_row_structure) + #print(check_col_structure) + #print("------------------\n") + + # Checks that the entire structure is True + for row in range(self.size): + for col in range(self.size): + if check_row_structure[row][col] is False or check_col_structure[row][col] is False: + return False return True diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 6cfdcd5a2e..48db573526 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -50,11 +50,9 @@ def test_board_filled_handler(tmp_path: Path) -> None: grid.cells[0][0].guess = 1 # Comment out the line below to see different colors for singletons - assert grid.board_filled_handler() is False - - assert grid.check_victory() is False - assert grid._latin_square_check() is False + assert grid.check_victory() is False + assert grid.board_filled_handler() is False assert grid._blocks_fufilled_check()[0].id == "F" assert grid._blocks_fufilled_check()[1].id == "H" From cb6704953f4d0e13310ca825bbbdc92bab2ac6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:29:24 +0100 Subject: [PATCH 47/74] feat: cells now have their own color attribute (#106) #101 closes #101 --- bot/exts/fun/mathdoku.py | 18 +++++++++++++++++- bot/exts/fun/mathdoku_parser.py | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 165dce98cc..11db5706c3 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -98,6 +98,7 @@ def __init__(self, column: int, row: int) -> None: self.block = None self._guess = 0 self.correct = 0 + self.color = None @property def guess(self): @@ -107,6 +108,9 @@ def guess(self): def guess(self, new_guess) -> None: self._guess = new_guess + def reset_color(self): + self.color = self.block.color + class Block: """Represents a block in the puzzle, with its cells, operation and colour.""" @@ -256,6 +260,14 @@ def board_filled_handler(self) -> bool: else: block.color = (100, 255, 100) + # TODO this currently would overwrite any color changes in the roman lines checker + # We should first recolor the blocks based on if they match the block sum criteria, and then overwrite their + # color with the colors from the roman lines check - where only rows/column that break the grid are colored + # -- maybe in another red so its easy to tell apart? + for row in range(self.size): + for cell in self.cells[row]: + cell.reset_color() + return self.check_victory() def check_full_grid(self) -> bool: @@ -271,6 +283,10 @@ def recolor_blocks(self) -> None: for block in self.blocks: block.color = block.compute_color() + for row in range(self.size): + for cell in self.cells[row]: + cell.reset_color() + def __getitem__(self, i: int) -> list[Cell]: """ Defines the indexing operator for the Grid class. @@ -295,7 +311,7 @@ def _generate_image(self, cellSize=80, margin=30, outfile="mathdoku.png", saveTo y_start = (cell.row) * cellSize + margin + margin // 2 x_end = (cell.column) * cellSize + cellSize + margin + margin // 2 y_end = (cell.row) * cellSize + cellSize + margin + margin // 2 - color = cell.block.color + color = cell.color draw.rectangle((x_start, y_start, x_end, y_end), fill=color) # 2) the guess diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index 0def98f5b0..bef72e77c8 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -100,6 +100,7 @@ def _create_cells_and_blocks(expected_size: int, created_grid: Grid, grid_str: s else: block = seen_blocks[col] cell.block = block + cell.color = cell.block.color block.cells.append(cell) return seen_blocks From 773cbf224ab74007ae8ab052903c24c0d7ea2fd1 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Fri, 27 Feb 2026 17:33:19 +0100 Subject: [PATCH 48/74] fix: mathdoku hint tests fixed (#107) #93 fixed format inside test_hint_find_first_empty_cell and test_hint_find_empty_cell_after_some_filled tests. closes #93 --- tests/exts/fun/test_mathdoku_hint.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py index d95d6df806..994c4e219f 100644 --- a/tests/exts/fun/test_mathdoku_hint.py +++ b/tests/exts/fun/test_mathdoku_hint.py @@ -38,17 +38,15 @@ def _load_5x5_grid(tmp_path: Path) -> None: return grid -def _test_hint_find_first_empty_cell(tmp_path: Path) -> None: +def test_hint_find_first_empty_cell(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) assert result["type"] == "hint" - assert result["row"] == 0 - assert result["column"] == 0 - assert result["value"] == 4 + assert result["guess"] == "A1 4" -def _test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: +def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: grid = _load_5x5_grid(tmp_path) grid.cells[0][0].guess = 4 @@ -58,9 +56,7 @@ def _test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) assert result["type"] == "hint" - assert result["row"] == 0 - assert result["column"] == 3 - assert result["value"] == 3 + assert result["guess"] == "D1 3" def test_hint_cooldown(tmp_path: Path) -> None: From 025174d5c4f0de88a06a40949c0cbc5bfb11cbe3 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Fri, 27 Feb 2026 17:34:12 +0100 Subject: [PATCH 49/74] feat: add emoji description output to start command closes #97 --- bot/exts/fun/mathdoku_integration.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 8df72b5174..bec32a6001 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -95,6 +95,10 @@ async def start_command(self, ctx: commands.Context, size: int = 5, difficulty = self.player_id = ctx.author.id self.grid = deepcopy(choice(grids_available)) # get a random grid from the available ones for this size / difficulty await ctx.send("Game of Mathdoku has been started!") + await ctx.send( + "Press ๐Ÿ” to check if the board's conditions are met (only appears when the board is full)\n" + "Press ๐Ÿ’ก to get a helpful hint on how to solve the puzzle\n" + "Press ๐Ÿ“• to get the rules of the Mathdoku game") file = discord.File(self.grid._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) From f8e690c34b05fe6007766fc558f8f8bc66ca2c5b Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:08:29 +0100 Subject: [PATCH 50/74] test: change test to be disabled after new changes to grid strcuture (#109) --- tests/exts/fun/test_board_filled_handler.py | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 48db573526..0d61b3faee 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -58,3 +58,49 @@ def test_board_filled_handler(tmp_path: Path) -> None: assert grid._blocks_fufilled_check()[1].id == "H" grid._generate_image(outfile=filepath, saveToFile=False) + + +def _test_board_filled_handler3x3(tmp_path: Path) -> None: + """ + Contract: The board filled should color in the right and wrong blocks + and save the board to testdokuboardfilled3.png. + """ + filepath = "testdokuboardfilled3.png" + + content = """\ +3x3:d3 (easy) +AAB +CDB +CEE + +A 3+ +B 4+ +C 6x +D 3 +E 1- + +1 2 3 +2 3 1 +3 1 2 +""" + grids_file = tmp_path / "grids.txt" + grids_file.write_text(content, encoding="utf-8") + + grids = create_grids(file_path=grids_file) + + grid = grids[3]["easy"][0] + + # Set guess to possible solution + for row in grid.cells: + for cell in row: + cell.guess = cell.correct + + # Comment out the line below to see different colors for singletons + assert grid._latin_square_check() is False + assert grid.check_victory() is False + assert grid.board_filled_handler() is False + + assert grid._blocks_fufilled_check()[0].id == "F" + assert grid._blocks_fufilled_check()[1].id == "H" + + grid._generate_image(outfile=filepath, saveToFile=False) From d72f67924a8d126eaf8a2abe8ba4a1bcfb6079c4 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sat, 28 Feb 2026 21:51:55 +0100 Subject: [PATCH 51/74] docs: Architecture and purpose of the system documentation (#111) #7 closes #7 --- report.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/report.md b/report.md index 0c7664a670..01a2a1f192 100644 --- a/report.md +++ b/report.md @@ -8,7 +8,7 @@ ### One or two sentences describing it: -Sir Lancebot is an open-source Discord bot written in Python and maintained by the Python Discord community. . On the project, contributors can implement new commands and features, or fix issues. +Sir Lancebot is an open-source Discord bot written in Python and maintained by the Python Discord community. On the project, contributors can implement new commands and features, or fix issues. ## Onboarding experience @@ -120,6 +120,52 @@ Optional (point 5): considered for acceptance (passes all automated checks). Optional (point 1): Architectural overview. +## Architectural Overview + +### System Purpose. + +**Sir Lancebot** is an open-source `Discord bot` developed and maintained by the Python Discord community. Its primary purpose is to serve a beginner-friendly project for developers who want to learn and contribute to open source. The bot provides a variety of features for Discord servers (fun games, utilities, seasonal commands, event tools). + +In practice, the system combines a bot core with multiple independent features modules (cogs), so new functionality can be added with minimal impact on existing commands. + +Our `Mathdoku` implementation follows the same philosophy: board logic is separated from Discord interaction and from the board-file parsing, which keeps each concern testable and replaceable. + +### System Architecture + +### Component Diagram + +![alt text]() + +The system architecture is organized in layers around a modular bot core. +`Users` interact through Discord, and the `Discord API` sends events and commands to the `Sir Lancebot Core`, which is responsible for runtime setup and dispatching functionality to the corresponding extension. Each `extension (Cog)` is independent, so features can be added, removed or modified independently without modifying the core logic. Our `Mathdoku` game implementation is included inside the `fun` feature group, but there are other fun features groups as `utilites`, `holidays`, `events`. + +### Mathdoku Architecture in the system. + +The `Mathdoku` feature is implemented as a subsystem across three modules with clear separation of responsibilities. + +#### 1. Game Logic (`mathdoku.py`) +This module contains the core game model: +- **Cell:** stores coordinates (row, column), block, player guess, and correct value. +- **Block:** stores block metadata (id, operation, number, assigned cells, color). +- **Grid:** stores board state and game rules (latin-square rules, full-board checks, check block constraints, evaluates win condition, hint cooldown handling...). + +This module is independent from Discord commands, so game correctness can be verified independently from the bot's event handling. + +#### 2. Board Parsing (`mathdoku_parser.py`) +This module reads `mathdoku_boards.txt`, extract board definitions, builds `Grid` objects, assign blocks and operations, and loads the correct solution values. + +This module acts as the entry point for board data, transforming raw text into structured game objects ready to be used by the game logic. + +#### 3. Discord Integration (`mathdoku_integration.py`) +This module connects the game to Discord and manages the full interaction flow with the player: +- Starts a game session. +- Wait for player input messages with a 10 minutes inactivity timeout. +- Handles guesses from user inputs. +- Handles emoji reactions for hints and block validation. + +This module contains no game rules. It receives player input messages, +delegates to the game logic, and sends the result back to Discord. + Optional (point 2): relation to design pattern(s). ## Overall experience What are your main take-aways from this project? What did you learn? How did you grow as a team, using the Essence standard to evaluate yourself? From 63c1439c3895209ebbbbee1c309e8e2ebb1194ec Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:00:48 +0100 Subject: [PATCH 52/74] docs: Refactoring Patterns documentation (#113) #112 closes #112 --- report.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/report.md b/report.md index 01a2a1f192..cb9ad40fb7 100644 --- a/report.md +++ b/report.md @@ -168,6 +168,33 @@ delegates to the game logic, and sends the result back to Discord. Optional (point 2): relation to design pattern(s). +## Refactoring Patterns +We have implemented two refactoring patterns from Martin Fowler's catalogue that align with the overall architecture of `Sir Lancebot`. + +#### Extract Class + +Instead of implememting the entire `Mathdoku` feature in a single file, we decided to separate the different responsibilities into three independent files. + +- `mathdoku.py` owns the game model and rules. +- `mathdoku_parser.py` owns the board file loading. +- `mathdoku_integration.py` owns the Discord interaction. + +This reflects the **Extract Class** pattern: when a class or module +takes on too many responsibilities, each concern is extracted into its +own unit. + +#### Extract Method +Within `mathdoku_parser.py`, we decided to divide the parsing logic into smaller functions rather than a single large function. + +- `_search_for_grids_in_file()` reads the board file and applies a regex to locate and extract all board definitions. +- `_create_cells_and_blocks()` iterates over the board layout, + creates `Block` objects, and assigns each cell to its corresponding block. +- `_read_block_operations()` assigns the arithmetic operator and target number to the corresponding block. +- `_read_solution()` sets the correct value on each cell. + +This reflects the **Extract Method** pattern: when complex logic is divided +into well-named methods, each with a single, clear purpose. + ## Overall experience What are your main take-aways from this project? What did you learn? How did you grow as a team, using the Essence standard to evaluate yourself? Optional (point 6): How would you put your work in context with best software engineering practice? From 4a062bf71336380124c00a317161689394bea07c Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sat, 28 Feb 2026 22:05:42 +0100 Subject: [PATCH 53/74] docs: Test Casestraceability documentation (#114) #102 closes #102 --- report.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/report.md b/report.md index cb9ad40fb7..7865d78615 100644 --- a/report.md +++ b/report.md @@ -106,6 +106,32 @@ The bot shall notify the user that no hints are available if all cells in the bo Optional (point 3): trace tests to requirements. +##ย Test Cases Traceability Matrix + +| Test Case | File | Requirements | +|---|---|---| +| `test_load_valid_5x5_grid` | test_mathdoku_parser.py | FR-08, FR-09, FR-10 | +| `test_load_invalid_5x5_grid` | test_mathdoku_parser.py | FR-09 | +| `test_load_valid_5x5_grid_and_check_singletons_have_blocks` | test_mathdoku_parser.py | FR-08, FR-10 | +| `test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells` | test_mathdoku_parser.py | FR-10 | +| `test_grid` | test_grid_class.py | FR-07, FR-08 | +| `test_latin_square_check` | test_grid_class.py | FR-15 | +| `test_addguess` | test_valid_guess.py | FR-13, FR-15 | +| `test_hint_find_first_empty_cell` | test_mathdoku_hint.py | FR-19 | +| `test_hint_find_empty_cell_after_some_filled` | test_mathdoku_hint.py | FR-19 | +| `test_hint_cooldown` | test_mathdoku_hint.py | FR-06, FR-20 | +| `test_hint_available_again_at_180_seconds` | test_mathdoku_hint.py | FR-06 | +| `test_hint_all_cells_filled` | test_mathdoku_hint.py | FR-21 | +| `test_victory_check_won` | test_victory_check.py | FR-15 | +| `test_victory_check_lost` | test_victory_check.py | FR-15, FR-16 | +| `test_victory_check_won_with_division` | test_victory_check.py | FR-15 | +| `test_victory_check_lost_with_division` | test_victory_check.py | FR-15, FR-16 | +| `test_block_with_id_gets_color` | test_mathdoku.py | FR-10, FR-17 | +| `test_block_with_unexpected_id_gets_color` | test_mathdoku.py | FR-10, FR-17 | +| `test_image_generation` | test_mathdoku_image_generation.py | FR-17 | +| `test_board_filled_handler` | test_board_filled_handler.py | FR-15, FR-16 | +| `test_recolor_blocks` | test_recolor_blocks.py | FR-16, FR-17 | + ## Code changes ### Patch (copy your changes or the add git command to show them) git diff ... From 45210aa09df51aafe573c27814f07ab5a11b5185 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sun, 1 Mar 2026 14:35:22 +0100 Subject: [PATCH 54/74] feat: add coloring of rows and cols that are not latin (#115) #105 close #105 --- bot/exts/fun/mathdoku.py | 73 +++++++++++++-------- tests/exts/fun/test_board_filled_handler.py | 6 +- tests/exts/fun/test_grid_class.py | 6 +- tests/exts/fun/test_victory_check.py | 6 +- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 11db5706c3..475e0e36e6 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -161,7 +161,7 @@ def __str__(self) -> str: print_str += str(self.cells[row][col + 1].guess) + "]\n" return print_str - def _latin_square_check(self) -> bool: + def _latin_square_check(self) -> list[set[int], set[int]]: """ Checks if the grid is filled correctly in terms of a latin square.\n I.e all numbers in the range per colum and row exist. @@ -183,15 +183,20 @@ def _latin_square_check(self) -> bool: #print(check_col_structure) #print("------------------\n") + wrong_rows = set() + wrong_cols = set() + # Checks that the entire structure is True for row in range(self.size): for col in range(self.size): - if check_row_structure[row][col] is False or check_col_structure[row][col] is False: - return False + if check_row_structure[row][col] is False: + wrong_rows.add(row) + if check_col_structure[row][col] is False: + wrong_cols.add(col) - return True + return [wrong_rows, wrong_cols] - def _blocks_fufilled_check(self) -> list[Block] | bool: + def _blocks_fufilled_check(self) -> list[Block]: """ Checks if all the blocks are filled correctly and meets the requirements. \n Returns the blocks that are wrong or True if all blocks meet the requirements. \n @@ -229,8 +234,6 @@ def _blocks_fufilled_check(self) -> list[Block] | bool: if abs(result) != block.number: wrong_blocks.append(block) - if len(wrong_blocks) == 0: - return True return wrong_blocks def check_victory(self) -> bool: @@ -238,37 +241,49 @@ def check_victory(self) -> bool: Checks if the board is in a state where the player has won and will return True or False. """ - return bool( - self._latin_square_check() - and isinstance(self._blocks_fufilled_check(), bool) - and self._blocks_fufilled_check() - ) + result_latin = self._latin_square_check() + result_blocks = self._blocks_fufilled_check() + return len(result_latin[0]) + len(result_latin[1]) + len(result_blocks) == 0 def board_filled_handler(self) -> bool: """ Handler for when board is filled.\n - The method calls the victory check and colors in the blocks that are not fufilled if any,\n - and returns True or False if the board is solved. + The method calls the latin_square_check and blocks_fufilled_check an colors in\n + the wrong rows, cols and blocks in red. The rest in green. \n + It returns True or False if the board is solved or not. """ - wrong_blocks = self._blocks_fufilled_check() - if isinstance(wrong_blocks, bool): - wrong_blocks = [] + # First make the entire board green + for row in self.cells: + for cell in row: + cell.color = (100, 255, 100) + + # Find the wrong rows and cols in terms of latin square + wrong_rows, wrong_cols = self._latin_square_check() + + # Color in the wrong rows + for row in wrong_rows: + for cell in self.cells[row]: + cell.color = (255, 0, 0) + # Color in the wrong columns + for row in self.cells: + for col, cell in enumerate(row): + if col in wrong_cols: + cell.color = (255, 0, 0) + + if len(wrong_rows) + len(wrong_cols) > 0: + return False + + # Find the wrong blocks only if now wrong latin rows or cols + wrong_blocks = self._blocks_fufilled_check() + + # Color in the wrong blocks for block in self.blocks: if block in wrong_blocks: - block.color = (255, 0, 0) - else: - block.color = (100, 255, 100) - - # TODO this currently would overwrite any color changes in the roman lines checker - # We should first recolor the blocks based on if they match the block sum criteria, and then overwrite their - # color with the colors from the roman lines check - where only rows/column that break the grid are colored - # -- maybe in another red so its easy to tell apart? - for row in range(self.size): - for cell in self.cells[row]: - cell.reset_color() + for cell in block.cells: + cell.color = (255, 0, 0) - return self.check_victory() + return len(wrong_blocks) == 0 def check_full_grid(self) -> bool: """Helper that checks if a grid is completely filled.""" diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py index 0d61b3faee..8082014aa3 100644 --- a/tests/exts/fun/test_board_filled_handler.py +++ b/tests/exts/fun/test_board_filled_handler.py @@ -50,7 +50,8 @@ def test_board_filled_handler(tmp_path: Path) -> None: grid.cells[0][0].guess = 1 # Comment out the line below to see different colors for singletons - assert grid._latin_square_check() is False + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) > 0 assert grid.check_victory() is False assert grid.board_filled_handler() is False @@ -96,7 +97,8 @@ def _test_board_filled_handler3x3(tmp_path: Path) -> None: cell.guess = cell.correct # Comment out the line below to see different colors for singletons - assert grid._latin_square_check() is False + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) > 0 assert grid.check_victory() is False assert grid.board_filled_handler() is False diff --git a/tests/exts/fun/test_grid_class.py b/tests/exts/fun/test_grid_class.py index f4b8a5ede0..7aa3e968a2 100644 --- a/tests/exts/fun/test_grid_class.py +++ b/tests/exts/fun/test_grid_class.py @@ -24,8 +24,10 @@ def test_latin_square_check(): print(grid) - assert grid._latin_square_check() + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) == 0 grid.cells[3][3].guess = 1 - assert grid._latin_square_check() is False + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) > 0 \ No newline at end of file diff --git a/tests/exts/fun/test_victory_check.py b/tests/exts/fun/test_victory_check.py index 6094e28a13..38603c9852 100644 --- a/tests/exts/fun/test_victory_check.py +++ b/tests/exts/fun/test_victory_check.py @@ -82,7 +82,8 @@ def test_victory_check_lost(tmp_path: Path) -> None: assert grid.check_victory() is False - assert grid._latin_square_check() is False + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) > 0 assert grid._blocks_fufilled_check()[0].id == "A" @@ -170,6 +171,7 @@ def test_victory_check_lost_with_division(tmp_path: Path) -> None: assert grid.check_victory() is False - assert grid._latin_square_check() is False + w_rows, w_cols = grid._latin_square_check() + assert len(w_rows) + len(w_cols) > 0 assert grid._blocks_fufilled_check()[0].id == "H" From aaa4420cb28f5118cee21001f6dfb035a60f6727 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sun, 1 Mar 2026 15:05:30 +0100 Subject: [PATCH 55/74] refactor: change format of colors array to fit style guide (#117) #116 close #116 --- bot/exts/fun/mathdoku.py | 102 ++++++--------------------------------- 1 file changed, 16 insertions(+), 86 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 475e0e36e6..6ec2bc4398 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -5,90 +5,25 @@ from random import randint COLORS = [ - (255, 50, 50), - (255, 66, 50), - (255, 81, 50), - (255, 96, 50), - (255, 111, 50), - (255, 126, 50), - (255, 141, 50), - (255, 156, 50), - (255, 171, 50), - (255, 187, 50), - (255, 202, 50), - (255, 217, 50), - (255, 232, 50), - (255, 247, 50), - (247, 255, 50), - (232, 255, 50), - (217, 255, 50), - (202, 255, 50), - (187, 255, 50), - (171, 255, 50), - (156, 255, 50), - (141, 255, 50), - (126, 255, 50), - (111, 255, 50), - (96, 255, 50), - (81, 255, 50), - (66, 255, 50), - (50, 255, 50), - (50, 255, 66), - (50, 255, 81), - (50, 255, 96), - (50, 255, 111), - (50, 255, 126), - (50, 255, 141), - (50, 255, 156), - (50, 255, 171), - (50, 255, 186), - (50, 255, 202), - (50, 255, 217), - (50, 255, 232), - (50, 255, 247), - (50, 247, 255), - (50, 232, 255), - (50, 217, 255), - (50, 202, 255), - (50, 186, 255), - (50, 171, 255), - (50, 156, 255), - (50, 141, 255), - (50, 126, 255), - (50, 111, 255), - (50, 96, 255), - (50, 81, 255), - (50, 66, 255), - (50, 50, 255), - (66, 50, 255), - (81, 50, 255), - (96, 50, 255), - (111, 50, 255), - (126, 50, 255), - (141, 50, 255), - (156, 50, 255), - (171, 50, 255), - (187, 50, 255), - (202, 50, 255), - (217, 50, 255), - (232, 50, 255), - (247, 50, 255), - (255, 50, 247), - (255, 50, 232), - (255, 50, 217), - (255, 50, 202), - (255, 50, 187), - (255, 50, 171), - (255, 50, 156), - (255, 50, 141), - (255, 50, 126), - (255, 50, 111), - (255, 50, 96), - (255, 50, 81), + (255, 50, 50), (255, 66, 50), (255, 81, 50), (255, 96, 50), (255, 111, 50), + (255, 126, 50), (255, 141, 50), (255, 156, 50), (255, 171, 50), (255, 187, 50), + (255, 202, 50), (255, 217, 50), (255, 232, 50), (255, 247, 50), (247, 255, 50), + (232, 255, 50), (217, 255, 50), (202, 255, 50), (187, 255, 50), (171, 255, 50), + (156, 255, 50), (141, 255, 50), (126, 255, 50), (111, 255, 50), (96, 255, 50), + (81, 255, 50), (66, 255, 50), (50, 255, 50), (50, 255, 66), (50, 255, 81), + (50, 255, 96), (50, 255, 111), (50, 255, 126), (50, 255, 141), (50, 255, 156), + (50, 255, 171), (50, 255, 186), (50, 255, 202), (50, 255, 217), (50, 255, 232), + (50, 255, 247), (50, 247, 255), (50, 232, 255), (50, 217, 255), (50, 202, 255), + (50, 186, 255), (50, 171, 255), (50, 156, 255), (50, 141, 255), (50, 126, 255), + (50, 111, 255), (50, 96, 255), (50, 81, 255), (50, 66, 255), (50, 50, 255), + (66, 50, 255), (81, 50, 255), (96, 50, 255), (111, 50, 255), (126, 50, 255), + (141, 50, 255), (156, 50, 255), (171, 50, 255), (187, 50, 255), (202, 50, 255), + (217, 50, 255), (232, 50, 255), (247, 50, 255), (255, 50, 247), (255, 50, 232), + (255, 50, 217), (255, 50, 202), (255, 50, 187), (255, 50, 171), (255, 50, 156), + (255, 50, 141), (255, 50, 126), (255, 50, 111), (255, 50, 96), (255, 50, 81), (255, 50, 66), ] - class Cell: """Represents a single cell in the grid.""" @@ -178,11 +113,6 @@ def _latin_square_check(self) -> list[set[int], set[int]]: check_row_structure[row][guess - 1] = True check_col_structure[guess - 1][col] = True - #print("\n------------------") - #print(check_row_structure) - #print(check_col_structure) - #print("------------------\n") - wrong_rows = set() wrong_cols = set() From 35966d749685841022c3a3b0143dfc01efcbfe0b Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 18:03:31 +0100 Subject: [PATCH 56/74] docs: contracts added to hint unit tests (#118) #110 closes #110 --- tests/exts/fun/test_mathdoku_hint.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py index 994c4e219f..41506e631f 100644 --- a/tests/exts/fun/test_mathdoku_hint.py +++ b/tests/exts/fun/test_mathdoku_hint.py @@ -39,6 +39,7 @@ def _load_5x5_grid(tmp_path: Path) -> None: return grid def test_hint_find_first_empty_cell(tmp_path: Path) -> None: + """Contract: Checks that the hint method returns the first available empty cell.""" grid = _load_5x5_grid(tmp_path) result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) @@ -47,6 +48,7 @@ def test_hint_find_first_empty_cell(tmp_path: Path) -> None: assert result["guess"] == "A1 4" def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: + """Contract: Checks that hint skips already filled cells and finds the next empty one.""" grid = _load_5x5_grid(tmp_path) grid.cells[0][0].guess = 4 @@ -60,6 +62,7 @@ def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: def test_hint_cooldown(tmp_path: Path) -> None: + """Contract: Checks that hint cooldown is active when fewer than 180 seconds have passed.""" grid = _load_5x5_grid(tmp_path) t0 = datetime(2026, 2, 25, 14, 0, 0) grid.hint(now=t0) @@ -71,6 +74,7 @@ def test_hint_cooldown(tmp_path: Path) -> None: def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: + """Contract: Checks that the cooldown expires exactly at 180 seconds.""" grid = _load_5x5_grid(tmp_path) t0 = datetime(2026, 2, 25, 14, 0, 0) @@ -82,6 +86,7 @@ def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: def test_hint_all_cells_filled(tmp_path: Path) -> None: + """Contract: Checks that no available hint is returned when the board is fully filled.""" grid = _load_5x5_grid(tmp_path) for row in grid.cells: From efed26cabb6ee6768d6108bf9d24f6fa486811b8 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:21:35 +0100 Subject: [PATCH 57/74] docs: added report images folder (#122) #120 closes #120 --- reportImages/componentDiagramMathdoku.png | Bin 0 -> 153564 bytes reportImages/mathdokuClassDiagram.png | Bin 0 -> 107988 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 reportImages/componentDiagramMathdoku.png create mode 100644 reportImages/mathdokuClassDiagram.png diff --git a/reportImages/componentDiagramMathdoku.png b/reportImages/componentDiagramMathdoku.png new file mode 100644 index 0000000000000000000000000000000000000000..7d33cdb3d73d5e92e3178df065cbfe2e6cfad58d GIT binary patch literal 153564 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 literal 0 HcmV?d00001 diff --git a/reportImages/mathdokuClassDiagram.png b/reportImages/mathdokuClassDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..51a3ced56a8c9b7b7e80c38dae3d8a4ec91f8bdb GIT binary patch literal 107988 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 MMn$?@@_FF@2QMSzPXGV_ literal 0 HcmV?d00001 From fd42c470ac533b39f12527a96ef473391c8ec721 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:32:02 +0100 Subject: [PATCH 58/74] docs: add section on what we were proud of [P+ Q7] (#123) #119 close #119 --- report.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/report.md b/report.md index 7865d78615..16ae99e61f 100644 --- a/report.md +++ b/report.md @@ -225,4 +225,5 @@ into well-named methods, each with a single, clear purpose. Optional (point 6): How would you put your work in context with best software engineering practice? -Optional (point 7): Is there something special you want to mention here? +### Optional (point 7): Is there something special you want to mention here? +We are proud of our implementation of the Mathdoku game. We created a nice interface with the board picture that has both thick lines per block and a nice gradient of colors. Furthermore, we only store the picture purly in memory to avoid creating unneccessary local files. The overall design is very good and modular which made it easy to parallellise the work and integrate it all togheter. Essentially we believe it is a very well developed and properly implemented feature that goes above the expectations of the issue. From 49eebf37024f19fb254069f7e2ae15ebf42d1694 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:40:12 +0100 Subject: [PATCH 59/74] docs: added images to the report documentation (#129) #27 Added UML class diagram image and Component Diagram image to the report closes #27 --- report.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/report.md b/report.md index 16ae99e61f..9b31505e67 100644 --- a/report.md +++ b/report.md @@ -18,8 +18,6 @@ We decided to select a new project because the remaining open issues in the mock The experience of selecting an issue in this new project was more satisfactory and straightforward. We chose one that the whole team found particularly interesting. The issue was clearly described, and provide to us more flexibility in terms of implementation and decision-making. - - ## Effort spent For each team member, how much time was spent in 1. plenary discussions/meetings; 2. discussions within parts of the group; @@ -142,7 +140,9 @@ Optional (point 5): considered for acceptance (passes all automated checks). ## Test results Overall results with link to a copy or excerpt of the logs (before/after refactoring). -## UML class diagram and its description ### Key changes/classes affected +## UML class diagram + +![Mathdoku Class Diagram](reportImages/mathdokuClassDiagram.png) Optional (point 1): Architectural overview. @@ -160,7 +160,7 @@ Our `Mathdoku` implementation follows the same philosophy: board logic is separa ### Component Diagram -![alt text]() +![Sir Lancebot Component Diagram](reportImages/componentDiagramMathdoku.png) The system architecture is organized in layers around a modular bot core. `Users` interact through Discord, and the `Discord API` sends events and commands to the `Sir Lancebot Core`, which is responsible for runtime setup and dispatching functionality to the corresponding extension. Each `extension (Cog)` is independent, so features can be added, removed or modified independently without modifying the core logic. Our `Mathdoku` game implementation is included inside the `fun` feature group, but there are other fun features groups as `utilites`, `holidays`, `events`. From f86a67898bcfea2b900c6c271c5aa4bd9931dd23 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:48:34 +0100 Subject: [PATCH 60/74] docs: add statement of contributions for Elias (#130) #124 close #124 --- report.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/report.md b/report.md index 9b31505e67..850825bf07 100644 --- a/report.md +++ b/report.md @@ -19,16 +19,13 @@ We decided to select a new project because the remaining open issues in the mock The experience of selecting an issue in this new project was more satisfactory and straightforward. We chose one that the whole team found particularly interesting. The issue was clearly described, and provide to us more flexibility in terms of implementation and decision-making. ## Effort spent For each team member, how much time was spent in -1. plenary discussions/meetings; -2. discussions within parts of the group; -3. reading documentation; -4. configuration and setup; -5. analyzing code/output; -6. writing documentation; -7. writing code; -8. running code? - -For setting up tools and libraries (step 4), enumerate all dependencies you took care of and where you spent your time, if that time exceeds 30 minutes. +We tracked the time we used on in this sheet: https://docs.google.com/spreadsheets/d/16DlTOOYmCEq0yRz4r5hQJV9sXRtIiayvQccEs5ZUgww/edit?gid=0#gid=0 + +## Statement of contribution + +### [Elias Richard Nรฆss] โ€” GitHub: [@daDevBoat] +Elias was responsible for setting up the basic structure of the Cell, Block and Grid classes. He was also responsible for adding victory check logic and the coloring of wrong columns, rows and blocks. Elias had also a decent amount of involvement in the color picking and distribution for blocks. Furthermore, he was also responsible for making the boarders around the blocks thick. + ## Overview of issue(s) and work done. From b96dd84bb849934e58f31c926bc6c50242eacbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:54:56 +0100 Subject: [PATCH 61/74] refactor: make sure the code fits the styleguide (#133) #80 closes #80 --- bot/exts/fun/mathdoku.py | 66 +++++++++++++++------------- bot/exts/fun/mathdoku_integration.py | 19 ++++---- 2 files changed, 44 insertions(+), 41 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 6ec2bc4398..146079114d 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,8 +1,7 @@ from datetime import datetime from io import BytesIO - -from PIL import Image, ImageDraw, ImageFont from random import randint +from PIL import Image, ImageDraw, ImageFont COLORS = [ (255, 50, 50), (255, 66, 50), (255, 81, 50), (255, 96, 50), (255, 111, 50), @@ -25,7 +24,7 @@ ] class Cell: - """Represents a single cell in the grid.""" + """Represent a single cell in the grid.""" def __init__(self, column: int, row: int) -> None: self.column = column @@ -43,12 +42,14 @@ def guess(self): def guess(self, new_guess) -> None: self._guess = new_guess - def reset_color(self): + def reset_color(self) -> None: + """Reset a cells color attribute to its block color""" self.color = self.block.color class Block: - """Represents a block in the puzzle, with its cells, operation and colour.""" + """Represent a block in the puzzle, with its cells, operation, label and color.""" + def __init__(self, id: str, operation: str, number: int, label_cell: Cell, grid: "Grid") -> None: self.id = id self.cells = [] @@ -62,26 +63,25 @@ def __init__(self, id: str, operation: str, number: int, label_cell: Cell, grid: def compute_color(self) -> tuple[int, int, int]: - """Computes the block's color.""" + """Compute the block's color.""" return COLORS[(self.color_id * (len(COLORS) // (self.grid.current_color_id)) + self.grid.color_offset) % len(COLORS)] class Grid: - """Represents the full game board, with all blocks and player guesses.""" + """Represent the full game board, with all blocks, cells, and difficulty.""" HINT_COOLDOWN_SECONDS = 180 # 3 minutes of hint cooldown. def __init__(self, size: int, difficulty: str | None = None) -> None: self.size = size self.blocks = [] - self._cells = tuple( - tuple(Cell(col, row) for col in range(size)) for row in range(size) - ) # 2D tupple for cells [row][col] - self._last_hint_timestamp = None self.difficulty = difficulty self.color_offset = randint(0, 80) self.current_color_id = 0 + self._cells = tuple( + tuple(Cell(col, row) for col in range(size)) for row in range(size) + ) # 2D tupple for cells [row][col] @property def cells(self): @@ -98,7 +98,8 @@ def __str__(self) -> str: def _latin_square_check(self) -> list[set[int], set[int]]: """ - Checks if the grid is filled correctly in terms of a latin square.\n + Check if the grid is filled correctly in terms of a latin square.\n + I.e all numbers in the range per colum and row exist. """ check_row_structure = [[False for col in range(self.size)] for row in range(self.size)] @@ -128,10 +129,10 @@ def _latin_square_check(self) -> list[set[int], set[int]]: def _blocks_fufilled_check(self) -> list[Block]: """ - Checks if all the blocks are filled correctly and meets the requirements. \n - Returns the blocks that are wrong or True if all blocks meet the requirements. \n - Will return False if the input is invalid. - Will return False if the input is invalid. + Check if all the blocks are filled correctly and meet the requirements. \n + + Return the blocks that are wrong or True if all blocks meet the requirements. \n + Return False if the input is invalid. """ wrong_blocks = [] for block in self.blocks: @@ -167,10 +168,7 @@ def _blocks_fufilled_check(self) -> list[Block]: return wrong_blocks def check_victory(self) -> bool: - """ - Checks if the board is in a state where the player has won and will - return True or False. - """ + """Check if the board is in a state where the player has won and return True or False.""" result_latin = self._latin_square_check() result_blocks = self._blocks_fufilled_check() return len(result_latin[0]) + len(result_latin[1]) + len(result_blocks) == 0 @@ -178,9 +176,10 @@ def check_victory(self) -> bool: def board_filled_handler(self) -> bool: """ Handler for when board is filled.\n - The method calls the latin_square_check and blocks_fufilled_check an colors in\n + + Calls the latin_square_check and blocks_fufilled_check and colors in\n the wrong rows, cols and blocks in red. The rest in green. \n - It returns True or False if the board is solved or not. + Return True or False if the board is solved or not. """ # First make the entire board green for row in self.cells: @@ -216,7 +215,7 @@ def board_filled_handler(self) -> bool: return len(wrong_blocks) == 0 def check_full_grid(self) -> bool: - """Helper that checks if a grid is completely filled.""" + """Check if a grid is completely filled.""" for i in range(self.size): for cell in self.cells[i]: if cell.guess <= 0: @@ -224,7 +223,7 @@ def check_full_grid(self) -> bool: return True def recolor_blocks(self) -> None: - """Method to recolor all blocks in their original color.""" + """Recolor all blocks in their original color.""" for block in self.blocks: block.color = block.compute_color() @@ -234,14 +233,20 @@ def recolor_blocks(self) -> None: def __getitem__(self, i: int) -> list[Cell]: """ - Defines the indexing operator for the Grid class. + Define the indexing operator for the Grid class. Grid[i] will return the i:th row. """ return self.cells[i] - def _generate_image(self, cellSize=80, margin=30, outfile="mathdoku.png", saveToFile=False) -> None: - """Print the Grid to.""" + def _generate_image( + self, + cellSize:int =80, + margin:int = 30, + outfile:str = "mathdoku.png", + saveToFile:bool = False + ) -> BytesIO: + """Generate an image from the Grid, return a buffer holding the image""" fontLable = ImageFont.load_default(15) fontGuess = ImageFont.load_default(30) img = Image.new( @@ -326,7 +331,7 @@ def _generate_image(self, cellSize=80, margin=30, outfile="mathdoku.png", saveTo buffer.seek(0) return buffer - def _find_first_empty_cell(self): + def _find_first_empty_cell(self) -> Cell | None: """Return the first empty cell (`guess == 0`) in row-major order, or `None` if all cells are filled.""" for row in self.cells: for cell in row: @@ -335,7 +340,7 @@ def _find_first_empty_cell(self): return None - def hint(self, now: datetime | None = None): + def hint(self, now: datetime | None = None) -> dict: """Return a hint for the first empty cell, or cooldown/all-filled info if a hint cannot be given.""" current_time = datetime.now() if now is None else now @@ -363,7 +368,8 @@ def hint(self, now: datetime | None = None): def add_guess(self, guess) -> bool: """ - Takes the user guess and checks if its valid, if it is -> add to cell + Take the user guess and check if its valid, if it is -> add to cell + A guess is in format A5 4, where A = column, 5 = row and 4 = guessed value. """ guess = guess.split() diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index bec32a6001..0907e16ed6 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -61,16 +61,11 @@ def __init__(self, bot: Bot, grids: dict): @commands.group(name="Mathdoku", aliases=("md",), invoke_without_command=True) async def mathdoku_group(self, ctx: commands.Context) -> None: """Commands for Playing Mathdoku.""" - await ctx.send("The Mathdoku API is working!") await self.bot.invoke_help_command(ctx) @mathdoku_group.command(name="start") - async def start_command(self, ctx: commands.Context, size: int = 5, difficulty = "medium") -> None: - """Start a game of Mathdoku - size = the board size. Pick from 3-9 - difficulty = easy, medium or hard - """ - + async def start_command(self, ctx: commands.Context, size: int = 5, difficulty:str = "medium") -> None: + """Start a game of Mathdoku. Size = the board size (3-9). Difficulty = easy, medium or hard""" size = int(size) difficulty = str(difficulty).lower() @@ -212,7 +207,8 @@ def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> return True return None - async def magnifying_handler(self, ctx, user) -> None: + async def magnifying_handler(self, ctx:commands.Context, user:discord.User) -> None: + """Handle the magnifiyng glass emoji. Handle board check and Win action""" if self.grid.check_full_grid(): await self.board.remove_reaction(MAGNIFYING_EMOJI, user) @@ -227,7 +223,7 @@ async def magnifying_handler(self, ctx, user) -> None: return await self.board.remove_reaction(MAGNIFYING_EMOJI, user) - async def hint_handler(self, ctx, user) -> None: + async def hint_handler(self, ctx: commands.Context, user: discord.User) -> None: """Handle hint request via ๐Ÿ’ก reaction.""" await self.board.remove_reaction(HINT_EMOJI, user) result = self.grid.hint() @@ -239,13 +235,14 @@ async def hint_handler(self, ctx, user) -> None: else: await ctx.send(f"Hint: {result['guess']}") - async def rules_handler(self, ctx, user) -> None: + async def rules_handler(self, ctx:commands.Context, user:discord.User) -> None: """Handle rules request via ๐Ÿ“• reaction.""" await self.board.remove_reaction(RULE_EMOJI, user) self.rules_msg = await ctx.send(mathdoku_rules) self.rules_msg_exists = True - async def resent_message(self, ctx): + async def resent_message(self, ctx:commands.Context) -> None: + """Delete the board message and send again""" await self.board.delete() self.grid.recolor_blocks() file = discord.File(self.grid._generate_image(), filename="mathdoku.png") From 69d166108a14c93590a836a4ca5b5eac11879e5a Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:00:08 +0100 Subject: [PATCH 62/74] docs: add section on code changes (#135) #121 close #121 --- report.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/report.md b/report.md index 850825bf07..4247b49c2a 100644 --- a/report.md +++ b/report.md @@ -128,14 +128,7 @@ Optional (point 3): trace tests to requirements. | `test_recolor_blocks` | test_recolor_blocks.py | FR-16, FR-17 | ## Code changes - -### Patch (copy your changes or the add git command to show them) git diff ... - -Optional (point 4): the patch is clean. - -Optional (point 5): considered for acceptance (passes all automated checks). - -## Test results Overall results with link to a copy or excerpt of the logs (before/after refactoring). +Since our project does not make changes to the existing code, but instead adds only new code the diff is essentially all the code found in `mathdoku.py`, `mathdoku_parser.py` and the `mathdoku_integration.py`. The `Sir Lancebot` repo does not have a test suit in place. Therefore, the test logs we have are generated when we run pytest for our own test suit. ## UML class diagram From bd48e162219d2b1f9004de633d6660a04c7aafd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:05:22 +0100 Subject: [PATCH 63/74] fix: rules get removed after pressing the rule emoji again (#136) #134 closes #134 --- bot/exts/fun/mathdoku_integration.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 0907e16ed6..829122b4be 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -147,6 +147,9 @@ async def input_number_on_board( await self.rules_msg.delete() self.rules_msg_exists = False self.rules_msg = None + if emoji == RULE_EMOJI: + await self.board.remove_reaction(emoji, user) + return if emoji == MAGNIFYING_EMOJI: await self.magnifying_handler(ctx=ctx, user=user) elif emoji == HINT_EMOJI: From a6f46d3885c0750b1814e280fd1e011236bfd2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jannis=20H=C3=A4ffner?= <57415092+JannisHaeffner@users.noreply.github.com> Date: Sun, 1 Mar 2026 21:48:43 +0100 Subject: [PATCH 64/74] doc: add statement of contribution for Jannis (#137) #125 closes #125 --- report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/report.md b/report.md index 4247b49c2a..7df4b69812 100644 --- a/report.md +++ b/report.md @@ -26,6 +26,9 @@ We tracked the time we used on in this sheet: https://docs.google.com/spreadshee ### [Elias Richard Nรฆss] โ€” GitHub: [@daDevBoat] Elias was responsible for setting up the basic structure of the Cell, Block and Grid classes. He was also responsible for adding victory check logic and the coloring of wrong columns, rows and blocks. Elias had also a decent amount of involvement in the color picking and distribution for blocks. Furthermore, he was also responsible for making the boarders around the blocks thick. +### [Jannis Hรคffner] โ€” GitHub: [@dJannisHaeffner] +Jannis was responsible for the `generate image` function, which generates an image of a given Grid. He was also responsible for displaying and updating the board correctly in Discord and setting up the system to handle reactions. He was furthermore responsible for creating several small helper functions to check if a board is full, verify user guesses, and recolor cells, as well as several small bug fixes along the way. + ## Overview of issue(s) and work done. From 78c0c2e4397ef187a5fe13efaa17d494c6f6fc15 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:16:16 +0100 Subject: [PATCH 65/74] docs: add statement of contributions for arnau (#138) #127 closes #127 --- report.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index 7df4b69812..43b5c022d1 100644 --- a/report.md +++ b/report.md @@ -24,11 +24,15 @@ We tracked the time we used on in this sheet: https://docs.google.com/spreadshee ## Statement of contribution ### [Elias Richard Nรฆss] โ€” GitHub: [@daDevBoat] -Elias was responsible for setting up the basic structure of the Cell, Block and Grid classes. He was also responsible for adding victory check logic and the coloring of wrong columns, rows and blocks. Elias had also a decent amount of involvement in the color picking and distribution for blocks. Furthermore, he was also responsible for making the boarders around the blocks thick. +Elias was responsible for setting up the basic structure of the Cell, Block and Grid classes. He was also responsible for adding victory check logic and the coloring of wrong columns, rows and blocks. Elias had also a decent amount of involvement in the color picking and distribution for blocks. Furthermore, he was also responsible for making the boarders around the blocks thick. ### [Jannis Hรคffner] โ€” GitHub: [@dJannisHaeffner] Jannis was responsible for the `generate image` function, which generates an image of a given Grid. He was also responsible for displaying and updating the board correctly in Discord and setting up the system to handle reactions. He was furthermore responsible for creating several small helper functions to check if a board is full, verify user guesses, and recolor cells, as well as several small bug fixes along the way. +### [Arnau Pelechano Garcรญa] - GitHub: [@arpega75] + +Arnau was responsible for the `hint` feature, including the hint logic and cooldown mecanism in the `Grid` class, its integration into the Discord game flow, and the corresponding unit tests. He also contributed in the design of the UML class diagram, the Component Diagram, the identification of functional requirements and the project documentation. + ## Overview of issue(s) and work done. @@ -219,4 +223,4 @@ into well-named methods, each with a single, clear purpose. Optional (point 6): How would you put your work in context with best software engineering practice? ### Optional (point 7): Is there something special you want to mention here? -We are proud of our implementation of the Mathdoku game. We created a nice interface with the board picture that has both thick lines per block and a nice gradient of colors. Furthermore, we only store the picture purly in memory to avoid creating unneccessary local files. The overall design is very good and modular which made it easy to parallellise the work and integrate it all togheter. Essentially we believe it is a very well developed and properly implemented feature that goes above the expectations of the issue. +We are proud of our implementation of the Mathdoku game. We created a nice interface with the board picture that has both thick lines per block and a nice gradient of colors. Furthermore, we only store the picture purly in memory to avoid creating unneccessary local files. The overall design is very good and modular which made it easy to parallellise the work and integrate it all togheter. Essentially we believe it is a very well developed and properly implemented feature that goes above the expectations of the issue. From 4f7397f2e20a97b46d1d4cb7be285d020c8f8351 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 22:58:58 +0100 Subject: [PATCH 66/74] docs: update functional requirements (#139) #131 closes #131 --- report.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/report.md b/report.md index 43b5c022d1..9d83c32843 100644 --- a/report.md +++ b/report.md @@ -52,32 +52,32 @@ The command `.md start`shall start a new valid **mathdoku** game session. - **FR-03 - Grid Size:** The `.md start` command shall accept an optional grid size parameter. -- **FR-04 - Independent Game Sessions:** -Each game session shall maintain an independent board state. +- **FR-04 - Single Active Game Session:** +The bot shall allow only one active Mathdoku game session at a time. -- **FR-05 - Hint Message Publication:** -After starting a game, the bot publish a hint message and automatically attach a lightbulb emoji reaction to that message. +- **FR-05 - Hint Reaction Availability:** +After starting a game, the bot shall attach a lightbulb emoji reaction to allow hint requests. - **FR-06 - Hint Cooldown Mechanism:** There shall be a cooldown period of 180 seconds between consecutive hint requests. -- **FR-07 - Board Representation:** -The board shall be internally represented as an indexable matrix structure, accessible by row and column coordinates. +- **FR-07 - Start Parameter Validation:** +The `.md start` command shall validate the requested grid size and only accept values between 3 and 9. -- **FR-08 - Cell Data Model:** -Each cell in the board shall store its row and column coordinates, associated block, current guessed value, and correct solution value. +- **FR-08 - Difficulty Selection:** +The `.md start` command shall accept a difficulty parameter with the values `easy`, `medium` and `hard`. -- **FR-09 - Board Parsing Validation:** -The board parser shall ignore any invalid configuration that violates the mathdoku rules. +- **FR-09 - Board Parsing Robustness:** +The board parser shall skip board definitions with invalid structure. -- **FR-10 - Block Data Model:** -Each block shall store a unique id, a mathematical operation, and a target result number. +- **FR-10 - Board Availability Check:** +The game shall only start if at least one board exists for the selected size and difficulty. - **FR-11 - Input Request Handling:** The game shall prompt the player to provide input moves during gameplay. The bot must clearly indicate when user input is expected. -- **FR-12 - Leave command:** -The game shall provide a command allowing users to leave an active game session. +- **FR-12 - Session End by Player Input:** +The game shall allow the active player to end the current session by sending `end`. - **FR-13 - Invalid Input Notification:** The bot shall notify the user whenever an invalid input is detected. @@ -95,10 +95,10 @@ The game shall identify incorrect cells or blocks. The board shall be visually represented as an image showing grid structure and blocking coloring. - **FR-18 - Hint Reaction Trigger:** -The bot shall trigger the hint logic when a user reacts with the lightbulb emoji on the hint message. +The bot shall trigger the hint logic when a user reacts with the lightbulb emoji on the board message. -- **FR-19 - Hint Contect Logic:** -A hint shall return the first empty cell in teh board and reveal its correct value. +- **FR-19 - Hint Content Logic:** +A hint shall return the first empty cell in the board and reveal its correct value. - **FR-20 - Cooldown Feedback:** The bot shall notify the user of the remaining cooldown time in seconds. @@ -106,19 +106,26 @@ The bot shall notify the user of the remaining cooldown time in seconds. - **FR-21 - No Available Hint Notification:** The bot shall notify the user that no hints are available if all cells in the board are filled. +- **FR-22 - Board Check Reaction on Full Grid:** +The bot shall add the magnifying glass reaction to the board message when the grid becomes fully filled, enabling the user to trigger board validation. + +- **FR-23 - Rules Reaction Availability:** +The bot shall provide a rules reaction that publishes the Mathdoku rules during an active game session. + Optional (point 3): trace tests to requirements. -##ย Test Cases Traceability Matrix +## Test Cases Traceability Matrix | Test Case | File | Requirements | |---|---|---| -| `test_load_valid_5x5_grid` | test_mathdoku_parser.py | FR-08, FR-09, FR-10 | +| `test_load_valid_5x5_grid` | test_mathdoku_parser.py | FR-09 | | `test_load_invalid_5x5_grid` | test_mathdoku_parser.py | FR-09 | -| `test_load_valid_5x5_grid_and_check_singletons_have_blocks` | test_mathdoku_parser.py | FR-08, FR-10 | -| `test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells` | test_mathdoku_parser.py | FR-10 | -| `test_grid` | test_grid_class.py | FR-07, FR-08 | +| `test_load_valid_5x5_grid_and_check_singletons_have_blocks` | test_mathdoku_parser.py | FR-09 | +| `test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells` | test_mathdoku_parser.py | FR-09 | +| `test_load_valid_5x5_with_double_digit_difficulty` | test_mathdoku_parser.py | FR-09 | +| `test_load_valid_9x9` | test_mathdoku_parser.py | FR-09 | | `test_latin_square_check` | test_grid_class.py | FR-15 | -| `test_addguess` | test_valid_guess.py | FR-13, FR-15 | +| `test_addguess` | test_valid_guess.py | FR-11 | | `test_hint_find_first_empty_cell` | test_mathdoku_hint.py | FR-19 | | `test_hint_find_empty_cell_after_some_filled` | test_mathdoku_hint.py | FR-19 | | `test_hint_cooldown` | test_mathdoku_hint.py | FR-06, FR-20 | @@ -128,11 +135,11 @@ Optional (point 3): trace tests to requirements. | `test_victory_check_lost` | test_victory_check.py | FR-15, FR-16 | | `test_victory_check_won_with_division` | test_victory_check.py | FR-15 | | `test_victory_check_lost_with_division` | test_victory_check.py | FR-15, FR-16 | -| `test_block_with_id_gets_color` | test_mathdoku.py | FR-10, FR-17 | -| `test_block_with_unexpected_id_gets_color` | test_mathdoku.py | FR-10, FR-17 | +| `test_block_with_id_gets_color` | test_mathdoku.py | FR-17 | +| `test_block_with_unexpected_id_gets_color` | test_mathdoku.py | FR-17 | | `test_image_generation` | test_mathdoku_image_generation.py | FR-17 | -| `test_board_filled_handler` | test_board_filled_handler.py | FR-15, FR-16 | -| `test_recolor_blocks` | test_recolor_blocks.py | FR-16, FR-17 | +| `test_board_filled_handler` | test_board_filled_handler.py | FR-15, FR-16, FR-17 | +| `test_board_filled_handler` | test_recolor_blocks.py | FR-17 | ## Code changes Since our project does not make changes to the existing code, but instead adds only new code the diff is essentially all the code found in `mathdoku.py`, `mathdoku_parser.py` and the `mathdoku_integration.py`. The `Sir Lancebot` repo does not have a test suit in place. Therefore, the test logs we have are generated when we run pytest for our own test suit. From 3223412bbd446a4e7407bcd563598d4ad0453982 Mon Sep 17 00:00:00 2001 From: arnaupg <150595254+arpega75@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:14:55 +0100 Subject: [PATCH 67/74] docs: update extract method explanation (#140) #132 closes #132 --- report.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/report.md b/report.md index 9d83c32843..f038393bf6 100644 --- a/report.md +++ b/report.md @@ -196,7 +196,7 @@ This module connects the game to Discord and manages the full interaction flow w This module contains no game rules. It receives player input messages, delegates to the game logic, and sends the result back to Discord. -Optional (point 2): relation to design pattern(s). +Optional (point 2): relation to refactoring pattern(s). ## Refactoring Patterns We have implemented two refactoring patterns from Martin Fowler's catalogue that align with the overall architecture of `Sir Lancebot`. @@ -216,7 +216,7 @@ own unit. #### Extract Method Within `mathdoku_parser.py`, we decided to divide the parsing logic into smaller functions rather than a single large function. -- `_search_for_grids_in_file()` reads the board file and applies a regex to locate and extract all board definitions. +- `_search_for_grids_in_file()` reads the board file and applies a regex to extract all board definitions. - `_create_cells_and_blocks()` iterates over the board layout, creates `Block` objects, and assigns each cell to its corresponding block. - `_read_block_operations()` assigns the arithmetic operator and target number to the corresponding block. From ba6a0f76a356506a3ccedda1d3a50f8bdf2cb333 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Sun, 1 Mar 2026 23:56:54 +0100 Subject: [PATCH 68/74] docs: add statement of contribution for Jonatan (#141) #128 closes #128 --- report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/report.md b/report.md index f038393bf6..ddce9b8ebc 100644 --- a/report.md +++ b/report.md @@ -33,6 +33,8 @@ Jannis was responsible for the `generate image` function, which generates an ima Arnau was responsible for the `hint` feature, including the hint logic and cooldown mecanism in the `Grid` class, its integration into the Discord game flow, and the corresponding unit tests. He also contributed in the design of the UML class diagram, the Component Diagram, the identification of functional requirements and the project documentation. +### [Jonatan Bรถlenius] - GitHub: [@djonte] +Jonatan was responsible for the generation of `Mathdoku` boards, using a third party library. Furthermore he implemented the parsing and loading of `Mathdoku` boards into memory in a structured manner together with appropriate unit tests. He also made a first draft of the coloring logic, and contributed to the design and implementation of the `Grid` class. ## Overview of issue(s) and work done. From 93d0bfe2ef1e0e4485b79beb051fe921ddc91383 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Mon, 2 Mar 2026 00:37:15 +0100 Subject: [PATCH 69/74] docs: add way of working (#142) #41 closes #41 --- report.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/report.md b/report.md index ddce9b8ebc..7ae0fe6ae0 100644 --- a/report.md +++ b/report.md @@ -233,3 +233,9 @@ Optional (point 6): How would you put your work in context with best software en ### Optional (point 7): Is there something special you want to mention here? We are proud of our implementation of the Mathdoku game. We created a nice interface with the board picture that has both thick lines per block and a nice gradient of colors. Furthermore, we only store the picture purly in memory to avoid creating unneccessary local files. The overall design is very good and modular which made it easy to parallellise the work and integrate it all togheter. Essentially we believe it is a very well developed and properly implemented feature that goes above the expectations of the issue. + + +## Self-assessment: Way of working +In the previous assignment, we evaluated ourselves to be in the state `In Place` in the Checklist for Way-of-Working provided by SEMAT. In this assignment, we have made significant improvement in regards to the points we were lacking in the next state `Working well`. Things such as structuring commits and following a clear issue-branch workflow have become natural habit, and continous tuning of our practices and tools is something that has been thoroughly tested in this project, considering the difficulties in switching and adapting to a new language and environment (we used Java in previous assignments). Furthermore, when something has come up that requires tuning our practices, it is quickly relayed to others in the group such that all can adapt quickly. + +One point that was debated in the `Working well` state is whether the tools that we use naturally support our way of working. While most things have become habbit, we could sometimes struggle with differences in optional (deemed non-necessary to use) extensions, causing slight differences in formatting between programmers, resulting in non-negligable time overhead in some cases. In this way, and when resolving other versioning-system integration issues, the tools were perceived as getting in our way of working. In that sense work was sometimes delayed simply by just the tools we used rather than by the actual work. Therefore we argue that we do not check this point, and are therefor still in the `In Place` state. From 539065c3e76d6cf5009b6e846f970b8d9476b352 Mon Sep 17 00:00:00 2001 From: djonte <90456387+djonte@users.noreply.github.com> Date: Mon, 2 Mar 2026 00:46:16 +0100 Subject: [PATCH 70/74] chore: add hard (and more) 9x9 boards (#144) #143 closes #143 --- bot/resources/fun/mathdoku_boards.txt | 2199 +++++++++++++++++++++++++ 1 file changed, 2199 insertions(+) diff --git a/bot/resources/fun/mathdoku_boards.txt b/bot/resources/fun/mathdoku_boards.txt index 9c4efdd23e..fe78505ec8 100644 --- a/bot/resources/fun/mathdoku_boards.txt +++ b/bot/resources/fun/mathdoku_boards.txt @@ -17641,3 +17641,2202 @@ h 3- 7 9 4 3 1 6 2 5 8 3 5 9 8 6 2 7 1 4 1 3 7 6 4 9 5 8 2 + +9x9:d5 (easy) +AE7aUTTPP +AEKaUbbb8 +NNK6RMMGG +NKKRRCMWD +VVKRCC3WD +YVIIHZZ5F +Y9QcHZLLF +YSQcJJJdB +SSSOOXXdB + +A 5- +B 24x +C 72x +D 45x +E 6+ +F 5- +G 3- +H 24x +I 42x +J 14+ +K 23+ +L 4- +M 392x +N 216x +O 14+ +P 2/ +Q 3+ +R 9+ +S 1152x +T 10+ +U 5- +V 112x +W 11+ +X 3+ +Y 30x +Z 288x +a 4- +b 60x +c 6x +d 2- + +2 5 7 8 4 9 1 6 3 +7 1 3 4 9 5 6 2 8 +9 3 5 6 2 7 8 4 1 +8 2 4 5 1 6 7 3 9 +4 7 9 1 6 2 3 8 5 +1 4 6 7 3 8 9 5 2 +6 9 2 3 8 4 5 1 7 +5 8 1 2 7 3 4 9 6 +3 6 8 9 5 1 2 7 4 + +9x9:d7 (easy) +BFFEE2eeR +BHaaYYRRR +cHVgPUULL +c8VgPCDDL +NNAAPC3ZZ +bIIKKKMZW +bbO9ddMMW +GGOXSTTff +QQ2XSTJJJ + +A 13+ +B 7+ +C 2- +D 4+ +E 2- +F 6- +G 42x +H 7- +I 11+ +J 19+ +K 10+ +L 17+ +M 432x +N 3+ +O 2- +P 9+ +Q 1- +R 20+ +S 17+ +T 17+ +U 4/ +V 1- +W 3- +X 3+ +Y 6+ +Z 80x +a 1- +b 17+ +c 10+ +d 4- +e 2- +f 5+ +g 1- + +4 3 9 8 6 2 5 7 1 +3 2 8 7 5 1 4 6 9 +1 9 6 5 3 8 2 4 7 +9 8 5 4 2 7 1 3 6 +2 1 7 6 4 9 3 5 8 +8 7 4 3 1 6 9 2 5 +5 4 1 9 7 3 6 8 2 +7 6 3 2 9 5 8 1 4 +6 5 2 1 8 4 7 9 3 + +9x9:d7 (easy) +bbbGNNcTT +BVVGHXcAa +BBCKHXcAa +dLCKRRJAa +dL4QEEJPP +MYYQQZZFP +MDOSSSZFW +eDOOffIWW +e2ggUUIhh + +A 14+ +B 32x +C 6- +D 17+ +E 1- +F 5- +G 3- +H 7- +I 3- +J 5+ +K 18x +L 8+ +M 4- +N 1- +O 12+ +P 192x +Q 25+ +R 1- +S 40x +T 2/ +U 1- +V 18x +W 24+ +X 7- +Y 10x +Z 12+ +a 17+ +b 27x +c 22+ +d 40x +e 54x +f 12x +g 4- +h 4+ + +1 3 9 5 7 8 6 4 2 +4 6 3 8 1 2 9 7 5 +2 4 1 6 8 9 7 5 3 +8 1 7 3 5 6 4 2 9 +5 7 4 9 2 3 1 8 6 +3 5 2 7 9 1 8 6 4 +7 9 6 2 4 5 3 1 8 +6 8 5 1 3 4 2 9 7 +9 2 8 4 6 7 5 3 1 + +9x9:d6 (easy) +bOdSSHHH9 +bOdIS9CFF +PPDIYYCFN +TTDIWeeNN +GT9UWRRRN +GGQUBBMMM +cQQQEAaaa +cJZ7EALXV +6JZKKLLXV + +A 4- +B 21x +C 7+ +D 15x +E 5- +F 42x +G 24+ +H 21+ +I 192x +J 12+ +K 4- +L 18x +M 15+ +N 23+ +O 2/ +P 1- +Q 6x +R 9+ +S 20x +T 96x +U 1- +V 2- +W 3/ +X 2- +Y 45x +Z 2- +a 18+ +b 7+ +c 5- +d 28x +e 56x + +2 3 4 5 1 6 7 8 9 +5 6 7 8 4 9 1 2 3 +1 2 3 4 9 5 6 7 8 +3 4 5 6 2 7 8 9 1 +7 8 9 1 6 2 3 4 5 +8 9 1 2 7 3 4 5 6 +9 1 2 3 8 4 5 6 7 +4 5 6 7 3 8 9 1 2 +6 7 8 9 5 1 2 3 4 + +9x9:d6 (easy) +YYAddBNJJ +FFAKXBNNc +aFQKXBSSc +aFQK9BLWW +T2QDDZLWH +TCRRGZPPH +9CRVGMMPU +EEVVGMIUU +OO7VGIIbb + +A 36x +B 448x +C 28x +D 30x +E 5- +F 26+ +G 16+ +H 45x +I 120x +J 10+ +K 13+ +L 3- +M 18+ +N 11+ +O 8+ +P 14+ +Q 10+ +R 40x +S 3/ +T 4- +U 112x +V 22+ +W 48x +X 12x +Y 5- +Z 5- +a 6+ +b 1- +c 13+ +d 1- + +8 3 4 6 7 2 5 9 1 +4 8 9 2 3 7 1 5 6 +5 9 1 3 4 8 2 6 7 +1 5 6 8 9 4 7 2 3 +7 2 3 5 6 1 4 8 9 +3 7 8 1 2 6 9 4 5 +9 4 5 7 8 3 6 1 2 +6 1 2 4 5 9 3 7 8 +2 6 7 9 1 5 8 3 4 + +9x9:d6 (easy) +AABPPSSDD +ABBHPSeD9 +LLBHCFe4G +bbMHCFFUG +TTMMKK3UG +QcXXEEaaW +QcNN2EJJW +RVVYYOIZZ +RRVYOOIdd + +A 21x +B 18+ +C 4- +D 48x +E 140x +F 11+ +G 60x +H 70x +I 11+ +J 5- +K 3- +L 3- +M 10+ +N 3- +O 48x +P 15+ +Q 24x +R 8x +S 64x +T 4/ +U 1- +V 18+ +W 4+ +X 4/ +Y 108x +Z 7- +a 6+ +b 27x +c 2- +d 2- +e 16+ + +7 1 3 9 5 8 2 6 4 +3 6 8 5 1 4 7 2 9 +5 8 1 7 3 6 9 4 2 +9 3 5 2 7 1 4 8 6 +8 2 4 1 6 9 3 7 5 +6 9 2 8 4 7 1 5 3 +4 7 9 6 2 5 8 3 1 +2 5 7 4 9 3 6 1 8 +1 4 6 3 8 2 5 9 7 + +9x9:d5 (easy) +fWTTEEIIG +fW6QQHIIG +XXXSSHaaN +AAFbbHCCN +UcFJJMeeN +UcD7YMRVd +UUDPYRRVd +ZKKPYR8VO +Z2BBBBLLO + +A 1- +B 26+ +C 6+ +D 6x +E 54x +F 1- +G 4- +H 105x +I 294x +J 8- +K 2- +L 5- +M 8x +N 36x +O 12+ +P 4- +Q 1- +R 14+ +S 1- +T 4- +U 960x +V 288x +W 5- +X 16x +Y 18+ +Z 2- +a 14+ +b 72x +c 35x +d 8- +e 4- +f 5- + +4 3 1 5 6 9 2 7 8 +9 8 6 1 2 5 7 3 4 +2 1 8 3 4 7 9 5 6 +7 6 4 8 9 3 5 1 2 +8 7 5 9 1 4 6 2 3 +6 5 3 7 8 2 4 9 1 +5 4 2 6 7 1 3 8 9 +1 9 7 2 3 6 8 4 5 +3 2 9 4 5 8 1 6 7 + +9x9:d4 (easy) +ddLEEccRR +1HLEbTMNN +eHQQbTMN3 +eOOCCC9WW +DD5BCaaKK +SSFBX1GUU +ffFBXXG6Z +VAAJIIYPZ +VVAJJYYPP + +A 18+ +B 504x +C 504x +D 9+ +E 90x +F 1- +G 9+ +H 1- +I 10+ +J 192x +K 9+ +L 8+ +M 56x +N 21+ +O 12+ +P 12+ +Q 5- +R 4- +S 48x +T 20x +U 4- +V 75x +W 6+ +X 12x +Y 11+ +Z 7- +a 18x +b 30x +c 4/ +d 2- +e 7- +f 16+ + +4 6 1 5 9 8 2 3 7 +1 3 7 2 6 5 8 9 4 +9 2 6 1 5 4 7 8 3 +2 4 8 3 7 6 9 1 5 +8 1 5 9 4 3 6 7 2 +6 8 3 7 2 1 4 5 9 +7 9 4 8 3 2 5 6 1 +5 7 2 6 1 9 3 4 8 +3 5 9 4 8 7 1 2 6 + +9x9:d7 (easy) +DKKMFFbHH +DSSMWbbPP +YYggWAAPJ +aaUUU9ReJ +QhTfBORe6 +QhTfBOGLd +IINNZOGLd +I3XXZcGVC +1XXEEccVC + +A 15+ +B 4- +C 13+ +D 2/ +E 5- +F 3- +G 16+ +H 15+ +I 315x +J 7+ +K 6- +L 3- +M 8+ +N 7- +O 36x +P 144x +Q 11+ +R 7- +S 3/ +T 9+ +U 105x +V 3- +W 1- +X 29+ +Y 6+ +Z 2- +a 3/ +b 168x +c 8+ +d 6- +e 2- +f 5- +g 2- +h 4- + +2 7 1 3 8 5 4 6 9 +4 9 3 5 1 7 6 8 2 +5 1 4 6 2 8 7 9 3 +6 2 5 7 3 9 8 1 4 +8 4 7 9 5 2 1 3 6 +3 8 2 4 9 6 5 7 1 +9 5 8 1 6 3 2 4 7 +7 3 6 8 4 1 9 2 5 +1 6 9 2 7 4 3 5 8 + +9x9:d6 (easy) +P3UNNJJbb +PZULNFHHH +OZZLNFYRR +OQQ8gKYRG +ccAAgK8GG +CC7XDDMWW +VVBXTTMee +VdBfIIEES +7dBfIaaES + +A 4- +B 12+ +C 4/ +D 9+ +E 21+ +F 7- +G 30x +H 10+ +I 9+ +J 35x +K 15+ +L 2- +M 3- +N 432x +O 1- +P 27x +Q 10+ +R 252x +S 1- +T 56x +U 10+ +V 13+ +W 8- +X 2/ +Y 1- +Z 18+ +a 8+ +b 1- +c 3- +d 10+ +e 12x +f 3+ +g 6- + +9 3 8 4 6 5 7 2 1 +3 6 2 7 9 8 1 5 4 +5 8 4 9 2 1 3 7 6 +4 7 3 8 1 9 2 6 5 +1 4 9 5 7 6 8 3 2 +8 2 7 3 5 4 6 1 9 +2 5 1 6 8 7 9 4 3 +6 9 5 1 3 2 4 8 7 +7 1 6 2 4 3 5 9 8 + +9x9:d6 (easy) +II9AZNJJe +ddMAZNPRe +GMMZZNPRQ +GffEEBY4Q +HaaE8BYCC +HKUVV4YDc +8KUWWSSDc +hLTbWOODF +hLTbggXXF + +A 45x +B 15+ +C 3/ +D 15+ +E 18x +F 2- +G 2- +H 2/ +I 9+ +J 1- +K 7+ +L 11+ +M 19+ +N 12x +O 45x +P 6+ +Q 2- +R 8+ +S 36x +T 2- +U 14x +V 12+ +W 18x +X 56x +Y 48x +Z 16+ +a 1- +b 14+ +c 8+ +d 2- +e 32x +f 1- +g 1- +h 2- + +1 8 9 5 3 2 6 7 4 +5 3 4 9 7 6 1 2 8 +9 7 8 4 2 1 5 6 3 +7 5 6 2 9 8 3 4 1 +6 4 5 1 8 7 2 3 9 +3 1 2 7 5 4 8 9 6 +8 6 7 3 1 9 4 5 2 +4 2 3 8 6 5 9 1 7 +2 9 1 6 4 3 7 8 5 + +9x9:d6 (easy) +KKEIAAGGG +OOEI2CCGa +UUcYY7Faa +U6cYDDFMM +JQQQLLTMR +JJbbLZTMR +8WbBBZXXR +NWHPdd8VV +NWHPPSSVV + +A 2- +B 7+ +C 1- +D 11+ +E 1- +F 5- +G 17+ +H 16x +I 15+ +J 15+ +K 10+ +L 384x +M 22+ +N 3- +O 1- +P 16+ +Q 6x +R 315x +S 30x +T 16+ +U 42x +V 126x +W 28x +X 2- +Y 11+ +Z 4+ +a 32x +b 12+ +c 36x +d 2- + +1 9 7 8 3 5 4 6 2 +9 8 6 7 2 4 3 5 1 +3 2 9 1 5 7 6 8 4 +7 6 4 5 9 2 1 3 8 +4 3 1 2 6 8 7 9 5 +6 5 3 4 8 1 9 2 7 +8 7 5 6 1 3 2 4 9 +5 4 2 3 7 9 8 1 6 +2 1 8 9 4 6 5 7 3 + +9x9:d5 (easy) +3UUZZfCCP +DUKZVfXIP +DcKVVAXIP +YcKLbANNd +Y6QLbSNRd +WWQLbSSRH +BB8FTeeGH +BJJFTOMG8 +EEJJOOMaa + +A 4+ +B 28x +C 1- +D 4- +E 9+ +F 6- +G 7+ +H 2- +I 24x +J 72x +K 135x +L 30x +M 30x +N 21+ +O 19+ +P 48x +Q 24x +R 2- +S 8x +T 27x +U 126x +V 24x +W 4/ +X 5- +Y 1- +Z 22+ +a 28x +b 16+ +c 2- +d 8- +e 9+ +f 12+ + +3 9 7 6 8 5 2 1 4 +5 2 9 8 1 7 4 3 6 +1 7 5 4 6 3 9 8 2 +8 5 3 2 4 1 7 6 9 +9 6 4 3 5 2 8 7 1 +2 8 6 5 7 4 1 9 3 +4 1 8 7 9 6 3 2 5 +7 4 2 1 3 9 6 5 8 +6 3 1 9 2 8 5 4 7 + +9x9:d10 (medium) +ddPTTQQGG +JJPT9QQaa +cORYYYQaE +cORYWWAIE +7RRYNNAIX +DDfBCCAbX +M8fBKKVbb +MFFeeSVHU +ZZZLLS3HU + +A 16+ +B 12x +C 11+ +D 10+ +E 1- +F 5- +G 13+ +H 4+ +I 14+ +J 4+ +K 4- +L 1- +M 3- +N 5- +O 42x +P 48x +Q 7560x +R 11+ +S 24x +T 16x +U 2- +V 3- +W 8+ +X 3+ +Y 18+ +Z 16+ +a 16+ +b 19+ +c 1- +d 2- +e 8- +f 36x + +5 3 8 1 2 7 6 4 9 +3 1 6 8 9 5 4 2 7 +8 6 2 4 5 1 9 7 3 +9 7 3 5 6 2 1 8 4 +7 5 1 3 4 9 8 6 2 +6 4 9 2 3 8 7 5 1 +1 8 4 6 7 3 2 9 5 +4 2 7 9 1 6 5 3 8 +2 9 5 7 8 4 3 1 6 + +9x9:d11 (medium) +UKKXXdTbP +UKBBBdTbP +ccHZAARLL +VSHZARRRW +VSSZAGOIW +5CQQQGOIW +FCDQMMMI2 +F6DNNNIIE +FYYJJJaaE + +A 21+ +B 21+ +C 4- +D 1- +E 6+ +F 216x +G 3+ +H 4+ +I 756x +J 504x +K 240x +L 1- +M 90x +N 12+ +O 1- +P 3/ +Q 288x +R 22+ +S 180x +T 3- +U 8+ +V 3/ +W 19+ +X 2/ +Y 3+ +Z 15+ +a 10+ +b 4/ +c 6+ +d 2/ + +7 5 6 2 4 3 1 8 9 +1 8 9 5 7 6 4 2 3 +4 2 3 8 1 9 7 5 6 +2 9 1 6 8 7 5 3 4 +6 4 5 1 3 2 9 7 8 +5 3 4 9 2 1 8 6 7 +9 7 8 4 6 5 3 1 2 +8 6 7 3 5 4 2 9 1 +3 1 2 7 9 8 6 4 5 + +9x9:d12 (medium) +GGNN1QQPP +cJ2KeOIMM +cJYKeOIfM +FFY9VVIfX +ZRTaUU8XX +ZRTaWWLLL +bSSSW9AdL +bHBBECAdD +bHBEECDDD + +A 10x +B 252x +C 2- +D 18+ +E 13+ +F 1- +G 11+ +H 2- +I 63x +J 9+ +K 48x +L 24+ +M 15x +N 11+ +O 35x +P 16+ +Q 2/ +R 16+ +S 10+ +T 2- +U 1- +V 56x +W 96x +X 48x +Y 9+ +Z 7- +a 2- +b 16+ +c 7- +d 3- +e 2- +f 1- + +6 5 8 3 1 2 4 7 9 +9 8 2 6 4 5 7 1 3 +2 1 4 8 6 7 9 3 5 +3 2 5 9 7 8 1 4 6 +1 9 3 7 5 6 8 2 4 +8 7 1 5 3 4 6 9 2 +4 3 6 1 8 9 2 5 7 +7 6 9 4 2 3 5 8 1 +5 4 7 2 9 1 3 6 8 + +9x9:d10 (medium) +ddWWU8VVa +AAW7UcMaa +eeBNNcMII +YSBD2OOOO +YS2DbbEEO +F8TTXbKKG +FJJJXQQGG +HCCRRPP7Z +HHHHLLLZZ + +A 1- +B 1- +C 7- +D 3- +E 6+ +F 6- +G 80x +H 35+ +I 6- +J 72x +K 14+ +L 20x +M 1- +N 7- +O 672x +P 8+ +Q 4- +R 2- +S 28x +T 7+ +U 2- +V 13+ +W 9+ +X 18x +Y 2/ +Z 72x +a 16+ +b 288x +c 1- +d 6x +e 1- + +2 3 1 5 7 8 4 9 6 +4 5 3 7 9 1 6 2 8 +5 6 4 8 1 2 7 3 9 +6 7 5 9 2 3 8 4 1 +3 4 2 6 8 9 5 1 7 +7 8 6 1 3 4 9 5 2 +1 2 9 4 6 7 3 8 5 +9 1 8 3 5 6 2 7 4 +8 9 7 2 4 5 1 6 3 + +9x9:d10 (medium) +DW6ZHbbNN +DWZZHbJTT +UEEEY8Jcc +Uhh4YgKKc +XaBBYgKVV +XaBdLSSSf +FIMdLRRRf +FIMOCCeGQ +AAAOPPeGQ + +A 28x +B 42x +C 3- +D 1- +E 14x +F 1- +G 5- +H 10+ +I 1- +J 13+ +K 11+ +L 2/ +M 1- +N 4/ +O 4- +P 4/ +Q 7+ +R 20+ +S 14+ +T 6- +U 24x +V 11+ +W 1- +X 6+ +Y 210x +Z 210x +a 32x +b 60x +c 90x +d 9+ +e 10+ +f 11+ +g 8- +h 3/ + +9 3 6 7 1 4 5 2 8 +8 2 5 6 9 3 4 1 7 +4 7 1 2 5 8 9 6 3 +6 9 3 4 7 1 2 8 5 +5 8 2 3 6 9 1 7 4 +1 4 7 8 2 5 6 3 9 +3 6 9 1 4 7 8 5 2 +2 5 8 9 3 6 7 4 1 +7 1 4 5 8 2 3 9 6 + +9x9:d11 (medium) +SSUNNAAXX +c8UNNNMMX +cPP3KZZFF +BB7WKCCYQ +BBBWEE3YQ +bVVDGGG2Q +bJLDTGRRR +IJLDTOOR3 +IILTTaaHH + +A 2- +B 1890x +C 2- +D 56x +E 5- +F 11+ +G 23+ +H 2/ +I 16+ +J 3+ +K 3- +L 20+ +M 2- +N 3360x +O 2- +P 7+ +Q 17+ +R 864x +S 7- +T 16+ +U 1- +V 32x +W 5- +X 17+ +Y 6+ +Z 7- +a 9+ +b 3- +c 7- + +2 9 4 6 8 3 5 7 1 +1 8 3 5 7 2 4 6 9 +8 6 1 3 5 9 2 4 7 +5 3 7 9 2 6 8 1 4 +9 7 2 4 6 1 3 5 8 +6 4 8 1 3 7 9 2 5 +3 1 5 7 9 4 6 8 2 +4 2 6 8 1 5 7 9 3 +7 5 9 2 4 8 1 3 6 + +9x9:d12 (medium) +BWWaa7Gee +BPPggGGYA +fZZ9SSLYA +fHHIISLL7 +C5HcEEXXb +CJJcFRUUb +CJMMFRQQT +DDNNNOOKT +2VVVddOKT + +A 9+ +B 2- +C 63x +D 13+ +E 10+ +F 28x +G 21+ +H 36x +I 10x +J 17+ +K 18x +L 48x +M 2/ +N 13+ +O 18+ +P 5- +Q 5- +R 7+ +S 7+ +T 27x +U 14+ +V 105x +W 2/ +X 5- +Y 1- +Z 9+ +a 3- +b 2- +c 4+ +d 2- +e 1- +f 2- +g 4/ + +3 8 4 6 9 7 5 1 2 +5 1 6 8 2 9 7 3 4 +6 2 7 9 3 1 8 4 5 +8 4 9 2 5 3 1 6 7 +9 5 1 3 6 4 2 7 8 +7 3 8 1 4 2 9 5 6 +1 6 2 4 7 5 3 8 9 +4 9 5 7 1 8 6 2 3 +2 7 3 5 8 6 4 9 1 + +9x9:d10 (medium) +aQ6NNHHZZ +aQSNNPY6L +OSSbbPYYL +OJeFFPKMM +OJeWWEKDM +AAAIIEED8 +CVddIfBBB +CVVG9fRRc +TTTGXXUUc + +A 90x +B 21+ +C 21x +D 4- +E 35x +F 15x +G 2/ +H 3- +I 15+ +J 5- +K 4- +L 1- +M 12+ +N 20+ +O 11+ +P 108x +Q 4/ +R 5- +S 12+ +T 19+ +U 6+ +V 30x +W 7- +X 4/ +Y 45x +Z 14+ +a 3- +b 2- +c 24x +d 8+ +e 2/ +f 3/ + +8 2 6 3 1 7 4 9 5 +5 8 3 9 7 4 1 6 2 +4 7 2 8 6 3 9 5 1 +1 4 8 5 3 9 6 2 7 +6 9 4 1 8 5 2 7 3 +2 5 9 6 4 1 7 3 8 +3 6 1 7 5 2 8 4 9 +7 1 5 2 9 6 3 8 4 +9 3 7 4 2 8 5 1 6 + +9x9:d11 (medium) +ITWccOOXN +ITWW9KXXN +DD5MMKXCC +RRG7LKEaa +RYGGLHE9P +RYYFLHUUP +BBYFFddAA +VVFFZZSSQ +VbbJJJJJQ + +A 1- +B 4- +C 1- +D 36x +E 9+ +F 22+ +G 48x +H 1- +I 2- +J 864x +K 15+ +L 12+ +M 5- +N 24x +O 1- +P 3+ +Q 40x +R 36x +S 10+ +T 2- +U 7+ +V 20+ +W 18+ +X 105x +Y 28+ +Z 2- +a 1- +b 5- +c 14x +d 1- + +5 1 6 2 7 9 8 3 4 +7 3 8 4 9 2 1 5 6 +4 9 5 1 6 8 7 2 3 +1 6 2 7 3 5 4 8 9 +2 7 3 8 4 6 5 9 1 +3 8 4 9 5 7 6 1 2 +8 4 9 5 1 3 2 6 7 +9 5 1 6 2 4 3 7 8 +6 2 7 3 8 1 9 4 5 + +9x9:d12 (medium) +bbFPP6Vdd +MHFPccVGG +MHUUURVaa +ZAAUJRIaN +ZZTTJKINN +QSS7fKDDL +QOeEfYWLL +XOeE7YWBB +XXXEEYCCC + +A 10+ +B 1- +C 15+ +D 4- +E 12+ +F 21x +G 1- +H 6- +I 3/ +J 11+ +K 12+ +L 54x +M 10x +N 14x +O 3- +P 13+ +Q 3- +R 7+ +S 6x +T 36x +U 21+ +V 17+ +W 6- +X 126x +Y 19+ +Z 17+ +a 336x +b 3- +c 5- +d 8- +e 40x +f 3- + +7 4 3 8 2 6 5 9 1 +2 8 7 3 6 1 9 4 5 +5 2 1 6 9 4 3 7 8 +4 1 9 5 8 3 2 6 7 +8 5 4 9 3 7 6 1 2 +6 3 2 7 1 5 4 8 9 +9 6 5 1 4 8 7 2 3 +3 9 8 4 7 2 1 5 6 +1 7 6 2 5 9 8 3 4 + +9x9:d11 (medium) +H1VWWWWTT +HHVDDcRRT +bGGYDcBRJ +bUYY6FBBJ +PULLLFdXX +PSS7AAdOO +PSCQQZZ9O +NNCCKKIEE +NaaMMKIE4 + +A 8- +B 13+ +C 7+ +D 90x +E 15+ +F 5- +G 32x +H 18+ +I 5- +J 3+ +K 18+ +L 18+ +M 2- +N 14+ +O 240x +P 10+ +Q 4+ +R 96x +S 144x +T 14+ +U 5- +V 7+ +W 22+ +X 13+ +Y 60x +Z 11+ +a 14x +b 15+ +c 3- +d 1- + +9 1 6 5 7 8 2 4 3 +4 5 1 9 2 3 6 8 7 +7 8 4 3 5 6 9 2 1 +8 9 5 4 6 7 1 3 2 +3 4 9 8 1 2 5 7 6 +2 3 8 7 9 1 4 6 5 +5 6 2 1 3 4 7 9 8 +6 7 3 2 4 5 8 1 9 +1 2 7 6 8 9 3 5 4 + +9x9:d11 (medium) +NNaMF7YYY +NLaMFDDUe +NLccFIUUe +fOOcEIAAB +fXPPETTZB +2XXVCCWZZ +RRXVJCWbb +dRQKJSSS1 +d9QKJHHGG + +A 5- +B 2- +C 14+ +D 3- +E 7- +F 17+ +G 14x +H 2/ +I 4/ +J 140x +K 4/ +L 4/ +M 4+ +N 1008x +O 11+ +P 2- +Q 5+ +R 10+ +S 20+ +T 3- +U 10+ +V 45x +W 4- +X 588x +Y 9+ +Z 288x +a 2- +b 5- +c 252x +d 3- +e 3- +f 2- + +9 4 5 3 8 7 1 6 2 +7 2 3 1 6 5 8 4 9 +4 8 9 7 3 2 5 1 6 +1 5 6 4 9 8 2 7 3 +3 7 8 6 2 1 4 9 5 +2 6 7 5 1 9 3 8 4 +6 1 2 9 5 4 7 3 8 +8 3 4 2 7 6 9 5 1 +5 9 1 8 4 3 6 2 7 + +9x9:d11 (medium) +SBB8iiWWW +SBBMM5FFT +dNNCZZffT +dACCJJJOD +UAAAYYeOD +UXGKLLeHH +bXGKccIVg +bhhEEEIVg +RRQQPPIaa + +A 1134x +B 21+ +C 8+ +D 14x +E 17+ +F 5- +G 9+ +H 5+ +I 14+ +J 192x +K 2- +L 5- +M 3/ +N 2- +O 6+ +P 5- +Q 3- +R 7+ +S 5- +T 14+ +U 1- +V 48x +W 14+ +X 2- +Y 5- +Z 21x +a 3/ +b 2- +c 5- +d 1- +e 8- +f 14+ +g 2- +h 4+ +i 6+ + +6 9 2 8 5 1 3 7 4 +1 4 6 3 9 5 7 2 8 +8 2 4 1 7 3 5 9 6 +9 3 5 2 8 4 6 1 7 +4 7 9 6 3 8 1 5 2 +3 6 8 5 2 7 9 4 1 +5 8 1 7 4 9 2 6 3 +7 1 3 9 6 2 4 8 5 +2 5 7 4 1 6 8 3 9 + +9x9:d10 (medium) +2EEGGRRRZ +EEW7MPXRZ +bLWTMPXHZ +bLaTJccHH +VIaaJJJSS +VIIOOdU7Q +VIYB3dUAQ +KYYBFC5AQ +KDDBFCNNN + +A 72x +B 13+ +C 3- +D 4- +E 360x +F 3- +G 1- +H 30x +I 17+ +J 1008x +K 3- +L 8+ +M 2- +N 16x +O 1- +P 2- +Q 21+ +R 72x +S 1- +T 6+ +U 2- +V 105x +W 2- +X 16+ +Y 36x +Z 14+ +a 22+ +b 2/ +c 24x +d 11+ + +2 9 5 8 7 6 1 4 3 +1 8 4 7 6 5 9 3 2 +8 6 2 5 4 3 7 1 9 +4 2 7 1 9 8 3 6 5 +3 1 6 9 8 7 2 5 4 +5 3 8 2 1 9 4 7 6 +7 5 1 4 3 2 6 9 8 +6 4 9 3 2 1 5 8 7 +9 7 3 6 5 4 8 2 1 + +9x9:d11 (medium) +ccJLLLLII +EEJJMNWTT +KKJ9MNWbC +KZJYPPQbC +ZZ5YRPQbA +SSSGRRR4A +9VVGOOFdU +aDDGG8FdU +aBBGXXHH1 + +A 14+ +B 7- +C 1- +D 2- +E 11+ +F 11+ +G 21+ +H 30x +I 4- +J 15+ +K 13+ +L 20+ +M 1- +N 1- +O 4- +P 23+ +Q 7+ +R 15+ +S 63x +T 5- +U 2- +V 14+ +W 15+ +X 4- +Y 11+ +Z 168x +a 4- +b 112x +c 3+ +d 4+ + +2 1 8 6 7 3 4 5 9 +6 5 3 1 2 7 8 9 4 +5 4 2 9 1 6 7 8 3 +4 3 1 8 9 5 6 7 2 +8 7 5 3 4 9 1 2 6 +1 9 7 5 6 2 3 4 8 +9 8 6 4 5 1 2 3 7 +7 6 4 2 3 8 9 1 5 +3 2 9 7 8 4 5 6 1 + +9x9:d10 (medium) +SaQDDDXX7 +SaQJDee7Z +STQJ6KKAZ +bTIIGNNAA +b8HFGYYBA +PVHFGOYBc +PVRRWOCCc +PVVEWOMMU +LLLEdd7UU + +A 26+ +B 15+ +C 10+ +D 240x +E 3- +F 10x +G 84x +H 8+ +I 7+ +J 1- +K 3- +L 14+ +M 3- +N 4/ +O 315x +P 18+ +Q 21+ +R 21x +S 15+ +T 3- +U 14+ +V 14+ +W 18x +X 3/ +Y 12+ +Z 1- +a 3/ +b 14x +c 5- +d 4- +e 5+ + +5 2 4 8 1 6 9 3 7 +9 6 8 3 5 1 4 7 2 +1 7 9 4 6 2 5 8 3 +7 4 6 1 3 8 2 5 9 +2 8 1 5 7 3 6 9 4 +8 5 7 2 4 9 3 6 1 +4 1 3 7 9 5 8 2 6 +6 3 5 9 2 7 1 4 8 +3 9 2 6 8 4 7 1 5 + +9x9:d10 (medium) +3YYYPPKHa +RRUUUUKHa +TTdd8DVVV +TGGFEDCZZ +OMGFEDCZL +OMbWWJJZL +OMbWSQBBL +cII1SQNNN +cXXSS8AAA + +A 30x +B 17+ +C 1- +D 9+ +E 12x +F 1- +G 81x +H 7+ +I 1- +J 2/ +K 9+ +L 10+ +M 13+ +N 72x +O 16+ +P 63x +Q 4- +R 9+ +S 19+ +T 13+ +U 14+ +V 27x +W 17+ +X 1- +Y 19+ +Z 24+ +a 36x +b 1- +c 20x +d 35x + +3 5 6 8 9 7 2 1 4 +8 1 2 4 5 3 7 6 9 +2 4 5 7 8 6 1 9 3 +7 9 1 3 4 2 6 5 8 +6 8 9 2 3 1 5 4 7 +9 2 3 5 6 4 8 7 1 +1 3 4 6 7 5 9 8 2 +5 7 8 1 2 9 4 3 6 +4 6 7 9 1 8 3 2 5 + +9x9:d10 (medium) +FFZ9ffDDb +SSZZINNNb +RRZIIQYEE +JVVddQYYW +JccUKXXYW +PPLUKKeMM +PHLUTOeM2 +GHCCTOBBA +GaaCCBBBA + +A 63x +B 17+ +C 19+ +D 4/ +E 15+ +F 13+ +G 9+ +H 2/ +I 10x +J 4- +K 162x +L 1- +M 10+ +N 23+ +O 12+ +P 18x +Q 2- +R 1- +S 1- +T 9+ +U 19+ +V 7+ +W 2/ +X 8- +Y 210x +Z 15+ +a 4- +b 15x +c 12x +d 4- +e 1- +f 3- + +6 7 3 9 4 1 2 8 5 +4 5 1 7 2 8 9 6 3 +7 8 4 1 5 2 3 9 6 +9 1 6 3 7 4 5 2 8 +5 6 2 8 3 9 1 7 4 +2 3 8 5 9 6 7 4 1 +3 4 9 6 1 7 8 5 2 +1 2 7 4 8 5 6 3 9 +8 9 5 2 6 3 4 1 7 + +9x9:d10 (medium) +WWeeNNNbb +USHCCXXRR +USHHOXQcc +MAHOOIQLL +MAKVVIQBZ +M1KVVDGBZ +5EKJJDGGG +EEFaPdTTf +YYFaPdT4f + +A 4/ +B 2- +C 4- +D 15+ +E 36x +F 2- +G 17+ +H 1440x +I 3- +J 5- +K 13+ +L 2/ +M 14+ +N 56x +O 54x +P 6+ +Q 162x +R 5- +S 30x +T 210x +U 1- +V 945x +W 1- +X 8x +Y 1- +Z 2- +a 14+ +b 14+ +c 9+ +d 2- +e 4- +f 18x + +4 3 6 2 7 8 1 9 5 +7 6 9 5 1 2 4 3 8 +6 5 8 4 9 1 3 2 7 +3 2 5 1 6 7 9 8 4 +9 8 2 7 3 4 6 5 1 +2 1 4 9 5 6 8 7 3 +5 4 7 3 8 9 2 1 6 +1 9 3 8 4 5 7 6 2 +8 7 1 6 2 3 5 4 9 + +9x9:d11 (medium) +cIIaaMMWW +cIKKEbb6e +OOOKEBBLe +YYGXTTRLU +9YGXTTRRU +VVS9THFQU +NNSSdHFQ7 +ASSPdJZCD +AA9PPJZCD + +A 15+ +B 7+ +C 2- +D 9+ +E 15+ +F 13+ +G 1- +H 4- +I 10+ +J 24x +K 28x +L 12+ +M 3- +N 3/ +O 32x +P 240x +Q 4- +R 23+ +S 27+ +T 16+ +U 60x +V 5- +W 3- +X 7+ +Y 10+ +Z 2- +a 3+ +b 3- +c 56x +d 3- +e 12+ + +8 3 5 2 1 9 6 7 4 +7 2 4 1 9 8 5 6 3 +4 8 1 7 6 5 2 3 9 +1 5 7 4 3 2 8 9 6 +9 4 6 3 2 1 7 8 5 +6 1 3 9 8 7 4 5 2 +2 6 8 5 4 3 9 1 7 +5 9 2 8 7 6 3 4 1 +3 7 9 6 5 4 1 2 8 + +9x9:d10 (medium) +aaAAJJfNN +IIbMeJfYN +cIbMeQQY4 +cRRVXBBBT +FFRVXXTTT +HF2DDGEEW +H2UZZGCWW +LPUZKKCCd +LPOOKKSSd + +A 2- +B 108x +C 18+ +D 1- +E 21x +F 19+ +G 2- +H 7- +I 126x +J 13+ +K 18x +L 7+ +M 5- +N 144x +O 2- +P 20x +Q 8- +R 144x +S 24x +T 21+ +U 2- +V 4- +W 9+ +X 15+ +Y 7+ +Z 336x +a 1- +b 14+ +c 10x +d 11+ +e 5- +f 11+ + +7 8 1 3 4 5 6 2 9 +6 7 9 2 3 4 5 1 8 +2 3 5 7 8 9 1 6 4 +5 6 8 1 2 3 4 9 7 +9 1 3 5 6 7 8 4 2 +8 9 2 4 5 6 7 3 1 +1 2 4 6 7 8 9 5 3 +3 4 6 8 9 1 2 7 5 +4 5 7 9 1 2 3 8 6 + +9x9:d11 (medium) +NNZZ1PPRR +BBDDSSffR +bb2DIIIYY +TTaaEE5JJ +ccAHVVGGQ +M1AHHVVQQ +MKLLH1FFQ +XKUUCOWFd +XXeeCOWWd + +A 5+ +B 7- +C 1- +D 14+ +E 5- +F 432x +G 2- +H 18+ +I 20+ +J 5- +K 6- +L 7+ +M 56x +N 2- +O 9+ +P 10+ +Q 378x +R 13+ +S 4- +T 24x +U 13+ +V 22+ +W 6+ +X 8x +Y 6+ +Z 1- +a 8- +b 2- +c 2- +d 11+ +e 15+ +f 7- + +3 5 8 9 1 6 4 2 7 +9 2 5 6 7 3 1 8 4 +6 8 2 3 4 9 7 5 1 +4 6 9 1 2 7 5 3 8 +5 7 1 2 3 8 6 4 9 +8 1 4 5 6 2 9 7 3 +7 9 3 4 5 1 8 6 2 +1 3 6 7 8 4 2 9 5 +2 4 7 8 9 5 3 1 6 + +9x9:d10 (medium) +bbWDDcccF +ObWNCII6F +O6NNCIgZE +aPP1LTgZE +aHHULTGBe +hVRU3TGBe +hVRUMSSJJ +KX3dMSAYY +KXddQQAff + +A 3/ +B 6+ +C 3- +D 3/ +E 3- +F 5- +G 7+ +H 5- +I 54x +J 11+ +K 3- +L 1- +M 3/ +N 56x +O 40x +P 5- +Q 9+ +R 11+ +S 20x +T 15+ +U 17+ +V 4/ +W 6+ +X 6- +Y 2/ +Z 6- +a 5+ +b 180x +c 112x +d 240x +e 4- +f 5- +g 40x +h 8+ + +4 5 1 3 9 8 7 2 6 +8 9 5 7 4 3 2 6 1 +5 6 2 4 1 9 8 3 7 +2 3 8 1 7 6 5 9 4 +3 4 9 2 8 7 6 1 5 +7 8 4 6 3 2 1 5 9 +1 2 7 9 6 5 4 8 3 +6 7 3 5 2 1 9 4 8 +9 1 6 8 5 4 3 7 2 + +9x9:d10 (medium) +9WWIIaaXX +cc5RIaBBB +UUUR1GGee +SSQOOOGDV +JJQHHNNDV +LLLYYNNDZ +LKCPbFFZZ +AKCPbTF1E +A8MMbTddE + +A 1- +B 17+ +C 2/ +D 14+ +E 1- +F 23+ +G 11+ +H 1- +I 96x +J 3- +K 16+ +L 14+ +M 15x +N 14+ +O 42x +P 10+ +Q 17+ +R 2- +S 13+ +T 1- +U 126x +V 10+ +W 5- +X 5- +Y 1- +Z 18+ +a 120x +b 210x +c 3- +d 2- +e 2/ + +9 6 1 3 4 8 5 7 2 +4 1 5 7 8 3 9 2 6 +6 3 7 9 1 5 2 4 8 +8 5 9 2 3 7 4 6 1 +7 4 8 1 2 6 3 5 9 +5 2 6 8 9 4 1 3 7 +1 7 2 4 5 9 6 8 3 +3 9 4 6 7 2 8 1 5 +2 8 3 5 6 1 7 9 4 + +9x9:d11 (medium) +CCAPPLLbM +OcAAaSSbM +OcTTaR5JY +OHWWaRRJY +UHH9GGdEY +UXXQQDdE7 +XXFFFDVEE +3IKBBBVZZ +IIKKKNNNZ + +A 72x +B 240x +C 35x +D 24x +E 18+ +F 84x +G 6- +H 36x +I 18x +J 1- +K 1440x +L 5- +M 2- +N 126x +O 180x +P 10+ +Q 1- +R 16+ +S 6+ +T 8+ +U 2- +V 8x +W 6- +X 20+ +Y 60x +Z 14x +a 15+ +b 2/ +c 5- +d 2- + +7 5 4 1 9 3 8 2 6 +9 7 6 3 2 5 1 4 8 +4 2 1 7 6 9 5 8 3 +5 3 2 8 7 1 6 9 4 +6 4 3 9 8 2 7 1 5 +8 6 5 2 1 4 9 3 7 +1 8 7 4 3 6 2 5 9 +3 1 9 6 5 8 4 7 2 +2 9 8 5 4 7 3 6 1 + +9x9:d10 (medium) +DD5YYYBBf +VeFLL2BBf +VeFZZHHTX +CCNNZKKTX +G1aaIIWJJ +GGQQQIWW6 +PccEAbOOO +PRREAb3OM +PddSSUUMM + +A 2- +B 120x +C 5- +D 2/ +E 2- +F 1- +G 180x +H 2- +I 120x +J 2- +K 2- +L 1- +M 16x +N 10+ +O 16+ +P 378x +Q 6+ +R 1- +S 1- +T 1- +U 2- +V 1- +W 448x +X 20x +Y 378x +Z 18x +a 1- +b 7- +c 1- +d 6x +e 1- +f 2- + +8 4 5 6 7 9 2 3 1 +1 6 7 8 9 2 4 5 3 +2 7 8 9 1 3 5 6 4 +3 8 9 1 2 4 6 7 5 +5 1 2 3 4 6 8 9 7 +4 9 1 2 3 5 7 8 6 +7 3 4 5 6 8 1 2 9 +9 5 6 7 8 1 3 4 2 +6 2 3 4 5 7 9 1 8 + +9x9:d15 (hard) +4RRRMMaaQ +bbTTWWaQQ +O7HSSYYYP +OHHLLJ2IP +NVHDDJIIP +NVUGDEBBZ +ccUG9ECZZ +XAAKKECee +XXddKFFFF + +A 2- +B 2- +C 4+ +D 168x +E 20+ +F 22+ +G 6- +H 20+ +I 72x +J 10+ +K 9+ +L 10+ +M 4- +N 14+ +O 5- +P 17+ +Q 70x +R 17+ +S 20x +T 11+ +U 3- +V 4- +W 36x +X 19+ +Y 48x +Z 21x +a 14+ +b 5- +c 4- +d 6+ +e 4- + +4 9 2 6 7 3 8 1 5 +1 6 8 3 4 9 5 7 2 +2 7 9 4 5 1 6 8 3 +7 3 5 9 1 6 2 4 8 +5 1 3 7 8 4 9 2 6 +9 5 7 2 3 8 4 6 1 +6 2 4 8 9 5 1 3 7 +8 4 6 1 2 7 3 5 9 +3 8 1 5 6 2 7 9 4 + +9x9:d10 (hard) +ZJUUUDDII +ZJbbDDG7I +8OOPPMGGI +EEQQ8MeeI +LLTTSSKXF +VVVSS2KXF +NNcHHH5CY +RRc2ddACY +aaBBWWAAY + +A 17+ +B 11+ +C 1- +D 27+ +E 6+ +F 2- +G 21+ +H 36x +I 252x +J 11+ +K 2- +L 5- +M 2/ +N 3/ +O 5- +P 1- +Q 15+ +R 4- +S 2520x +T 9+ +U 8+ +V 18+ +W 7+ +X 9+ +Y 360x +Z 10+ +a 5- +b 3- +c 15+ +d 4- +e 2/ + +9 5 1 4 3 7 8 6 2 +1 6 2 5 4 8 9 7 3 +8 4 9 3 2 6 7 5 1 +5 1 6 9 8 3 4 2 7 +2 7 3 6 5 9 1 8 4 +4 9 5 8 7 2 3 1 6 +6 2 7 1 9 4 5 3 8 +7 3 8 2 1 5 6 4 9 +3 8 4 7 6 1 2 9 5 From 0782977842330237f0dca01bfb9c7ae33bb618f3 Mon Sep 17 00:00:00 2001 From: Alexander Mannertorn Date: Mon, 2 Mar 2026 01:25:55 +0100 Subject: [PATCH 71/74] docs: write statement of contributions for Alexander closes #126 --- report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/report.md b/report.md index 7ae0fe6ae0..4ca56dcde7 100644 --- a/report.md +++ b/report.md @@ -36,6 +36,9 @@ Arnau was responsible for the `hint` feature, including the hint logic and coold ### [Jonatan Bรถlenius] - GitHub: [@djonte] Jonatan was responsible for the generation of `Mathdoku` boards, using a third party library. Furthermore he implemented the parsing and loading of `Mathdoku` boards into memory in a structured manner together with appropriate unit tests. He also made a first draft of the coloring logic, and contributed to the design and implementation of the `Grid` class. +### [Alexander Mannertorn] โ€” GitHub: [knasssss] +Alexander was responsible for creating the first version of the Mathdoku Discord cog, which included implementing the `start` command, and functionality for inputting a number on the board and checking if it is a valid input. He also implemented the `rules` feature, and wrote the "Overview of issue(s) and work done" section of the report. + ## Overview of issue(s) and work done. ### Title: Kenken command #989 From c86fa752eb2d64f0e708a5da76d4cecf29c9fdd2 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:52:40 +0100 Subject: [PATCH 72/74] fix: remove error in docker by adding a setup func for mathdoku.py (#146) #78 This was tested by running the docker build and seeing that the error was removed. Which it was. close #78 --- bot/exts/fun/mathdoku.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index 146079114d..ac812c54cc 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -2,6 +2,11 @@ from io import BytesIO from random import randint from PIL import Image, ImageDraw, ImageFont +from bot.bot import Bot + +async def setup(bot: Bot) -> None: + """Setup function to avoid erros.""" + return COLORS = [ (255, 50, 50), (255, 66, 50), (255, 81, 50), (255, 96, 50), (255, 111, 50), From 89e77d74d399f466872923574ee14dc2d791598d Mon Sep 17 00:00:00 2001 From: daDevBoat Date: Mon, 2 Mar 2026 11:54:38 +0100 Subject: [PATCH 73/74] chore: delete test folder and report to prepare for PR --- report.md | 244 ------------------ reportImages/componentDiagramMathdoku.png | Bin 153564 -> 0 bytes reportImages/mathdokuClassDiagram.png | Bin 107988 -> 0 bytes tests/exts/fun/test_board_filled_handler.py | 108 -------- tests/exts/fun/test_color_gen.py | 13 - tests/exts/fun/test_grid_class.py | 33 --- tests/exts/fun/test_mathdoku.py | 17 -- tests/exts/fun/test_mathdoku_hint.py | 98 ------- .../fun/test_mathdoku_image_generation.py | 60 ----- tests/exts/fun/test_mathdoku_parser.py | 228 ---------------- tests/exts/fun/test_recolor_blocks.py | 58 ----- tests/exts/fun/test_valid_guess.py | 60 ----- tests/exts/fun/test_victory_check.py | 177 ------------- tests/test_pytest.py | 18 -- 14 files changed, 1114 deletions(-) delete mode 100644 report.md delete mode 100644 reportImages/componentDiagramMathdoku.png delete mode 100644 reportImages/mathdokuClassDiagram.png delete mode 100644 tests/exts/fun/test_board_filled_handler.py delete mode 100644 tests/exts/fun/test_color_gen.py delete mode 100644 tests/exts/fun/test_grid_class.py delete mode 100644 tests/exts/fun/test_mathdoku.py delete mode 100644 tests/exts/fun/test_mathdoku_hint.py delete mode 100644 tests/exts/fun/test_mathdoku_image_generation.py delete mode 100644 tests/exts/fun/test_mathdoku_parser.py delete mode 100644 tests/exts/fun/test_recolor_blocks.py delete mode 100644 tests/exts/fun/test_valid_guess.py delete mode 100644 tests/exts/fun/test_victory_check.py delete mode 100644 tests/test_pytest.py diff --git a/report.md b/report.md deleted file mode 100644 index 4ca56dcde7..0000000000 --- a/report.md +++ /dev/null @@ -1,244 +0,0 @@ -# Lab 4 Report: **Issue Resolution** - -## Project Description - -### Project Name: **Sir Lancebot** - -### URL: https://github.com/python-discordsir-lancebot - -### One or two sentences describing it: - -Sir Lancebot is an open-source Discord bot written in Python and maintained by the Python Discord community. On the project, contributors can implement new commands and features, or fix issues. - -## Onboarding experience - -### Did you choose a new project or continue on the previous one? If you changed the project, how did your experience differ from before? - -We decided to select a new project because the remaining open issues in the mockito project were very complex to resolve. Therefore, we chose to siwtch to `Sir Lancebot`. - -The experience of selecting an issue in this new project was more satisfactory and straightforward. We chose one that the whole team found particularly interesting. The issue was clearly described, and provide to us more flexibility in terms of implementation and decision-making. - -## Effort spent For each team member, how much time was spent in -We tracked the time we used on in this sheet: https://docs.google.com/spreadsheets/d/16DlTOOYmCEq0yRz4r5hQJV9sXRtIiayvQccEs5ZUgww/edit?gid=0#gid=0 - -## Statement of contribution - -### [Elias Richard Nรฆss] โ€” GitHub: [@daDevBoat] -Elias was responsible for setting up the basic structure of the Cell, Block and Grid classes. He was also responsible for adding victory check logic and the coloring of wrong columns, rows and blocks. Elias had also a decent amount of involvement in the color picking and distribution for blocks. Furthermore, he was also responsible for making the boarders around the blocks thick. - -### [Jannis Hรคffner] โ€” GitHub: [@dJannisHaeffner] -Jannis was responsible for the `generate image` function, which generates an image of a given Grid. He was also responsible for displaying and updating the board correctly in Discord and setting up the system to handle reactions. He was furthermore responsible for creating several small helper functions to check if a board is full, verify user guesses, and recolor cells, as well as several small bug fixes along the way. - -### [Arnau Pelechano Garcรญa] - GitHub: [@arpega75] - -Arnau was responsible for the `hint` feature, including the hint logic and cooldown mecanism in the `Grid` class, its integration into the Discord game flow, and the corresponding unit tests. He also contributed in the design of the UML class diagram, the Component Diagram, the identification of functional requirements and the project documentation. - -### [Jonatan Bรถlenius] - GitHub: [@djonte] -Jonatan was responsible for the generation of `Mathdoku` boards, using a third party library. Furthermore he implemented the parsing and loading of `Mathdoku` boards into memory in a structured manner together with appropriate unit tests. He also made a first draft of the coloring logic, and contributed to the design and implementation of the `Grid` class. - -### [Alexander Mannertorn] โ€” GitHub: [knasssss] -Alexander was responsible for creating the first version of the Mathdoku Discord cog, which included implementing the `start` command, and functionality for inputting a number on the board and checking if it is a valid input. He also implemented the `rules` feature, and wrote the "Overview of issue(s) and work done" section of the report. - -## Overview of issue(s) and work done. - -### Title: Kenken command #989 -### URL: https://github.com/python-discord/sir-lancebot/issues/989 (link to the original issue, should it be the issue to the pullrequest or the fork instead?) -### Summary in one or two sentences Scope (functionality and code affected). -We created a Mathdoku game command for the Sir Lancebot Discord bot. We only created new files in the sir-lancebot repository and did not change any existing files. - -## Requirements for the new feature or requirements affected by functionality being refactored - -- **FR-01 - Help Command Exposure:** -The bot shall expose a `help` entry under the command group `.Mathdoku` for running **mathdoku**. - -- **FR-02 - Start Game Command:** -The command `.md start`shall start a new valid **mathdoku** game session. - -- **FR-03 - Grid Size:** -The `.md start` command shall accept an optional grid size parameter. - -- **FR-04 - Single Active Game Session:** -The bot shall allow only one active Mathdoku game session at a time. - -- **FR-05 - Hint Reaction Availability:** -After starting a game, the bot shall attach a lightbulb emoji reaction to allow hint requests. - -- **FR-06 - Hint Cooldown Mechanism:** -There shall be a cooldown period of 180 seconds between consecutive hint requests. - -- **FR-07 - Start Parameter Validation:** -The `.md start` command shall validate the requested grid size and only accept values between 3 and 9. - -- **FR-08 - Difficulty Selection:** -The `.md start` command shall accept a difficulty parameter with the values `easy`, `medium` and `hard`. - -- **FR-09 - Board Parsing Robustness:** -The board parser shall skip board definitions with invalid structure. - -- **FR-10 - Board Availability Check:** -The game shall only start if at least one board exists for the selected size and difficulty. - -- **FR-11 - Input Request Handling:** -The game shall prompt the player to provide input moves during gameplay. The bot must clearly indicate when user input is expected. - -- **FR-12 - Session End by Player Input:** -The game shall allow the active player to end the current session by sending `end`. - -- **FR-13 - Invalid Input Notification:** -The bot shall notify the user whenever an invalid input is detected. - -- **FR-14 - Inactivity Timeout:** -The game session shall automatically terminate after a defined period of user inactivity. - -- **FR-15 - Win Condition Validation:** -The game shall validate winning conditions by checking both the Latin square properly and correct block constraint satisfaction. - -- **FR-16 - Error Identification:** -The game shall identify incorrect cells or blocks. - -- **FR-17 - Visual Board Representation:** -The board shall be visually represented as an image showing grid structure and blocking coloring. - -- **FR-18 - Hint Reaction Trigger:** -The bot shall trigger the hint logic when a user reacts with the lightbulb emoji on the board message. - -- **FR-19 - Hint Content Logic:** -A hint shall return the first empty cell in the board and reveal its correct value. - -- **FR-20 - Cooldown Feedback:** -The bot shall notify the user of the remaining cooldown time in seconds. - -- **FR-21 - No Available Hint Notification:** -The bot shall notify the user that no hints are available if all cells in the board are filled. - -- **FR-22 - Board Check Reaction on Full Grid:** -The bot shall add the magnifying glass reaction to the board message when the grid becomes fully filled, enabling the user to trigger board validation. - -- **FR-23 - Rules Reaction Availability:** -The bot shall provide a rules reaction that publishes the Mathdoku rules during an active game session. - -Optional (point 3): trace tests to requirements. - -## Test Cases Traceability Matrix - -| Test Case | File | Requirements | -|---|---|---| -| `test_load_valid_5x5_grid` | test_mathdoku_parser.py | FR-09 | -| `test_load_invalid_5x5_grid` | test_mathdoku_parser.py | FR-09 | -| `test_load_valid_5x5_grid_and_check_singletons_have_blocks` | test_mathdoku_parser.py | FR-09 | -| `test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells` | test_mathdoku_parser.py | FR-09 | -| `test_load_valid_5x5_with_double_digit_difficulty` | test_mathdoku_parser.py | FR-09 | -| `test_load_valid_9x9` | test_mathdoku_parser.py | FR-09 | -| `test_latin_square_check` | test_grid_class.py | FR-15 | -| `test_addguess` | test_valid_guess.py | FR-11 | -| `test_hint_find_first_empty_cell` | test_mathdoku_hint.py | FR-19 | -| `test_hint_find_empty_cell_after_some_filled` | test_mathdoku_hint.py | FR-19 | -| `test_hint_cooldown` | test_mathdoku_hint.py | FR-06, FR-20 | -| `test_hint_available_again_at_180_seconds` | test_mathdoku_hint.py | FR-06 | -| `test_hint_all_cells_filled` | test_mathdoku_hint.py | FR-21 | -| `test_victory_check_won` | test_victory_check.py | FR-15 | -| `test_victory_check_lost` | test_victory_check.py | FR-15, FR-16 | -| `test_victory_check_won_with_division` | test_victory_check.py | FR-15 | -| `test_victory_check_lost_with_division` | test_victory_check.py | FR-15, FR-16 | -| `test_block_with_id_gets_color` | test_mathdoku.py | FR-17 | -| `test_block_with_unexpected_id_gets_color` | test_mathdoku.py | FR-17 | -| `test_image_generation` | test_mathdoku_image_generation.py | FR-17 | -| `test_board_filled_handler` | test_board_filled_handler.py | FR-15, FR-16, FR-17 | -| `test_board_filled_handler` | test_recolor_blocks.py | FR-17 | - -## Code changes -Since our project does not make changes to the existing code, but instead adds only new code the diff is essentially all the code found in `mathdoku.py`, `mathdoku_parser.py` and the `mathdoku_integration.py`. The `Sir Lancebot` repo does not have a test suit in place. Therefore, the test logs we have are generated when we run pytest for our own test suit. - -## UML class diagram - -![Mathdoku Class Diagram](reportImages/mathdokuClassDiagram.png) - -Optional (point 1): Architectural overview. - -## Architectural Overview - -### System Purpose. - -**Sir Lancebot** is an open-source `Discord bot` developed and maintained by the Python Discord community. Its primary purpose is to serve a beginner-friendly project for developers who want to learn and contribute to open source. The bot provides a variety of features for Discord servers (fun games, utilities, seasonal commands, event tools). - -In practice, the system combines a bot core with multiple independent features modules (cogs), so new functionality can be added with minimal impact on existing commands. - -Our `Mathdoku` implementation follows the same philosophy: board logic is separated from Discord interaction and from the board-file parsing, which keeps each concern testable and replaceable. - -### System Architecture - -### Component Diagram - -![Sir Lancebot Component Diagram](reportImages/componentDiagramMathdoku.png) - -The system architecture is organized in layers around a modular bot core. -`Users` interact through Discord, and the `Discord API` sends events and commands to the `Sir Lancebot Core`, which is responsible for runtime setup and dispatching functionality to the corresponding extension. Each `extension (Cog)` is independent, so features can be added, removed or modified independently without modifying the core logic. Our `Mathdoku` game implementation is included inside the `fun` feature group, but there are other fun features groups as `utilites`, `holidays`, `events`. - -### Mathdoku Architecture in the system. - -The `Mathdoku` feature is implemented as a subsystem across three modules with clear separation of responsibilities. - -#### 1. Game Logic (`mathdoku.py`) -This module contains the core game model: -- **Cell:** stores coordinates (row, column), block, player guess, and correct value. -- **Block:** stores block metadata (id, operation, number, assigned cells, color). -- **Grid:** stores board state and game rules (latin-square rules, full-board checks, check block constraints, evaluates win condition, hint cooldown handling...). - -This module is independent from Discord commands, so game correctness can be verified independently from the bot's event handling. - -#### 2. Board Parsing (`mathdoku_parser.py`) -This module reads `mathdoku_boards.txt`, extract board definitions, builds `Grid` objects, assign blocks and operations, and loads the correct solution values. - -This module acts as the entry point for board data, transforming raw text into structured game objects ready to be used by the game logic. - -#### 3. Discord Integration (`mathdoku_integration.py`) -This module connects the game to Discord and manages the full interaction flow with the player: -- Starts a game session. -- Wait for player input messages with a 10 minutes inactivity timeout. -- Handles guesses from user inputs. -- Handles emoji reactions for hints and block validation. - -This module contains no game rules. It receives player input messages, -delegates to the game logic, and sends the result back to Discord. - -Optional (point 2): relation to refactoring pattern(s). - -## Refactoring Patterns -We have implemented two refactoring patterns from Martin Fowler's catalogue that align with the overall architecture of `Sir Lancebot`. - -#### Extract Class - -Instead of implememting the entire `Mathdoku` feature in a single file, we decided to separate the different responsibilities into three independent files. - -- `mathdoku.py` owns the game model and rules. -- `mathdoku_parser.py` owns the board file loading. -- `mathdoku_integration.py` owns the Discord interaction. - -This reflects the **Extract Class** pattern: when a class or module -takes on too many responsibilities, each concern is extracted into its -own unit. - -#### Extract Method -Within `mathdoku_parser.py`, we decided to divide the parsing logic into smaller functions rather than a single large function. - -- `_search_for_grids_in_file()` reads the board file and applies a regex to extract all board definitions. -- `_create_cells_and_blocks()` iterates over the board layout, - creates `Block` objects, and assigns each cell to its corresponding block. -- `_read_block_operations()` assigns the arithmetic operator and target number to the corresponding block. -- `_read_solution()` sets the correct value on each cell. - -This reflects the **Extract Method** pattern: when complex logic is divided -into well-named methods, each with a single, clear purpose. - -## Overall experience What are your main take-aways from this project? What did you learn? How did you grow as a team, using the Essence standard to evaluate yourself? - -Optional (point 6): How would you put your work in context with best software engineering practice? - -### Optional (point 7): Is there something special you want to mention here? -We are proud of our implementation of the Mathdoku game. We created a nice interface with the board picture that has both thick lines per block and a nice gradient of colors. Furthermore, we only store the picture purly in memory to avoid creating unneccessary local files. The overall design is very good and modular which made it easy to parallellise the work and integrate it all togheter. Essentially we believe it is a very well developed and properly implemented feature that goes above the expectations of the issue. - - -## Self-assessment: Way of working -In the previous assignment, we evaluated ourselves to be in the state `In Place` in the Checklist for Way-of-Working provided by SEMAT. In this assignment, we have made significant improvement in regards to the points we were lacking in the next state `Working well`. Things such as structuring commits and following a clear issue-branch workflow have become natural habit, and continous tuning of our practices and tools is something that has been thoroughly tested in this project, considering the difficulties in switching and adapting to a new language and environment (we used Java in previous assignments). Furthermore, when something has come up that requires tuning our practices, it is quickly relayed to others in the group such that all can adapt quickly. - -One point that was debated in the `Working well` state is whether the tools that we use naturally support our way of working. While most things have become habbit, we could sometimes struggle with differences in optional (deemed non-necessary to use) extensions, causing slight differences in formatting between programmers, resulting in non-negligable time overhead in some cases. In this way, and when resolving other versioning-system integration issues, the tools were perceived as getting in our way of working. In that sense work was sometimes delayed simply by just the tools we used rather than by the actual work. Therefore we argue that we do not check this point, and are therefor still in the `In Place` state. diff --git a/reportImages/componentDiagramMathdoku.png b/reportImages/componentDiagramMathdoku.png deleted file mode 100644 index 7d33cdb3d73d5e92e3178df065cbfe2e6cfad58d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 153564 zcmeEv2{@E(`}f$DETsr(M5PjvP?i~OREk!TEqm6n?+j6tGD=a2sie)m@5WY=82dif z?9A9_%$S+)9wKee^SsaT|GxkGzTeYv$YAdKn(MsI{rsKhee0~cD*IagwIC3P{p5*b z=RqK5ArOf1BP%oTn}-qXY9P?M(-udMo;`W=sL)vln3;vODF}4p)RddsLFmqA?(4S&b|+lL2w&$;xW4Ohg6~FNOTCTn3xXLo z#qwC$i&59mrO$xTR3&r{z5q90?OBM+8w_Cp6$DE@9C+~r^yTD~sZ=o~{^ggAJi~m- zAf|o368pZeeL1gw@!~;WSuaXnK_PR&b3K={mgF=l>Ph(7?Yto0V~3wgJYbs=qO1Ys zUlMxQ&!GI^NT-)(;l*3cN8&~N>5k~els(?l@vdxmkC%OosKiaSNfFS;5wF6%+@QOV zk7;WeAD<26NMlmp@cc4;3b+j7?k*j`C3(GtnOJma_>{K`QA_?Yy#6$}_RF-iYVw|l z!y+s}{dKyOr_{79)b=TdO{I>POJ7CrV7qm0VBP54E_0Pf8{&>GXx`tteocxjXqN;J z2T@E)T5bJ?q$yP!MhA&_zN<$RPeuz=3iKKWEcosVV&7IB`b8yxbAF>~kpBZnvd^^} z2a&uiDO<~Agx#->g`%Ze?>y>pPcr?O{d5~;w}I?t|MS!|VbSQAy7Kh2WBJ>>Kd0;y zlUQ%Gr;LuNH-I%rMkpk7u=}BRlx3hmYGr7US=i<{n>iPR4!Thne zS-MzC=y(}HJZFo-Im~>mg|I_5BR90(MGF1|Jo}KI?skDq{U$X`v;H?n!YIz(c4BXaZ^IW?s8+V>n>ZuNtbRn8gb!#FvSPVQaf@3q*^X?Tg;> zM%tswD<|#KsZUWOPd%PO-Vvvcr%gZP6mh-j6mt6M?JFWnpIap_JU%%G9muN;q|J=0=)fsYWnA$ed%%##H)E zfnuDDgn?(JuQkA%862-NOxrWt7O@fhl)2UlAKr3;x&G>mwp~m(b>PlH%m~4Sap%@u zQguEn(y&$#k$R4U|K^A17iv#C?Yt7i6H{5cHk}{iFZ-grc6#ViI-7UMoH6J^>7z%x z2YgecBzSJcUgdkVPRIA5QU7HbO+-c!$%}8a#3RM^;Cl1!y&ZIk-CT-WdAp@2nF#yy zHzXAcOokCm7a*O1@SQcRHyJ&zi~6aa5H{Ihw*C}re?U}#f53$a?U9}NJ2%`q{X(Wz z5E5KbX5FjXyT5m*5yRx>lrTbQi-z#MnUizdVduBs()c9$FjO{lE;Qu6#J${;unJ2P z?o{bRK~Il_hX#M_|AP4<@>`iQICp?ewTRG zAhA%5{D^pFR_g$N)$IXQwdeiy;q{glqSx@(U^1KeL--ZhpMN-Vch6mwyK4NBLmP8< zL>)z{NWP4y=d6*e@s!#9MDvO6nZg13sy$UlpES#mGB#h~P8ZIYN)NXfZ<3Fjy%_n@ zCRXr`c!NY!XQKBJ(l!R;N7C8r=#1AGv-E9@(FuHGoKRX{(X_k1HLm`7{p+}d(HD`= z6m+sq;gfB7xs9(#xqsFjSUcb|ATvOcQZYVge9SmJqKXI8JpTXyf&L~DXj5=m6BDo^K<8@SwVp(cay44M_NXTWggSXvgrSv`ig=5hTf1Lv4?DjJrj;9rx0qSK21? zT(;+ok#g@yS>nsdlha|$(FkNDTneX^4zfZ?1 z)5L|eCGU{hpyKXzdln2OAX@vfY@&zr-%n%FgP*%HLE52(&upI0m-ODi1YhTE-1|zR zT6@9j-i6wc7pU;<5*E8<_vl|-n279#jhs4g%4A0~YL@6y=}K_z#mR@O>*g8`51uz1 z&e{Jubt)B6$yizK4ecS_#<7H(@5Np2$uyci)qASqlU1CTR%3kA6}@a?&yxx({^r!C z@IP)ye_0I)1?H=)%dx_1-mb(gbEI3NeWHvk63Q5*T!L=eRp!)6LOw>4tq)gTbhuSE9 zJb1Y+=s>l_nAIoC5leMjtl~zGHwo|K`n%B~P@JWVO@e&9r`!AWBlmhm7*yF1bKtP8 zqe{{43x@48neC$Y)pZZ?d8hVWYby1Qi{~(*-?tK_+oN{~F4Jo{m_e|TOMI=5%iWeg zIb(*cipW}P>z!RN#L=TZtXGxSk6(woL^UJn9F5Rv?jh0P+W~(wn|yX+VqRoE27!5x zus~EIA1S`|9$84w5PdV)o3pVBbXl%; zae(5VL6kQ6?&1g8Zv{miw13Ht>u1JCof0W2C~kVBG3-9v-Sx;)gLtVU2Ce*dgkA+n zWxA*0&%EPfPdE@>+%~;@((KF`&>rA5D~OSfAH)Q_q65AlbOI}{Rp@qs7#4p|4*~^R zfEd5NM;-W1`xgp)(XLtg&T#)Ghz0m*1MubcjDGpu%tFr?mR~b|1pWp(eBtQHlfd^2 z#tx>YwvN|ePGfoJCV^k9u{)vd2mJXR!B%l(ZR$_{`@hOua^TqDG6V5a^Iy4yM#x{2F5iY(n^`98-?9gQ6GXD!@Jt+kI?*Z?#G)__Ru-+w@H@q(ZJ z^liyimtJ0Vsf_Hw)t9dN(~p;4ax`@~3bO$gb%K05u&)=d{^QpR6(wnVUqy>0LNERn zpfqHyqU6e{LDoKhEd2~P$SoGfG%f<)0XC!kqc;Km+qLu^culusQ*_I9I0&Q+I(h8y zMK`)JjHjQr>bHPm#klB6b9>z>i;i~zJ2`E(qhU=xWBqx9d2{B~kDTjp($hYa zkJ{!5(p{~jS-nA7F-NoV#6~z~1cmb&RB)*k-@A7vB61$h#H(9_8CG{~`@F%TJj_{N zdvxEM1T`NEo{cdM;=6X20*|?Y(MVMiJYB(L(DDdb#t~X?ro>B5Xjmn11Y)K2a7y zD3dtKgFGx;SSISimr7W-VR8o~r84xrd1#R2mGiE!RyI+8CRWl}F1Lq0E5#S$>}7_}X|J+v8_zHfq+GYr?4MVI9sJG5uv8SU}Z+a9z{pm`1(fm3&Sl+%OQIpB-5cT!Ugn+%#W#zj|K*-SdVFS}+oKM@m z$@kS1ReeG9IrPn~FcdbuFNFM@r^=(Ctxf)BabKG48n2(vgSOwsaAF``K&M!eSwU?w zla8e?ZABI^ET_Vk^7!R28{yjlzMDm`3%Te~x-i><7m$=I!F%Whc-_iBMdS*48r+~e zrd&R)<$M0+#O{69xsagP6FnEMGH!#=ow`@@x`_3N7ySd_8ybG=0|<<}j=$Rhkn;V9 zH=*EUv8cq-!B2Q?eSIuyxUTL#W#F)k73zvkxq3z{%Vn?Y17hb18#f;g(g1FKWtNYb z>-84>4Zv_xV2Z{W6PY{EV+(^;y5;PVrez2G#;C{VL+&K=GFI3?@-p zP4Wb{$}`MIsr!6`ZZRXX<#lA^kUBc$)myz7saB9)!|hm zgH25-Y2~VT(BgeED2AZtci#vn^U*PL)q5zy&$M4=`i+og#M0i3{}iVUJ(2z$BU~K< z4oj-Gn_J7g(rnhzYP6-bFUAwJc%Lf(0c%t)u5Y;mVhFNOiblP%iNF3EWzcq>_GZui z#(41dw(H_D%1!R33I2$z4wHQ|oD4yau1(yEOEXO)=43dJjZM}n`7Yik0U+Qtk$0@t zD!z1O>08kT^|G49zfn3iZ-F-t6--$+ac;0F4(Asd%};GAF4wW${|vsLlRn9Kp2m#QnKR7NqxLdZ zf)`57b6qEDWeMGBg}#MMh*he0ncA21K?>h)#Nc&=N29lIqoJ?H$KI1(PleZ|dT&+^ z_`H4niS<`OQdKQ=0-xi{8{?f%vm=K)i&fjZ{n12vB99{Fn;xKPs6}3MryE;!qlq=z zinkD}=<{*T5VU@O*fF8wz{wpjR)g(J+p3k;BqihZ%990b^Tdz*m3f@{gBAs!X!tYvko)+iv=LICVy9439LI874xo5I?1sHeiiab@y-*I;gRcZ zQhG!l{Z_33e@kC~iBVRhNOj+te_!29N0vTW&%4@P8U;A2EocvYBu&~9%BP#t?_OH{ z_tox{&IpbDlJY-swnPc9`$@c3q@oqFPUwi)eb$$(fGsq$9%eUd>a%Q;e{~b>gzJv6 znJ6~*rt@aoy2>G2(e?QtmtW|yMZbckeoRRFd|4l#6b={Dr1;RA}uL*4f<*w`HtN47`Z0wQr{l8A=FN$8_5fP->i16?Q(r11XK@Gs3 z&S%Ylf)Usgi*7-iCCyBdR5tll(GIk!Zx;O*nB>Wgl=}rd6)# z-*R227+n76#mZ%@BgZiU(s5TyOH(uV{rmTK;XShRYrSSv`c_%}rRXsMu6~g8G%>F? z75hMhqfYMCZ4kWxPcq%HtmZ;Y1p;DLYLM56@*R{cDE$*wb$kmr$c~4K&q&-xFqf@T zA;ZX7z}s2nO(M{L>`gA2r+%$J(+ggV7nb}!$aM=qPa344|D7B^hpmvx%1Sa(O-+sB zZN{D&@*4247j!&)->bTAU-XG>GH8yocg#w(Lp1+u8sVRK>YsRO(XseXi>HtY2YnAx zcEB6SBsrAc{CG=Bj81;VY#uWA{oP!Jt^qR)dr}_UITEEX*oQHoU~({eFxP3U+*S{} zOI_UDNuKSkn8!FMN?{<`)0s0LPbZBmsWA6R_CyClYr8175C>o8VKh2-Ewr&gsr^`{ zBn9bs?Wwlrj-@53)xZc-rD0Cc(vK#KI(f>PqvDV%^L4Hx|snI-8>iPo#4kO&p5;CY0lV}%&TU%VomB;zS zl1q6JTd6@Qx>ZKZk`C| zp^wv^4ily8ggQw*8ZHRWIV*CyjhkI(1$t}_V-7>JA3o&kRuKlS9e2%$hxePJZGqjgz~(a%2BRGA3t)UXu|BIn8{dTY zdTBth=gfiOJyoI4(w-Nmpxtc<-s3niFYJpX^n#p_x_kK{Oq%|DIK`7p7DILvOv2FP z(K#L%iuce1^8_ESI)dC5fO2DP8F6{D{>YA`$vej24$jMCYZ-!~O{Sm;N|NS+h^T#N zgTF$zPXdfzJ>u$8AQwCxavewxrOhc>3Hq0qH%K&Y^4fF%96fTqs`GoV%xO?>QkSzn zK0P9;DbH(aCXCo+62#2KNvPs=LC*JTk-9)`V?EGzxNaCbXEZStg{6j<1^eJWV*T)t zX9nhkAz=Yo#pz~vPTqJGti^lcx`u0#Ho31!_8MW{-?od~I;rZ~MaEU;8W8UC&g3xI zdWT^Ns^BKfP%g=pH<5Dpws`?D0mF-$x{Ga+%R7v3g1R4p>z0CyTFMrUn!gtG!_YH@ zj#SccJ0_m$$v)>QFxDlliNcL*jfzo;53%lWiY)bL-39PTnfODxlqsr@JsyoLcteVc z!FCDall@gu;#2uu9@kvU$XMBqbd)X(WIx}p<&*@SX29HvD>tHcI@Q@@^fUu^%^~QH zq4&)r;|J@3lr;Ciq`N~qX>|g2b!iC5D5mu-OryJEIV1Mav!?H|n|#lU56qxaNLg-f zTe#_2g_6aYC=&%Z++3@bmg}eWI(?iH?K?XMo1@#QSK$TV>~_`7N&^ASGbCI1=J}h= z1x+KWn+lQ?CS}Z>0z0NdFu}-~2}&S-uJ-U8P6q99+BJ!Rhtxuuj=(JhVqZ2#rx2x1 zWZ|Nz0n|osC+A)EWw?U$q@;obvJ#VkdJ9V9sgIHC)QIY?R=XnRO>=U)=JMJQGcqLf zaRZVWJ`JIif})6OD5fgRKv)Pc<;znLsP`!Ll7{C4_)NVhrT+!C7)9!(%)t~VvwehW z4YbK6A*f-iyQA!I2m>{fb7Rw=0mlhgp0$JbV5)aC2eUk`32cVKJ>4W?323nc80*AzW-Fw5-C!7Zcn+mfLaYj~+)I9&j_AdpH5 z@2ALZ9FyC06FztD(%ijEb2v;Aq%d!C*2j96b3dpg?C@3pFmCv$A;vjrHbx)A!4L#g zuVNL(vpo#99A^BgA@-vUjQS>P4+44hMBhTX)p_+()UiWA!i|=*mt*~!vws>0loNqET+Pk|H*-FhU5r_AB#0|Pt8s5T=b}gW>h1vs# zAW@|$KTKmG6~FJMWTxL8@;RJNLSl|_dB`@50Z_!Xzo?a}kH`RQ(JlnK(FL)AaOWoi z_@RSha<6L~-+!uGy7x_D_l0|RHi*yf^WAJyHS;iDj(<+@%GatRa(L^OJu|;42oI!1 zv2c~6z9i+=qltE|%u5Qf4PnV|dtjZA54Nb?IZ2mn+|UnGj$f!^yBRqfQCcpd zG&ezVLBT&WsL13^sU~@P;^FFWTrNY9D}0C>lLQ@2=-lwe8A^*>aP5U z*Rs2%X@uG3s#2y6J20k`zU)Q4Sc5Wn0Z(85UHyt?rz6G|Y+WM^Fl4EdrR7^5H#LBa zLemlFi3D?r5o+>`fF?D03a;yQlIpqzGWCI5o^-}vjxwIpSkB9C;7Pe*Fm#l}P=_KA zpd-|p8LCg2qzlTY49zY#Ss55l5nn?(+Klk1DbRw)_3S(3n~YFNMVv^5j_LYX*Cn@b zQ@`BBw1-nIFmz>o8Xn&GL`O(xYnS^$AR=Nnz(ycZLtS~ig*`CRD2&qAHG;K^Lk<}O zj_yS!)hHMBa@Bp8_Ih6*O!})8e`_^B>~89558ekSTM4a7cP+sSEM2J9<#c7Pl+je| zFx=$|^{9KjPkS&tD-DIaN^Rm0g18mG&Wp&O)Wg`oeEhL|^gC zc=1_!r1A*p)LZztPk#lxsTtx=ownm9q>PYNVs9F=NmXOG*JkDT)~9dd@zjY{wis1vkx z@534+=d*lci`Upma+g*RXq;0WNU6x_k}tKkzjhPzq|joO%C z4YC52pUr2M7yc@5m_4yRYNky(_qEbML*qZWy9cq(-5nzLq)F)Mkp|s5Yu_ zwq45iQRnl&N7d?5b9eU~1Gt4n1qvO1xM?ySFtTtp&Sb(+$Wlr>VU9-Pp(sMnyE%Im zq(1pD`M3eu3|~dfPtT)dB;^o~PLI0CWmi*ruFv5&KuGVCX2F$CF<@#6_5;)>!4_A5 zUkf8;z`XKw=1a74srfT`7;)Q$K5{Mzo961)K6}4wVJ?;oGjACmLmV?0<<~O6sl7p; zqgC@IlHh+0>EYD!{}}198NlrvJAHsq*4Ub=P3`~^4ReH{twoC>+ysCvzSfOFMpqDn zNfrWe-9Hr~mSAkl$PAfLmRp%u)!0L6@VD+haXG$q0Q^CCC%;$kh;?hck1c>2@bq%x zT)NNSsqX@%yJP=#r8@?q`TbXttBLz}i2o3r{67y~S;PkB&Ggvj=Pn@wZ5PG(L1Aru!U;z{B zPXlNq@@?;bD_p?z1P$}oY;fhu6i(VI)L9V##|?JXG3%)y%bjI^C7zN7a{DB60$3`Y z2Z$adChj|G0icuQNrEEXH2J`H!PobF4SzATNf(Z53~0(l68RvPwNrVxzlsmvdMy9cHe*^cXrr~n6nZ+x$u4#X z8F!0Qe2okZi#>Y+*9`OkxtGp0#9ucBS_jig1QdVIO3B~AZC`(Msj4I`D%K(;C0ToL~MUv5{d1Zp*x2lvO_8*86n zSbLY&zyUOJiT?T4us}-@ZEHZG_Jg;yZe<%j-gyXVX#Pqv2WarBf&Pe|M@RSl(63YB zPT1#b*LkjpyXNQG++qn&oVd^TP3Bt-lSM*uyn7EchaIMt(aT3Vqdo%5hm#^YrGHOF zcfY|UXJ&b&nmPvSrb6=s}8BHVB*e)FphKGy!JZ`-G<5>7| z^^Nd=-}#P)&3Utl-7+KJqn<1(fP>DF79L=~UZgGF^K;S=IZkV7=%mJzjyK7>JP0%>e znYXUlFb#tBo66A2QdejYE$p2jcYh%}O|@UBCUZ}q;UfqS9rd}F5h7@+279*D$k+>! zNSpZ%?e&|bmdls}|UV?aP;Ur3!GS zHKpJ_Pv_13Ttr%Kl>XXRoc~E+*Y|iM`eutc==qQW04v~LoM)qiV)Dy%)0WQj*4%W?<0G$d8plijS#Iu zP88kx$gBfBRf|V$L@4u?{-GH>bEp)iT;}<3Hb0EU&%tEi#~NsgZ?zdlGPNlsrk@gv zU-wha_gm*=_o1cENdQ2$&ql-T21)B{*X44z(s#|ICN$Rb(s3%{_~?_hJPUyB>aUyX zo#XI8qmvtT5s>zeHu!&|4SwR4Q&ejh(4{v?F<;^+p!@FPVtPZPquex{@{<|)89e)n z)(YQW9rQ?!mEJsK$#MFAkV0nG(m`W+wMSdd0X6MCi*3!K*uMR0)ZbgIm8I(M1J^s4 zANwr8=$Ce-*LU_@7D0NZ490r}Abb03h^i-4^9-lW`O za*|y#M&fwM0`s3t)~a?x^!F=0i)Cq{310cL3BC-qAD5)9Ce{!V6TaOa-(7YzEKK48Ly^Hy3-P!={!Cu`LOv^Yz7^%nj( zwL%A2b3N|Tk`8^as6+p)H8yaBuwV{1x;wOVg*&Z7za@W}sAUtkNM=6_>aeESqx{t@cxxg@_liyE3LvI`#B4&(Z% zf#5qoZxIpKNjvZnrL5D72VUN=eBfa28tTw5R52}-Uune5KWw+u+WhAow@dVJrj5Bs zsqaopPUh;m`oX$Mes0{&r+MfnrL#Qq=389F(Eok3^Do6!%18fW;wpIr5SRfz*H<^_ z(PoH!*N=mHXpv(Oe$S}8C@o0g0n(g|WNx-0>!qmW3f8~_{c~)yT$b!-ivBwHVGs!P zOWait1Be%GQglYh;T~zrh$TerH$Z9n5CCsaKkl<*X|-h9Y5`fFcYRz6iEHNqw6fth zRt?Mfi77LIpsOV7I7>e012EHQM3Dv_vF=3uw>%gX8WDr({mC>dZ$wcgN<*K^X#Ml{uZhj6>D4QqW~#8v+9a%6xh&No$A^oEX7 z?_`SRmypGNH6d(So>FO%MxnTTH`6}aw6?)oz(X^dUhZk)E)~T_{xFU$^*C_Fq`s*V zZZVf)1PrEl1~;OMT8g+;E6Hj&zMlyA%>oxW{?Tv|+H4~S8jDXPY-L(vaorUbR~Z-3 ze-8gc51i&7UZZ8@vX498mt*~XXX1)u0i$Ww*=U`ei<;Hga#^z?)(pXaH^FWt8PF-T zY97xHc)l=uW+}2YQ*`onNCa8;Gl(TF8s8JwpZZu zBOsw(D2T@K3;xEbd`M|^1ndwnS!@sN*;m3H;sltfT4AjlJwFh>PP{##vc~JXY+Wo( zcp7DAZ^bu@0|#_C1Bi&O{hf$td8I2JzEw2GX5Cukxx62jd_h|+Mtn+aDgN@KttRxJ zp&kGSeY5gGngaY9J@4{T89%T0EjK_T?kyfbw(ewpOfp$iMl4MZ=8!LM5ls}LRDl}=RRL2l7KwU#9WopC!&J#i+M3^`4?)-ePD)}~ z0n;YAeWy7pXx>*zmd8@w!NqRH`{K)hx!s`rI8g1olqwBz@rd=guGo*PdlGcYkwae{wr`axSC06%WyltQxd({z;*Saqz@cy0M?LTi0 zSTbjUv~W#<9m2-OHpy=G8JMrt6*B00_bXuR7s~EiYvb9!(Av1@+||7R%#dXy@mSMn zI1m6my8=W+2mVe(wA@4~u2f`3OT#hWK)=!Zv754%wpuOE{KKXAtBAH5SLgnvVCgwr zrJY;9hq7sDXySLGwm9pM=7+ZeDG1gs9>EFv&9uq2(~D8R`*N6htA`t3^~03eR@$0w zNlR({7;9Qu|39IKuv9pxjsZSy*}yVUciz(Od9ovG^JSxUhe()Vo6A z?}k8rO&bBpQXQqGdagqDN?7)9oHhD+(C)Gp|Ho%t9zFcO<+C2*ME3Xh`;cQ}cTgc| z%-ZGkK$i~%VDa7I8i!t>5%1elnj162L|EdljV^Y5)56go+rcJSdH-@2qMy&=%RczXWRD1H){1Pn@FmUQ z@>Y(dKvYukXgh|BdVzckO6h0dCM!d0IAcNNvz8Ntm6z#LgtP2IuOJ#e1XMz0Sn|Q# zf-{$2sqf1zNJzUkE9XG!p+2QSn#U^N!L{ZlTj;u{{cVC4CW{AI=<{OkDne?Y(3(EV zaq7Y|f!;x@I7ez*!bXh2M5~Q474r@P%cT~O!gLjUKOQvDc|_7e=b4fM@w}6iDT-BA z|BS|NsnKaB@`ZPeNgBXB%)QAZetZ&!!wQLD7W10m^C*@u9}n!YGAmF5 z7as^pa6F`V&t^_5VZ6PC^90W6wL-$Qpz|RbijxIgfQv=ji6T8VoD<8)#A%mbAikM4 zND8O5ulS=q6wrOs9!C`67Tdh$6v2?|yE~<$QUCgwb5oGiiFn7yjVI=82y{0-{Hi42 z8BMcF=u~|?VAfSD$YPywKSb|;G)&!;mWk?BdY78-_wJ|20@zh zdK5GA#*G^UW@$4ZO${rb&QtmS9OCl7R51N-PY?WudtzfPIgW!8-q`|4iShAH!NI`- z)DoMiO@yJlzt-yL;lQmO;F%utr?H%rQ*Y{^{Culin$b?_~DbiK9 z3k&Xzq5HYkyofg_Fc-h1GY0m$vZ1E@$RE^0mQ&Q}f2AOTxH`2efr~m4y$G!Qry{Zl z?fP2A{J&5U`C5DV|E6I2-<}@$DLJ?jPybU^SpB?;|0gJ(AIb{UWP@YBQof^%*KA9|rq;&?a^#1y}|{v`Q?{3*r;C7k^i| zo%ss)0Ss-&;R9ljGyrB16g5P9V3p5E30odaum4@?C5dKM-Q`C=HJjwfvvw~(%qsIZ z5-sR@8tx_spC7$hx&Y3zt{sH06Cr${JrHRvl3s*$a?RP>8zD?8T)o`SHtzOXdq=2D zRC)Ke#~;xUnaMNDPm)r#ixU%luez^l=yOuGaG7WO~=+V3)iDA&i z0QKem=Fq*g8H#rqIiTcOa%;2?p|*V}{tz(C;@eQb7Kv4lj#8H!9Xz*A$m|Q6J_}aP zc>+k9tlBUzG*2&r>gxJYnEIO9U9tn{fx6%~sOXBn1tnXwZ}hEK`8H}0>;be3akGfxmiaqd+yA_B15rt0dsj4J{DQs>igAe7!Uo z13bJZKCMq1IspTE2>!;EOB+>>1c#1PeGRq%y8SELT)fg;mX<%G+@!TMuwA#b0+366 zDHmel*V!e{4*&oS(6(bBKO1pi?>aE3C}L#;zD40Yw7t)nk(E<3oGDOGyrAgudldTx zZLUq+%bVRD=%qdqr-sM{5b~g55;tF*zZ8kek07V`Rc`!X|7I!nE z{eAcb<;!b2*8@zuo}M+4eltb_(|7Y(4AUbO8^$ z1%VC)A>VHPZ{ng1@_Qww47#^3Q=5(BNEE^)a?N>acVAd_AgE-#Y_`uOC6p~r)q6FS32)yV2l;Jb7~HFQ{bSquPyIJBt-bmU1!=)D4u>I;7R>4Q+6$TjCt zhaf9kVdU}=v0Se#1O@gx&u8VaN&uBR8TWH5UsRQR&*>Z|Y!j^W?9Gu$UdVf!bl|Bm z3-pvLiAWQ$ZSfbm7d&)~QzF0-AyZzxw)HbGuf7lVAs!F6$ar)B*7>=BJ9b&7LG;tH z%4W93$3XY6#BI+e6@b*wMORkFBI3f7k zrz0ue&tH&FC77c0GVgo4v>xFxFzGomABa2?v8@>+61+D*RCf-!^~Tgq>n*Y~DvxJL zIETwwf+o|mnpP*cr)Qd!KHE#4dkcfp>umit7LQh@oNtziSiYn|UR>LJyZ3yIEb@D;yMx<+TD0Iy z4)yV@d^MUL!6W{plGgw($*D-X{nWFM;igI2Y+L1!VPK>UyLe*uxV^U9bDsUl8WAe@ z)+;aYDL-u*M5cC*i18|-h;uBpjXSlvCSR@zIuP*aphxHdSW~i&anzv&{hawi`#N)6 zwL?iUFsL$+{i`}zWc`xM=HdrgogsfBoqk?xh8Xd(fln>uz4155F%`InBcMAT6YhLY zRZ6cGV$b=%Y%~%d9jkvtPagzj2)cKaGfccG$y4B+);mbJw|aiZzAhi^z0}DoaR-8) zf?gc9-V(5=ZG7aTz!_CqHU#`SH<`iJ~4T6@F=LqrI%=**K(eoRjgP6kSS$Q zu}r9>-=KlJDhHoi)bPxxYxE9ro@@FnSBb9BiY6tN$SJA8E!zZ}^e+_>=TvhB-$qoe z4ch8yZ9Jmy>H1EQQZkxa{KO{a(#G*GBPkl6;%XC~sE_0Xx|ZZ`qbn<~FXSnRs?h;^ zNkA7Cf)BmssCBi?Z+chfQSpAQH(@FgY%+7S&on1CB9!ccIgf>#rRHx;07kA&52vb= zHpR5_CG2Ro)zxq5vb{#N^>)i2Dx*epE$k8#&w60pY$)F9SzcxOoQXF%?=imBap2KI z;jk{s2?$|E%mP)H`iiPxof4gXM>A-9L2uU8Jv_=oj>1jD@nmy?9Fz#s=150T~yaC?oQ-_`nX=zc5376xjWI3DaWF6 z-3~ti9YI@Ask(+7!ATcu?S@*WRV&yobOy_5w-kPP>2H!{Bl`gT8ZNqt%CEUAOTgV5 zdvhF$ThfDH&iR6b4I7T0A($r!j>~etq!gEnMgaz){nlvtxRKf#p?Jo{x6&~-D;F{j zT+~t<>?}2Do57taZ?nwC>MkuOV8cixYVlT#TR;E=vLB%ZI&dCO_0#-cowO zqBgqrx=|95k;|hnPtZPkjhvHF0o5<>qfzkQz5J)1I+uFC%D)ikkVBd292gyQZ^L0z zy;-UHCUUKBys+aB-^CP;I3DbzTzjJHPU0Tm<5F0@uL<%72lWTl5Y16pJ1Kh*W%+CPqnNeW=j zTirY2Y5ldMQMsO%CgOeZOf1i#@C_ zB6=b}>6cgOos^o-ls3J0gZF^|)?KA~t3B#*4V-YB+hpGK%BDH6*SOODDZjzkX|vIf z8LtKROtxz&xy-^%8f7at&XM=7r9044+niuN8+NAI#ynGxTE@VtRUoeO+*!=MJGIYr za+h6gvlZ^l!^Cnu%gphFl+S@f$SyOA1c@{INIJq}bhmeQcA*<{fMGXyNi{o&(;Tja z=Q|hDnnJ!hTwg79*chN1zc=~twL{wp_1sP?3=gDLY&(Fp)k67z4M3m4RREZdRPI~Lv zOZ9e|N0COP0;7GRa%3)a*|t|sD!HUpHMk>?fsnjy*ttG5>XQN(Zm0oZ08#UOOnJ;RvPsZH((qYrYEo=p`_r2ev zcW+g`i1AFs#7-1eP+mVa#^rVo4TWD2s|@i*hikm@7GdBZ=go|y>KQ_tZ?cQckeYN& z=jHQSx|LxhC#)2y`E3@{B-o%xQC=kD}U}6bO z;n$TAs5$z7*n8`!s@8sOco9lT7>FXs5(N|l3_^MlV$dR82Av85(jX~FC?F*zph$Pa zLO@cwTe?|vE#kc=pu)5Fv-f_^dB-=-_m1$-o_Y$k`_x}CE-pUT3a z09{39TCGR+qFcx0tXT_hrUyZIGd4-yqKbsLA4Wirp;xn#@WjlgrS+4UMvNNgXn%$l zuz?6=urWyk9s6-8Uet#ZWN}Us$4XOQdt=e;{p||~OGIbfNf7V=D5;}Z=`ritCPnf- zRG>;yo?cGoFCFQ;C-a1APG9bHncTS!SIK}I_AhDkZa+kQWe;rT&fh6&vS>EN&%W9u zgNfc5a~D2BVs@lV3XQsYhcd;4kVr)%GM@ajexq`miUAFW{MSCP}?JHL2b5hvu)|&@MZ{=pB~D( zi(Bc(#K7mf>1j9UYQBC}VK}L3lRbwRvLHoJJYT6@=vZqXF7D;5KI=LoGo-w{POQ~? zZ55*_GaTcs*%2o~(rE*)LA7U{ffd7PN>skJZF+-gF7uWr} zUGLX8E4FB7SL~=p`j7enfmc#iJg5ETg&o?OtXx5 zWj-$qZ_+Qs7(&OK#6aKk$O3Jq`pHEHCU@XEB^Tb-S2ZizS;Zse)Pi?&e;>vc8z#A z)#pRm*t$3BvFgR&g9vEA%V#|Dy2&<1gTOSCeGsE;&Y=+0Gjhz+H|Q+Kvuszlq^#QC z*i`#fea)A@Fnbz~m#5YEIC5ER^wl^-#%iw?< zgtxQ5L+Kh#ZokcAyY;#BWc-upjUHFVHZ?}hiZ3_71K;msqp#_PMz?ogp18EtN2g4y zgXPv$AJ`x{+TO1j+$}M&o~iO!r*TpKN#py`7dUEG-fygbSfnSSv%GuacsEM8L-+~M zX5Fgb6FDJYm{%mY?Js9}Gxk6BFpa`dM9Hn$T7}Rgws${G>@~dIJmy+ns7v%^%U+kz zNW4J%&6jS?A=Z(~EwlGfl;m9`-p@;i1tE9-LC{26UE!W4CVgIhl9! zu65>1KeVX7y;Yk|Hf<0!h@L9jOu4vvoKSj+duh3Wl&+Fl>Se@O&?$s=Y=nbLk!{F& z{&=Fhq1Et_#m?>Y-cNc}Iyr!)SxAuEaS@g8(n#=%-=uF z-$^WAmGCZ!PO4C&ZvJFvWQJTTn@vM!oonUwtKTo>8=Q^+a6mMSJ2wxHtCkS2k2!+fylWnOiYKVC7WU)1MF}~Zndc>{TADEo;+l^z* zBfi?L#Y;wZEWRtCnmD^9V-rH|+wm2dgF$G!(rVs#-?1J_jNTD`Bt6GSrkc`FP1juX z7Qz@>WmRQ^+2hxhT}s5Su)Sb)<}Kwna%?eMkI{hACZ0*xaJ$!aT8bIE!{|zb=bg`L zvl?@^EW~dUAA2}I^hT`~D~%7mpp<_9sb^MA$g|4U0vFomkBcqs_=!ra)s?Nh8vAqg zF~A^%+lEWiI&zr@f2LDB&B()wAr$M6*>!Fkvy<=B+K(Z!x%RFpS5i>RObsQ8$&pVY zT|qTzmCVbHMcr#!tO}CKCE%HZx6d>S?pMFNk(J&jh?E4IWP<6dOUtOG)XFt^@Uet=^9U5ME07_tkFI23?S+gy6{@(_Oa z>n8zx;|ak~KjE7z`{o7Z;1(yL{Cob-^Y07xbLXTyV3_O2;r5mF^3Bo**M}BzQ-l97 z^Vr`m2nZe)gN(ZDALeNL2nysID?=d6&g~FpC&3vG`h%uFy?xtx2X@Dx&6W%*BdV8& z&dkZb1-Mliq66T#$^d>Dwp^RwJ}_}HMQ9i=1;y@a{$-_SkBfmVhjHLRToxIqT7Fym zkE-PZ6Nfjv*v$ag4rKsM>m-Pr&>Y(yrYt{L$^AJmutX$?XN3C&(jE}fObYE%_n}?- zcR&cV#=jmGlY?fd|Bvd~gU{izs6dlUJv7N*&|>(7A=N@yY7lA) zPw{Ii09HpH&%}lMP=WbwzG3BoAme?Y7;c0aga?m{3K(?FlX;u%``(ZXNYh)?mB@MCv_-KTqNt-#UKu) zOnOKU$+0Mvk&WlaUMAeN!G%A8>w^{WC3|T)EZ;d7VOVFof$7GqY9qMR5n4|<&i!8C z=cIP02d@FSGhWomeJJ(c5d{tfxY}-y1l_v$ofzy>zB(37ZD)E{d7Rr4`n^anhjk@r zmp9W67F~aWnfTB-ytV5_HuS!XB_W0nes%0!k8rK%5Zk3PevQsy{XX=z9D%=y^FDMK z`S{d*T7=Yn7ISz(i)d7udVbbN453X@V*0@meF&}keG(TPA=hzP<8R) zlddfHryUQzL1EVdgZ>VR92P6i#QzwG5l`h_B?+1GFH2nXfQE4Q&=7u|qI#d_C!;M_ zM9;P+M%ihBg8|Y&S|2k~?0Bl+j^bv@U(17mZG0K>yW@ZDcY~7gZ z4_{~y+J|7>UxWBbduVrI-iwFJZ#868g!f(ey^cO)yMB*|bs!w%>w75SAMB_hItF(g850Gva;JbgdUP}6@&O7!W zT8+2XaFl=YNe7Py(<3?ll#R&O#4NpnzpWAOib;;Rn|}I6nq0j_7y1CL^amOcUQqmD zQrBXl*{FnF)Pv^a+9;AZI4fYgVwMI?L{7wKVaT?8|Wn*4zy`15A#RCk=ly z3I7T+R;l@H0J<)MACt}g(W?x8YYHcOIs5*A0N48m>`xa&=awI_=nH%2DeMNnqItm( z%~~1jq~VZs`z6&^63S7tSs?dlF^$A_ki-=Aad{U_i9LxwhmO+|z&qd)zJWoxS8Yn2 z*Dc`#GhPkca0I~ncSI3$m~iMd&{$-5hE_uUE!08icYZe{J~QuHUIp%QxF zWa%^g!6C%!y}!i)yT)>%#>T|E#!rs>PLt#r?i|`ULUNB|*)=l6abTR(*RpQy8QAu9 ze%(8rTV4>^JouER2ek$$;4ffC@`QcIahZ#;bzVv<3KfG1rR~uI`@`|M3S848zeDWv ze_=NJ>B7O;C_-7Dv=SisI%?GX_DDWeB?#DKYgt!Mq?`i4CzYM<@qe^fY|qI6%W)80 z(g6{a;+E9g<>&367zmfGAV%71`GWmjbIKGlOv2;fG+fZ7a71uo*s4u@=pgv*3oxLt zkVBNV_s)OEoJf*m(B2gpx*v}l3qvv|VI4S5O%O9E?|hJl{v+3a*%O?BnCCGv*bnXd z$=VNQqZ3d{neaEU*H73Uuc;}gi5`=_809hb+g-pMmY_sH_E0qWBYl7WBG_{sWgSus z(jTkeOn-+2dxRx8IoR=s64PIHHE^K?VzB?oru@mC|A!~|CoRup>_y0ww3jm;rRr}X zF00@x`I&iLLLaj=EU5gT@GEmg6dnnLC$gW#f%_=h_si+hn;pn)9?dd}hU`=Ho(FvC ze{`s??d)(6KpwN{B*EX2^$z8@*Z8~6@Oa%WW-dn6padb#5?)-4ncCCHF>;ME?yut zii6^|qd9g70f;(%ey5M8fYOXG!lNN(H3k1*w*E&>|1TqxI<0*mw}r9kMzf{_fM48qVMK9_Dw=@r z)mNUMUwmC${5jnJNU%&ztRdx?@6Az`2#EYhhOo}ZkW|;|?JE*t5 zPIIV%S|XU)<+bnctf|rs!Wa5rnIIi2Czocidyx4m8`4NbItY0N}#oR+kM`K zzi<25EdFa8CN5QoEHB|*?hM@Sl}IzK|2pauU_^PNrc-TKm z`+fpp*RyG}kQkY~Znb&K>X(ytK*~Z9`7pHbrx97DFd8P{eQPa4PopFr6-;_U>wK!Y zr*Ir^80{)A>%C6}Tr#$AWW~nuU(awRFj~Fnx?5dc{k_N=IhQ9IxRBIi-G6Iwy}{0q z&OkByo#1xk7h$C&;$-CNmmc)eC5z=yiQ(*^bVzh^H(-X{-(9SoDZ|LyYIG!fxGJE} zkG*fFOC*Ngz<<7n@)}Q2LWwuY&LMard97Sac#^5KdTA?Ii9rDj-cgiu0iD6{ICy~ zv6ylT=QxT|Ys>YjHFhR0c-+obq(WQZRXe)~uP;n~y zysMr0=^viW&1zb$Omp0rc0munM~@w)$Ru_{#hX^By^QaXTjyIhZ?7{}ImO!%P{`T> ziEnI64d3ZBBeP|kS8dGt1nO?-GwY-x76s0pGl6n2nnO7lfIrIKoV^*>9B!7!6CW2B zUGM?fNB_xL;(Ac-$Zt=zTWC-Kboo>!E{iCrzw!)$vj2yX(!JH*Y^LeCEs2}alS9nv z(@&Y*lLX{1D5lOmS~`YHcxowzfoj{f#aUA+oU-w$cj7L^7SQ(+NpgQ2-&J}$^Rx8{ z9+AONiBDCJwfeMIkdGyebb584E9BdpTkKr+<~4s(zfBetXD1wghNvSkYICDoGCjc2&xpU8+d+L}zLr>{q18M@ zkjQ<5Gev^T>K4il_=dp;hvKRIQ4)lqiw;*%nwT;8qR=l zKQ-A2`)DXu`wq>-A={?As`cnuN5i?1Dzbhbams3qwPL&09LlDPL!T?9wECZrznskT z)rc8+?{4_?A<|WH_3RCSu&}jH`dO-0xf*xXbovT{bd1DJGpEhUEEc|gZq_J{v|}Im z+GfWcHMr(z?ptJKq&8IM3KS0tXCpDHZ?=c)P6BFwU%n-GYZRyv{c9??ne;MLRA&p< z0?8RD&)eF%Q`pq?*cKXh2RV1Mk`mReA4MjAP=mdo`h^ojsO~cfVHUAUq&iAH)WU}? z1Y!oC5zs7O6<(G|@*Y@!mq zMd4{>9~m|JeVN!tb_&-zIOFMQfqH{z8>u&Xp54-Cd>i~U;5_yhS26anP%Gk_=&o*fE3c$ccyzW@ zTD@m%JwEPM9r$taalPsFJ5?@vS&6;Y1B+YrqHJTI$Jnmpp9y9U$$wBXJJ;)dQ{BU{ z?iv-UD8+8++JrwoxY(jPF=u0zk?+nc@+36L#F3OxT~_{+Ok|y*s4eFqz&mOY)-Ky(9rejFtp?!?Fhv9U7zHv;grmoTC_)<*0kv3@}bqCt1=KGkO zAr(C%neOAPp`8wxKsZKZ{i|3B*!7W63H%xF{CO4yvoGI<#M(Q~KS$j(ZX;Z+Dc4J+ z&JMRy_vG9H6v}MgoVJ49vOfWa6XBg{{(kG7bbZq~IJyx%t9W662ae;vS#+2Wg9n1p zjH&V0GW5Lyw4GH9HowS>tfEDSYZCDSHLgZHp=^M-U4mrn*0CeK6Q9o_@4I3h4cY)0BAHzr8P%9uiwV6w7@Ycs1h( zrWyOVgz3h!^sN%c&ovK3t}3$FH;joS^p5ap_M3QjO62Jybv(1yevtiG9a)V8 zR~ddn#|}AQBFd$yp?g@P97yp25r!Ln#^q@TCT%cTt>GqQ38Bo^N zNa0K?50%YuP*L`>#hENoxB&Tu?&KXJ9?>j$yG(Ml>hta)p8!A9drlynqWa_2qZ1%d%$o2`?Z|_{DlB$KY^N2w?*2R`xW(?#io`5Q8}EsaJ%S(aVO;`Ye+ib%EoTgCt8=Y zVR&|{;U#pT;$uAgy^`UDvya1P*8j6r_Tr9c+!9rThHd(ysESkXk`_3Cz$^U5%xkLV8{Lb5(*!K+%i%W5 zDlZs5i_|)1c=NqvpU7Wb`P4&(fV4tqfmBu#c_)cmsw+7op%Vz{jumbrKQ7l?%?;#c zAKaPbHY%A89I{)p8F@K3!TCF(FN9#9%D*85cTu0bBsTk4CF%5Na2I97-Pg+9g=Ax6 zvYSdHptPP;g*4*iLcV@x(Nz+$O>esq6^m70=I^|jRPe%Hq)T`2zr8`F{ z`&y^RHtt|~0DZNJHnN#^WMLF1JOoE+YCGR3KT`RWGoglt#=`gL?3mSvDY2V+nR-tR zJEO;zfjBg9jvt9~sPPK;MyAj)JwyO!(H=jM_^JU%d&)7<9LU2JjOT9;J6}Ns`5*5p zhE!wu+9OAjg8@9EYdMGa`;23R!!VpA=;CVrT4%6Z!B6F_tReNj4_o$lzj)_A$`}`n8e&!?oMf&3Ja=P z62xWmu8G=in|{jBDU8-n8LjRyt8%PD)i?^0{yK>;%uG;O=sL7)2k!YDz94uaPZulS zkaTm@3Z1pCN6Y3L2{G@k@D{Dg9J~iSR-Yh&l^JXJaYV#hsAN6iQ+j+3q(uuqB%KeG ztc;+nSA=Ks?O%qIpuMZ-ZB|>q*0L14g%{pM{qq+9B@vrgqJ>=oQ8{(*-jl>K7Ng>` zaKZb3xsS_C2$)n)dCPwN#j$&R@V|nq{m(@S{{>e&B>FiHccFd6=_g_6dSLDSvaebwYxxH%vxG=7FkenBf@3th2XW z1CWR<4tg*LQ)YKOA=ts-h#%RQC(haUk7x%3i~VK2HUZ(4Ne&5C0P$~I+JRjh{o{qq zzr>37XxgS`qJJeF01624w0rk6Crbrm6OW>BoyZ=&Vkx-7Nc{*@JPLdFOZUU=-P4ud z83S?Q?{Gv`;n2mv#db8%EY;${e`%I>i|K#2To5Tpsw=<+UE14In*0Kr5_Sb*2VB*&=GI^er>GbPY%8GKJ+0u1z~3T`5R~%geAk{_?w`ztau0hun791bX0vzi z_P;rse*uQ16%=&L5@KWLsD8xOcA=;N{QMt+*3Q8`-~ZxL@BhE(%>OYAkIS17cx$>= zH2~YUxTFE(xqKQhO?~PV_6S`7{(t$>aX|To0odyt+(SvzzbfxMA)@NKC54B*8nBjr zSAfd=3L}HRBr1HVB;9*H?)=9uw4FzJw;zA|^(L)3lcC$A-=7?D3U>zu-X-949XoE} zIy?kU^TGAhlLtblFapahO_NbwJvnUgVe}i=VDhTge@8--5DD=E&BJQ^PWQ+EPY@dV*e?}v+c*f-gW**2s7)2An4-4rgPT+a;Jdp z1NZ#@Goir$7|Z%!?({eN|G(TRNX-9la;M_iW0217=|=m6nI<#nU*S_+&$p%SrSu~8 z8~F?CD@2Of%Qu=55)w{{HESXq93=P{2w39ZOZ9Rd)%X8Ifp z6e_8qIBbW+ENued@_t&2@IBQ4d$RK3#@U|SX6_j^1t2!LiJSQV(Vb@Zm}?8<%G#0N zk9AOs`QLx!(1{euCmt2oM&_62#cYsw5nfFZtvRyZpIrQj#r0Py1MaxpNXPu>hv6jt zY(}=|Nizk%CHUbZkSfnNZIs{y;*`*+A&|yl4qU|KAUU%>>B=##adL7nsC3k9bVpoA zAXIllg+sg4ObqF}MDn{Bb`ZuNrnP_c`o+cMQ$%+j@sczk*J$ixxxigWQ?bj?)v2^g z&&C{FLz+9SmShw-8S9Sxei-~sI|BelQFuDgf20P~@Ohxa%O;2c3y6>fHC@_`6CZGX zEhm_c(`mRpF5`V5X1`Vs{=H`j+=VB*56Ucu1dvCnYh1cTd>~qq;DSrJ#})lnNDz<0 zz)g&t2`D%AgBTJ8=;}oE-Dl^2?iI9FB+%W1jLS0@?2Ea<{flSKClNU+mm%djG`P3^ zZ+~$A4#nW6BQ$tzsV+g7g9R-Z{3km5XZey)I}r8T;6)4R}3;$DuM%0@d7e{|~v*+boj&yayfa^Vwl+dp)(CEPJq?EBvua&e}@J1q<%S&a(5zY1OMA zae6f;5b>G-@D8gm*A0|ztl84lbyT`l0yv5e)}SNMuu0DZ>jAT{AlXZt7M|rc-od^P z?j8@U5Ry_j>E5Y-2P@%`1t%WC3x>78F}p^A{y9+<;3;uo<$;19Vh z-(!=^rhWtma{;>YSpWQU`vD^v<_AZcI7*c2c;#zwDy<>}L5qN{#`H_}&%8;2;rd8` z!)fisRd}G!7MvKYoy8fA4L5{1WVKk7^tW(Zy~1d~^M5fZV4K7gb#@)1MB}er%LEP!Fk~zUTU9{zy?+kuJ}X(nNeEzfM;OXSF_Fc4jp@H9lea+I7IS2%&lsc;8ISNRzXgtsD8^|#0d?t=qXhWpJ88zDFy z7{;sv(LdZGmPRStADvak%{KK_W+6$8cV@Gtqb%1;>ydZCW#<}90+xWrQX(TsrB%M0 z3bVGlAEz62o=)VIY7**RY~kOkr@Rs_u_Io`qiF|0+mf~0BQs8C0+5^0BAd>@@_+op zg)Ot@t^TPtX!cn2>Qy_fn&RB~lLZk8nBfY1y;A;SF$87zK9T6WdT?ysYR^$t({Bde zH8fFF{B_yL?sa;Xzefa&bEM% zw+)>{_R*y?KhHDpFa_XDVv|qYGqSy0FKXGn*}4XG9imgURtdA55L0-P`hJv+C6rA; zL@`5~X;o%hLN#z9OceBZ=zQeq5A{a6QBfu_wAz_Il8TpH4Z2^D1)bMB2J# zd=*4pO=YM_1+yDzn%QkSw`-L6nb)cX>wS&f=@IYBFr;zajPfm$Lr}Co%^tVz^MRWA zXl^bjwbaF>jhUUR(PdyJ+fhv4>avm^>5Qc^vYE@xP%yjKX}r~1yKZ1L9`vbbg)#83 zpdySkb(FrQ#*DJfD{MLCt)IU?X&18@0j-Uf18@8jpb$vA|DuMkRX*0+mgrA*LfK@K;9|E~K#5h?^%ku2r#sCRv5YXRoN49^ugTPK;@N;&H03G4uK$vc6g*xsch? zfU(Q?!bI*^R@!5WZf$)#e`4OP`@y%zR#AnVWOX9l4aPR*uBrh_Mj4;DdFI7T3U$KS zvBB0HYfTvAYQc4E*^ICfW zSfuY{|MptCHDzzymF=dg5w1F}Cegg^g?*_MFOR_ZzhG2DG7nadE6QpTNj zmX%NJdQMc6BCL0snVY8G4BPR$=aAflU!k)5`%hI7@!-32JS^TwE@e8sMF+tvha>xCz8>K z6Wc(6SWfdd3EW@g5y_2VgzyYWx>bp(MG2t$xPI2Q{^pH=R&#LuUjB0^#awpc_euLj zBijbiQ|9|kI5erYL>7ja&KPtxq}8?=Fss?19oIdUfRe#*C3mamVy!N&jn4vCxTtSw zT|uK?|Fu~2rBIKWu9@yrn7D}zi|h4~R+Y4k)i^3r&j)Wm9UjxBaoWDUS2FnVeIIurSFTad^HUza$Dnr(rlHS+Aj`$btuL%sU@g3K=k_%?T_t zokNic98-Dyyc)T}f^H##7wzb{P~4c~JPFg*Hm(VBt#WU4C$QO)!Emx|e}684>CP)!Re%H82?yj?%7qQSgEVYx=f@@kc5BmGgr zu%jz8Ph_L5PbX%bCHJ4Iemk3Q?$?Z-WnJ~4*qNO4`b<|uL*{KM^g_9gYXzoY_~o0} zwdnS#OmkiXueA0@n|a8oS5dW@tCL*PBO6xqwWjHJK6{Gs$*Ng&mbH?3HGeUG`!f|x z@2ha(MeLxZRr^w8I3w$6JZ851cWmmO>~PRQX*D2pj<-KL_uE&4o`&xrCC%BuWCp$a z7-fO!=+{RHH#R^MuC&IY&%p+T1`V;1uB3)yF9c7Cf8ZIkRftzPmy3G$w3b)vww(?i zXvH(dU0}YiglU{H{3K$nfoQr3&u9#(pJq_HeR5`n6?8Syn9E!ZBoU;0w`?l3)bN;) zd?|S+nI|!T#fWW{O`^4Nb=#_v(3$is5RAx>TY6mXwF8PA(v4^Xo*O_EW4gkacSSt?OsM)g8X#trlopPdXWk^x>W9511-ww%AKzL|)K ztSysrREIk4O$oXL7LV@BbCUB(Rn6>FROY@fn^fc$Jv~DVH{^*A*UvM|7eFsl2JoQO z)RoaQ`B4j*1h1|`(40AYa!7w(cX14*0IKjZyH!CU3oX#IHNzU2Q@X@Zj4ST znl9Tm=c~K6+O4Kp_3b0+5fju7pH&@2`hy5m(8ceGelF)b#a22k2uze8zy>(O=NsMM zgh|03Ay>15kTplqAD-}fzrlP5`gqjpciX)<;(GMxM$=-;qJma3#^~Ziqgp3u{GgX^ zu=CoLsXfdrcHABR9AaFTxKPDg{K-^wVV|vTXUzK)*Wgtoy?u^8g19qt=qPKEQutzn z)g0lc>4^cl8mLhnI_yR6`ewg}H>=$M72hL)C2nuWDA2}7Z*ZI+5);J~J=F#uVj+o{ zX*E|~3gi_O4%E6fp4GRa4OBI}cLZvh3fzy7SSBR-j2lnWum(3O@#{Q$5-#@9fANO> z+DM2|%#;edh-;}g+gRs7N-m03Jg?HVVP^|mAi5n9HHch5)_<#iMGhImIgrtIp24a6 z)f5s+%1a2;M^B52(b#u@ej_83(!^h`KgeI)b1v)lUM+YrQd_5#r1dIdg@IA&GIQ1d0emSie)gpR6Z!hx zfw_vLZSMqf9t8JxZ~#UbsxdB#$0!J0fY0-pEyUo8l`x`(Jh*#PpnJo9kJLuN zW)W)-ZoA}I2zVF;cT>s=sIcmO)i}_?-Ul5wk5{%P*^h%3bR4UJTY4F~Sh)G*#j<&r zZWXizv~()gA-N;oy%Ka)TnUA7pubJE({a#iO?gb3%l_rMCouu>nJ{Z0jPoa3K`%dU z3^C{HwQhk4P>~m|Qq_CB99fBUc4h6}TJAkcvT+R8=ClyEeZ#h>XV~q?`p6E05WM3rH^6H{?-J&vKJLkWmD4BLIhI%6NZR_UR{ z{nHI|kmWORgE(baZuv~yifw8(8edRnx?m+PtXW$dULHKNgPjoT zJB;Wi?0ztr$&Fb(fL#a8Jcs>iX0KJ~xM7)$+t`e45=O$a8fIQdP8Wt{KNT!g&A~#8gY@5+P(o9jCwiu z=;rlg1~M<+M_LM6nWO}Ergq~K?NvEdsJr7Z0Xg~c=z^ksIh8OWX=p>=jN77E5cn7| z>G8~Pg$S;#Q;F2$a~1Tj7$hdHaNV}EF#?~WR;ee{T6(XwBS!=3F`ceAe8ma`(~N;j z7kVsfKCOuuxkhI{n@l&?;u!eRJ0$ZZ+W_{sp3(LZw{qia)2*bZbs^~XMd{fyv;>Sf z2y63`_{*F&IajO(-Z$6IcujJ-e8Y6x@DlMn>A^d$Qu~6`xsP_T?s7(B(Ztkt&Bq@a z{=r{8zN`&Vslnzu<@$x=)#}_)OdAms9^$uBgcW2lae2OMIDw`rtS+f_t_{2i_OXJZ zpne{H2ukeFSQ6O>OKb$B$q6P;T#NTYY7scq5^HxNdb#LguPPD2c)E2-dvB@`Ju2}70w4L0o zilL^78b*DlSq1A~wVIaKa_#t0mb@;6vM*?gq274J_F2T5;`_@~1N10Yx9all1p|%s zajngKVfl~Q@4J}tJl`jAHfjk@7tIK>Zcy$>UbSy{&Zqvv^kY4Cqn5`~Uwrxp%XN7Z z?HRW8Q;2cAE)wbqGto%84h4+k$TVF;0I+_-2?xHg9|9T9+gTpyPK}U;fJ>IV3z%+e zJUc_#dQEc!Z{ksJyQyrxelXy+Ut4hv*Qhz~D7qdc7kUZEdJVE{#-!1wMJGdg`y{hLMr%Z&BXdVAWAJp+FBLL*E-O-tD(`;D!3K?Z09wFny=c^`&`PXwjg zlG(^-CAD;b7ET&v5q2S4GeS1thhBEIr%Cslh!*lbR6@rjix}BFWTINku?Q;}A0H@( zWRC*gzER!Gy`ZO@W;h9k6Wa@P?w#OnhKVfRhJ5rDx!^I=j>Q&|N2^hF>>5z_NhaRN z{0#Ys+*!jg=Ty+9mpo*MxKC_jw$R3Nj4hh7^a;on@7R>noIj@Wa?@d9b%w2t_PdBt zR80L$1FU34!N@TtTe?ZEo+sNs=DmE2J|F1%+GNxr_VH_j3Hzqnz(CVQs+X!qL2Ii{ zt?wLn0=r*Pf@Z}Q3*Xph0Ym%%+@Xla!3$CZpf4 zXESVY22-@(2t}v@oZuspH~Ct2QDbxeteRkL={r{L4&FS|=7`dKVP9l@pcYvUt-T6H z8Hm)6&I^GLwxLu(3T^?A9O1h)%Nkg2$wgaIW*nQ!CcK+LpaSdheU z%4&TaYL;ewDuVmw#EgL5`JAPf3VE$PcovK(o^i|GR_+W%BdM6Ng*`c3{rtWhPM-XR zOA+j2(U9?c?49B2?(l14c~HYha)W>XV!ja7TXKbFv$s%p8&hotGFPK3zO)D0PT4`h=o4m^QN`*lVq&Pd<@!6x3-PX3-An(1hxgzrw1_ocN z@UTljnN~g>#ozK4J##fAsk_daX@su`CQyn)cmcGi5Rga#hP0*){>UL@1$ZJKaL${a z96Sf{L}p-N4#Hi1pJJ!HLaI9Npp>Fod%X*aMXQOaIz*t(&jO*mYKU;12xhKSwdF<@ zj>!)pwQ4=xl&mBTlKgV)Jkn+i#~ zNz)Qlx@A*i)QlgXJ)yhmP0K>vjwShwCe75#`l-oQVV0fTw^aDT&8N2)>gy=K>w=VA z?a8a_jL9}0w2TbjYRC}dtDQ>MO>b>#Pdw{rG!=J%eI&Z)`D!<8e67I&F&1j#?u8Sc zbRfop2C=yC8@Gx%uk3)ut&;2ChL6N&y_Na++F~o-*hVK*{8=(6M_6eKmrrv6$+T|} zTotsmBb@c$K7veo^pX$%qO1pP^fL2!2)mJNMqy_ozULP;YX+1xcP+QoquMNn%KJ6U zG_~8e`aj})iVXw}+$>PO%o^p%$f`K0wG(!CLyJD{HJh{=;f6W9vLR zi8!TAhKXET`;0VtpxcbEKR;|SBN@q_KTiL4wANkiPLbK9bO7i=r&P)wjP_aXYt#+x zN^Uqg4FW6U=9rc&VpHeqX#n6u4YGe-{f4+AF&S`$_luW4(MAlhY?1bGPd9tajNuNJ zIV1Np=gjqK_8r`4Jot2C;7X3IUoNF|0q8gS+%vB`(ncObsy*<;Un0-OCugFPHqN{p z301o2-2#DBY|;@$`2Kh!Tr zRXEs|+6T*l1;gpacxkLDjnIq?NFTO6E`d(*@XbD`?UOA8W`d5H|Jn|1*r05J8B0LgRR5vWQd24lsqk&e00quxtszaWHkTaAO)=rA{JRP#M_CN6%_Cl7D&u-K1{K@5xf{qQ*3)NTWWLg$q}LF=X62Dwu2(}ITOn_ z2WSCRX#6*tmQI4t1VHQ4>+%{_V)xc*GcQ zeBw-(QZj(on0F|fV6u9*6Mff$ETPHA_!a?`4-F^kbBH%S@ zLt;h;H`5pD=QTMVGuJbCQ6hlK<~BS5VPp6;x?dU1K@z!xF1-nN_hzpD%k!}Rb%Fp9 zFI#F!PKS-7B$?q@{GjniNCW8QSAr5d*xUEuk3)Uq`h-7hQ-jWkljjhZzs-5J`TQ)5 z*6_h+8V(!^?O@DgFA9cp=UZw!IY1u!E#AoOeEwoCR%pe811*Ul@>Ky$v?i)F_kum5 z5S#o2qNDrTvh9cp5C{S&;1Wypt9kyD)Ir}SM z>@&l689+OsvekC@U?@(w4+nCGNhC{;fgDk$HXlK)TE}*P1O${gAK(hU6GxSC0P~}F zC(?hHMKqdZ0>CAs@X&n|fJ;(2L_@8g zV!dhiyZ`QaUxX}(PHCJsK7b!uMeMp8)F>{5uXI$B7qbwud61=Nb?6co}3 zN#=GNT45nLz`^*vfHxqzwCeFdH2AW_1x!yu_|l3<9{ww(Yq_BE-q#%vl3vcjf7MD% zNIG)sz@sFU!N3^Wgq;>aQhJnt1zfmcrYLG8A6*A}hHvWtA?TKb`kt`}fj(x?<8O!0 z9C|zsJT803Wl;d;U`fD2Yu})BZ6Yx+Pa?1c-;H_!o8nmD z&jFV2J0Tt&w0xj<&a)clJ?3=1njyLJKo>N0*%b(uFow_VOov)T$TX8x$T^;{Ex#*J zUMYFc@mS$^KLzJ^ek_sSsqe>JGKp=t6n5dBO9}DB?fWrm8h5L%I^kZqzs#k_hcGV}ClvO{+l+}u2Bh;;en}7Xia0v4>&9eV&4E z-5Bc^PcUJp1tHF0I^{j;nu`5IQyzVECC_x~eQa3DvF)VRC`l6deHPYRXE8}CZ(#2B zYfICAG(m(7KkzPi9WKe@Sbe`d`B0-)SNDU|P2q{>LWxbWVVvM6U2%_vW(sY0N{Vl= zbA75z+?ZXTPn%xX8ZHjmsIepB&Z!(T5opBg=pTERVBeAQJnFl`ibx}K-C1ay6x4Gy z9@3(EX53M^f{7om?2i5CPa_m)8(yx4e$Xz$Czq^c?%lff-|emi65TT{;yPC zz~qwoGW@1S4`dsD?_u*u8N2t|x?79&e50H0qzsxeJY7K}H31@-x~*w(YL7k1tRF}a zaM=}YOfpF4Xx0XQk#J`?-uCiayUD!NopO~c%aNq;$rqDMo=G{J%XnAr|9Exfj1Q?> z=?VCf?&r2H#b0{ffvbE06sU=CO5=BnSJM2STPpHqtpDKH~`IP$E&_=*Vr;5k}M zm%S#B@lkQh#dss|^DC}!VWvg8GY@odNs>0763%ku9$Gi(b>JjKEaAp}#_d&3^@Ss% z&rGE#d*#S_o1Mebnfp;nx4QNwAwL|BxJ0#@Vq39Up#g0D4|go_C1b{s`u z2VrPwfn~%z`;@v#z#2`Z8aYN!V z!Jmz&ki zq|cIa(~i1yD*x#bx6@GzF_wf6}94|s7&zP_n+-e1~7`*)iL8qTrZm(d>` z#=dkNZ|?Qix6dTWt?}{kzng;IpC^(6=r1I!ILXvc9kYn{0*qq?B}#Z z>Hlo`E`zI7xOSGfB-_d-9+a}d(lDtpo?mic@3?r`+WaaNHm({sw#912iF>#0-69_0 z?3w-VZG(=06YyjuR5X}Qd;v}mvE2@iEKLfupi6XZL3LepP?I{5fROex8LV+(RRum_ zOUT}h%E0TOJNfxuL=_$!v366)es1iY5Bd8GQVivYV0(624BqRvP7qxU*FOO-`fy#P zZ@f>61hg>dz67>Z@EERLUG$%x`N0zi`#CyS*g!qa1y+45I8)#Wk~=379C+SIz6nh! zugSDL4D0*)s(KNLOHM-6SY|P`48>Go((v>fJW#mus6ai3=#}z z*)Xt-v7J#~Bfg%PLd!=z#o)UAUY~=s*P|7BvgPIb&D$p%>!=ZHL>#aG)RKgtlRSR` zv1W4bE@6#NEa_9YSn)YuSoj$VJe{W@wZc5 zd~K&{Y0mxG!SN%OX#cqH40jx&onllQaqbxa{nKLI932bMFJt&M$&F@@%G zT>U*@$4}pzboevN2mytg{*&W{t^}X7$uZE6^ATT(LJi1>eY?9_*f3BCSdcndZ zy7^ViSJ;DLO$MqNGkWce8u?#wV5&9(ZNL$B*H3NVBd@<6_w5HP< zQH>fA{^!^X{iy8LRkD&EC~T*c=y;L%uYO$vcIer`A&4a zFGjw&G|lxQBKkGo)gP7|$D+SB4ETmvlfd73TXi5oW>}@L?+803etdhiQ$^6+ClMc| z``vnNl;}aE^?F8iK#YJ2j+ejb&gR9f{7?o~v(Ct?tSS#*oY~pXKZ_oGvHqm9{%sa2 zV~ee6Ffh+}exjmsw$vo^u*x(ow}Delf! z;h(47hm-`*?IIp73FHgkOYrkMRMpqiEHaY$ooTZsNe_^l?`+|A!c{T#3-(k(w`EumrohitM^fQ^d_@iwS{{|Pb2M7;!Oh2dS>E6 zMcvjOR^I@o#>|$xeY2yk8hsy`v8Jl1S^$eGyx7!dNVWCOdbRofMBC&o4()LA7@OP^ z?csbQDif=7m(KR2XMQkIcD!rTzflOQ$RC@h`@^MHvo1pumPq;bjG@nT z#03R0AB-PT4pEPTuR8g0P}y9txg+XF${=^Po!^MJN5rhhaO#N)U9YeBEN@vt$(hd2 zQ7k;0#V;hfua4MmEE98l=_XmPZ&Rvnxnp_n8$PKhm#tE!aIh))CA*~cZVI*L*;C$n zDvhX0zOWxrueyF*_Z@MRKwe)Ta#dzpCh9OrU%D{p(LDhG= zl94i6!LbWZqeL`i<_ZC3nNO;3ZS@Bw&hRTUiWH<@pREohSC*dayHO$0|2(LLFGcfG z{p?k>`4Ecc=MfzG*5gGBYX;vIX$+3-`WQTrn|-);^suyjUw`8sluxkj`_N;r z6;oN^bjs1Vc#ktHGA)1jTAt`w_cg#Q!@1~MQ}VXdiIn8E2|NMyWy|&g;?fP;BfH+t z*cI^{8`pI8rcK`ytce#!x66w1Q7166Mp35aMz$8rs!rtG$hBFmpv8f)g>oyZpzOKU zyQ-ht&KoP2I#Q2Wr%uyZ7>wd9d~Gt)7;^D64%C`6P~3rWJW8H?dn0eLB*UVwdzr5) zsCF`BsS0&F-AS=Vd>UTQ02xs`24jFRFLDZ8k>m%khO-Ao)z|V?s zCHE5Z$!RMaJs0IEy_hFOe)4h08oS1p3!!mbcCbJNJk%`iE!zJq!ANRA-*pC@rJh2) z>8k-@&EMbTs|rqi*fbjmoVcaC4WRLNL*$gxHOAeE@n`Cq$84yL>bB)=S7m7_x>K?! zQ2y=y6dNu(N+!JTy?NLL(JxIYhiZfV4`pv1ROJ`83)3kAA|Vo6KtNJjdV_>02-1yo zgVNnfmvl>aH%JMRlF}*NEt`h#*@W_YXTEdJ`;R)~%(K^8cVG8)uZ2TMN;H*_c3LBt z_OaH5(iMVJ*$KIP7SF*IBx+mLou)B5_iFXmabB7=bz5y)oz~urYmTe_u4$gh9M_w$z*jS0eEWn-nR2cA?m)8W%W}xG5$Ur zBK5<`w?E8n)vaBGc;0EB;d+vCd>41(I+=pE8_5qWlyMI0|$B!>D>=9ZP-ZtK`HkEXigt2${aQG?`jd%(vh^_6H$TUUm z<~L^wMwjKJgLR2-=n^B1i|j8>Te2Q^{AXw!i5(;C|Jf5`g9hT@!#$uGko(5d_lPKLa5JuI6OBRg2XJF%O4&R4uDEcr zSbdwH@6#`QDc#e$cX6^*RUdl3yWGXo{~M>8@sdqDzF|KHT?fA~aVmF{8l5-Q`Ax4u zVH{zgO2T0KesM#SW0;G3fc>m#7~devaH7RRvfd0DmP64m$V7X0;zV+6tz5*TOBFI( z{)?PvmfPi~-ztBu$2gxZx$dW~+nLXi8sD2Wnyg=K(hszIpvJE5c4WNCg+mI)s9T$9 zh|5`YSrx9 zJWwL-)U6C=ImP^Aq%SXLFjmZ1PcK==)ulo#C8z#ao`7KD;p@H3LrJ4{HoGyP>xbR79-%lw6kP{#JED7|Q|!F;KjP1WNzqvJ`Hp$`E2l3@PG zdfLdp8FO4^&0+ub@o4ccW5}jO#bM+n!`@gWe`jMTW9;7ei~?y*PYb@U{#dhrSARSD z*L;B6T`TJ_TP^el60Kmdw?Xx<MOB0)OGs>O!>T$?UT!H6vRl2#O=}BjlYr53?s6Q=PY{}V zz8Xn~)N)++`B7@NALhxD!=K?9mOXx^V>RC(3C+M!qY!8377Xbj7QKo5)30qmjXbvU zgQI9C5>4)1)^LF<`_H+4RPjq!vU;|CGm$5loXscfqgQv6dIAotbD($j=RXNN&`oSQ z9DQTXu{>od@mQNRh&^_KJsNI0DVR@1Z(4a|{Q z1AZRccVSy~(nbypNgnLh&1t#fgGVF{+3zk_n(t#-bKq|FyjDmfnn~ZuKUyI`UUOMp z395XgeXz-^+)+JE1JBlqpPpc2Eo^nq#}?m-2LI50kX5pczD9E9yleo$Y4~TR+4hz% zp5qT6PC}TPH(m2&YNeJUZBMzfY|5f>H>IChXidKT^#+M>cHXG)qTt=ZAu(S+3b^yl zvemoF0{FI0S##VUOb5mt?R-%!2qp>~+BLC4m6arQ{duuPmdeY=1m=n3at~_+GZOH@ z4h8tHc$D3Wn26_=M<4WB|FY-c(GjAewTMBtca?n@yVoQ5?#c4r{@bDsr<)@Cbpi~K zAZY>eXF$!?d<>%m8|wDrOGrMF&F1?EnonKxJv!Hrq&-#3`rSP%H>`%7euE_HDwk58S*Y*%f&F^QoMN%g^k+zSK=Q_R1k)l z*Z;X^CqoqXHP`~I?-0Y}6JHgTRx+nmO-u$iJm1RB=uy|1<82v9 z%@y3*Qw1w2C=G|q?B-<*~c*Kdw{C#7OdX zYAc&eRAgHx+veLq&EtZ~*eA*cB`Hw_cvY7t=;T8tf94E{04Bwf-9R1h;#X%Qp}jfV zl&U-rx1L}-&$epd&h8(m~AZ5;{C`)9dcP>iCw;;$`cWr zaH+c$nYXJbNQb_@m@1})XIT-^kY z{z~~;Q>Q|Y5BqmMWq%KinH$copl*d%p93xS8g!}2c zZ+>sk?0%vOT_ExolhbCLp8E}7`y_r@+NwR_y4;AoDOmgM(0+*?RCE|Rixe)sqVyQW zy;sM3s7d8m@FUiS$Y3q;oXOUjYFxJw-(T%4EO8^QzZMRMSu2O8(q!6RF zhd@3bMso^YIBA^iUfE;R`MsH(ti^^l&!y2A)4g*>aX6?S4LXhY}D~TCieM zGhz48*KK@Hkh)u+18ekQSmK90ttOuNn$F?;q_tKfq3!$b0-g<*_UTUBKe9$=`Bjby z6U!5aT=UtsBIWZ`7k{W_Q4$iUas-a`t$bJMz9_X-Hxmj?<$hSBAuS;M3AYwEJ;4Re zEAWTr`Wwg?Q1LSZl|MF9TW~rqh=CWt<>Gx;x`Q-nUnVrK8zW~fDFh+&hs0g%`)%M%r{N* zz!7=3J_1yu_d&tf@7HBsmipZ%?%`>5#JT(fPOVOyrvi`G{E-Qq1LB8L9kX_F$3PVU zAu0Koo`PK(n&NqTi=oAcwv0Gik{vCYiAhiwQgGNv-Fo)p?D=nU_-m13F+T}MII?#_ za{ze=jktgMh&>0-N42gH5*-!OO-qA!+g+FJGRi~kK4b?WVY#0fohSE zM^;+`-siWo387j$d)9VVo6^);>cgj?7CCfjyZFhE=K!csMIB&MtYwzC%_^;}c3*9T z+rfUvlWuycAola*H;esNceUPqqvK z4QALNC`Mc|)L1~=f+(XCt!?w?^b-uWC&d!zN)`m)h_0Go;2>;jUw|Acr$cU5KYElq za?YN~j?Feb-Yw~NWI9A>I9gHoY+!_TVwjuCLB@R!$QNTd`a~UJ^SkU}^Zm)dS$)q3 z9rtT1@XeFWqa;lGjfHAm9rS;8mvjY9h{*FO!hR?0KG%XhE4N7)G?ZIOT(0Qqi5RA0 zV<>@1c6WDG+^wCoSdBs5@?coy7^sr;TPElhJvr$?&a8e#%8&TU~9lI#v-1=}PIf94i9Z+w{!G(3U-M|c7Q z9g3Z)VrV-=+y%I6%Dnn(OVN^L^Z~U0R9y689oGr~ohbAru0c5+epp(v!F{d!SzSr8 z6Oair-RZBv>P;W=m(emymmlZ{=r`6o55CFp3w34;%}@_fo64(w%C=I^ad{BWWiMBd|~nW(~aQ~SZ}0<{s@mg(I2x+rp0MS=O80$AK$-(A&S zzur*to657(rCP$zGbB?JW!&us1raTUZc>Lbox%z)vJ_L{{LKj4tGZ0J#bHdWnbHbA ze=+S`1vu8MA|KQcljreC;eY3I`Y){2Cb51t#CzF3wp+iw)rZabX%~9)F=y9t{m$M!>ggD<$`1<2PEJ_*QFS9=u zvSrMqQ=VraCq@9(>uzg)4uIc1eemu4li~i)PDFYgL>~k9c@jp@nV|&7$HD5pyk8)e z$X#uN7cyK!Wf7M|@cYfR^SmK4I6tPy@(A>q-Rw7Ts&S69r>^(EEGT71~9n3V5$ulAJ* zs)VbB6i0T6Zu%>wscPL(%RK-iieV-8)csozVS+A<#b0z=!J4os~_Ktp;kw^T?$ot3{P zs8Z{q~RC&tEprS0l8ho});{}~tbC-!i$fwH-^!H}VlA%eA_ zEo$bRPG;H3q^JMma5Tw@^B_IfaGOf-G&RuaXiD3^={1G`S5RPGgm>ku*EyuR%J0fV z(J7GuU2+;1L?stWCl6!-zSZ3rUdM`Oqgn54z71(BfMs)Mq}~!#tR^!@i)aBY>`*Z) zzrEGV9`R6&b&3Yvymjvti`9)Z`spO26^F0H}A;dC(^Lxn)_jBPLW}3jVbqV z7Px13T@)kfVkG8W99XMB<_iamSM2P2_d`h=3?DmK^GH_#tKUDi%_Gv5GCo783LM_H9a7Ph*Fhm>d=5yAdHVHd$vx?5W<^fhTsF@N)Pu2dW3XN z1M@(V)a!dZ-}@t@h7!Co5osBT%dlK&x`1;f+mbMFGaa59^_19 zs)Z5yL^+*%DkT?fZ`DTcSME$3VQR~aaH8Y6KIP9pYet_`Q?fanmKwpm7f~_VLY1(- z!s}IoaH4mNc6^Z;(Mlgqc!8N!oy%lEmHTogafAb69mfR0(+CyZ_Z#SG7`Y&yIscP8 z^0`yp`4fXr+5IV(OUuB@y;)?X`ZSnb&`Sx{_Mbq zsY@+y0v4sSREYFurY#OWdpytbM8&+vd9C|#;8@WmN$u-EBW?tj<%4()7Zu$qgReWQ z`*?3MT~Y&1Gnc=~;q=GV_@2C9<Pz$duD}S;Xk}&^J=jPs@Ew z(|JZMSCo4*1)cI7xlXHlypie2wS+p^-4k+TX?8nnOC;QeYXyH7e3V%TUCw~^Ho6iN z61HELG7sFZ;xEJEBVOrw(*29<{7a%LOYZ_B~&EP~)IK7iTG~Q1oZjNnTu? z3z;UK$a)v1)I(6s7*}ra^)g}jP4gH5`(Ky#Y1Ww7{`V4Nplc&c6o-xXAs-jskZ$6L z{HO)Tm$w3d)3vHc$-CM}@RwPx8ducli}T7@MyHr3gUYqI`c*Hky%fs)3Wwh@K0G(5t4m$TuT>x1fy3%q2@g=1_wX_pH}ndq9TOMKGh1n-qZE-^T07 zXRD4ubU2ralqpGCy384|tew+UK2GTUNI!ak8?!||;(3M7PCk9V`M6;6FbACy!N=`* zC3cn}4`L9-6oPb*$H`+>hm%wue1z!=G?)v}0Md>pgcMQwieT}z3PIHoi`I<)Md8MS zql@K;FSZ?DUm>$b?-KL&`V%b=1YrJ!or0ET}-? zkKLDsi;GNFvyom)dTY0U4NG5*p|qP4=5>Ob@nIbb*0aw%be?fQcdrr~!cg6UiWk948}0FoPp7W_PU?U82Ye*V|~LBz17i)zbi#ye`ZfymyU~UQy+`5L9cofx?^VaWiuj(5(SDQjSoLloun zbkW9g8aN^B_wOEFc~!wD(1W5*NO|yYeXuAk*dI3Q@nOl# z+~zQ^w+X?4cRVv_BWb3J6g>+C4jo%EV`p>^+6x{kp9#Ds+Q94m{1Ui6^4g_~aurrg znkLURPje_NEr#N1-Hm#bjoW8>Brpt^cg_P%c`498pUK8$^f7ccqg6k^|Us zmuy4z9jYPN?B?WuoS}F9-UjsC6yTg(>3P_c`tw{E7>Z&sApzOHzL|!fKoAThM=(KA zKwbOOTlH!^C`oR{m|C-kA%u6-9|W4aUY^qGf0lP^-xuePo@+;%s5Z#&sj%-hypPS+ zJlDp^O=OLeV7mHh`*T>AWtMyKU3aKdCU{W!agl@bfW%IFl)OZz<3YAuuKehbo`mxb zD#>f8b>V`njJel6-=n}*|4f2;p&ogg#$kax-i2O+l~1mul4kY}@12Y%~=@d1a$b0aM*n6OgC0mg?A>`YLdjE2=~FZh{Cv$o_5 z**(5N*el`A;y@*SMj}mq_uG?JO$%Ny#r+dj9$uSssb*6(Ph)Mbm;n#b7(RPsA~{(^ z1>=Ia=oJa?Jpn}>4_+DAp!+MM$zED{hMf$Y6B%jylJq^K(u$ zkXmec$M8=;e}TFu?>7<3!v~6c2bdP)xQEg3xFlAHLZ8qUSw8D~E_3c@`6IXcMda!< z9l1-V@8LbFWykjbe?*0P*h!ZUBzJ}DaUC(RS(Vk*Z=L;(Q=p2wWo*t><7!@{B?2@q zC|whbg^izH;aU_XzoSOU&XVz z%)-1bJg(VBuWy~m0NX4G2{yLODGIkl9E^QLI-{jcJwB zQHQ*+b1@^+ESZ3asy8b3ApjZ7fq6ENb8mA`W1)roeyHbRKDNx_AA5h{PyR^A+w`I% zxw?Dm(d?4-ijc7QvAlZD#Iqi_n1&--bK#dyvsH{G4Xw3ajsYdI)>=P&ATQJ0ShfYw zll^IpgBAhw`_B`xL6}-n_02`coshoGyeLfztRNy zvcT;6#qF5m5iD6WW;NC;;l@_CQSA}p#cpp$VLyCZM6cE_c2|v1oF^N@&VAZYK9Shr za9dY|sbodR-i}wcNIZd^{hMB4nbcQY+*luglnqm>U_ZtA&Wd!yZo^^df5PEyngggF zkod=oeFAeKe8&TEB_vRdXiSnV4FE;`GiY&7#m*^2vS$5`VjGy$ivs$mlq{{&bllrT zrW6UpC{@6Qk(oy9`}yvKz7nxp&8NP}9hr)df$(V&1%&a-QSSrpjPmtEbRZnFWMjSZ z29LtE&?oeU+t3I3Ra;%qvINFu0xTa2at95Qy~y*UTq4KSP3Z=R`re=4TTF|bZf;kp zrey;D=2ZrLt+Lc{x9S@FUv;%T2Kes)A$vee*5$bm1rT&7-d{+FT$9hGv8jV#`hikD z=4eTE1~lj*kb92cbM|ceb%QrmheT971Afv+@f^J`n6R19D8KZOi0}?CJcRA`N6S@l z?teG>OOn4y0D+_(xaWNtplJ_033=^CU?ogzXfN0toujOA`Sqv9nYAwh%42Q8dEvpl zdKvL^B5xOL~E|8?gRP9nool8Ug{+{oX7gk1Rm3TJmkk)u5P z1ZIX!xiPNQu_cP?jc+()as|hZ2}EP{4QJI$+gj*Yv@R-70g#dLbn~4=29KRYnwbqq zO8zQGm9Of5dsm`=6C$erNOWLcMoQ{E+S1}8V$$!h_3=Zp8s0EQL)w{MXGtJbuKn5@ z6#JR$ZLe|Odo?Z0sXsJ>H8Afg1qCeuFgtJytAqx^iGBc+jrh`ZU@Hf36TOcL#0 zg;H|vkJ;MBRDZ};I)1-SR{%WJ_ggWB>r~qwATunu)#XgA0WL4*Dp`(!6@7Upt?GPu zlZcCxj(B+jiSShGO$H04mDZdHCFM&!PRNE@YR)(Ijgx3Xldmf)E$DAKzx$6#$sq;XE7ja0d=)9*e<$Vx+$BPxBr<^J z^5*1z5;YYopW;?^Mc|;}kc#YJG+&ObIOqNUqfGUI=I#EnIi1Q&F=JpKPm3%TU&Cvi zZdUfvfLxvSLYAF%PP~gjsw5Ul!*Dp4nnG9$fjLG9*nRw z<+e?{R+jYTYVN>7@olVe9q!TxF3`!nJW(;)l9vOBuW=zp8H6a|YETuAQ2SCw^eOv)#15*qz&sS$QqPzX_Cq>ME;q82O-FeZ7%-4kAVfjY;LT4Qf<*Cva@L--BN}@mT z$L#=#mn5_*+Hrgpc*pl)VSU`JABn-m0zhSF-q+1;@zW9KZ$3d;EdA43UrP_O!Q6@B^c90KUkwVb~M4mPab7$tyQ z0XY_Yl0Et7&Tq3X(_gq}#qeh@_qshQ)tNsje`;gzg`V+#_UACX^Lj|@M*ZA@)HR30 zz(igE0mrvn_o8LbhacM6-<5sw`6^Q#pn#TAhLDJwJIZVkbsIZU{<|JgR0ELF6ky;+ ze80|{&rm7mh;W8(DJWN;@DhP4N*|Yq@Lfgmxhd8Le;;kuTxSrpNYV}1GvP={ z5_D$}I%wV;mJk)StU&jiKJQp{{NQ05M%W?_3P@Pb@R0V$B2G!OQLua-kZ(M|3cJq# z(qBP;N_vAv?rx?2{MJGIt<-NF1Op1zkB`kQXJXJ14v$ASrO@mHXt^>u{qCmce_aZp zpkIW74a)!WJHP&Cs!I#!rH0tAC0j3R-uHsw{!C%n5kL}e5xRERD4|y+gH2%0 z-DP0`o)ua;>lmEberGV&b+9({xH%E) zO(b2Ya=FnbHPhiiq7d3?7OJ^tgWpV9u=8gRqD z<}p?{6MV}<^XJIVKJwyxbNH5@c91q<)_Ijffc2osedO4*CEGt|j3xM0VcwBau))0*rz_u9MJ51ghzV$2;zl z&)t8kE=j&{VkIayBMeZUVmdahdcT)i@{E2&4S0pC(T~lT16C7SQGxH{jAp-156I)Q z1Wdu(JHFtKL4}h+4JzSdlNU_rbY8O?(N9#q9aKuJ__(bzF)BO+z-1aE_z2ocLMJE* z9$w8mmc%#GzNu&7{poee4S3-aCxg{H^QXs%A%$A(L0hO>iP`jrhCH*;i z`0!!02XKc#6V=heXSO5`(W{(?Mu;TOPozXd1?2el zLHk1UJIca{Fi)|}I|>pH#YvzLsNw8?z^K*T4r^GgX`f_f^CPM3>957U$eP(qu5_rE zk_adON*f~p%s+-0BQPI-2=6dn9DgyO@lVBJZT}X9Ub;KB`FDy!3pF>X*Tc9nR$wEj ze}hhrj#atjFI1X5P<>MbYfw-jqKVumze$f67NPT@%I;uj=(d&=oeeq47J z4+^;t)xdJz7B>_E&g-mC4aoYQ@9dy{hEF2kbAio1cKHE%{Xz=xP10JsPhshR!+=5P zBybn!BhuYd)-@q8xJHn!=_h!%u-^jOUD%KG2m!~ZJJ9wA;G!0BT)kl2(KC)Xo0xzj zh`T+4C4&LuJ4b+jdj#@Tck5zICl6GLC1nNigW7`-G>N@50Pgbr|Ke^#Oe-e(x`dsoaqW4ZCw{sP(&S?0iIc! z@}P7F-haKR!S&*1=BN-G&@K;se!#531z0!QMLRFbe@8%*0Q`TYU7T~*y_L%Aa`ZQj zILO|t45i<&X12((h+zg7FFhi}sagYif`1q6Cj~k$@ z-kCFY(}sTBTEtDU&H=dd4|~auUa3ULXeb9)FG5KU=refZ-09w@{>HJO8|g{nsId)S z++TW?@OEmk@9mUDfZ*O1P141--J2cu3s3Znh3Ix@FE8ZaMw4~V8~X+BXtF*u;V~YU zx#}37c6a7oMJp9}!=*x1_5zfo-ych^upTA8SZJ>@1BgaL!Vj(_bS5R&glW8CB% zM5Z7NFjn-Ua>I8cOeZ++DV=d3Uz_di+$*l85j5aIITu&(LNVq8k{{+`xR%xfbPvJ$YuHgksk*G8kGz$#Y!_z?FTTj%AU~HZ$q% z2lU5l$ko~#T_rOlLkq1znfS*}Z`)nqV$OJfE|izJF04HHw?nsFs74Gy0NDK}-i++5tywF3+M$W{#6fL)*J zL|@gxL&A4oGGNlYRu8(3PpQpfm3KS{(QSMZ!@ga4xbdQ3E_Qz zairx-fwUXLp_QeCk^D){ON!~cIr^0ZA;4=Qu;0ylqJEUw!D_A3MCcAz)k{}P|M1!} z8LfI3RAc9sr&vw%Md1PL+``t&1%F-su691+^wnA9ST1Z3nZejI@*BH|z&>NWZm?e? zpXj%Rz?W*hDs;d|v zcD^0CPX(J85=sEdory8l;IaI+68W1zz6D8<~sr>zx6LnH~xh? z!krb7Fl_T$ouz~M_Nk)ZaXF<3qcl`uX?E+6tQF4`!m{Y0GJ#&>U6R@1eThQ_#s!2* zk;-+)CxtDBiw|_$BbKRDU3Q|QbpNESYIPa9+riGQlghVyaV+_?`W>D3vPoYy#zKL> zMD)~H=^@M9w23*!hA0I+y6`r22hj`F{mOU;S>mzU)*g#h&Tb)Xup+*=4C&U>!iK}p zpUPFTd>W82pu>#0J&xQNYJJFYAar>Q^v(B{97(&rOEyH()5wR5cyDNr+Y1zG)-yx{ z)3nNanLop!?AsZ-OJPV}eHhVFd9mp}xwe)vg=JG(u0Z0So=f{hQt4tX#MR-w7sr}E z%=b3dONA9e4Kz|Jq3jQGN^v%^HH(}9JYQPzl&(?@8x)%V&5_nT@ z>qQ%Xu7!d}Z`;7JaKSfoH#wpD7inbSTZgo-ygqC?0*#Ul07Ok$a4(hVe!2H130wxl zRHbjcYJF*E*^fAn+)6MvVm=CjjyP6n_%YzqgPqQfnK{>UPQ%zwR-2{*37C#Ik1dAO zBq_rk9j(ldccW$kVUlBLRDY`c(G{#dEo|uX!DF9K?Tr-6yhznjeQ7v7&6Y!mFIhuM z3@|xdwfz%1b4)cRe zlp2lzMyWD6#sq|IahY#SvSO;n?jws9VV673je2v!Mu}V{LD!(h0>y2X(R|Yoemept z^lC460Z^PHgg^&P5~!lRJ96SbF7a5du+pSXQ;X=nwnVhT$A;tN;{N{C~tA^@nuFn zB^ek>2#y~#^{FmEirgC7waPBuhv>B4maPXgzA#cd3QQFhd$WoJHv(F{_g z<7CH5zVHB3VyoMz$ZHUT@%FfH>t-i)_BwN{)$7I2mYizgv^M zOMkknzzG?c=$_>rDGU7-JM51QO021?{o`WbA?{>Z@ND0UswFFzs`d%c&96R)xh|(F zMM9_VmQ%omP**5PDftcf?XL|AY`Y2FbD~_kqtH7MgxH_P@b{Amm7ichGaQ}5)YuaZ zitfQ808<>&J}!duz{N6rtC{Kl104$(>E4rPepp}@z5VF8axdU)eZ+AqiVLqs!vWIM z6?pp94pvYB$?0(ZH6w|jeYU#w>8m7#{EIgvb|)B;-=fU|E-oD%z!Z!TFihGT-6i|? zCqz06cjW~JO{XS^8munm^wI_6;y!mx2G>8QWnl1sKwH*lWNs}4Tm{9xm=BX&5G$+0 zS={ktPpZ&gT>THJipH{KTt_)RTQCY1^0vxL$V_RhxPx)xGqWi%;UwUm4heEw2+MVq z)q^r1b0;wyxh*k1d+C0#gyzp|piCgMxd{b#GaG;(l0Zl=bcC4oR;@YXfu=L#_Z(4` z3?OgA0@k z{!BiXNGN@!o^+7xbaG&UBc-fHJ|>~??4humBG2mmX9@-Jyi-oYT%~;nUvMABmTjAB z@f%Ejo_|dIP(~gTXe^Fa$(1a|sWbOgX{`M^rmHUc5sJEKPI_K~{pPKKQ_;QJY-ivu z8^#wk8dRJ$>iN++2tEWvoR&8Qw?0xx&>anVaaC{wd=VLn8x4751RCN}T~|=tbPs-j zGQDvIC4J}|A1!A@ahddxlW)!u0Wli__WA>MeI#7v5psfQgop+oS0Y&w>utYy| z9WZvII3jEl(RkZ!Ck&pAwaIkYC31O<1(J1Iv0Hxyoo`0L48%{SMFs%&$Myi-R9G2y z7QbDIHV6-7v|SfB$d?&aJxyL|6#~3fBwAo<+f#qy(hnMvcPm;xMMZH4!UYBGw*+7( zW@!o|cFVx(7Ea~Fp2!B+FE$xV#4fGsb)A%-L&Gqt+Sc<$6hYJHI^9Xvb*mB!*b3nv zhX3{FZQc41s9T}5tj*{5(h2}a0coWAflqD(F!hg0(e_WvSBgb-r+1Q<1b--DU=IUH zv_ly=v^cz&i7$y-qXGlh51?@t8z&b#0i*h{RVX!I1hj=#&`|kdwN6fxUID=f!>x%| zeXtDRbtPH%oBn<2vAfWui#cqY`%cS`fz@n)@KC$TJ|l? zaw-StLG-#8HE8rR#QgCS#wPntcv!Sn3>~TkAh1hFc)lz@tf1q2h5PZ=CeApA?}OEB z!vRY8dsVVyRIDfr5~{-9GpX_TaUo!!#^xSE;N@3ZbiX-|j$WU_h;iI|@=_}Rk+gIz z#bvWjXr5WfoYV!9A8%=$d}P>VOZ-AU1GqGSkhG?d$3-P-Wo~a|+*&$QPBXxFf`MB> zIce|(8F-NUz2qbQ1q+#rcZ^jB371HfE+;`gObN6A7PR?v8sQ zNO`qC0kvn-9+J^KK)^#3+6g8T67~?m#t^zMAhiodMe!k{fg?fjLj@t>HlYhC-ETI9 zaZf_0wjft{sQCQJn5!%IppHXsMvY!ZO@-bvZ!bBE+LX-HK~C%onWW9F#MzDHk~ZLa zHbzOK1C+{L1N^larJ2Y_ic^FC&F42LwdY!@fpj(n>c+Q@Q!&$ZcoQDT^gr zBkq~CuP=w>kx7(QD(X-UwnefW76^VUR~Z+|H2dRajmc0>+@Gj5SrfL5=)54Mh;VSa z@3a=b5nU=;+qO~4SdlINzUe#uke&=*()I%W43Dg7#q9X;x}}SAIoGae)S%5p2j)iJ z{fR^3<2`;-2h~lk`Ss)V$NHqX^2(a_)`QvjS^kCJgiUK^mDi$Xc=90iD#+Ukq?}9M zYt_q_ndHGP4Wi3(IlDsN7#Y4LtlkxxE)2%Cov%*^e(EnB_D0^_dk; z=Ui*w!!Ua))sE)K4iY7uY55@sBg%5;Cn};@x>O9QI!Vr?M_TpWl|ivNHZx`&wp9%? zhd$%;MKb#q$GsBE#JTcL_NOyA#<+W0vWQw2js%t(seYY3|L!=gQ3jQ7*IDrJ&Qs-Z zvSd^aGA1vjI-r2g&R)8AP3Odd4vfV<=1Og%$zk&SxG1J)axtNK)0G1;r1U{Ks78?s zrp5+if$7f6tRT4}ur1BO{P{LKzgv{&UC#5HheXy3V;Cpkd}dU6C5bNr_xBDS2%6N5 zA+13iNn|;uc6*%!HQ-R}Hx3I-hn}2nq^MWVbq(RybBmsz?LFQ*FrU=nweG||%{e8t zPsxVS`Up8B-R54aeeHp6zfrTfWON`WFY8*r%W%+`D!Hh;M@p>N4mov#Sg~zyA1<#$ zR$M)mofmP3X11JPso75O+K)wLZf9p~)tg?7@+?r!#w= zbZnNtwOP3)*UNJ#Xv#VB;uI6sE0W-GFOw_LkI9kI6=G#~;FinlS2O#3;j*2#B`Dnp z2njoT-B8A|Jv}{hDl0j3BD8ZDoF=DyG5O43562bfmm#G+vXu$tIu9g+4E*8svbr%} zv^#%5&BINK8PN&b@yD-l#shdU6n)8RPAGdLXK`E3Rt!72GMF-PCPnfRFC{Y?^a3M> zaAz)6^UpMbd9rOdcZ`qsx|e%p3)_1hhO}|?oadi32`elW9=J}|A5gA&dCp|MJzm}3 zD-!5*wLJ+nYA)AEH>+OfJLm}Ok(2CkWuH;);j(kOU@RUy&`_TSg9qRt};j2wYa~Ga@$}FW*$;Wa%B)|&= z7sZsX<}2xblI(uuo(qeV%tXXSz>0^Ry+E+u9``0qDnzOZ&vFHs`L@8WuFJ~b!cX;8@^(sehl~nUiz`fN8PhJfFONf%O$TTPTa`je z&Ps6Nht5v3FLRnsNxOTcL%=%c?`BF{)UBYUJo(8Flf=SW&<`w(l zO+JYsJFHjj{_gJnBLaeUj*abY+lHnR_VQlgtohK43}zP*By1I8D}!kq72>Leu>pDv zf5FoaNM1x)<5{c%xizgZO)|CiKzU>mLgca38Ecd;ku=S8~0F?zg%gvO$QX z?+ngfjR98$Pn%~u*DsJ@2lv)0@ml;kD2F#=4?tnNzX;Rf4dVfm!cJfhSg9KA^){?c zy$3%06@~jdeBg{2VTC$EP}pW8m3ap?1}R@HMoZU<@E*2@s2B79IeOyu;dFH5BtrgR zyS2TPY{7scfSMZ^jofVquJkR5lfuLSw=4bzeqxIL<|k|m2OR$WL_bt#p+8}8^Aje# z?y7>nRTGqJ1&J~Fddzwi)^=aQXjTsJLCRgylUprQ9VmQ4+UWHe>?22v>sRTGx;Ywt z5Q?zoh)$1ectZCy#?%Y}Zct_i-1rN>>cZkNl`^!ycl&5BnL>39_i+D)QniuvZsYOXPqhK2RAVSL=* z^?uWky8$Lvu-n9ifVD#69?c)5wUz7W5EA)Mz0xQ{#t+rue7<0RQT~9nW|uamt?6N< zb2!T)omLef*{{9ufR9|*uj^3FG2-Nqo+G<$c}f+X@(&(4<49W9V_qB};;|Aw4kvd5 z+oE3U@6?cg8Qx!MfS%syZ$OX~&KJN2yD8m1*z)lmRUu9q*J_@PZaq$x>x2Ww_<(n9 z}kQ@jB|CV+hG>yPOx%r+b_v6#{^F z6L|w#3QmnaC8U>x_}^MEVA@7Aj;Ns5tvW`)qqy^-dbXzshGHZ0GH@+NAK_=U5Bv4S zCV|+z7E`-bWF|{0v;r37pP0rnV&<3Sts2{l|Q=ZA>ipF*$WdHwqBW4#mVs!-yzBV<-1VZ&Puyd*H(}Ph{+a+W3 zIJQhP+I?N;7f+0JkB@SF@p8gxtTM5~?`@neA zt?9A8lz5c^m|h&JwC(cz{4!wRwpwSSYF38MFq-K1DMI>^y}s!$loaSMl+u39amOx7=1iw&93D)q6(e zKdWYcN4*_r@^XpPV|SRM6Ho%Zt5DzW#j{zl-sCymYDjS2Q3o6&DD8S{xC1^l|AHcx zfyj0G$;hzvj>UGeK1L5=rb7RPAq&;p$ah6D4Q^HXdz+VL~ z`70gS-S1N^s6WW%ISUy#4lB1FYVz<`{^VO{@KBMJX2jcE#hmun#6I-B1l9-Fnxq#!miHZ4qB&X;c@UbcD*=X5G-BPZ3nwcVwZC{(J-c`x5vAzX*G{(hapfAI!|I^x<>zl*u?)Z!sE+@#lP z5s+j-3iY6e)Vms2AKeZgIp`w)|JZvEpr*I>T{JWaD1w551qh;IrHF`9BOoY>bWu7Y zf*?f{kP-+*L8U1wDgpu`T||1Xg3{5@d+(hjKqzkcJaIabLO5qb7q*W<7`$| z)_TkHK5toOzl<+oBu5*$(~b6kOD3bSSmub^o9@1iq*7M+KzgC>P=EpM9=_Nf>_>#_ znx%@F;7NP1+m{|%qNlvsGR3SFvBvV4e2z@^3gEQzNTOVyw@ru?2M~cv?K|=bFAAh~ zAu!Vyliwn2){Ooh$$klH0I(*McN&|c*urYw)Yf3-lsIKD`JX!<14LJwLtZq8dU72z z0=4m8TT}?+;bMwiThv*Cii&FC@$&NWgz)u-Pq<9Djo44MQoqRJ(WfK`WO4Bz-uf2* z`6JJ@S??hYWynUFyIopvL;pn99qkc}$F0rFQ#0A}l==aE+lj1HHwEuX_<3Hn$I%^1 zYqFH@*uUI%)V%lX4z`mdSujfxYvx;*ZrGZ+3ivy7^%CG2DZfWoT|P4&vIr6?Gx6Pi zU@kd{f(HH|lY9*Q?eeo5(krXH1_-$RbPSvJ$%GtA;%^AiarMZv^Kj-cGmjF7ryL;R znADqvjvHT_3C=}E0J;h%&q7B>x_-Y<5bU)-nKrbmi-c~=;eN@4Lvx}Fr=4W3YVUw92dU_4WHtt%Ab zRP1vQ_M zy>nfGq3G9`_k?Z}?x0h2GM!X?uf@WPQ~`YdeX+X%uhAY%*z+FygTcdpJMLha<(Oqj zA4VS`eR7kLIL!xcvt#LDse)26F0{ePkTNgA6L#RkCu4PBF2bhP%_%C^GXD-_h8&&QD2;5$uJcUFiN&K=$xu!tQ}L|ry2){^{*$*Ud|bYDoo9K2unFqj zWGlhjG#rQGQ|Xvwn$(Vr-Xf#e0D^}h7{N?lbLj4xghZ`FIY>h07q)HN*0&Fb$7gry z-w0|M1YyfX)jg80wEx$nNol)PVkmj%3M`1-h8; z?@hxk;jU_4v9mw~6^aBY6}Eck$Wfn{`Q?$rMM3rw6q?&9)^?2iaCyy3FsW|cF2(*A zQ0XXScsaI;rSlauGmY<4DIOxLM+8rK$c914(jo7T`_wPtyLb1BO;+tH7J}JWV z$PG1(Bx%&%@6>k^f)eEtyok=Sbz)5!o z&w}un^0=9Nkn9r=TAhP$2-4@Z*ph!6DSZ8F*Yzb$$F)LIP%!@>vv8D!#tXd0QIaWC zza!heNqI6z=(T$!;jYf#Apc5uyi}KTcGaZ72({`bFaM*Z0ZmH;CeY#I^MI8D9`oBypq}*gPXEQ0UpH?Q$`29RVp%r8=)3`amXbdZrtszE^q zp2AJ`FNQ^q8`8;SB1aa_+<(L^2IeLd>{3>$Beh>KjlN{h=Sw#U=AW${!v}V~j92_}6{ZZKR6}%&w2${0Sm!pI{SB_ha)Xtb^Y}X!!k3`tcuvlzEhkC*Tt_-N#F9 z)whyDU-fKd$#p4!QTjmg6m$h9aQgJ=Hh4n)Okv^q`qtLgk=C*$!qh!G0SRXb;WjJj zyzT}q%W-GvzyzbE3{h>T+q!}p41BuZ6{6eK;x8aF(C=vqa}jRIDhAKU^7d?h4#WDn zz+BTHTN1=slR;eE+qMQT zR7D^1>T)eGA@gVvcgpiNE-*_Ax*T`fNSgGU~&+f+OL5uqNU?HyY;|lQO9KR>EIevo^B`!5(^31tKQ)~lFC(Y1#%-x zJ^$pf%gGO$O zq{#M?W~9b#Dkd23yz8ssm(9QubZF9}deHgj*;MEGSJ zm_W|Muy?umGg%ptgE6d+-c~ZI`zdnYoTwOl*!0Pfy1d%0Ys_d)< zj7UbDC2%?GeUXbKhPHf*8w4TDwT>+MKQ01>cq0J)&j@5{zg~eY@gOH@aT(+=F5G3q zRxyDnjKr3Ed;CMFFda@al5;emeP3WagN@`PCG5-n7I`8Ac}~8b=2?pReK(liFV~_B z5wCWa5fckB^Va*GS%T@^M3^5k+;+LCh|{^eneg~|mg?s8 z)R(O3u&6fn(iVZER-Lb0ezc}`__q4~z||c|F9>SXyx}uCe9w}_z?{fI?sE0j*|1*- z{awI-S)QUOvBEJWDFq&Tk(ELH^vG%Fu3t^E4PEQTrufjCpK!#-sJ@DVfEqq4yS-*R zkouMI_^(SPw|FU7Nx7D{g;LTRmOuBhPG56iXRmf;k zc+53f+PR+Kvkd4`*r49SOC6Ku>>Fah1>K5jULLlSfaV1VkT=QHk&Kt!FIQy!o~N!$Vrh!qf@kxwOMWe~E6AkEfZ zoS5SV)F0ZtrZPEyZx+tO!pbOX;0PuS#5Ydv14!5oyZv8;*MJ$wk{$Q&v2(5lnYQ%* zVmsjF&xy18mK&F02$r8*qGs})-c7oUA6NWEL^wEwuLc7)BKT=dAd!WE*o^`}5|`|P z&K*3XS;9^`wt3I%=(k$u`xq)E*bEf0O4 zpVuh8VjbQ)Y#5R(1@cCe$vO`3kFRf8nU6c8L?q<|1LP^%xII$g2&Grh$$m_rW>UwV z#cm{49t0tuc7mrM_eb!PD6jzVcXmI&eYBiT@-%Jxm_XtrLkb+BHs$gy*v<;u-NZ3b##}NB@zY&*IWPryT($hED0PiKZK5s zEsJ4Yhn62&!M|54!n)ZWk)wetuO=M80Af1h)Ph?Ho#lmI((5GI^d{kklwrcZLk9POtK3C&svu!8+w9=iTwvvan$S>Ti=Q7a8Kyr^iQnmJ_&G<@hf_Gd zI_Okp!r~fBCE)|O?8M%;0ef!yu`#11%8Jhbu?i-NRjR}GT{2C znf4m7gsKv(Pd#NB+v_R-7^6Y)f$5UA#y6Z7Pj6&%5H}#hP(FR|-WI?^biO(C;htzW zb0;`li>n};1JQy8)h)Fl;}TK0{*UdrDQ9XXlM`!;Y4mdFgGZ^?9Yj5pM$zyEzg;&N zb~pt}QmvhIm%CyN+L4OLU&9T+`@?F7{CoU%+&LboANyx%d_kxV)e*@yZj%u*E}eUw zbpVqi5~x4Z2V6lmH`*#&TU5E~3iqgj8-9<&R~yA6Ls>(;6ey>RK^R z)@}z;>qGei1!)~)F-hwZfY#x~Nf}QP`F-zjTbp>&Ept=7t4_Xe&A|(_LarEzNdZ5e zfjrGo_KrvEi=mMOD4OZt=Ji5?VKX|~RBhhRH)ubDfbBE6 zLSlGL0l|;@4}yPV1qzv6?N>ITX`K`PZFbHeyI=xn76_n`7VrzLDJVeZ8MwIQ6Bu5n z#-Mm18tc`x;qX+}fobThqWA60k%75hgwBc!?S+{g+p>P-AGD~!WdhT};ShHCAdm-5 zC}}wfN+{M-c`soHvWG|b0SU$^26Ru%wa?ib82eEt+@_2mxCr8D`(=|)8$#lEK>ck( z;*k52I#!dP=@7E`<4|KXdq>7|k$Ec7berp|s0n+Uf+R_miW7pm!FEOGPuANEtIuNi zR-FPI8gW{&xg9H?UXOGsz?;B+1*BjGqle4aqy~H$lN*Rk$Yo7{49FkO=FjwbiL*OHpXnGvrKU=x41SFT8+~( z?_rp-`xQF)m^h1t?3M?`shEAjx_UbIirGhMggYXyX&cz5&T5@eZ_V$T7R}TNFh2&evKrPq)Ra zSdX@w4lS%2VdLX)O$L`!3tJM)J2h5A^VtrcE!#97??U#@lRzP z^WrakhO52`oa_}J;Lhaa-mwZfJ^?~7Y9mU1;0HNAqwOEp%)ba1#_1u+_o;_rzEOYp z&v-)`7KRx+Q_PzFb+x5#-a4FT$;b^?$Fmy2f$EWbMAZ?o%3!K}hRbF8RXR(#QFplI z{_ry{?PfF9Z-*qN;v}zWv@hYE?E747+imY-6S$)WxJm+RmL7?$;v09Ju~#_r#Q7Uu zQe!JZm+-E>{#T7ypdv@3<@`*4pt|JhVxfG)Xc^zigMh`hbUw_pRO36pa`HPv`5I}N ze9{6LI#A=w-;s7R_BCkYTQ5hO{PXH8tj%B^3*Te0P<~%;e|qQg1C)^cho5*? zz5%1ovpw!3?akNy7TA1hvg1%hf2M7IdviFXzE)0QVseBLA=SgUeS2(2N5e`BU*Elt znyeBNAsZ>B%>nPzuePW2-Vec~_8ti!=q;8wVq%Xce5hWmS0Bl0orpzpjo?T_zo)pn>Cvne?`*l%;OIq1v{J@J(}msjt{r`or!ys^CB z{eyPU{#kN99ynQ>4qy16l$=}TCS0*?w-jJ+$AWY;?LZr2hhX*4YASSm7Qfo)FX>2_@dMcOJj^ zaGs-P)iP|vag}wZ(YpCVv!3`DTl>)DWUHQ!_j#-MBJcl5XRJv-H$8TF=12c?m+t(t zqDj{2iq1OAvtc}H%Wo-+@MwSe8gb{WmfN!ovy1(bGl63>tmD?V-97fq%-=fWdg6RB zP752$>2$|M$hqxBh1d_eU{1-2v6el1M9}`pr*9SMrb&*z@vCn+X!E1rZTq?5s2P>z z1y8xSR9&pRIw$f=AHI!<44lf;&^8L80ch{D3mGSmUR!4}KzD!!9L zu%fc_yq8jYDR}x^o1g!()d>AwJyGjdoPRH5v8sL+6JpsaAG{QUdT=jg5Fjiox=$vk z8sD|cqM*)_h9C}@iO)g_2aE%rqHCwR1{PVW+O&YUL zTKPEVb%kuwBk}N<)i`VMB+1pcVPm@Vd`I50G`L*KGnwvLy5c%q78;-@KO9G^s65$f zH0s1@MBy?1aNxG_tt%CjF#GS$P-SxJ0zo2_ucn;xMS^fE!X;sq+t})&K7z4X*Vnig z@64ZTUys);IyqmHO-6;g!!qUEx4)jdDsivv+(>!QL^*4|4Mq){ zHg*@hWmxjPOm)@%s`+fU;cQPuKK@3kg{_PCqTF)R^UIsbqkFt`Zg!ta{zV3kl|drtCxcV)a&)ZJWXjN*Y&m{ddV7yYmo6)oQ* zT_#XdNlVT&i{dkFS72O$sxO#6tZ)gx>lpQ#?7Q%_bMX>>-)+pSrD&NQ*_#0XZ&&l*Sks*}mu95t1%T*-;s@w6= zsdIw21IJ3MerhcKl->C=XH~GXDm{SU?pzGd~%AVkK^h@!$00Y|a^aP&1`f)B=AK%J9L{_axe% zuD!--)ObOB@0{&S;2h{#`J8pTL!U$TG&8cv*Z3Cf-fS>FhLHm$naYDgAlu){4*l z#+&s-M|&cgQKx-7Tz)J|$Tp~At%+zocEWmOQ*FhmHPEkb${YkrF8?}CTj}PfkjbX- zSbDYG$5>Ozli#1%&A8mfYud}8tCP)HESJ9|Z8M&e9V9|k#Dn$^x>0c}Ma=m{yQ03e zt)45;AiX?Lvh5;U^4)R&cR~oe!+CRTq2bK2>iMP9srPY>%fR>&uD)M>MTMz%VOR4E z7CYWKFc%>iZ`*u38F*efS|)2|i_BZ9%hJ{K{>fCb9o!MlL0=mkdUSYQQwX-GM^%xG zX%9PJINE9xdii2Ba-1?h5lV#N{sgn{%{-0m&WTw^wyGa&dR`V8#U8f%R^Ydm(O;LV zRTg^jl5q_;A|BFxDmtH^(v_{Z+;MyVHOKzrGwD?;<9e8+&ed1M_`|+=`Q+TPLZ6DJ z^`}x3F=DhRu(ueE+7v+*-K7P6gB#}>TQZd>i3ttqL^9 zGe+lE)=plPR58!M#hFsbELR0GzFCM^8ff6Qbc>%1;QwloIaZtY>oxUhE=sr-`(hQJ^J*`cKAnr@E*pGi zy}P8bdcg;q$Kya?t055ub>m}H>u}-2vo)dsMXE*lD*<$a9gqRy!}oZxLg>=`)T_y5 zd7X#N*aiPMMnuTUy~VIn)7nSHJz@D<9P}tbc`};X$3sU-e}aK~EdPQfCB zO@4!|qk@PLDlY;jN@ZMZ*=)ffGbSzNGDqjR)l*o2F|QMjJ7$&ycyP!W2kJr1Le=R> z3hxGwi>*ZPbQt~8dv6`xGL~vKIQim-O{Qcq=-HDRPyH2yx#XFbZ{@PBQ{oN_HHKEW zb7?MbWOe+@+zPYx+c(tqf9K{PH|aYEEI{!7rCb!1zL#UKIFdJj#yd7xYU0Z%nMOO zkD~YlopT_ZGGo9?cm*&hhs)v~tgrjk>hwTCwvOjIQg_}OmBwYJcSKCscO^Nso@+L; zPkT80RV)oAb!Ktpy=084^CH2?5w#GN-{z3M_+3Dg}j-fG08AW zc#I;i9kzUz-lxx5{gd{!DlW3J$BBZELYd(>F?(2bx%lcGJ6B|}EsgP?CmiSLuu=Oa zEAb~$gxN&S_!3<=_jH$=KO&P34n6OEIsIXhooo4^@tgRha(R1{56*G9a{?gpBK)km zbEtX#d0W}1Vk1=W8@A`ZJ1qMLMR49Lw|7uxSQ?r&ty1Tye{Gl#k$Ds{V_XF*Yu=24 zx-aR5YMUA5DmGWL(v{Dl?&&n%CgxGQuUsbCgMKvEp?+zMl8zhd78L3CVjKwi{uXX6 z>n3(9eDUd|%V*o>gRRqT3x1xM5KkA;PTfh3-F@4nYV69TPj zdXplli|@t*BCh5lb)5Q7j=WJEi-9PKRENGt+Oi`R#=0M!v4dLl{a?Q**iQN|e@#X< z=%V^tvRrNn$G_G;Z?UAk+JwqJiRmVczqUV-D=ZVS-0`qX5`(F6;d9B(Ge|vgdh`Qc za;}InwVyWc4WGOTm+@n7B1=Kd6mf2UlkhsVRlvqVh8r)*gt!?u%7fa0krTt@Iik3Zx#miu+9MTKW^{P%ysM(& zz8fGT2l`4$h3XzE$*tS;+R9fz!q&RSrSERDH@Yoj0dwOrZ)YDNuNc>^K8_u>jHBg1 z$q&Pd@VSQO+0Q9FED4{p7FID7f;H8}pUtXQZgM=G&r5$gu0qHu6AO7mhz*~NW?uJ4 zFbYm`#&k|I8hP+93IOO3k6HPYlJOR^+u#eM&0~IL?w9~}TW=*qCLh&~Rmx`ZQ`fa3 z(eJVMD)+Dor9MmC4#ho(`OMM2nc|8sx`OCeP)OU(Oo83kd zUK+N8XUqy;@**`x;o>4ljg&B(*!GBbJpb0Nn!Me?@tl07MkxpZv;kiA$e=ZE)-nyn zzbOMvJbRc}HuyO2a}<*#F{2KL7FgMpI`k~*Jc6m zq&k$K^Y!igs7Y~ZIp|}A(xv3wjR4OQ%2#q=nh7~S2M39vwS3EiEEt7HE-f8mj92zf z^b?1eH*~k$)0NPQuEp1DiG2oHpd!F9=&KtkVrXxPsw_yANVR<=!Z9ulrBnB=#)F9)r1n- z@7`Ur58((W2M>@hS}Tea(LsX_Pd0N5V)PlrgbzNXX~9TDQhnTX^=LnVupps zT9&z~1O}b7*VDZdR?~0otF$8@v&J?%DnjL=XD}WrbCm)~|G~#ydovasVoj@tw z^@35p0(DgOe)eJuc@AG)99x%GgXKjY%WovgncxTgZ^G&XEr>)8^5orwy_8-JZ7mM0 zpu$ArkC}n*MS)`=6C+0r6%v3>IgfnBpqm`vJ1hEq*+>;4qqc&~jidi%{$?F-72d*I zYf_~?4~Pc$b9}Xd(O(m>#~JGNK+j&n;La=5OwOT{X&_ZxOFSoEkj(tgdqqB_D9M;+ zu7AJhmsU7~P~rtx_YY2bZQC;Hn0@S=o64*B^u8PXm*?&;h}hPnN#Rnc8}TKN zr00u3&qr-P7m@V5s->)c<*Q9h>T%38(WZHQt$~e#x*E``1dcBPXvn{qDSucI;`1(k zS8d`aP%~Wv=Pi0UL9LWnplptu0u5y0bVPwu`)z@XE&?Z{U%mAK(okPecz84bE2;K zrRX}HrE(Jl>(W8%a*@`x`(LpxR7>-J;<9MJ^D0LQq6!R7c##C1|Mvy`D22z`FLxQP z8L%*LtZ;<)d0AjQ{#C}C1VPdGUA=mT;jXR^Bje-dxo!e0SDo6}z1Q(hO{LkuF`*o# zst(7mj=e8Z!w$a;S9CKPe4_Iz(9ax=I z`zfHdxfmPa%0lH8a*r7kzZ^wlYG76mILd*BhHOB`&vtL3<3Ziqy`eK6xQXW#n$#kC zU80Q)13p8gu$Qp#*)qP9>yC-(2xL9>-6KMSy^A}Kpirn%iFZdqgBh^eTeOQi#dngM zk;D;SlO?~($upxBCGrc}??B~90^DE9XP5&P@pAfxOO6*|0=FOYGe8~IB>QUx|Hy{) z-bXAPZ@-7}(RlPu_%Pb!uJ(0idhKIIpytl(#KyT3;mCA12NNi}pr2yceY@RO&s~Vc z&&V~KcTu4u#|Q7+c-`W#xf$Y1=k8IV>&)qIF7toG-rii8rU&`iG7w|2M!W=?Og8he zQ_#2v{IAYV%dy0G(CGLPXi5;QsGUH~bSIdid*Wn+ue$3qocIyTmX5*TbJv0{f@dnx zuy87oC(;|y_0SQ>o!ISw+zHUdgx}G{1HnKUWe;lk2vJaaB{f6@H-lLq)nmfGd)E#L ziO_+T?F8PGxa@DJ_?jxJzXBCb>R&Of-r!F`&AS^VWoTq3Fz4-Fu%d~ZFCV|V`10vn zgwVsSB*y{}kU?!9O-or`L*i-7pg4fWSWqhoP!ax;eq1alzKVSj*>M~E`L6D!*1D&m zpe~2Zx-9W@Ph(`l)j(xXp%zK^AZzP>n6H*J9719e^7*VpRGF_xsil4h2~&P`0(*uV z39Iwo-MYcFA(SAPD0~3@C7TU29_@_0dH5nj2g!4tComz|PTRZUk{}#`wTBvXa_1p} zeIB*5DfSpR(b!ut>3hJRw^N!nmnvI>6V2X1bcqZ?&yVa9Ja0O+`%j?PjkppUmAhLs zB{h?BX7ddyu+SEMmW>K7L~S@YMRs4}DegqB!DUK`Etg0Zn5hMt-BHALi$!J^0_{~^ zRlOIKIi5y`v8~@O%yl<(YG9ZLkjc|?)cSgQp-FzX>802eO|f3UmrZcck+KJ%cqsE+ z?HDL{R*JvyK?-b!{$5Or7Le$mrwp5*+|*8>vVv<;hy_ykea$0mwuQz>Ft|j3|A^Ny zPzL%cC|HOIfi{6j3#89P7D1!}d zmm|7*X+;0twD9<*1*JBb`P|5XNADPU&{M=xKT!4UcC{VhO zwOjXt+C;K26ZiYIS4L6HNpa5 zshdZV{Z0XKI>ptn*$TXeZ0R$yEsYL*`WAU+&6cji?DHhxr}pJ|-+`h5)7)_W0(e zd(&hAXH-lyB!3qjLE#LWyZ3LJ$j2eDzHRUg{ZAnl9_E4MT~=@$Kjz_8V8D}c_fqpg z-{v^IjTq5-hK~srESdrv8H~-ck_QBd>-Mui1K1bd3v2cTSYhyO5Br<3G_ZnS zFDeJV%sA#1i8qL5fa`icg;A75e@Xte1Be(m^7@ARMt)ELtBWFtGO-aUJvs&|@wW}+ zAZbq%2OBLOhx{QmQyW#$q-w!6b#M`CF_+u(z;byicRe=@7CICXizmPDVu>-&|Mju| z`+V%*^DFD=`O2KzpH+&t){o`#WDE1&2vXF=-U?XY8u@_}a+G&0Q?<@m7M8c_T zRtzrqi=^?CC-La-)hpjQ1gg`l@YH76H-N_ra+C~CV@7`b#kwC1XIe9VV5ph^noZa_ z{GFho!{vgo0UtVq*aDc3IPnH)ySe&W9O{q&ozm)n%~3mfWmy7rnY4|OXElys zzRBn}BF3By0Jew@T+*S-xm6IzjQEzecHO`xy@c2}A0m_kgs2Q4L{&BjQEH|w3c4Rq zV`j_p(x?uohMN!Mp&&kvIxSWn2+^?{Bj&Q;&pjv8HX2)yAGCq#wU8m1UWJ45->&%Y z%^@+y8xR;lT&c9+10oh+Lp2NA1}Jc%*!FGH=2AdJZs5sH#Q0z@WbnKJRy}YK>&w4o z4~;VL1L4-m$yL#?tE(I@Ico~5K)CTSg^G+PRshS9zgo|J(jZ;=-0B^7~(1e{e}bn|m*@X@Zt$2NUM zO#u17MEu#oM;8%mY7{{~KvF(n$RSAd(}EIiH_42&Vua)22LDSTH3T7kD_&EssjaR3 zf>IxiB#k6Z$I_S1i%#FO8vDka%9qfQerV8j7t@~rAgWF~P z;iQ7uA)txgA>xj@Ah=4xxM^$>b`R*#kCT!udq-4`sR#sm39P}zanSDapOjq&wd(ui zlS{Juv}_`P5((oR9Lx`+FD5FUJ+OY8&@>jv-H^KQ4>c*xg1)$rrLY1cyT90_;J;&b zU@vwN6DWTZ`Vo0Vz=HiIoCUET?hk%zigj$_Ihw?R0oEAwwfq-?H3DKa|1}<9gEwXZ zQ`NvqhWbiO{}WgXQ3(GXs@-{I%e@4Us7eH}Y?q-?%&s0)KoEni0GgbgjYamiqW?{d z)aG!8XjV!Q5x;3b!+2ols34LW95BSlk3+5UiIbC)=@x^>wh4s*^WGAV(jMp23WP>? z`VnpG5yG}Fd72=yF-66+D*{7N^CV%z6f#YL{eQVkq7-VG(ZEK7Z%fw)sQ*Xc*T2jF zS-D{vdCD0go=mCSSR+7dslc(Zu?HRiY{Qv$Z{`D3!Rp)pO#O$^L;Mg(3Q_nFhk35Z zgHS-6f*OyWBCvc>e+9HteMBWa5kQSZmd#U{x}Xr=ZY_lW4SI+;HR8AI&0q6F|JMWm zvj^7xdn-+x6$*9E2`0Xc!%RsDIZpnV@VVSD5TGI6e9PI(;VY^NqAEyj{Qm`6fgue{ z8DSJ$G^6wy>XR2ZKO(z%O3#6-P2Cf84YtXU^~5`inDd}7M)959xnVt%xPTl@$^mr+ zDJf|^%E07)}X>9SF|ry11b=rH?_f zQf6%wBZVi#ZIfva%4VNU&F*+|O8Xk%-IY`MvEW%eas_;f?eX8|zKCsm zP@4QPQ-*oN{?u>Is`2WXlwM^P*{VMr?anQ*jXEBcLA(U#apcm-1|yHue*#ntbr2}@ z?{e5{WOKbQ+5;})!L~=Hi4x8(_C5KoGOVeK-NX_Yf$!jF5X`~KTjZDunGQj0P;g76l>Wpj|FhNLq9%@2@gt8_0ha>5FtQFrnUMN1 zz>KOqThyRt!tV8L1T?r2E9j{I@aOjClD79ZaW8+o^Dw49w(CnUO`VA+n1FHd@ysz0 zLB=XYUfqy)DBS#lmmqF-gX^GKr>j6#m_!hNKN780fIk7Rc8*Tj7wz=V2L1P({4Pof z0L9JVL-!K4S(6(R5Acv8W?Oz5{+DtMEJUvMA*m$^^o(!+vtz69ub8`oE)2^ zwB?sV!){W;pRHX9hS6efPgICHInq4mdALS;sWBqPnl;%Juo2E(e z)4ylG4Om=diC>Y$+4j``n@#fq<*+nZgc=ksr2g6Vvk`5lklx zjYFK+32qroxM>&KqGD=Z>B1c;JLd-HDf{%qjl-lA%72dzyqIlY`Up1I zMHPG}4k{5XJ5LXKp)ZE9o&_Tq$YSc6bPzJ23Tx#&|C%R1kcC`^2DC5*hq5;Sw2U#B z+PTR$@-v3ZY%Cd*<@tRHnwDkeW%{EgDsRa{Reo4}5G?l_TE%V2 zFs?lkkmR_2Iuz9JDUWVpB98>_uy0dO1VpG}_urE0&l~nIouZiUo89^VKLwH?hT8hC z`gsoTK+_9Sl(2E$Jq=RXLoSX#q%vQHO)RKvJO*2wE`H zrPXT!0!BAL_OXgolQl$Fke4T6$XUfgWQKN7t+=(XXOj0Y-3g(1UalvTEyoBA)?g>r z-IVS5+q$XWHWt5K#Iq^E^cPidpw8^oR$`6Em4B(60AN|$?Z*ZfcZxreD&=Gf$Z3G; z550+}a+d;WeQjb9mEqqPQT@{u2qMl>K^aCc7NMV1V8;9}2Q^2H0IdCbQXmWk4e-E% z!MhFP+K<5CexOuAseWRMyzO)&#vdATQ_Dg6^WBgv&5{o$H?b6%CancK_nu#46Sf1O3Nw{Ml|RJpyHB zh3{F1(4|mrTFnKF(N771xj6y`fxZb0kCP%^%Q@V?toU1r4aT9+QW?sF6WE7o00 zQ2(E{?B*~U%AJBJ`nLuBZ}LOrKpNcTzsmR*V-pjTro33}Px5p{2O7hr;Q0(uw#}1u z!Qkd;5I2zK14}C$xd2RbOT9(wWs@9@vb)*%^GRHwCit5#Mg|GD-trtrft+hW~+BxHJdbrsF@NnQ?a~&oOjwx&O^g}=pY+JPqIOOFZV#r zp;C%GYWY7V@EfPri&w1~y~f7;#vqdL9yGNG&Ic994GtOpRq-UKjG6NeC00%EG>^77 zq=YN3D0O`mJ?y_ZeE|44Xj(XN+6otOwfVnX4Om`B;F7@(H_8-#hoT3Sg^!4(alQXm z8fO}=ehf5|td%K3HL&LcSWP$yn)yGKLh*q8Z+NCy3p)^(++MK4Vq^P#SMxWF z7L;26gcg7c$5@FT;LX3;RUQ~&anT!fT9s7bv2A{poXl*Ux#D9B6%pG#IG^qaaKEL@%0KkyJv-!w^sLZ!Z z(Dm+L)D00G2OCwy)~!(gBRi_4X7;tq@)QbV&CJu6Sd%$_%%-O~Fjl-X?Ul%aDk0_2 zEXE~7wbg09DaW_)nW9C^=U*;3Iqdi>uc}~2e!wCIqoWn;E*wcz@ThNpSx*a1Ju3Sp zNKWXbgbft0C-e|Dn-Y4&GA@oo?Mb*N3pKIkQB{@u9A_5<>yPWEX#-L_Ty)XpwB{bD zZWkK+kji;eLSmC!MkA9+(_X=ALUT9&dA!n#iR+hPb?nRURgN1%Mf5;PKH{V+%RD#L zy)&6yBh3T^W7%}cgKln7!ihuPVu)`rrH@Ze@}X+JGf4RiLK(!Fg}u$K-;5~X6BNq( zcSPhr_@9rC33-0ff6+PEoZ#olp#k2(3T(^-ue`0&2 zQWs|AI+NWs?}K(jxsKf2SV^iLFUIlL>@~q7eCZttk3&{NTV}AMvEsH(D&}9n19B7B zmd872>+5fbu1tpdfjKilA3MG_KVFzN$Jh1Fn#<|B-I-dEtN-SbccI<6#(m`NnC^~5 zEn}P1;%obi@D4+&;^N||i~DrkE=}-p;bX6-m?UW$SEh~>#jGaVapD7kWe^ zF73E~^~e^an%Yv3K*&?XVSY!p^DMh|F(0FM((%cyoS3V^byUt_#&c)I__Kv_#q0B9 zn_GwNvIvT%`DJJ3>V*YuoDLtsnSIVVF&^JjxXs%SFCpOTtQK)8tw~?OG`C7NY=pzf z_JD$hK&#p}@7`vA;ZXJ!-uI|Q?s3`5M~vlY7<}Ug8AJVXdBVsAfs~O^)-1cNvSJlb zcYg1Vhf_};Gj~x@6#3FO1_nP%l0u_l2G-}xJ08s6r^R`5x`gc!bop_A936WyCTYaq zWiB#(c5G=N?D*A)ss=r7Bpma||C@;)X=te5#s%Vr!Wyko$r9O_6yhyKqEYwTLF2 zm_qcbxuMBCbK)Hi#SW`$x^$8C14Xn( zNEr3T&j&TipU(KNk_V{0nv;!d_;%e+nh_EbQd8cs{6Hs3q-vcsgC0rwfDH;_|IE!)>px-tE-1`>go(jC&SC&gIt0F*`Sr zL6wwlk+?o;xJ1Fqd%HI0l*qjc{0crNx*S%NxVc0o(z&tcVuDx3vgN7I&W(*?%{VBm zDk((k4k)~1^4s>%c8SnZy}F0OO-^FWrA58PdA_4nnMb{`=h;blOQ(7Jt0i5}*>e3P zk%3295Y7vzGwiK{wu@cK!zJNAa;dWGVoghEEH7-VIigEX@!y!ddR7u z`a$J|^g13r=QxqYz$nKp4v(7hgBly4hv->Bx`EeO^y88iZ@~Mzkm48pxg5#CKmo*Vn?4R`AV9IZpZ{!P8 zQrksK-v5f5?^7RdeC)(njOZ?9q zbns{ng?mb)2}3g+7L*=Uq0^=^vc`vCQ>BB;j@9`o%?7zxLbp8PPU>*hV(NmQoYfco zf_;IZqLv)y53Kl>JG>FKmMa9lBUx>>b@v<;XtI8Uu-+iy8m`qWRk z!VJ9nCK*DV%_9xsb=BfMduGyu@PJcF?@H(;;o}63 zk&NcMl+>IAvueq?saeYr8>WXlOgWA+waJ5D5IMA)!fhYl>Gr%qo0bwcsV{=gJyXwO z`cxMEi_`;~+j^70+i>eRZ zQplqE^7ZSf2fixXE-i2;aNI_3?;E-T)g~KZ3%$ z{V>vL^;hBpB|o#7-97_CJI`oY+VorqDpPnT6$xiL<3dm4l`gUL!dGPV@c3(UA1aB- zH1@@j-V@iS!s|Sj(sfJvXtyZOUI==3xF08r?h-;gJn8iTR;YRvmCz90WpkuuEajYz zqeWTU;QR#)%M~$tk8f{Pc86`h`ec__-D^4Bk0>~YWX52sgATo}VDG%G`hx~_e4Sza zWbHlEWv|)n6W41FT$ht}VxXX;l##A23s!L=Qf#bKPGQ zWDE-x+aD&}#VqxL{MSn3f(*LTE))AtNU?|ab|Mm_e@ar6pIp2mPZ-SsHO(sAfe zF8}@ffYx6P`w{|1yYjJNti$DwA@bU2IIj~5j}9=QP1V0|wbgf9U1cL*8lnS+%S@kdyymjuP?bcT(o9BBUCB2lW^5W5pa8CEF zGqdtmGZ!4Sn^IE_O30V3eYJ(4*@B(;#d$~gCc_!JIx8j^#oCXn8r?-gd&P=A{U_)_HHKb@a{M~88!-@Llz!%}U5K-#mYzWmAyl<& zt3_Q|Xx8-I#r%4WN;;-?#!mO@u`hlSwod(y2j zO7f>giT3=5ZJU4@4E|Cc*)pk%qotIQVTq)p1r|WzF>D7BnNdk}slG%$0CK`a{=U>+ z>}s?GKhqr}%&qC4?u(>X3H22k^ZcX{_HLMDjBa`HdCdN$^YUz>ORa72;&7v(r9@zZ zu8sY&NO@?{(Kqu{tR3@A;`}(9T>iP}!l1NU(< z&EbeCj6ht|wk?$Ud4FXgFl+n%Pf<@>?mWZZxt`B#V=qXfs)T+c(ks`JJZiK!Z{V03 zem;sLR!`5a^Lmy6=69q2y!*IfltTUKYdJ~Z3n=LlE9#v6%uS|-JU9|1 zq_b@3Rl-%tgqqRAQ$1B>oZ1(nIFh1x(?_S>O-d{EQXYN@THL1C&eHFvfbL4=!3{fS za_AIbCzE?`r(6s;JXdl&VD8*}PR<^R9?wvhh+IXE@QPXZHo-oK0;l5Z4h&_vX>C(y=5e}w%O*(_m z3>rQ)$Q;a`93`8^TpA?Wv{tE9cWT00?0n=n?D`?jE?gv9z9J3PHto(4Z;_`&D3i=g z=F^85-PlX_MVDS1JWZ$nCjDCXGP?%msvJ6HSPeOw&cS|eB|{S&PZX}a=&GOnC3bx+ zmM!f_Z1^-zC+(>X@l7hY*~crxIg>8-=uTY)qP4*uC2zIO2JXk-csLbH<(`m7d3g4m z^6E!gI$XYqs~*aSLw&I=sdP$Y;ktn|+CHuix~c{{^|F~&`~rlP4e*A8TjsiCs5~e9 zS~De=F*HPk?K=4_n^Mjd4#V)x90nqnTo<~B7;r^OS}ebGKIaTgX?n)(&_Cu?0J4Ic z7at_@yfv>447hVsuEs3jsIH*runBpeEz^Bzf8rqkHC3IC^8$S8OW&e31F8#MOFo0& zNC$@Io|T>L(4Uo+l>}#Grd}+QYofBD&&#hfpRNu75%Q8KZPW5Fz-YBJxYOl?#AzA9;QZ`+kCyU>>>=Kv|cSGr0uyu zu{)ppR()ycebZD+tgNBeHm6dwfFBvJjnsVp+iSULG6XWIMJKZWthsEuK51249uz%l zNo`H@P$@V<#P+CbD6sSze|{liiw+}EeLrks!16{h8ZnHPW|f~K>)dzsJI|u~kQj?j zlysr>Bw1FoSwJ=R9r&SKIf31{-?cw)$W>H}YPWtW7(RiTVwR-3p74+fK-|ddiM|8i zV{+Z*-D%?-$i|}dYbenzX;m4-y0r4z4%co{bz&VXcNw>2JKZ|ZSVQdjo~B|OIMce$ z-*%=lm=;oFwe)1l-gxLR!K?a)$M_!2|1-vd_-h`t)HC=MBhs|o4<`n~T4xv&U8%fK zDBQ+ai*Ae>Es6;48&L0U$PNTYVN*q*{t>3Y4%<2K`~b7`B1J_jBYq!_eND{QcfV z&|SPdiN||^B89*Q6K>9j3gs&q_dWVc&oiOJVEA;TZ!moZU_(b==eg#$`|SZzHlLi- z>(XL2t6m(B1WruXaxw7SY)#M220aB@ZHo)XajEt1w=#TgB~t5e9*v5>q5!T&wZa$o zzo#H#?mv$Z{b>e&_R_U#V0Q~)I}0mL7Jh&4vgm%G1mM1E{8YFuuAst`(37ym0F^-y zPs4WxjrutWGJIIC%!U?{bC8NKocakufvKhvKHn;jJRqPE`wE&%Ju}$@U*>0KCO&)i z%($Lx;lavNT;UyW6)vBdL)bT`~?K8FjQ<_5$WUP|Xa1^}?z~8W41Uf1{b_|)=B z7a}l!vCRxKi0$b{LhJ?ebDnxShrDe%EyAT(T&uNHRbwV#6@5@5@xfjxc-&g^TWIegasr*CY7H z8^?h`gbpjd_*jgn8OyzfK1OUq?R%&pFi&>0O9B)3iP6EwU_l6B$Iu{ic_bkI3ybm{ zAMj0~(zGyqNA^cr_RmQOKYQVc>844Wf_<2+U3ePDZ z8AX$_e>KJcmPdvTnMr97O>J-sZ%`F=Ma#SbY4F@79K#m(Eu_;0*Pdt$Ds!q zvD}&S2Eco|A~(Q)@TIppSle|%Zzytnc9$^txl$CgH7GG}L`U8gqaJh>UcA*97Z5tkc$Ur0mILvzo7ib`G6nK8Ntap?t@k8 zOK5>pzU*>(Y{0!#;$B$1fasBiu@S=0aFDm@`KH4v!$VU+iY~Mt{@rw?l z>geiT5RhejoU$LLg~wS#w9p=n^5YW8hz0d}%Umpz<;AB0q{{TCNo zL#)_-;dpHX(Aqjq|9u=&%=%ymVE}*!e#Eiv?(VXCH*VZ$ZzU#q?q~p_Trp9v_U*#; zQAuEuVQ>OxOf>4TKySdtM)K_bPBeb;x6v2{%@Ye5VWR(jH-CU)aD$K$CJqG{kfUZv z)q;M|IgnVu!m<1OP+&V(#sJ7pz5DOC6VS!i!|9sY&e1i2e`BIz)RUt-@bneSP1EJG zLK)iVh>DFfEFI1w!F=`sI-bWqnKgLTB~N{y-(&Ag;aXz5!r;9Jjz?N^S6+p;Xi7tUW|^ol}5hv}kyTyd&q zX5m*WV}`kJv=Nn2kW;)f)GfpeW(XE*Z0==UJjM9(F-jPrj^;145p|mpbU6bkX`xHr zlOl-1#Ka^y%}@^@MaSIj(=%crE$9FQn4`7J(ujQ`q)tkHlsqXvmYA&4qa@IfUv$!6!_nM1h?BW5{@a{PTwka6Wn|n;Y|uL^(t4L)SdscQSf1D=IqI?X zJykfim6#k%(ETUF0bEzualVyi^bF?wFUav{!Ue@n$@^WHU_~Mf6Bp}&KWC7EHU7F> zvDaLXKdxXuX3*q6jXk3g^Iw^|P(zO=JA1S2k0=r8&lJ3a|-*dM<4ryGt;KIfjr6$AX0L>%WB*49NJpjdfp{P{vHeLx)okU8ua;oKZX z8}i`)*B;{2WdRH8LOX5xi$g}m&!B}o`~Cmo*~3r6=jJRu5&%$W+kJN7#hNT|E;cay z_I6}O^}Bm`64VQSC#a_x)e$Q~Q9V@}1x-rNM9r2FEIC2~`|h8QhYwn^406dmDFTG0 z#`e!2pBzu{_$ky&{xalesrJ-(t<-zV0lwM%iV69XVkXqoj!P)0mss9q6cd93kxzW| z?oW44H4a2SK?pYebIf2t0DLNmZr9B-V0Ec&61sodW(E*W{o@Ia4=po9D(^f+3Qfh2 zD0&B^Yp}%j(;pf=yP9LVHdz5XC%g`EyLF55Vwr`&M&$yT=U5&A3sW~wWd3Paz`%&< z0nTZG{Z9~yrXFOf(j=R&B*1<%7OIl`0z*;IpAi9|X9h|+5RaTi8^#23KA3F+s{7h~ z{}FE$*!3Sj*^dMNQ&c}~u&EW_8a`Xnpr9s^u(SVq1-!*5Aphd$hYbGzkvpKD43!9r zsj|w8($XGZy?S--mUy%mp?lJ)v%f&`!~78Xm~A}B^Uu^thtMJYiu6!-fJefYda4ME zD6k1!UrQ9MidvT?J@RDa_k}@fL1A%r9PdvXjSeNI*#{Ow5s#f_FWPtRS;6o}!iW<6 zxX1*4No1(SjL;zzZ9V{PuG0CG34*wy*qO; z^*r)Menqj{ku^j6@pzq662XV6H;3A(+9MtigVA0fiJtOH_Jnb1qWaE09Q4|^3!!S` z3uHk1!fi`8#f!40s%h!$aN1?&*x7u)&0=JB_dW<-g0Nn59Gh?Sj+R>9jFL^y%uO$PhFR1`swv!HAE$h0B(MMvEq|{MrvCnQ8 zs3s~#<2z?fkq;9x;?YD|;k)S2VNLa&{hmn6K&nmV;z|7j zPjsAINSR@Uq$vRd9`IMI#zyVjolWM{`WAeibFJMmQs*`3;S{kONtN!{f3SMt?l+?6 zBu*BGAL)b0^kH&=C+=aUrO(I&~9hFQg};BQ4iE)dEyCrm4W9QX9sFf?ndlw#lzM|0(N(PE)1+t}-&mWe zNFOs0!Np1&42rSQq!`3OJo5i&17%dkIb(O^`nD~g;*}W=xOMe9G$03K|BpQiBqz`N za4hVp2V1|JDj|`?JSdLDpLz^YIP^&GbM=@!111OMSE%x-Ai0u?u~!4W6VB^FX!|h= zrN?^~WfFL9Wk&QJ^k%eR z?cVvxkBJBZ-}UZJC@0(HvGp_+U_asBC~w)R$m*Ej{L6J3F;LRAtA2zDR zgUG2+Y{`hHDzEh?@&++bFGWy;1W0b-G!#G@&WJqr_rMc5`32|tNeWb4-t?o%bY0k3 z%N1#T>1A4R%I6WVCT0^K?S6ulU5|{`D+@9~pC!FNu^zqSD`<^#vVi~nRx9)^kH{#+ zw9hOV%o7);Ad;!Y_`a!};e+FaL8lw@ z_m4!H@bT$F_crL9`&q0#O81jrA{GFmw#}Nfa_M-0@HigG)j}lwTsZ5TX%J)?(IDjvqc18rTYM zAo9=Ci`?!SlMvGzO5L5P!w1fRp|@n-QQRAL1&00vpRgPiMAg7wD(}b|Jbg1j69VxI z(WkT7fX%pvHMk_fb}DAWooO5H0Y3k(+%Cm>^g8QC%I?HBPOyAxmZgsUrz+>UOYo7- zr-WS!_=P6!r7{CxE?8&)uY9zTD?5b`kat}FGXngg0syml#wHq&4|-;HMn2^~cnyCZ zln9Op$Pp1HAV=H^m(dAFVmuR=4uQ{6=>HZQx$aQ-aB+sZh?7Tah2!#q8^rB!{R-6L zKqB2X`yH?)zhILSgTX*eiU`U)y6Z7jfz4upqy$&7kUsnykqi#&v8QD{3(UypVQ{$( zDf$ytU#=Kox2^r>+-Zr&2*J2Vy^0DGKGWwLN)CTYL_g=7t5@{wQ0$c8v;C*IeCxAE zbIwUFM<17*ug_shs&7gayVt70rys6j-X)_0zz&CVsY7oO%0LwQg3mXeLh~=oQS7Ax zE~10*@0Sz_3PQ)sd{T5NL?46u)L?YTBy=7l(%NH!qH2EwO|#?`7_nXL@bY+TOxHH^)F} z9V-0WD(|izW7I+p5-?{Yv&fRoC|@iz|G^vH>J}gs38ORUT9k)Q;S)Ir#u+q!tM?JI z{NR-(6#w2UUQzTmKVf9V6|zLZKmWnVh?44K z?_cky(|>%NF)z|TETluSs=LmC>rgeEw#+wh>?kj1`N~t%QzABiU47k|>74djDh7hq*GV1930GV?=~rcf_(3mF^Q$$6Y0hbb zC)d{->&-w^M*4LkIJ@P28l($;aQn%%RjdfBA%}7hvIx-uul#fvN}hgf+RyC^hbYl_ zq>yffmJo+EMJcc`cA8uApc?RtCgK(O`$g;_KGk5lY~ zxI&`ILnwT{`Jdjt^jc7FiRT8-hDvuFx01!u!#~3o3aG)mVbAfI(VW0b%tI1lQ)!p# zZ4f~(gRuM$A}F{Co^&ARM|OgRfvP?ABoK=Px)%@u{c)YYigu{rBGj$`4}16jqHJe={Dz=@Clt`kS1pHs*^#GsO_t5WfU4iKcFztmm}K-uGx;1=Jy z#EG?F!SJ!y6KN{ezru|_pfVNO$R%n_pfuhqtgLUc@ zoBDzR&liJ&{{==S?4CW(>oe6FPgFi}C6nsx_3k^i530JSOV?V1oT%SC2l3(&^U760 zG$dsUCdG2NfbE_%Br)3p@4WaMo(F4hcrfESC)seNRlM&jO)qC8vydN-NkEl#T(f*a zh_i>kBh^UtOA+C~@J2Gq)WILCA{t)9ko|921=icV#uxEm_#N-A8F;x*RVXO@hJgWo zna4){L%DqjRpEJVkjtay=TS*xT5$3Ipkc#A>j7s7VDF#l2FNF_$ta2g*!zNUQlkOf zTh-s#10?KCGde@_miz_T!@8n7&&~_pxq~z`Oe%0~k-yAD05kxeal!k~o)H2j_x~aI ztwV>pIGeS}({Tj)lG!N#cq?QX)x2XD>4e)a1SDGkCMt~QcGt4R2^Ivb)D?}U^dKmC zp!-b>6a;uz@NP>eIXO9n@K5bX1T;Q1fweVrnH||eQVZD589bd3`9L?|D>QJZ6bj;% zkbRQ?sRGO&$99@n5n|yq-z0Sqo;Z#Yz)?nVZB{@gFMj1W7jvA+Ky^t$0l5>CAA!n} z&A+mM(?}d%3hJjXNq`Tr2$QZ%#s3lXbj7_HuH2dP(uJ^!!au_*fHigO^tmS*9?Q+> zmC}7mq<+G74RX;hk;TiG$K@hQw2#j5=ri^Y%SlwPQa}!K{2AzRI0+1OqrM#v)2bq& zTbA=12L0!3=l>Cp`Tt7{I>{d)ocn(bVgk(~oNk#^vVk{*$VXu~=D z2G@hIJ-ADJgR*m|vZ$qPR*p{R&6_Fp!Z=ockP$Qm7vy;@>}<~-z9lk`4em`0#~VzZ;UC=@vn$(QqIcM}3mwik-_1Fr@BW3B zYq6Ya_VId1;#45F!q;G~ZwXO$99KIaLgW$*$sPQ{9*#kj7LrH24k9r?kZx(UbAh0$ zVVLqDf#C!aki+oRE~pQ@MJ*PU-xd)Gv8h}s(NDLx;7&0d0MVjQCPws;h)Q0xyd+g- zk8fRnV_`Feh(!&%>uhuA%r_g%&US0Zwk?*>iYJFV>$a}nvpCJek8TE!#9j-=<==DF zOWIA^Xvsh=ZN23>eP^Q?uRybLXsK|uEZf4(`mP<7{HipG3lqeOT;~{is%bFjpw`>8 zJuBj06(tf*hg%(`$6DlUkj=Pj*YPdkvFHa%k7(A)^#_*4gIZkQztSq0YA&4mqenhx zs-4Ewwx!=Ry?J)PPLL$qH^J@T)STz%;X?3z=Gj-+AuM_#foP)p`%H`W3t|ICi#KN8 zTDe`GlOTJb(p4*b?xQn!dchsY4m{%>TXQbZ=kI?D~vLEHb;3+y;ssH)Kd9#W5*M>&&nw) zsKX$!pn3Q2D9pdh^_CiUo{oKJDfJmxs^B-UQ(mPP8Mab>p8_J|sy?(2ksa-N3}VDR zcSi=%VRm1)MY1biLWKNIi=TR{c9Vlz&XQn*X6M}x39miri_??0hO2Z|r9u^;molLd ziy%uuP>aR29pSp}=BmuB&bz7C$1p_>eEhU#@lklqKLK|RqVL*4t2dM%|Hl$8FyX<; zepQo~%3~FWWgv&q`b|$$SK?xbX5v<@_0o_F7owDRCO5Q7^=@_Q$cQwzuDEe~m>(2H}rAl?$?)p^cEAl+) za+@Nb1Wy`mhLvww<0_pjcvWKBa1XA<;x8mVwC`>qzPd!d+S>}S`#!(ynFIs`n$xmv zjgg~={v(Go3InF$4(8J=SIzs5ZYA=L9OV)F$?F@lh07aG{RDgdy^${SAJ}GJI*BBvzS0Q_u`OPy>f3Z% zXpbDx(x5&N!z>z+Pj>m%{h4bvJikjW&(u}v@rSJeo=8!(UJS3O^^o+WiRA^uqn*GW zL%S;mjpLwQ9&{>`CiQkktP-2Bjk=)bR_F(|t#aY$HVpUVaqVNB!50=RN}Q(ml!F?> z3%Z@!NXT=fF&D}(a?26VXq+G|U9j^*q00Qj)5;p7(NN2e4|w^?AO`C5 zo!pKoY!}F$TALlAZg5b%<7)mVHVy+6MszMda9F?AH9*bVe_xrN zh;88u>XxY?){uL1XfjTPtCLuzQ2nq!f|!=24kx!gCMmVXg)a&V4)y zeldb>&87m+RDjw8k{%3vwcCnibS&Xqip!Rb{^qiGI6T-Hw&xG;7*3uy`iA?GS7t+l z!l8LxgmyL<+;@v&TQ30v=j@hn1r^aC-2OX*LZkshoBp?!$5SNri1N3MjLDck&I_-U z;W_AnF^qnT59&V|IUqKfvL-h7;GRW!q^pN!hV!NEW?%QOY?OY`3}D^sun(g{^o3UM zD`Rd~yt;Q+8#MI~r=}G%E|_y!Z^Rc@O;Gx38+~e`>fOQh`o=xyHF$n;b8vez9f;nT z^D(Ao@3mmjro37cHeK6YsVq*$Y^X}m=B*xIZY;-)lTu)77g{T0r?r$g$MC^1_JC?Q zt)~%-b>s{7`Q?GT_}1?a$u`=~rT2@x%3{eb>Fvqj+UG#~=xUWCC3>_Ku#|z90I%=H zWD|Y4Q_`;^#wV>LY_`WbuDlb=#>c~Z227TVvOC@DJr;oOTp1lEPt7vKS1q!0&vk?h zpIOA7+&+@hK~*PV6p}vJH}_K%!fJa@8`2Ivrnlu|%I6DwcOs&dSBY)(t@j?JY-TbW zS@FY~wzshwnL7xVe=fMbMqoLYIt)Zr)@vbGR_TIE=*P8(1neQg3k)2C@*k)p<2=*O zdBQCZGKe10g``ptQ-STdbA8eN+Jm~P3Q$2|Sl;Q0?AbjoYdm;?DE5YbTS`ZVjksp< zM>=V!a4Sb6D2CT}vgbYzWxC!T<(1J_4CJy%5_BlFixgBZ@w=_j9nE3DvgQ^`Hd~{j zyap-#3g^zSY+3<n~exlo)Y` zzUV6$yj%9+es*wA8YRVc;wwwy53!aJk4t%+P5g8XzX6F;a(Pz<&XWIpXHmT8+CKSa z*E03p=`ae4fh94Ia>_Vtg;y@~f=i_`y=#}Sz2u*=g{F;jn>sTQYkDCj@dgf~JQ01L z6jaizHI-i?gu&-%^8Blb*o4N#^sKrkErtZS?I$Z$=F6IA@-pU=7BjWy!pMZq4L@CU zN`Be({B3dgHvzm%{^QmKiA3+Oof(7RDe3KURDP1Rd&ZQdF+XD6ctfh38i(Ou3h-`W~ecY@-in+ucA`#`ej;v@;9OPiH_w zskbv)foXGx$oqT!>J|C-RY`nA*eWFgxe};OD4_|7{^?TveevkWP_67kP~hfq2m5cHsOO8yI+C0t0TUMS!5;ddu_AO z-Oquzqsa-Uc?NTn4cp@F_rG(9`Y^jdWa35lJ&k85wTH1S*<-K^?_+UqQu@>+UvlPj zsi(Cl5pt>^d3Hv><^5^@+y#g2;BE?QW(FbG=IGKeT>Zw*{7Tt$Lr1pil+Rfj!SJi1 zV?o&t^tZP^Y*R1kaNcaz=A40b^B3O>yt0;t_xYNwN8|9D3~Tr!(bR@W_-Qk7?j243 z;zs6>FUk5k=GH?QVTci?MYpftIlbsb+`hQDo)P@;EWM%KZUb|z$VqfKPotsg82Y3TiTtxr7(Q2!vnlU&yG|* z|8JOQ$}mVCOw9)G*a}QTe{7?rv2ey#CS^7}>u9POu41iRs;_i`C;Y1%&-)@sNF5WG z>pvVmP|Qsm}9p{bi4R)Y9t71-Hp@QuEt3Lk6U?^Gw<< z5^MnGO61jN$rM9{a<6Ibq{CdNr`>raR1GkgtgnSo?UJ*l)mvmQGB1hBo12meoMfO; zI85@zcHPQn&R07!0}f#kqCu#jvkook;}_I=4T!$R$jEqSa*`0RgSMIbWOz$wZz?nk zQw$wyAK-eVr!9?@#$=BoqMYY0E-|~;haxX8|LE#Np0;vzcC^IE(tEqZY*rSq*&v;l>un7ms8GF0!d@P+QSG%U9E zHuCDYohw`Uc*-I+xaV_oCsj_k;rf%;S`=(rD%ouL=uhmh@!VYC(nHB)uO5rac)xi- zd9;+?wVO0psK6bH+wR60ugyLp4i8=O;H=!OB{lI!B?anOKRy{Huf{>aqlwk!WggP#f{G6zbR@Rj001LL3f4r{pv`Re?j{yJ7pj zG;TECX@$`Y4>pl=tKp09>s|N_T{x|yGf2KJIoGlbm>y1G6m6?(#J|yL#IB2BnXlNS zaOzwd*s40nRB(5(jH3vd&4&|Li({b*#d!DP_V1SXlpU-F9wd=}GUAr=DPR0<#3L?4EojJC{yTN*eFj zb&J2em9%Q&R*tpB4w$xah&xsk?n|C8ZY+aOhHBh9fjJw7E>uLW(yUox;5t2FX;Sw^ zAc1#tX@LvPc*D+Cb$OnuD^QvI0kuxP?j4$_JMM9I33kEhcMuEn%NrXiwXGN!PhR62 zQ&KxS*6rChrEW7ji;3s9e$xa6K`({loqDj6YLE|vcK3qJk0^G>USW-SUE^zz;_4#TIQ0R&SC3wMvK z=FPc5!iPfO{;cwR;}{HU$)7xrL}!vQ##1w}0JB&lq%XXM&(+qtNx+uem9mGs>cGas zUq*;O0r!V&*|l3zTHSLE%LVilmaiw7k@|*xe zJ4`+&|MiCWn%1nA$$)$TgWSP_viG}(`83}Xcik-0sLxz`kA;5@ljVG{vCLWYFY5rz zoqtQ+w`wbO)=rtzppV5erY^fn@6scU{^+u1hEp2!gStX|+cA4ZW2Q~3Gp7?w!_w8h z6*2gtp+|BgmpR7=P7K~?*VP8!=*}bvZ=@*1j?eFT#}3IRyL&%NwpJ79eUHHy4U3G+ z=|$XrYaEN~vDpYHB0gI(Aq{OpHw%|2sn`-+By$mmWG;TQ4zKhKB@ia#&p>?X9-2P^ zrE}{fwrF^SVFo7p*^^SFpA=B_qxq5sb9J&sl{^T*RB5i!;55ii(R}g$OT?8&TP#?OAUr2Y>VDd@k=N&5`H>uBOKbJ&+j!yuVHrIR-mw%D6;kr zp&=;sM{ezHHGT{v73p65eb@arIQ?W`w>MO)i^ZI&tbYMx+A;I>{jpNY8A!wsz8+6GUfLT-dE4!1$o4?R;(rt z5i8cVi!kfN=gEA!SGbf_+Wy>h@wo(Q9qb{qeNaVV`K@xF*4^iNY|mu52i}jG9Z82< zrV$2GZwv7#la6Ob=kcf0MyuUj-0pjP8tz%LIPtU(#Og~S^*K^dljIX1?f}hLG?^i; zc@z|%92@?%wXW5j9ZkYCJzUuMO^+Gchi&0&)1IoDurv7LDyGQ4-?a+#-)9OOKvyG4?`^+r>hP)C~}1ey=Bp( zf(50V7AlNf$+6JT_wFW4ajOuJ8@jY&zfZ`Kng6;wZ8*&FeuJ<%b1&6tUb;WP!2J@- zz`b$eOVydU?+wb&cEHDrXx?iCwykN3=uQD11+%~Zmf5%pi1VG>7Oggsv$)^Z z%NO$DGG=Zo<@vyEFYWX8O2-l4Gjr*uPpyuc$KS}X?e*)=9U_SvS?az`O4$0LHSlJv z@`Ap2E8Dv#Zkj}$E9si3bZ^)?mkYR8Wq9 z%L!`bQRTe)%HIrwCCsgObLREjZ5ZtgcxCn0B+vbRZkPm8q@NleLXq)hjp50c$HD}9 zcl^__Ce$RD#YmsE6$|eZ8a&tzFKlS~(y?B=rQX}?`{<2J7Gr`0b1qA8N&2J}<0g{9 zqwzzs8&Go)$8oRN3RF*ry2kb(p~Un4S(8waoXu$O3m63VPGJ*hB)BI`9qh3c>AT?= zqh2b(c=YTwMZP$-#R)P~d5VTEO5xrX-8-{MvXgcMxZ=4?JZAxP%J%!^bC<^!585MV z_}iRBE#7#xV#}}Bb(l_Akdfw9pt=V*|z5DKktz?%z z^7LT2O@%y`{<=#{7?`^yX6D!z;Cv~Lz8;npBY;#&h}(_ z!{S3#Z}!gqm5bx~BQCdX#0@%c-;898`ugodZObjsq$oJ^6#Yk+Fs+ftqN`+^)z(3t z)@l3Sc-<+^mQ8peO12anW**YNyTI|FTUWO8u-qn5wDIV{(t8LaiwUuR*F?A@wxg$k z-Ao&G9jB$X;SIrd39+;Hg2ut9t8_rI^4e}zMJ#h1dViiaQjR%e4XSc^fhXewM7Qd! zD>sOF(n5?dH)Qef(nffNGV+TFBJGo{d3Ae<*Q;7DEvW@7et-iBb^+z7y$k#0pO>2@ zTK9!Fo8n1lszX_6sdc#emJ6A>GjgI`&*aUw&PNbkZ{ASQuLfgJHhM&c*~~OX3JvsI z>x+YEC^>3xCZg2x6g=Uno?47+v7Y_7mAh$m7%Dq)wq#MQk`lv<>NJ&J$&W!SNe0Cu zV)4a+gq){m!UV&+<-SfGvG~VlGt`m-HduUnPX8s;1&A;DN84zW@8|#0HVRBhQ^=J; zjS350R=i{lH%LX{5d#BL;k-Rx~5_TXyi*fx`q2&@JcUj5jtQ3M}vzOYltgGO>& z)0iusm0z^Z6^;*Ncl3@PNT~#qXTVHOW&yZGDkWlMB?BlFN5HkiD3@?}d!)jplz+2y zVltDwwI>iQHl4>sO(`L^OynNacpG5rwZpi0XKyIaV4}3jWodVH*)xW>)mhaWHg!s6 z{z2cZ+!=H3YYp~&J<{InfiVwRtt(dtELo3|E#`_jCDG8aaTZ-~Wk2F$EpH8pRr``k zlHS{0C{N|iV^DnC9L}0ma8L385kt`0ri3SyZ%9?K*W>E{A#(I<3YcS}YRJ2A}Xh7%&K9<80`UmN$EGQElv>NF|qYZ zsC@o3TFxAmQVOUEt^7G~q_cYquvHc~SrLqAYb}D%V zV3@|8JP|*hTTa{>T*XKFxg52D+A+$v?7XKREw#T5zU5Y@C|w)h$2R)bNdAn5^4bVU z5AT1fqvBL?ZWARb=+V3DLMF8EV9s^MMxgQQ>KG9qF5b)-eN2CiuN9?j>Z9GF%bDdf z&-~TO^TkepZnfH8Lhod-_t-ljS07pN6^X3cV8NGefHujW2u=^1b6c0>nyN4Mliswt zMLYRI121(lQdeFj@rA>PDZw^=gf|TnAi->FhKI-~<1kx?A0{~9U_>r+XrzCh9`l`= z>H{2>s>|k^1mDMkrp>11tZ80HoU6u5%rDN_xG!#~cgn1q!*MHZ+dH>cxI}bqbn7eM z^Q4cr8qC>FT(OLi7!){NtHxxCYOxh8xV`Jv7<2|3L9TD>zgXLTCJon4x!{%W?tY-H zdrCcO-snA{f#>NT1rtXpT==!tyUz+NzH=*t-ELZQV)U9`i!d?3#OB+q4UKEd>{%wv(>0u^UN@>ii1a!?%(3;J+ z8b%UUpb-R8SOA@aF}i;|=S>c@Bu5@`sK>g@2=w`FU^2^wHU1YyK_z(!E@*nA^Zsw34+9P@0xF@+*Af-9s|R0x?qdpZY@<6>pa!S zEGZAD8Hh`W$?I^}iPeK=(XCr|lQ%b2pupEIh!oMx|4F6!A*QJ4IdXlXzfd)3qX z3Zwfg1u=CdYI@?64sRC$GoKpT?Mm;J(ZcN+<5aMz-OBPwZ>QhZGfH~dLqLhGs|t`& z8=8C-D}^Z#OWr_j1(|}i^_%|Aeah^`GNZi+i_!LtWzRHddZi)f{_q9&Gs9C>B=2)3 zZuOa39NxdxqZpr9t#D|&d2VyGCe5-?EH1cOR#d&yp;7LAD3CZU)axOG7)Ue!P%H7ni~NigJpx>~E6w1hR! z%wtoy-}v)XX_`(MZ75Fc^B-aGv6)WDb;i0i6Q?nxyg^Ug0dgI53 z)hJ(vq6S~f6xo>rote&Zf7yr1kQ)>|mwA7f(!%rf0pkzN!DGGZ~Ky;USP`pcn_o{A9p+OhQGc3s>cP{SlX@eBhM`ZS}gcwR`y^FbDxIpL`Mr!fvt&*e}aT7#E;sghT}Vf8DX48c_;{yL2UW6m+>NF?IOl7N?{}u2jF0 zG|)Y1AnpLuGC%|Ib1(&>Hw0Jxq*sm?+-Rxww^R-VbPkmYU?w3R7^`vIOOO91*tcdT zfJ-h}ne+r+0I?}+w7SS$&=iQx{nzXHlc)ixMWIHvIl1oxO1~V@4sodH8hs4G4)L^~ zbVmCuar_m&7&@OiHT?4j&*`kYC{759{{`vIagXL74QoH8KG2*C(9`ztirIm-(3_4C>BGSP;QwY5u^h4q6HoUJnWd#=MpDuxQZKu6SJ+d5E7Z<$$t5Uiv1VTi<>U2q+Y6Nw+Y#icl36Hp?tQ7C)J!CRyn~0AIKhF z1bgm74M8j~{$5RR9rT8lFybtC@O!oycLBopbVZpbF<=S5|LEC= z=&7&qq!iQ_t?|*T+-JFmTZ(Qj_fZc7%E%DuShh=>Pn^?}B9$M{0F~G1pl?y~-JHF)wI=sXwO`#)1}^GMW+D z&bHM*p3H|1TegUt#R3;WLGm}+f`8#l|12)E>-raQ*^_$$@j)oH@7r1&ROkyV8{paH^Kog3_V!a8j6d;s;;I4^a z0{p51k->G4nMJ;m|4aA$-^c{OEob_-x4ac}t3oD_BS=2{jw)3Q1>K$C6GE`|SQ;9_ z2|BR)Sto?RIj7eDGu1$M*r%dcT$8=%p&Vo@>Ix&JS2)l~{flG=gopXdpOQKm=UZfo z7i1ZM{M-=z-Qgea9#=n#X1M!k-5+b`>+VYb2NH`ze_{VCV2lN%Xyl4YZec%G-$k8g zw!l9L1*vC`xtCeonx-cCy{8v*k!!FK5qO>)UIR&0r%jOaeo~QBR&$}@0CkI3#lYiz z3PBlJF82Q7X5>7M?IKG90Uc6?A^vLTSdXzvgb^nVwKVyiU6uSpLJ0)GKtc(zcr?gH ztiNcf2Lm*ovp`+-t8BgGBMVP@LOB(XeR;CP^VN~acS2B&Bihl94eg2X!S=fomFDYz zZE=F1hQdhCu`&(~j0!j&Jz}B&Dv6H!z-D;`lQt*59{*K>dc<-nBu-2}lr8ZrM5;_Q zM3Z!mDw$8zgAB);7W~Vp*$9IjvqSmOJ$^iT66BkZnLxrw`yHW4*AL`2jLhQ42SUwM zp&WWFQWRdx;FmiVvzR>Uoh3Lqn=81-kNG#e*y*5HvGwVerZ0dgWHpX_bI)$I zn}kO*%(pa1c&$)^AyPNdbiL?zPHLs?jhDLmZ=I@Yh14YjztLMwxK&=P7H-N_=GH5K z!IF7+ABG>Y6OPuBE(u!I?f+=%hFaA>r9tM|F!Qwr%pyY_{nb2y;Mu9i%uNJT^gGo5 zQ5^i)(C{bL9=L+e*$4--WZ`ln3{?D2&%}C0v5wpJ9t_clnY=vckL!hsXsZ9La~e4p zVM^&5J@LWq(LT^@p(-qsgZLl)(v9S3{>wS7Tken(Nd1RS;IEZl=#amC zw{)-23_S-tQTJE3BZ&Sa=bdQmI@C2r@{b(eFS@ACF%1KQTotVwH|~mB_#^wpkB6!5 z9054^zvWC{%$ZLPw2NDs-8*h(1tRe$=D$t=;|DYMf1K~ltz`yaA)rcmumQ}@4QEHB z9BPk>{rhOIFt9vwJW_qtk0cq$?>NS~H7P-gXK zDj?1?yZfu-ZzNVchTUjj5>G5>I4i{Jz2#LpveGkReDU`2=mV3dRL4I=)oOH+a}hq_ ziyjN1e?=F4zf+BM0|Dk92r!>Qpof^s7JA0%Lb2r76GtF{#1X7Flmsador@4Fhadg3JB*xnW+57u ze0+tP8^OG@>p$n68G*4OkD=jvz?30bIW9b1-`4p9%cCp5I)9{sJVln08}nHD?3=S9 zAbdQ&0=(JB-w}qItan)f549EKeGYhkreE((BkVumFaE5f`~iv<`!SdR6x90F=i{PY z`k(LR6M%n#TAseo>Sv$!4^POPK*4|t&|mm3EXqLzD)P}BrtsgXJM zCrAT!7tC{bH*4PnF2{G2Ef8(4!G6X!GgNfggOwRFGvfF(3e%G+F6T@<{I1LDBLj)?AUyU^WGh8EWDNW*iS-tn@o3~<#Xw)IeSvnj@+=yJ^- zl4Zgb(5|L*TQ4o#%K9?B;NbFLHa@idjjUnfdZ?&(h~WLm7uh>bOZo#P2fGNn@MS8w zW>PDyhZNU6#wr;*XPy`2r(cO-lV1)ZQ%$$@f*+8^dmZ>83L>}pUXWPz6e@3;9MFW! z^NM~=0%DVRh)G^lCUk#iqy1t!043~lbI06O0Hiw@xGFl3IQMZRL4TnRQgNW9Nj9RJ z3eB;>VWWL#@!({5P3BL&^9&sj6(^l4h4@YYqyPBc2Q&{j@}MR$v_m*FI5`~GLdlG( zlV_U%hR;vz!UnOv!_G%`g?&j%nXT6s*Fs9GqWR~Y2Hx6v8SLloAv)Cq*`^R`YLi<( z)FF?zW+lpPDigO@utbu`E_7Z1K@qiK#~H+c2C)umk*koZ!~*|1_3JR`EvQgwEb&>9 zA%IMZ5N2+2vh)wfOaId>3@D`nImaJ!fAzQUsr4DI+)RtZcw%zB`)B|_4$=<~E61Bd z%n$eqkSTzt)Z0ThM(ClLM+A9PEJ*j<70S@##deaxVx2)|uyX%Zp8SWHR_cI=y6R|F zn{2nsSqMd{hF(YEj)RW01IgWVg@9W%uXBU918!Rmke@iYV zceuYtS2s>w#M9A2NHec)ECVdTxx=t93Wh=E8yR`_)2m2VDi^n0F4g zs#IAq`vF^egWf?Zk4)n&EbHWwd+$fcHa=n7Uez*ew|%xcHJ=Z92dby!%NFeNyvcG(NTy}HZab2HoBh-5j7F|}o}0^StN z?QkJ-Xnb85e3#~1k?^7F1Dk@*slt#Z*(u?@e0~~{hS8Wz&Y5=7QLRNpjHiaR2^|o1 zB%|A~C&RPXn2l80U-?XjZp%^xmZ4M9>1f;%e zJfY|CX4|lxqi5?Ns4J$}y)=a1zkiNpmd_Fc4V~zVDHFJ|_so0X1;?5+mCN(R>Bm_r z#V}%`@;FPCdgtk;cQQgp`9yg7w_0nr)n+847Ft=qs|BBK66I7g5G40opRz4#*V||s z(^FK{dsC8Z~0KS2Sb3=C)o61M`=|bxq+~@aYg@F);VO zZC+mUt#xn0pqkUY^|u4)Ph<^UV_QmHBMx_FWtWw}Ck5!0+>h+$K<`^&93f}J=rDc$ zoMFW40_%XPfvu@eo_Z^vnZ{P=&NCI9{s??(n!>VsoStm~fyd?|URiE!mUAU+s;YJ% zTXi&+Ds3+z0bfYbDyM>3*vpE|+U=+yKXdPjQ)f_g_4k+Kya9{d&sdin*|`l;t}mU( z=BqrqsdC9olMM{*KLyE>)_Ss>jTYwZ$Aj@QcWm;|;kFMwV;Cd!?E*-Nmx0J(Y<6Q_ zd#S=(N3kF`Z1oM{T~~uWu^G;LMz#K=8$9ud)RjW59eDejDFpc93gOiz10%Vt zgqc@tpp2#e=&-A(pxxdw7E!`k*-bZ3==5g2bKi6#WG;Dd;kArtj@*aC?PnyXeJK{# zr&`l_()IHMU+rdYaB2zO=1K| zm&H;1%#oI%j~;WWvkf{rJeD+Lsl!J5pzG+MwL*D6v{*a9K#qyEywKC5!~6C|M=aHB zug4Ag-LyHZeX>od!z)fgY48`*r^Hcx1>cm?#tKg7&$vXeiW;z(yAMjTbzoa=VA`_b zEanOZSffe3kt{b$_sEQOwTv%j#(*t)BbIaS49v8jG4v1&XDzv1Xd@aZ#Z00UyGo?= zJ18udLBw2dJA=jSkq{0;Zzeb9c{mV=c!B7I?-g z!BviYS}NR2NaR~)hqQtjbONq+e>&4rBysWyF+`z|d}=shK8fKyHP?v`t#(Q1p(wMt zB#hI|FnmNXg6&y4h8W2QajV^(v-SL{n|91fK4=T__R!$$)U`gJxRqkgl}a#Gp&ce9ax3_k~R zo00G{%gXGIsyCm4L)tnqyQ$#2UZsy39rtCgiC$^d;8<DZ|7jtdeybt8sz{r_w4%fq2;-@nI1)+`Z~Vj@Xp=}{q!sgyPp6=kiS zl4Of)V;L&+NGghmDOnPc?CV%l36DL-zKq>ivd)zR&vo{qY{h`@YZd{Nd=B zx$o;buk-wzpYwB`_uR)0sw_04?dPw}3|499dm#3kBy8GL?V}OLrQ2s&lbMV}I<~1m zFYEh3U^S`Rg(Qe71+F=kiCDvj(kf$IFYe)5OS9PjF}nHr~nlPnl0Q>lEI(%pnkS zhO`!$meMJ{0#*tIPEYA!oHR9se{C@DI6fJqMD97L)>&D*{)Im^)p`ueD@S|ZmiVX> zg+RKP`dQ4sJ{vBrdA4gxBvEfGp8TuxhpQo+0{nY_LHj*N`FJMdi%JAPJrW0lLW6lyf=M6_V6!T`v}`(kj6Y+PvR>-^CK!%9n=;s z%AnI-CMes}Mo~89x0#u>lS{&z3P=2Tkg?FvLQJTJPrsWbcv|GPo!Lx>Ja?nTE#{%i zm#?Q3yIAl$&Jv>LknCU;u2WT0Hl9S%Zdj zNczxo&VcCFj8=U;$H}`@we`i=Y6~+J`%?2lT>-asOp!Yy>o~^zI|Do_PrP?Owb=S151NZ7V396P{|LQ1Xm+!4x8 z@4$)TK+mPlgsCfVZ*Z^bHe|=N#z0$H*+A)URm6`T#$qq-M`i^^C=WSbz1($Q0U2o~ znVTfkus|Ln+UX+3$4!0q;beE9Y z4Ed;nYE-N(hn5F_0#>L42`1~COH?xX(cRO1tbYBCt#yZMRFy}=%&K%S9a8>I3z&{i zoCid0K6#lx1(VmtkI`?YIhk1^WS}>9FvnLXIrG`H$*3;KrCA+j8JTYnWsz-Z@Vfxs zf9dv(Jaa!(%p5x!+*gWc<5juhrI99J6-{{L2OIYfy%0ae9^9|H>2<iLqq;&&Jqhv5M|Hb=k<*bfDJfFpQw(&CW2)*|$V(@iUTmE_7R(ahF z?TP{7ocN4=eOA3u`#X81Soe$^+1WOdY!znXP*zHwRSqX2yWHeXZ)=sP`={N_r1#{K zH~D!=v%c2mejXD?ac#M_L*W%XD*ecc$%xs{=?R?PMbV`qX_?H;55UXrB_q#IrwQSP z)?5>=Yc`j#p{Nqim5zt{oaZY3%3Y*lxG8U|HNPBB#$~7MUpy}nE=$3f)GyU&zO9ux z8^==P8>LqeTgGQO>|NE+a3Kd>)1SZmxNu-|hy$x_)pW z(%Y+E3-`)kW}lRr%==i3SbGd}A&TG=_tj>8Yg^}dBy8Ku%wDw|=gD@;tgH)X&FlBC zVPbc_ya;D1c|$EO$CJ)RJ=#0mJ(uLHJGtB1sr(mAzsyzvLAT;AVrAJGSn|TB@5(}H zc1$I#YA)v|-?K(Ej#?>L9bR~Bo$oj~saB11Ecg!V3dB+j?JD13lLN)FwpKpQ#CP5D z8~vz9bSt(CQ$;^Aa*re3pON;8C6dR5etZH3*5M>c$~%5y=53XDQDke0OE~NJveQfm zbq@{xKV)x)dN|e6)-c^?1WTU$c)isk`@^=D+HEcPZ7OZ^LLT)hgh;)#s`iC>GX;;C z8SJh6tDPG~l&iFEaTkb%#T2l_QZYEd#-Ks3~q?4{j$kel>U%k&I@ z(c3nwQTYRIiA+??JlK*UbPM;ewz6F6e&YhA>r+<%dNk<~HzHj&Hy~X3D%>}~Xlj7K zWTL2hwQu22k*#<6-BuE3uquaHz&)ENdl;Wp3&qYaMaTh}ed?|yZeQJ-C?g0|G@GipCqVX23I4B4VSf)WU;P!uHz$#c*nGk2C z#|M2UE$K`Lzw8`76nVmW;lyN-iJKjXfbJWLXp(VpB$n9tQ%TZ^Y1AwWin;UJl6VhN z4M8c%^(yIUok^*W@J?z|PJ5#(kpFoOX5npJ7m|aL*tdR^b38;;(IMvhbeNqF z&J;J^CZBV}+&Ev6vzx^)WpB}&bePzdS{{|T(vgY`$2OvP9ScqrJ#7(ck(2`lLHR|h zTgKBf4BOW9T%(Rw`<)>S6_q~^km+ecwq?m9w62eWQ%+~y-MQzkO?u(9hS0|Q9~E%J zv7t%X3-A3{d0X?;(}|8G%?!tgqm#p>E#xFWC6~4g_apo==nbRgd|pX%GLrMWsF!a; z8u<|@nm@iZK*64fh%%`+OYD+Pd%SUTI%@OyG{@`@#Yq234H#c_DoeO_wu7O0+zi@s z0sFY?i<%XSFJ2VvIFR)MY*?)$coADC)?9o$=JCQZHh( zA1;$DS3uWylO^^dN37 zf>7k+s!LKKn~^SZB%#{^6lU`ATLYQYoh%(6gpcJaS6WcUo3B&FJ$4A}$BhMP>~FtA zT^N}h2|moh63>M|PVrzZD5W^0Lss-?X7OGla*ju{;g1QbNW=l^q?%fE*5pWjf}6-F z&v=3!#lb*SChksCyhlu)t)nC1OSrXTe5vYI?tK|^MQ-9nTiEQjsiX%u%3u^%}>_cM7Stu4RySJM4~u<+}P3Fc;TU_ zd3nxLKEoQ{gj92g=pC!YjR{u}ja{w{ z@e_)sg^&^<~Z| zKIe9!$pp3Iqi@S{2Tk?e&fVeQk2 zXX_umlWnueM@3GtJs*8o$kw=a z)zFmu+3O=s2%>X)`+XCOqHCV{14u^;tWUX|>Z}H~aav8_-Z&|~lxUo`lPH3}i!p@R z{$kf>E~{FXOW|p26R`-9hvNGta`uV#J|0q>$*uCDCb`b>ac#GlxpYy|+_d5m%!H+1 z%Kl0fJ8io;6N)C<;gD0s>(Yu!;@thPaO@2jpG=tjjL+7b6LTpdj>eN?tk|ug!z|}6 zlZ+h~`YQ63+tQuO&}tuAh`ZgVeZs7>XN-L;G!vQv&`!eZ@3C}`^~*T&3nq)s2g+Ir6(4bSY(NR2$c3^C`03kfuM^Q*3=KK@ zd8osx6}2LhZWS!;XllBm;mtlAnIO7f)_sKNC~M*2U5Zz3o4!zCXR@GNJ!HZ!Q~Kjw z`#>J@*1de>z}fcX+@vG=A=FSGWSU!RB~HDa(nzWDsNp%#PAx#o3g%KT+scpXrE|_1 zb$2Eav6HhJ`})*If}0~9$@51mDy$s>6J35(StX>)4*Qf(-LrM+&uGcWQ>mH$#O*Qm zrea2ga@6wdj1cK^cbYZ$4Ft2b_cRpK)#iJzE|7khC7D-wIdwNE{8KF=XH-e!Jl3+|0-J{_F-~M>+~CoC}p<3q7%1p+%6@oB~z8ZGe)5)=hj~G`5851w9(|V4C=?;Yi31o zs)mpPCwIUH)4YY-`^rtW%xyO`erxb+ep#1nbMZyNtTHn}g>94}9`}T~v38GlM!wWR z<(8O*p}7{~!}pvX&QuDHwx{N2pentvHrTaV6HVeOEHaU{ z!jQ8{C8Dj+@hPF`mfq2OAoMGR!=|L$1_V=Wp`HS+jY{`*3NBIi|zi>V1`J zMrgN5Q10C1BeZ1BeUw#oFpusY*}bLP2RR*A>g18C)cw$(AmcbYL2$^JtC@BRm{E?^ zX#;8dTH#^2sYD#*j9o*8bwkUPmPy3AS4Eoj#S)35F0uFn)~@4$TJv&%eQ5Rxu>4#pT4K56!oST^@bLy?kidsn4uOi|Fr#9+n9z&o3Yi32;k?VC_9kCzo+ zi#q%`ec`E=Mm*DZt|f| zl1UWl?CJVm9wB4xDWgPeQK^%uOLR%L70OO+(DX)&<&J#CiL{p4jn36;W=5Tp4(P)X zG%J#FJEcc9iFYQEMaQN{;O8gEmC+tqnaQ&T_+l;vah{nqFS@(c*ZA6`pU(98gcb}| z=Ty+*2{xavZB)t28t}KauW^m1D7;>{ovJu5MNv%{@%w&)C)8v+1Hm5iy&bW$R*djh zMTZ?><@xo|{OtMY27eabGKJClAUKczCDds3F=5wL=4BQ&L!D0PXt--~D*^1GZ~VKl zwWkextc|vxjiCDA_&YO|m@I1Ebo9IHXB{G$N~VQvSzq-b$N9?gG)nXf&Qrq>n2pLh z#u^MRZu$}f&T;!9j>3KE5i<7RxXWVD95WYXx(5>r{gtCZ2hno3bB6N6r~;cua;wll z+TO9tF=&s>>g#gnn^|a9mK~k69ZDOX{H}GMWSZ+}E9!5fJBn@}d2|;i==HK)|K$e$ z(K5bD2c4;Rzn^DfQV-Qz zY;of|+`I(8yQ8+-_rO8G7S(G7?U4h`w$agTL{J2}h`JhjeUS+Hl*ga@8_cs+fEP6! zlL($BrmyX^EC`w~PDG8&S@XF2CBJ_Fg=NN4VQG&9mXDF{cdbe5K6xsy-NHih2XdAT z!OYv;#%_ERQ(y&>orA6(_)RhTs{mA)SzC-@?dOv^VfU*Nj@c0e- zQ#f{A3R)j8lwAKPH;+M&6XH>pSak8Yp-1sCyXrNfA9QYmVyj1XD)`%FN6KO4fJ(EM z0rH3%ErND)^zJ)rLA7XaSEvnVzOWmC(SMIyz7p?F=`&Yh$;oEccKscmfrB^Wrfir> zj#G3xE;|YypX_)21oAfRIvY@Ii0rHmNoZxfsDy_0wq-*GLAB4(#dA3a<9qUBk*Tjk z(hqy*NVf=Ytb5bj8;_3(KtWQn&4rx_&d-rB-+phpygCvW>#`|t@2_cYe8P6$jE^uP zp+lYJCaFdEQQ0Qm7iagRbzA_IXF1rR{Fv*6qU$UdM~_Jd7n|AN_)9p`Vo}So>GfqM zb-KUIs&8MaZpCL6*IWt3&9*+k4VHciFS{BB^Q@<@%&TA%Z+tlGv=)6yACPdk?+<+r z$Y=BYb|-6M3U0DoSg`hjT}kV5m@cyLXTM974^x-=797X)L>({$0Lk{w!7EgLwW zO1N+8sdJZZzDDl5#iZ0VvV{1ICGokwWa{Mdy>VqHcu~-8CZ>{}9*5)f{h&8eB+_JJdMiSn&B&y6yV?GDu!-_WX&TWc(4)cryEMNBo9z1P8 z+m)v|_!o0CTG{sd=D#o7%uvAiEy{)QY`ot!!P2Y-Z-y;1habS!sabzS9FFygV|;Hk zdsRvrDJQ1Zk@C_V4ikaaa~QUFG`!7^KZB=FX|y$(rE(|JO||J@0v!IjDY41F$P)lfY86G zm)H>h4XiCE5m0imAtE2V#=tzk01pTZ#BW^QY2?m_fb&y9WI?6Ii8UKX9f=o`Uo>-STG zL=kDsY~Qpx62gWp^Ud1zPPbVD-l1jX!?3%B3`7Ax{6qnhg^>mZ1|#kV6cpYptlJQD zFrvAceL)b_qoD@U*#&;^$1wmc%rW4|PY+pIGcGp-CC7kdTJ+smw}Q6+ZS*CifanX? z&}*mr1oMS$;A-*zU8+3@T&%&_e#)PY45k|(V95TjfZ_j^rTt+fH-Q9kU7_dTX*#@r zC_p?u|E(_qkH=as?)ZhR<+O#fAJaZF7%?ARw(tc6XR&hF9Lagw~4m!R~0X^t5T@UKur_lxf zj&3NGtMeZLsFDKI=xn}mSEG(N@S3P&nn9%U4-+K6wC!Sy1h6Qx?fjq*{OG;_4zmSz4)-PZbArVzCCMz1 zp$Wg#%%7kiiQF?_RcUEj=D76tDoOYH(=vtomeSgvd9U?qv>hIix&VWTJDyNKbZ!;$ zcDuGwrZX~RRY=1rlQ_AQOOujn9fbgg1e!_IQJ1uRL&UO9DjFi5rwt0lJ=H$ zgSTk#>OU(8qKc%Gn)UDDc$Cwq#DuS3-5CaAd$?FTp;gRU-e%1faemrDNz6M2OSVWY zv;Gbr7`HsU4r>cIb!y;Tpu@mP7EmzMRj{amf*}JyMO$~$3)j*&%cbrzhirJ zdi6J+GYCBAFL-u>Hz!yo^r+dl|BcXUQLuE4S-oCPP``-Y{9~ZU|AA~AfHr|kM;1>=@5{nsr zk+TP+kTpvwWPk+)prWK4?opT@bsRac$X4eoDk^%0Zr_h4*nwga>4?mt@YL5gB*^lK7u;KB=K`aF!2g;HNs-cnWh1O5uIdSgL-l(EisEK~(<4}ghPeB85c zQUkUPqp!uL4c7J^Tq6BQQup%OAdRc|=c*v56R^|}yqC#mkkRPggD{ZOz6p>f)`Y!9IToo5h$Z^%x!(?`{h+G2X z=D_I5Is5ek*l@5w;>y+>oQ zx0W9PbclXs?AeT*;XtecahO-0bJVEPQ=aT`!v-@Kc>?m`Q*^xUU5yOd-IRUZPv&g5`AyFY&mm>V~3 zXLcKv-53haTisXbRX|eqbk@09hyyi2hV)|sE0zbFi^^{xZ<>a@dm;rYrR;b^Cd#XW zNTJ2w2gK=iM}8xc)RCwb*D5nH z3(9)3B5$Rq)Eaa~xVBgssP^W6+7i$RQ~}fTa8SN0_U2#cHODbrg&9<|c#*123O8l) z$Go0#S9-&)xOi|zIomOKu?7s(Yves=PhkqSFfUn}Am=d8DB8}%LI)Vnj(8*^a{2Q~ zqjx(~d)Mzg)H%z8EX!cuzih8la_oP<-*sgvbO8Xq`1lzl3oS21Y^CM0||L*x>TnW6I^}+ z?K}o-27&moWGxg6%^yzxro;IZYU?lg!zxu`k5xgQ)62ninabKAogi$oIhH&tJh8ho zlM&-u9(3nCXIXeCuKR!GoI0G8KUoZ2BAFDVZD;1~pelVk1-l?MV>f1F$htHZ*62r) z_IS9t``Fw=&|i-V|AtLHrMC;zRZCh8L0Yai_>I)ee&X4BG`+a;JkL=#ouvocD<^zX zdKPM(rL)wKJ0>@Qa`?FN3pMlL#B%_p`Uze@lKp02`c+m#chsWo&Aa978ZQ7e&Lr3s z(*t#aTR=7NyCrATT;cLLN6e+~4t;~gO$S+*7!Gkv7|@K+25gd3I2O zbq0|xJ&RpfR{2&5r!VU|f-dp2D`q?06W>{HdZQd}dPb3Qw+dnbOVaP0{IEFYhL)1+ zm)-Fa;4%IMS~O3UD<5WT_Ra>{Qe&QRs6*oMlDQbg5u}2uovH)Ln$=SZw?#86N$FuY zlCh>bsWbRKW2TY=4b++*e}*7;%1wepK2wIk|zpNjs`VO(#^X81jAf ze-C9P;Af)BL=b(P}smg%HO4>6)<^4DlI|^v+;`whstmz+*~BQN)bwzU&tqN?4zM` zRS+Tr{?kTt>PeCFc??NiKbn?&Q(mtGN=8^re# zAZSqJlU%F8SinL00wn)r0sk~QrP4Y~K;86TN9QU$|M$`P6Og8)rPb%#*xAWbIUh7P zw+TToT~9v2HJcs@`qjLsTVd-X0OIJ<5a$JVtQDhRK8K(oPSnmc#6`xQfEo{^ieD*F zM%g+{CB4$65`E$LC$pd;^t?!q(%W)(LK(=zL5tFuqpS9|DoXz;k@|ljSEVb}3-1k{ z7qy6Um{3)_p!^RQnD8UBt5t1;-wW4B?pKU%C{_Ses*~;H{*BEaVH*V=Q9JBh!_E}1 z$Q7Zw+$IxWA$j8rt{}+dQA~^8!o;J;^fNOxg2KT-6K31<`N;vyPNgv)9#O055xm~V zFKIXZwA~dMq8`>y#=y|K-+M^QnJ>SNs(&gikT{pz3{pM8Dc2iZFn)c=M0>H+OxiAO z5=f4;TL9N;n|7+qJZm?8Uns%e2K3o$Nq}U9O)jSt-x%j2rNa~C(-3vc=FIK+t=LjX zd+N69)t5I(Vh0EzdC>KjV7%Y~l%O&~VW7S6NmC;D?N{Y-+LdbOzb&jkt!HFsDPjoW z$8|BA@~~0W-jvQyXX>Yq-uku|y^ekvm?uA!rmYo4W-y;0URg>jMh^b--wGzbbdPn) z?|kfWXm-Y|lBuMWIFAxJzIeeM(7b2rkZL06!6rI>=Vs`H*Xc=nUji*@1KJ4@oK=w2L{OLP z1Wh$&w$tFVLdkLSD;l1Y>uC&#^6b(WG&8eA*5ttq+!KdiJZfZ&;$V@1pq>KXj{%?c z`+ja=xjp3R%uY+w)IU$t)h8ezf_h7J;AG#qFSm0Fa-mWMqN^U!f@IJD{fU2J&GJWn zBlPzrS<>bEQ;P|DjZgd{lwH`~(LT4K5Dy*rqCpCxAUeMykKW#ymY8pAKhCCc=3U|M z&shpy*^pVW-S~T(SCm3e6re1~1iuCXOzc9s<0Xcwh_E?LJDe96ck#wC%(FuCcNPcy z420|j=wz;;WB1dR0z@F%t{{4|fa{(QAZKuV-rCr~0OLhIAg@?;^# z`ED+72rFKRs82gmi>C5i2=TK4t$mm{1oXl zqem+z3z-7+5f&B5fjFC=Yi7V$GjIzdBb-%8(lA-}TS5c*3y;$nsG!Tk2;M`Xrz({d zs9m1TI@GJu^_2yk?;zRMa|8RVqx;b@hsC3Zj!-TR;!XH&rS4-Dr9#GXamAJ4mY@ay zbeIBapxnJH)kO<#M1;+wj%;B`s3QEJ1((h0vcZSzuFG%yqHe-4W`j1=*Qtfb;oDtz z;v#>4e(DwT{cg3{E$ut%#y0f<;gzOati^67GJ!Tsqk~WXVyq^-E|3P3aY0qN21f*q z7&CDC;wz=bpxbe|U>f2nPQdAVI7^Mn!BuPILddu`vSt@gE=F&Ieg_#1Tnrs+u@o%8 zL+zk}0yE4{4V`7FFWT)UAh3fUfYQg<)8Zcm<~x8K#S^mBAo;82a)Z-ab&792gA~$n zZxFh5*>PI4w9dy{8lgd(24B|Py}*!xm>|nfH-~xZg1L>TIUxE3zy0HB0HBKbZlK?a zv2?8y;5Z0NL!&jE7$EfW$#RleNMOL^p7zsg7eAYwc;ec{<-6UFQ6e&sMG7=NYFz(9 z*R)-$uAac~dHNqVC!zKTmTo6|@F99*Ag~gOsQ2wHEvNHk^z?5cp=#T%FK~!@#>jGE z*BuogyIr}l_AP_h(}zl4hCnCAn3w@0FXJBz;1ggMQJ~J>?hNem{F-@)8r)aHNAf>x zCk8F(%}+-TdU!xC&64hihuV*1*64Jf2i$T7sBv}$pm9#WG3^gYF>v-4?EieI1?s@7 zKwa+DmnzT)O_NI}|4qMquayYY-(GzoQ($&6X_`I$*;MF81s*W4^V5aoE&%sL|mixyrhW>4cjE! z3qq+ouw>XW^%632aIRh1LBUh?S-JK0|Fj8|X(RJZ14(m6t2%MY`E)VdckjvjCRaW8 z@qA@4&Y(pMD^N;GeEyRq^c_8e4Q(?=y5cB>W>T>?;l2RC`p*rVavg5RQG*f|XQ`M4 z8sAoFuVduV*dB?H#B4${nfpJ3olfs|YXwDeFqP0GXRUW%bcO~;nO5Kzqe#`=g~^;X z68%g(%}DO_MVelR_q9)Io&TW^R4T4j#Npv&Q*-l8lgzBF7u5UemX<_6NaDRo*}m4* z;Ov;x2GlK!hQ%zk%A-Ah1MR#uchBBD^pPRx{vrKZ`K^L^&~jhnD7Hdd5!e4;Zbg0(;~A`Jsc)1Rs`h}X{el4KTHsMC|6#@iFfnt* z^o)hU_dN_r@ek?z$}O6@LF>J0uyVcs#D~xEqYRyUpJ_A(U#wzFNLrM~97YpTDlfHb z4K^jRG2}5&_K_T--TeO}k%Ot34hb;bl8C$fCad3*M}NTc))pg`BEF!5x`@CI zCP2p^2XqWH`c@Pt=3~x%W@?R=5p`8VNx1xlJoze>$tZUmDH=$RWmCxkZV6cxmR7Y} zyG|@5yf=w{ zBf}!noQBHQ&A?T}5|i!xs-d){_J1;g+rx-KhWi zgYQFulPGKSDdNGc3tV4^{M~{pXb4KHP!@+L2(&M~hy_VVZM6K7_2X5;waeV6Nb<|a zJv}L)-`N@X>qWtdi=aa}cH-k6&bd8OPqk;aUwnSw_4%r1EMvG~c~*Dva+!&MQ1tTl zVH1X!ch)EAjNCi>c0k~@&cd#GZY-G;#Dl%;N#0E+{n0xA^fW54wb0{lqVd<>v-JF* z*urB|LZ8@QSoqIZzW60eP0-?9T0UdE*=A`DavJonf=Qc5QFY^WfChSKQVS-&R2`>f z*Oe~$S0}u9e-6Y1vbh(WKVX_|p diff --git a/reportImages/mathdokuClassDiagram.png b/reportImages/mathdokuClassDiagram.png deleted file mode 100644 index 51a3ced56a8c9b7b7e80c38dae3d8a4ec91f8bdb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 107988 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 MMn$?@@_FF@2QMSzPXGV_ diff --git a/tests/exts/fun/test_board_filled_handler.py b/tests/exts/fun/test_board_filled_handler.py deleted file mode 100644 index 8082014aa3..0000000000 --- a/tests/exts/fun/test_board_filled_handler.py +++ /dev/null @@ -1,108 +0,0 @@ -from pathlib import Path - -from bot.exts.fun.mathdoku_parser import create_grids - - -def test_board_filled_handler(tmp_path: Path) -> None: - """ - Contract: The board filled should color in the right and wrong blocks - and save the board to testdokuboardfilled2.png. - """ - filepath = "testdokuboardfilled2.png" - - content = """\ -5x5:d5 (easy) -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH - -A 7+ -B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ -I 2- - -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - grid.cells[4][4].guess = 2 - grid.cells[0][0].guess = 1 - - # Comment out the line below to see different colors for singletons - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) > 0 - assert grid.check_victory() is False - assert grid.board_filled_handler() is False - - assert grid._blocks_fufilled_check()[0].id == "F" - assert grid._blocks_fufilled_check()[1].id == "H" - - grid._generate_image(outfile=filepath, saveToFile=False) - - -def _test_board_filled_handler3x3(tmp_path: Path) -> None: - """ - Contract: The board filled should color in the right and wrong blocks - and save the board to testdokuboardfilled3.png. - """ - filepath = "testdokuboardfilled3.png" - - content = """\ -3x3:d3 (easy) -AAB -CDB -CEE - -A 3+ -B 4+ -C 6x -D 3 -E 1- - -1 2 3 -2 3 1 -3 1 2 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[3]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - # Comment out the line below to see different colors for singletons - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) > 0 - assert grid.check_victory() is False - assert grid.board_filled_handler() is False - - assert grid._blocks_fufilled_check()[0].id == "F" - assert grid._blocks_fufilled_check()[1].id == "H" - - grid._generate_image(outfile=filepath, saveToFile=False) diff --git a/tests/exts/fun/test_color_gen.py b/tests/exts/fun/test_color_gen.py deleted file mode 100644 index 4821673080..0000000000 --- a/tests/exts/fun/test_color_gen.py +++ /dev/null @@ -1,13 +0,0 @@ -import colorsys - - -def test_color_gen(): - """ - Generates the colors for the blocks - """ - n = 81 - for i in range(n): - hue = i / n - r, g, b = colorsys.hsv_to_rgb(hue, 0.8, 1.0) - #print((int(r*255), int(g*255), int(b*255)), end="") - #print(",") diff --git a/tests/exts/fun/test_grid_class.py b/tests/exts/fun/test_grid_class.py deleted file mode 100644 index 7aa3e968a2..0000000000 --- a/tests/exts/fun/test_grid_class.py +++ /dev/null @@ -1,33 +0,0 @@ -from bot.exts.fun.mathdoku import Grid, Block, Cell - - -def test_grid(): - """ - Contract: No errors should be thrown when setting up the grid and the initial - guess of a cell should be 0. - """ - grid = Grid(5) - assert grid.cells[0][0].guess == 0 - print(grid) - -def test_latin_square_check(): - """ - Contract: _latin_square_check() should only return true if - the grid is a latin square - """ - grid = Grid(5) - - #Makes a latin square - for row in range(grid.size): - for col in range(grid.size): - grid.cells[row][col].guess = ((row + col) % grid.size) + 1 - - print(grid) - - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) == 0 - - grid.cells[3][3].guess = 1 - - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) > 0 \ No newline at end of file diff --git a/tests/exts/fun/test_mathdoku.py b/tests/exts/fun/test_mathdoku.py deleted file mode 100644 index ef051bb50f..0000000000 --- a/tests/exts/fun/test_mathdoku.py +++ /dev/null @@ -1,17 +0,0 @@ -from bot.exts.fun.mathdoku import Block, Grid - - -def test_block_with_id_gets_color() -> None: - """Tests that a block with an id of A gets a color.""" - testGrid = Grid(3) - block = Block("A", None, None, None, testGrid) - assert type(block.color) is tuple - assert len(block.color) == 3 - - -def test_block_with_unexpected_id_gets_color() -> None: - """Tests that a block with an unexpected id still gets a color.""" - testGrid = Grid(3) - block = Block("9ZZZ", None, None, None, testGrid) - assert type(block.color) is tuple - assert len(block.color) == 3 diff --git a/tests/exts/fun/test_mathdoku_hint.py b/tests/exts/fun/test_mathdoku_hint.py deleted file mode 100644 index 41506e631f..0000000000 --- a/tests/exts/fun/test_mathdoku_hint.py +++ /dev/null @@ -1,98 +0,0 @@ -from datetime import datetime, timedelta -from pathlib import Path - -from bot.exts.fun.mathdoku_parser import create_grids - - -def _load_5x5_grid(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ -5x5:d5 (easy) -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH - -A 7+ -B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ -I 2- - -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - return grid - -def test_hint_find_first_empty_cell(tmp_path: Path) -> None: - """Contract: Checks that the hint method returns the first available empty cell.""" - grid = _load_5x5_grid(tmp_path) - - result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) - - assert result["type"] == "hint" - assert result["guess"] == "A1 4" - -def test_hint_find_empty_cell_after_some_filled(tmp_path: Path) -> None: - """Contract: Checks that hint skips already filled cells and finds the next empty one.""" - grid = _load_5x5_grid(tmp_path) - - grid.cells[0][0].guess = 4 - grid.cells[0][1].guess = 2 - grid.cells[0][2].guess = 1 - - result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) - - assert result["type"] == "hint" - assert result["guess"] == "D1 3" - - -def test_hint_cooldown(tmp_path: Path) -> None: - """Contract: Checks that hint cooldown is active when fewer than 180 seconds have passed.""" - grid = _load_5x5_grid(tmp_path) - t0 = datetime(2026, 2, 25, 14, 0, 0) - grid.hint(now=t0) - - result = grid.hint(now=t0 + timedelta(seconds=30)) - - assert result["type"] == "cooldown" - assert result["remaining_seconds"] == 150 - - -def test_hint_available_again_at_180_seconds(tmp_path: Path) -> None: - """Contract: Checks that the cooldown expires exactly at 180 seconds.""" - grid = _load_5x5_grid(tmp_path) - t0 = datetime(2026, 2, 25, 14, 0, 0) - - first_hint = grid.hint(now=t0) - second_hint = grid.hint(now=t0 + timedelta(seconds=180)) - - assert first_hint["type"] == "hint" - assert second_hint["type"] == "hint" - - -def test_hint_all_cells_filled(tmp_path: Path) -> None: - """Contract: Checks that no available hint is returned when the board is fully filled.""" - grid = _load_5x5_grid(tmp_path) - - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - result = grid.hint(now=datetime(2026, 2, 25, 14, 0, 0)) - - assert result["type"] == "all filled cells" diff --git a/tests/exts/fun/test_mathdoku_image_generation.py b/tests/exts/fun/test_mathdoku_image_generation.py deleted file mode 100644 index c2b91f0c21..0000000000 --- a/tests/exts/fun/test_mathdoku_image_generation.py +++ /dev/null @@ -1,60 +0,0 @@ -import os - -from bot.exts.fun.mathdoku import Grid, Block - -def test_image_generation(): - # Contract: The generate image function should when called generate an image file named - # "mathdoku.png" - - filepath = "testdoku.png" - if (os.path.exists(filepath)): - os.remove(filepath) - assert os.path.exists(filepath) is False - - # setup testGrid - testingGrid = Grid(3) - cell_one = testingGrid.cells[0][0] - cell_two = testingGrid.cells[0][1] - cell_three = testingGrid.cells[0][2] - cell_four = testingGrid.cells[1][0] - cell_five = testingGrid.cells[1][1] - cell_six = testingGrid.cells[1][2] - cell_seven = testingGrid.cells[2][0] - cell_eight = testingGrid.cells[2][1] - cell_nine = testingGrid.cells[2][2] - testBlock_1 = Block("A", "+", 3, cell_one, testingGrid) - testBlock_2 = Block("B", "/", 30, cell_four, testingGrid) - testBlock_3 = Block("C", "-", 300, cell_five, testingGrid) - testingGrid.blocks.append(testBlock_1) - testingGrid.blocks.append(testBlock_2) - testingGrid.blocks.append(testBlock_3) - - cell_one.guess = 1 - cell_three.guess = 3 - cell_seven.guess = 4 - - testBlock_1.cells.append(cell_one) - testBlock_1.cells.append(cell_two) - testBlock_1.cells.append(cell_three) - cell_one.block = testBlock_1 - cell_two.block = testBlock_1 - cell_three.block = testBlock_1 - - testBlock_2.cells.append(cell_four) - testBlock_2.cells.append(cell_seven) - testBlock_2.cells.append(cell_eight) - testBlock_2.cells.append(cell_nine) - cell_four.block = testBlock_2 - cell_seven.block = testBlock_2 - cell_eight.block = testBlock_2 - cell_nine.block = testBlock_2 - - testBlock_3.cells.append(cell_five) - testBlock_3.cells.append(cell_six) - cell_five.block = testBlock_3 - cell_six.block = testBlock_3 - - testingGrid._generate_image(outfile=filepath, saveToFile=True) - assert os.path.exists(filepath) - if (os.path.exists(filepath)): - os.remove(filepath) diff --git a/tests/exts/fun/test_mathdoku_parser.py b/tests/exts/fun/test_mathdoku_parser.py deleted file mode 100644 index 7464c6973a..0000000000 --- a/tests/exts/fun/test_mathdoku_parser.py +++ /dev/null @@ -1,228 +0,0 @@ -from pathlib import Path - -from bot.exts.fun.mathdoku import Block -from bot.exts.fun.mathdoku_parser import create_grids - -VALID_5x5 = """\ -5x5:d5 (easy) -EAACC -EB3CG -EBF5G -E1FDD -FFFD5 - -A 1- -B 6+ -C 12x -D 8+ -E 30x -F 15+ -G 3- - -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 -""" - -VALID_9x9 = """\ -9x9:d6 (easy) -TTbbVCKKF -eMbNVCK7F -eMWNVRRd9 -QEWBLLRdJ -QEfBaaDDJ -YOfGSaIIZ -YOOGS6hhZ -gPPA6HHUU -gP7AXXccc - -A 14+ -B 2- -C 4- -D 54x -E 2- -F 5- -G 6x -H 5- -I 5+ -J 15x -K 108x -L 10+ -M 1- -N 1- -O 288x -P 17+ -Q 2/ -R 168x -S 8- -T 35x -U 3- -V 13+ -W 4- -X 36x -Y 13+ -Z 1- -a 11+ -b 9+ -c 15+ -d 3/ -e 72x -f 11+ -g 2- -h 3- - -5 7 2 1 8 4 9 3 6 -9 2 6 5 3 8 4 7 1 -8 1 5 4 2 7 3 6 9 -4 6 1 9 7 3 8 2 5 -2 4 8 7 5 1 6 9 3 -6 8 3 2 9 5 1 4 7 -7 9 4 3 1 6 2 5 8 -3 5 9 8 6 2 7 1 4 -1 3 7 6 4 9 5 8 2 -""" - -VALID_5x5_DOUBLE_DIGIT_DIFFICULTY = """\ -5x5:d12 (hard) -EIIAA -ECCDA -JJCDD -HBBFF -HHGGF - -A 15x -B 6+ -C 40x -D 11+ -E 5+ -F 24x -G 5+ -H 10+ -I 1- -J 4+ - -4 2 3 5 1 -1 4 5 2 3 -3 1 2 4 5 -2 5 1 3 4 -5 3 4 1 2 -""" - - -def test_load_valid_5x5_grid(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_5x5, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - assert len(grids) == 1 - grid = grids[5]["easy"][0] - assert grid.difficulty == "easy" - assert grid.size == 5 - assert len(grid.blocks) == 11 - assert grid.cells[0][0].correct == 1 - assert grid.cells[0][0].block.id == "E" - assert grid.cells[0][0].block.operation == "x" - assert grid.cells[0][0].block.number == 30 - - assert grid.cells[4][4].correct == 5 - assert type(grid.cells[4][4].block) is Block - - -def test_load_invalid_5x5_grid(tmp_path: Path) -> None: - """Creates a temp file with an invalid grid in it and attempts to load a grid from that file.""" - content = """\ -5x5:d5 (easy) -EAACC -EB3C -EBF5G -E1FDD -FFFD5 - -A 1- -B 6+ -C 12x -D 8+ -E 30x -F 15+ -G 3- - -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - assert len(grids) == 0 - - -def test_load_valid_5x5_grid_and_check_singletons_have_blocks(tmp_path: Path) -> None: - """Loads a grid and checks that singleton cells have blocks that have correct numbers.""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_5x5, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - grid = grids[5]["easy"][0] - assert type(grid[2][3].block) is Block - assert type(grid[2][3].block.number) is int - - -def test_load_valid_5x5_grid_and_check_singleton_blocks_have_cells(tmp_path: Path) -> None: - """Loads a grid and checks that singleton cells have blocks that contain a list with only them.""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_5x5, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - grid = grids[5]["easy"][0] - cell = grid[2][3] - block = cell.block - assert len(block.cells) == 1 - assert block.cells[0] is cell - - -def test_load_valid_5x5_with_double_digit_difficulty(tmp_path: Path) -> None: - """Loads a grid with a double digit difficulty and checks that it was loaded.""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_5x5_DOUBLE_DIGIT_DIFFICULTY, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - assert len(grids) == 1 - grid = grids[5]["hard"][0] - assert grid.difficulty == "hard" - assert grid.size == 5 - assert len(grid.blocks) == 10 - assert grid.cells[0][1].correct == 2 - assert grid.cells[0][1].block.id == "I" - assert grid.cells[0][1].block.operation == "-" - assert grid.cells[0][1].block.number == 1 - - assert grid.cells[4][3].correct == 1 - - -def test_load_valid_9x9(tmp_path: Path) -> None: - """Loads a 9x9 grid and checks that it was loaded correctly.""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(VALID_9x9, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - assert len(grids) == 1 - grid = grids[9]["easy"][0] - assert grid.difficulty == "easy" - assert grid.size == 9 - assert len(grid.blocks) == 39 - assert grid.cells[0][1].correct == 7 - assert grid.cells[0][1].block.id == "T" - assert grid.cells[0][1].block.operation == "x" - assert grid.cells[0][1].block.number == 35 - - assert grid.cells[4][3].correct == 7 diff --git a/tests/exts/fun/test_recolor_blocks.py b/tests/exts/fun/test_recolor_blocks.py deleted file mode 100644 index b2dc7b19f6..0000000000 --- a/tests/exts/fun/test_recolor_blocks.py +++ /dev/null @@ -1,58 +0,0 @@ -from pathlib import Path - -from bot.exts.fun.mathdoku_parser import create_grids - - -def test_board_filled_handler(tmp_path: Path) -> None: - """Contract: The board should be recolored.""" - filepath1 = "testdokuboardfilledcolor1.png" - filepath2 = "testdokuboardfilledcolor2.png" - filepath3 = "testdokuboardfilledcolor3.png" - - content = """\ -5x5:d5 (easy) -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH - -A 7+ -B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ -I 2- - -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - save_to_disk = False - grid._generate_image(outfile=filepath1, saveToFile=save_to_disk) - - for block in grid.blocks: - block.color = (255, 255, 255) - - grid._generate_image(outfile=filepath2, saveToFile=save_to_disk) - - grid.recolor_blocks() - - grid._generate_image(outfile=filepath3, saveToFile=save_to_disk) diff --git a/tests/exts/fun/test_valid_guess.py b/tests/exts/fun/test_valid_guess.py deleted file mode 100644 index faffad845b..0000000000 --- a/tests/exts/fun/test_valid_guess.py +++ /dev/null @@ -1,60 +0,0 @@ -import os - -from bot.exts.fun.mathdoku import Grid, Block - -def test_addguess(): - # Contract: A guess is applied when its a valid guess. The rows and columns are within limits - # and the guess is within a valid range - - # setup testGrid - testingGrid = Grid(3) - cell_one = testingGrid.cells[0][0] - cell_two = testingGrid.cells[0][1] - cell_three = testingGrid.cells[0][2] - cell_four = testingGrid.cells[1][0] - cell_five = testingGrid.cells[1][1] - cell_six = testingGrid.cells[1][2] - cell_seven = testingGrid.cells[2][0] - cell_eight = testingGrid.cells[2][1] - cell_nine = testingGrid.cells[2][2] - testBlock_1 = Block("A", "+", 3, cell_one, testingGrid) - testBlock_2 = Block("B", "/", 30, cell_four, testingGrid) - testBlock_3 = Block("C", "-", 300, cell_five, testingGrid) - testingGrid.blocks.append(testBlock_1) - testingGrid.blocks.append(testBlock_2) - testingGrid.blocks.append(testBlock_3) - - cell_one.guess = 1 - cell_three.guess = 3 - cell_seven.guess = 4 - - testBlock_1.cells.append(cell_one) - testBlock_1.cells.append(cell_two) - testBlock_1.cells.append(cell_three) - cell_one.block = testBlock_1 - cell_two.block = testBlock_1 - cell_three.block = testBlock_1 - - testBlock_2.cells.append(cell_four) - testBlock_2.cells.append(cell_seven) - testBlock_2.cells.append(cell_eight) - testBlock_2.cells.append(cell_nine) - cell_four.block = testBlock_2 - cell_seven.block = testBlock_2 - cell_eight.block = testBlock_2 - cell_nine.block = testBlock_2 - - testBlock_3.cells.append(cell_five) - testBlock_3.cells.append(cell_six) - cell_five.block = testBlock_3 - cell_six.block = testBlock_3 - - assert cell_three.guess == 3 - testingGrid.add_guess("C1 2") - assert cell_three.guess == 2 - assert testingGrid.add_guess("A2 2") - assert testingGrid.add_guess("D2 2") is False - assert testingGrid.add_guess("A4 2") is False - assert testingGrid.add_guess("A2 4") is False - assert testingGrid.add_guess("A2 0") is False - assert testingGrid.add_guess("A2 -1") is False diff --git a/tests/exts/fun/test_victory_check.py b/tests/exts/fun/test_victory_check.py deleted file mode 100644 index 38603c9852..0000000000 --- a/tests/exts/fun/test_victory_check.py +++ /dev/null @@ -1,177 +0,0 @@ -from pathlib import Path - -from bot.exts.fun.mathdoku_parser import create_grids - - -def test_victory_check_won(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ -5x5:d5 (easy) -EAACC -EB3CG -EBF5G -E1FDD -FFFD5 - -A 1- -B 6+ -C 12x -D 8+ -E 30x -F 15+ -G 3- - -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - assert grid.check_victory() - - -def test_victory_check_lost(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ -5x5:d5 (easy) -EAACC -EB3CG -EBF5G -E1FDD -FFFD5 - -A 1- -B 6+ -C 12x -D 8+ -E 30x -F 15+ -G 3- - -1 5 4 3 2 -5 4 3 2 1 -3 2 1 5 4 -2 1 5 4 3 -4 3 2 1 5 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - grid.cells[0][1].guess = 4 - - assert grid.check_victory() is False - - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) > 0 - - assert grid._blocks_fufilled_check()[0].id == "A" - - -def test_victory_check_won_with_division(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ -5x5:d5 (easy) -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH - -A 7+ -B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ -I 2- - -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - assert grid.check_victory() - - -def test_victory_check_lost_with_division(tmp_path: Path) -> None: - """Creates a temp file with a valid grid in it and attempts to load a valid grid from that file.""" - content = """\ -5x5:d5 (easy) -FFFF5 -AAE1B -CCEGB -CDIGG -3DIHH - -A 7+ -B 2- -C 9+ -D 5+ -E 2/ -F 10+ -G 40x -H 2/ -I 2- - -4 2 1 3 5 -2 5 4 1 3 -5 3 2 4 1 -1 4 3 5 2 -3 1 5 2 4 -""" - grids_file = tmp_path / "grids.txt" - grids_file.write_text(content, encoding="utf-8") - - grids = create_grids(file_path=grids_file) - - grid = grids[5]["easy"][0] - - # Set guess to possible solution - for row in grid.cells: - for cell in row: - cell.guess = cell.correct - - grid.cells[4][4].guess = 2 - - assert grid.check_victory() is False - - w_rows, w_cols = grid._latin_square_check() - assert len(w_rows) + len(w_cols) > 0 - - assert grid._blocks_fufilled_check()[0].id == "H" diff --git a/tests/test_pytest.py b/tests/test_pytest.py deleted file mode 100644 index 7c5440e3cd..0000000000 --- a/tests/test_pytest.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -How to run pytest: -For venv: -1. python venv venv -2. add venv or uv or install all dependecies uising: -pip install pydis-core[all]==11.8.0 arrow==1.3.0 -beautifulsoup4==4.12.3 colorama==0.4.6 coloredlogs==15.0.1 -emoji==2.14.0 emojis==0.7.0 lxml==6.0.0 pillow==12.1.1 pydantic==2.10.1 -pydantic-settings==2.8.1 pyjokes==0.8.3 PyYAML==6.0.2 rapidfuzz==3.12.2 sentry-sdk==2.19.2 - -3. run pip install pytest -4. run this to actually run the tests: python -m pytest -s -""" - - -def test_pytest(): - """A simple sanity check to ensure pytest is working.""" - assert 3 == 3 \ No newline at end of file From 90d68d3460781b15e6a7381ca48638c24eec9348 Mon Sep 17 00:00:00 2001 From: daDevBoat <113507675+daDevBoat@users.noreply.github.com> Date: Mon, 2 Mar 2026 12:57:47 +0100 Subject: [PATCH 74/74] refactor: run linter on files (#149) closes #147 Co-authored-by: Jonte --- bot/exts/fun/mathdoku.py | 177 +++++++++++++++++++-------- bot/exts/fun/mathdoku_integration.py | 59 +++++---- bot/exts/fun/mathdoku_parser.py | 1 + 3 files changed, 156 insertions(+), 81 deletions(-) diff --git a/bot/exts/fun/mathdoku.py b/bot/exts/fun/mathdoku.py index ac812c54cc..1d6fa4cab1 100644 --- a/bot/exts/fun/mathdoku.py +++ b/bot/exts/fun/mathdoku.py @@ -1,33 +1,102 @@ -from datetime import datetime +from datetime import UTC, datetime from io import BytesIO from random import randint + from PIL import Image, ImageDraw, ImageFont + from bot.bot import Bot + async def setup(bot: Bot) -> None: """Setup function to avoid erros.""" return + COLORS = [ - (255, 50, 50), (255, 66, 50), (255, 81, 50), (255, 96, 50), (255, 111, 50), - (255, 126, 50), (255, 141, 50), (255, 156, 50), (255, 171, 50), (255, 187, 50), - (255, 202, 50), (255, 217, 50), (255, 232, 50), (255, 247, 50), (247, 255, 50), - (232, 255, 50), (217, 255, 50), (202, 255, 50), (187, 255, 50), (171, 255, 50), - (156, 255, 50), (141, 255, 50), (126, 255, 50), (111, 255, 50), (96, 255, 50), - (81, 255, 50), (66, 255, 50), (50, 255, 50), (50, 255, 66), (50, 255, 81), - (50, 255, 96), (50, 255, 111), (50, 255, 126), (50, 255, 141), (50, 255, 156), - (50, 255, 171), (50, 255, 186), (50, 255, 202), (50, 255, 217), (50, 255, 232), - (50, 255, 247), (50, 247, 255), (50, 232, 255), (50, 217, 255), (50, 202, 255), - (50, 186, 255), (50, 171, 255), (50, 156, 255), (50, 141, 255), (50, 126, 255), - (50, 111, 255), (50, 96, 255), (50, 81, 255), (50, 66, 255), (50, 50, 255), - (66, 50, 255), (81, 50, 255), (96, 50, 255), (111, 50, 255), (126, 50, 255), - (141, 50, 255), (156, 50, 255), (171, 50, 255), (187, 50, 255), (202, 50, 255), - (217, 50, 255), (232, 50, 255), (247, 50, 255), (255, 50, 247), (255, 50, 232), - (255, 50, 217), (255, 50, 202), (255, 50, 187), (255, 50, 171), (255, 50, 156), - (255, 50, 141), (255, 50, 126), (255, 50, 111), (255, 50, 96), (255, 50, 81), + (255, 50, 50), + (255, 66, 50), + (255, 81, 50), + (255, 96, 50), + (255, 111, 50), + (255, 126, 50), + (255, 141, 50), + (255, 156, 50), + (255, 171, 50), + (255, 187, 50), + (255, 202, 50), + (255, 217, 50), + (255, 232, 50), + (255, 247, 50), + (247, 255, 50), + (232, 255, 50), + (217, 255, 50), + (202, 255, 50), + (187, 255, 50), + (171, 255, 50), + (156, 255, 50), + (141, 255, 50), + (126, 255, 50), + (111, 255, 50), + (96, 255, 50), + (81, 255, 50), + (66, 255, 50), + (50, 255, 50), + (50, 255, 66), + (50, 255, 81), + (50, 255, 96), + (50, 255, 111), + (50, 255, 126), + (50, 255, 141), + (50, 255, 156), + (50, 255, 171), + (50, 255, 186), + (50, 255, 202), + (50, 255, 217), + (50, 255, 232), + (50, 255, 247), + (50, 247, 255), + (50, 232, 255), + (50, 217, 255), + (50, 202, 255), + (50, 186, 255), + (50, 171, 255), + (50, 156, 255), + (50, 141, 255), + (50, 126, 255), + (50, 111, 255), + (50, 96, 255), + (50, 81, 255), + (50, 66, 255), + (50, 50, 255), + (66, 50, 255), + (81, 50, 255), + (96, 50, 255), + (111, 50, 255), + (126, 50, 255), + (141, 50, 255), + (156, 50, 255), + (171, 50, 255), + (187, 50, 255), + (202, 50, 255), + (217, 50, 255), + (232, 50, 255), + (247, 50, 255), + (255, 50, 247), + (255, 50, 232), + (255, 50, 217), + (255, 50, 202), + (255, 50, 187), + (255, 50, 171), + (255, 50, 156), + (255, 50, 141), + (255, 50, 126), + (255, 50, 111), + (255, 50, 96), + (255, 50, 81), (255, 50, 66), ] + class Cell: """Represent a single cell in the grid.""" @@ -40,15 +109,17 @@ def __init__(self, column: int, row: int) -> None: self.color = None @property - def guess(self): + def guess(self) -> int: + """Returns the current guess of the cell.""" return self._guess @guess.setter - def guess(self, new_guess) -> None: + def guess(self, new_guess: int) -> None: + """Sets the current guess of the cell.""" self._guess = new_guess def reset_color(self) -> None: - """Reset a cells color attribute to its block color""" + """Reset a cells color attribute to its block color.""" self.color = self.block.color @@ -65,11 +136,12 @@ def __init__(self, id: str, operation: str, number: int, label_cell: Cell, grid: self.color_id = grid.current_color_id grid.current_color_id += 1 self.color = self.compute_color() - def compute_color(self) -> tuple[int, int, int]: """Compute the block's color.""" - return COLORS[(self.color_id * (len(COLORS) // (self.grid.current_color_id)) + self.grid.color_offset) % len(COLORS)] + return COLORS[ + (self.color_id * (len(COLORS) // (self.grid.current_color_id)) + self.grid.color_offset) % len(COLORS) + ] class Grid: @@ -89,7 +161,8 @@ def __init__(self, size: int, difficulty: str | None = None) -> None: ) # 2D tupple for cells [row][col] @property - def cells(self): + def cells(self) -> tuple[tuple[Cell]]: + """Returns the 2d array containing the cells of the grid.""" return self._cells def __str__(self) -> str: @@ -103,8 +176,8 @@ def __str__(self) -> str: def _latin_square_check(self) -> list[set[int], set[int]]: """ - Check if the grid is filled correctly in terms of a latin square.\n - + Check if the grid is filled correctly in terms of a latin square.\n. + I.e all numbers in the range per colum and row exist. """ check_row_structure = [[False for col in range(self.size)] for row in range(self.size)] @@ -134,7 +207,7 @@ def _latin_square_check(self) -> list[set[int], set[int]]: def _blocks_fufilled_check(self) -> list[Block]: """ - Check if all the blocks are filled correctly and meet the requirements. \n + Check if all the blocks are filled correctly and meet the requirements. \n. Return the blocks that are wrong or True if all blocks meet the requirements. \n Return False if the input is invalid. @@ -180,7 +253,7 @@ def check_victory(self) -> bool: def board_filled_handler(self) -> bool: """ - Handler for when board is filled.\n + Handler for when board is filled.\n. Calls the latin_square_check and blocks_fufilled_check and colors in\n the wrong rows, cols and blocks in red. The rest in green. \n @@ -190,7 +263,7 @@ def board_filled_handler(self) -> bool: for row in self.cells: for cell in row: cell.color = (100, 255, 100) - + # Find the wrong rows and cols in terms of latin square wrong_rows, wrong_cols = self._latin_square_check() @@ -210,7 +283,7 @@ def board_filled_handler(self) -> bool: # Find the wrong blocks only if now wrong latin rows or cols wrong_blocks = self._blocks_fufilled_check() - + # Color in the wrong blocks for block in self.blocks: if block in wrong_blocks: @@ -245,40 +318,36 @@ def __getitem__(self, i: int) -> list[Cell]: return self.cells[i] def _generate_image( - self, - cellSize:int =80, - margin:int = 30, - outfile:str = "mathdoku.png", - saveToFile:bool = False + self, cell_size: int = 80, margin: int = 30, outfile: str = "mathdoku.png", save_to_file: bool = False ) -> BytesIO: - """Generate an image from the Grid, return a buffer holding the image""" - fontLable = ImageFont.load_default(15) - fontGuess = ImageFont.load_default(30) + """Generate an image from the Grid, return a buffer holding the image.""" + font_label = ImageFont.load_default(15) + font_guess = ImageFont.load_default(30) img = Image.new( - "RGB", (cellSize * len(self.cells) + 2 * margin, cellSize * len(self.cells) + 2 * margin), "white" + "RGB", (cell_size * len(self.cells) + 2 * margin, cell_size * len(self.cells) + 2 * margin), "white" ) draw = ImageDraw.Draw(img) for i, row in enumerate(self.cells): for j, cell in enumerate(row): # 1) The block color - x_start = (cell.column) * cellSize + margin + margin // 2 - y_start = (cell.row) * cellSize + margin + margin // 2 - x_end = (cell.column) * cellSize + cellSize + margin + margin // 2 - y_end = (cell.row) * cellSize + cellSize + margin + margin // 2 + x_start = (cell.column) * cell_size + margin + margin // 2 + y_start = (cell.row) * cell_size + margin + margin // 2 + x_end = (cell.column) * cell_size + cell_size + margin + margin // 2 + y_end = (cell.row) * cell_size + cell_size + margin + margin // 2 color = cell.color draw.rectangle((x_start, y_start, x_end, y_end), fill=color) # 2) the guess guess = cell.guess if guess != 0: - draw.text((x_start + 30, y_start + 22), str(guess), fill="black", font=fontGuess) + draw.text((x_start + 30, y_start + 22), str(guess), fill="black", font=font_guess) # 3) the lines between the cells thin_line_width = 2 - draw.line((x_start, y_start, x_start + cellSize, y_start), fill="black", width=thin_line_width) + draw.line((x_start, y_start, x_start + cell_size, y_start), fill="black", width=thin_line_width) draw.line((x_end, y_start, x_end, y_end), fill="black", width=thin_line_width) - draw.line((x_start, y_start, x_start, y_start + cellSize), fill="black", width=thin_line_width) + draw.line((x_start, y_start, x_start, y_start + cell_size), fill="black", width=thin_line_width) draw.line((x_start, y_end, x_end, y_end), fill="black", width=thin_line_width) n_over = 1 @@ -313,22 +382,22 @@ def _generate_image( # 4) the lable of the block - in the top left corner of the lable cell label_cell = block.label_cell label = str(block.number) + " " + str(block.operation) - x_start = (label_cell.column) * cellSize + margin + margin // 2 - y_start = (label_cell.row) * cellSize + margin + margin // 2 + x_start = (label_cell.column) * cell_size + margin + margin // 2 + y_start = (label_cell.row) * cell_size + margin + margin // 2 # print("Label:" + label ) - draw.text((x_start + 4, y_start + 2), str(label), fill="black", font=fontLable) + draw.text((x_start + 4, y_start + 2), str(label), fill="black", font=font_label) # 5) the x axis description A-... for i in range(self.size): text = chr(ord("A") + i) - draw.text((margin + 30 + i * cellSize + margin // 2, 4), str(text), fill="black", font=fontGuess) + draw.text((margin + 30 + i * cell_size + margin // 2, 4), str(text), fill="black", font=font_guess) # 6) the y axis description 1-... for j in range(self.size): text = str(j + 1) - draw.text((13, margin + 22 + j * cellSize + margin // 2), str(text), fill="black", font=fontGuess) + draw.text((13, margin + 22 + j * cell_size + margin // 2), str(text), fill="black", font=font_guess) - if saveToFile: + if save_to_file: img.save(outfile) buffer = BytesIO() @@ -347,7 +416,7 @@ def _find_first_empty_cell(self) -> Cell | None: def hint(self, now: datetime | None = None) -> dict: """Return a hint for the first empty cell, or cooldown/all-filled info if a hint cannot be given.""" - current_time = datetime.now() if now is None else now + current_time = datetime.now(UTC) if now is None else now if self._last_hint_timestamp is not None: elapsed = (current_time - self._last_hint_timestamp).total_seconds() @@ -371,9 +440,9 @@ def hint(self, now: datetime | None = None) -> dict: "guess": f"{coord} {cell.correct}", } - def add_guess(self, guess) -> bool: + def add_guess(self, guess: str) -> bool: """ - Take the user guess and check if its valid, if it is -> add to cell + Take the user guess and check if its valid, if it is -> add to cell. A guess is in format A5 4, where A = column, 5 = row and 4 = guessed value. """ diff --git a/bot/exts/fun/mathdoku_integration.py b/bot/exts/fun/mathdoku_integration.py index 829122b4be..4387f7fa99 100644 --- a/bot/exts/fun/mathdoku_integration.py +++ b/bot/exts/fun/mathdoku_integration.py @@ -1,28 +1,26 @@ import asyncio import re +from copy import deepcopy from random import choice - import discord from discord.ext import commands from pydis_core.utils.logging import get_logger -from copy import deepcopy - from bot.bot import Bot CROSS_EMOJI = "\u274c" # "\u274e" MAGNIFYING_EMOJI = "๐Ÿ”" PARTY_EMOJI = "๐ŸŽ‰" HINT_EMOJI = "๐Ÿ’ก" -RULE_EMOJI = '๐Ÿ“•' +RULE_EMOJI = "๐Ÿ“•" mathdoku_rules = """Rules for Playing Mathdoku -The numbers you can enter on the board depend on the size of the board you selected. If you choose an n ร— n -board, you may use the numbers 1, 2, โ€ฆ, n. For example, on a 3 ร— 3 board you can use the numbers 1, 2, and 3. +The numbers you can enter on the board depend on the size of the board you selected. If you choose an n x n +board, you may use the numbers 1, 2, โ€ฆ, n. For example, on a 3 x 3 board you can use the numbers 1, 2, and 3. The board is divided into cages, which are groups of squares outlined with a thick border. In the top-left -corner of each cage, a target number and a mathematical operation (+, โˆ’, ร—, รท) are shown. The numbers you +corner of each cage, a target number and a mathematical operation (+, -, x, รท) are shown. The numbers you place in that cage must combine (using the indicated operation) to produce the target number. The operation may be applied in any order. @@ -34,7 +32,9 @@ The emojis attached to the board have different functions as well: -๐Ÿ” Check: This emoji only appears when the board is full. When pressed, it checks whether the board's conditions are fulfilled. If they are, the board turns green and a victory message is shown. If not, the cages that are not fulfilled turn red. +๐Ÿ” Check: This emoji only appears when the board is full. When pressed, it checks whether the board's conditions are +fulfilled. If they are, the board turns green and a victory message is shown. If not, the cages that are not fulfilled +turn red. ๐Ÿ’ก Hint: This emoji provides a helpful hint to assist the player in solving the puzzle. It has a cooldown of 3 minutes. @@ -64,8 +64,8 @@ async def mathdoku_group(self, ctx: commands.Context) -> None: await self.bot.invoke_help_command(ctx) @mathdoku_group.command(name="start") - async def start_command(self, ctx: commands.Context, size: int = 5, difficulty:str = "medium") -> None: - """Start a game of Mathdoku. Size = the board size (3-9). Difficulty = easy, medium or hard""" + async def start_command(self, ctx: commands.Context, size: int = 5, difficulty: str = "medium") -> None: + """Start a game of Mathdoku. Size = the board size (3-9). Difficulty = easy, medium or hard.""" size = int(size) difficulty = str(difficulty).lower() @@ -73,27 +73,32 @@ async def start_command(self, ctx: commands.Context, size: int = 5, difficulty:s await ctx.send("Someone else is playing right now. Please wait your turn.") return - if size not in [3,4,5,6,7,8,9]: + if size not in [3, 4, 5, 6, 7, 8, 9]: await ctx.send("Please give a valid size between 3 and 9") return - + if difficulty not in ["easy", "medium", "hard"]: await ctx.send("Please give a valid difficulty: easy, medium or hard") return - + grids_available = self.grids[size][difficulty] if len(grids_available) < 1: - await ctx.send("Couldn't find any boards for size: " + size + " and difficulty: " + difficulty + ". Sorry :/") + await ctx.send( + "Couldn't find any boards for size: " + size + " and difficulty: " + difficulty + ". Sorry :/" + ) return - + self.playing = True self.player_id = ctx.author.id - self.grid = deepcopy(choice(grids_available)) # get a random grid from the available ones for this size / difficulty + self.grid = deepcopy( + choice(grids_available) + ) # get a random grid from the available ones for this size / difficulty await ctx.send("Game of Mathdoku has been started!") await ctx.send( - "Press ๐Ÿ” to check if the board's conditions are met (only appears when the board is full)\n" + "Press ๐Ÿ” to check if the board's conditions are met (only appears when the board is full)\n" "Press ๐Ÿ’ก to get a helpful hint on how to solve the puzzle\n" - "Press ๐Ÿ“• to get the rules of the Mathdoku game") + "Press ๐Ÿ“• to get the rules of the Mathdoku game" + ) file = discord.File(self.grid._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) @@ -130,7 +135,7 @@ async def input_number_on_board( await ctx.send("You took too long. Game over!") self.playing = False return - + except Exception: return @@ -210,8 +215,8 @@ def reaction_predicate(self, reaction: discord.Reaction, user: discord.User) -> return True return None - async def magnifying_handler(self, ctx:commands.Context, user:discord.User) -> None: - """Handle the magnifiyng glass emoji. Handle board check and Win action""" + async def magnifying_handler(self, ctx: commands.Context, user: discord.User) -> None: + """Handle the magnifiyng glass emoji. Handle board check and Win action.""" if self.grid.check_full_grid(): await self.board.remove_reaction(MAGNIFYING_EMOJI, user) @@ -219,7 +224,7 @@ async def magnifying_handler(self, ctx:commands.Context, user:discord.User) -> N file = discord.File(self.grid._generate_image(), filename="mathdoku.png") await self.board.edit(content=None, attachments=[file]) - if result: # WIN + if result: # WIN await self.board.add_reaction(PARTY_EMOJI) await ctx.send(PARTY_EMOJI + " Congrats! You WON " + PARTY_EMOJI) self.playing = False @@ -238,24 +243,24 @@ async def hint_handler(self, ctx: commands.Context, user: discord.User) -> None: else: await ctx.send(f"Hint: {result['guess']}") - async def rules_handler(self, ctx:commands.Context, user:discord.User) -> None: + async def rules_handler(self, ctx: commands.Context, user: discord.User) -> None: """Handle rules request via ๐Ÿ“• reaction.""" await self.board.remove_reaction(RULE_EMOJI, user) self.rules_msg = await ctx.send(mathdoku_rules) self.rules_msg_exists = True - async def resent_message(self, ctx:commands.Context) -> None: - """Delete the board message and send again""" + async def resent_message(self, ctx: commands.Context) -> None: + """Delete the board message and send again.""" await self.board.delete() self.grid.recolor_blocks() file = discord.File(self.grid._generate_image(), filename="mathdoku.png") self.board = await ctx.send(file=file) await self.board.add_reaction(HINT_EMOJI) await ctx.send( - "Type the square and what number you want to input. Format it like this: A1 3\n" - "Type `end` to end game." + "Type the square and what number you want to input. Format it like this: A1 3\n" + "Type `end` to end game." ) + async def setup(bot: Bot) -> None: """Load the Mathdoku cog.""" from .mathdoku_parser import create_grids diff --git a/bot/exts/fun/mathdoku_parser.py b/bot/exts/fun/mathdoku_parser.py index bef72e77c8..e3da1996b2 100644 --- a/bot/exts/fun/mathdoku_parser.py +++ b/bot/exts/fun/mathdoku_parser.py @@ -5,6 +5,7 @@ from .mathdoku import Block, Grid +# The boards/grids were generated using this repo: https://github.com/ghfbsd/kenken-maker FILE_PATH = Path("bot/resources/fun/mathdoku_boards.txt")