diff --git a/README.md b/README.md index 9c5e69a..872ad78 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ This is a package for manipulating polyominos and in particular, solving tiling To solve a tiling problem, you need to create a 'board', the set of squares to be covered, and a 'tileset', the collection of polyominos which can be used. There are examples of the syntax to do this in examples/fluid.md The example file examples/gardner.md uses the package to solve a number of problems from the chapter on polyominos from Martin Gardner's book 'Mathematical Puzzles and Diversions'. +The folder examples/ also has a couple of Jupyter notebooks: these show how you can work with basic boards and solutions, and how they can be displayed in a notebook. The notebook display should work out-of-the-box. + Note that each tile can play one of several roles in a tiling problem. It could be a tile which can only appear once, as in problems like covering a chessboard with one copy of each pentomino and a square tetromino. It could be a tile which can be used an arbitrary number of times. Problems like this are often simply to cover a shape completely with copies of a single polyomino. Finally, it could be used either once or not at all. When constructing a tileset, it is possible to include tiles in any of these three classes. ## Design diff --git a/examples/.ipynb_checkpoints/display_boards-checkpoint.ipynb b/examples/.ipynb_checkpoints/display_boards-checkpoint.ipynb new file mode 100644 index 0000000..363fcab --- /dev/null +++ b/examples/.ipynb_checkpoints/display_boards-checkpoint.ipynb @@ -0,0 +1,6 @@ +{ + "cells": [], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/.ipynb_checkpoints/display_solutions-checkpoint.ipynb b/examples/.ipynb_checkpoints/display_solutions-checkpoint.ipynb new file mode 100644 index 0000000..fd47e8a --- /dev/null +++ b/examples/.ipynb_checkpoints/display_solutions-checkpoint.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "c5f94563", + "metadata": {}, + "outputs": [], + "source": [ + "from polyomino.board import Chessboard, Irregular\n", + "from polyomino.constant import TETROMINOS, ALL_PENTOMINOS" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a990b439", + "metadata": {}, + "outputs": [], + "source": [ + "solution = Chessboard().tile_with(ALL_PENTOMINOS + [TETROMINOS['Square']]).solve()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "24061091", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAKlBMVEUAAADHAmpXhq27nv0P09O+aOhDO+yTXJ/3b0Je9l31av2rSvJGyVnRuL77hKSIAAAAYklEQVR4nO3Mtw3AMBAEQULekOq/XGXP5HCQ0udOAVOmMP+2CIWQMHmorBYh4TjhFnyzh8MiJMweertFSJg9PIXrI0LCccI7qLqG3rTwCISE2UNV97BZhIQjhtVSDSFh8vAFbS7Jkaszv9kAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "solution" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "076e18a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-+-+-+-+-+-+-+-+\n", + "| | | |\n", + "+ +-+-+-+-+-+ +\n", + "| | | |\n", + "+-+ +-+ +-+-+-+-+\n", + "| | | | | |\n", + "+ +-+-+-+ +-+-+ +\n", + "| | | |\n", + "+-+-+-+-+-+-+ + +\n", + "| | | | |\n", + "+-+ +-+-+ +-+-+-+\n", + "| | | | | | |\n", + "+ +-+ + +-+ +-+ +\n", + "| | | | | |\n", + "+ +-+ +-+ +-+ +\n", + "| | | | |\n", + "+-+-+-+-+-+-+-+-+\n" + ] + } + ], + "source": [ + "print(solution.display())" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "45085dd3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHgAAACgCAMAAADw11iiAAAAk1BMVEUAAACBwY3ac/hk0/mbAEoJ9QGTXJ7ydEoWUaeNs7AXWTtcDgONxtiUs2IrEdqBgtgbhHCs6SGq72YpD6d4r8iWYLJ1mXWi2op3zdOIlOALFnivsiUhhdoFzLH3PGk3RDY4tweuj5Rka49aRhpf1ElPVhqFkRlyJ7oUG+/958OetdD6VNMXulL7EKfxKRLFMco+FIFhhjlHAAABL0lEQVR4nO3OSVJCURQEUcNeOhUUBLEDsRdk/5tzVjWpeIMfd5i5gZMHh82O1HGzE3WqzpodAAMDA3eGz1Xi3IVKXILTQk8BAwMDV8CurxLsBirBQ9ULAQMDA1fA/WbmRmoQGoYuFTAwMHAFfNXsWo1C5sZqohLsgIGBgbvDNypx7lYZHocmIXNTBQwMDFwBu5lKsLtTCZ6raQgYGBi4Al6oWShxqXnoXgEDAwNXwIuQuaV6aPaonlSCHTAwMHB3+FkZXoZeVOIS7MytFDAwMHAF7NYqwe5VJXijViFgYGDgCnjdLHGpTehNAQMDA1fA780+1GezL/WtEuyAgYGBu8M/KnHuVyUuwc7cVgEDAwNXwG6nEuz+VIL3ahsCBgYGroB3zRKX2jcDBgYGLoD/ASi7eOqIz7E2AAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "board = Irregular([(i, j) for i in range(0, 12) for j in range(0, 16)])\n", + "problem = board.tile_with_many(TETROMINOS['T'])\n", + "problem.solve()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba1efa04", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/display_boards.ipynb b/examples/display_boards.ipynb new file mode 100644 index 0000000..6fad8b3 --- /dev/null +++ b/examples/display_boards.ipynb @@ -0,0 +1,154 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "id": "2f48725f", + "metadata": {}, + "outputs": [], + "source": [ + "from polyomino.board import Chessboard\n", + "from polyomino.constant import DOMINO" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "325f7427", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGMAAABjCAMAAAC45U6nAAAABlBMVEX///8AAABVwtN+AAAAV0lEQVR4nO3ZyQ0AIAgAQe2/aTsgGokHmS2A4eNHWpP0Wj2n2MjZlMFgLE2ZfaFbRspkBoPBYDAYDAaDwWAwGAwGg8FgMKob8a/93xcJBqOAceBqLulGA1OUAUFeFCJvAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Chessboard().remove((0, 0)).remove((7, 7))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c9cc6b80", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGMAAABjCAMAAAC45U6nAAAABlBMVEX///8AAABVwtN+AAAAV0lEQVR4nO3ZsQ0AIAgAQdx/aTcwGJFQ3LcmnI2NREia1qrpbNTclMFgXE3JvtAno2Qyg8FgMBgMBoPBYDAYDAaDwWAwGIONhv/27CmDwfhuNOzPJfW1AVLMAUGKU2ZPAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Chessboard().remove((0, 0)).remove((0, 7))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "1d7225b7", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAYFBMVEUAAADRfMe128vfBLykVLaAn4yORk/ED0Y7BRi4APlDH8DH99yFDqL6Syfy7QqxZoaXsVkrI/w6ikMOoXxywYIZDnn655alzz6GxzDOGxJq621IrlMSBZmPuhg0uoxthljQxCNxAAAAb0lEQVR4nO3MSQ6CQAAAwYmoKCKLiops/v+V3JoTyZwnXQ+oEHBAhiNOOCPHBcHQMMnwCkNDw5iwwA0l7qhQo4GhYZphC0NDw5jwgSde6PDGB18YGqYZ9jA0NIwJfxgwYsKMBX8YGqYZbgwNDXfDFSiJm8jaDSAvAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Chessboard().remove((0, 0)).remove((0, 7)).tile_with_many(DOMINO).solve()" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "722aee60", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGMAAABjCAMAAAC45U6nAAAABlBMVEX///8AAABVwtN+AAAAZklEQVR4nO3ZSQrAIBAAQf3/pz0L4gLjAlYfQ6DMYQRNSpLeKEfXMqLXzGAwGAwGg8FgMBgMxldG/9wdZEy+x2AwNhmNOV+/c1vfr/rfxmAwthsH5nywAgaDwWAwGPWzA//PJd2oACSvAZFe6vBVAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Chessboard().remove((3, 4)).remove((5, 5))" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "a8b83b0a", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAYFBMVEUAAAD/WpECZ4JtsHwtqH/UjXsc4mfFa/yqRlVFJ7raNUcvOyNN8Q0Hhde1j2rAMtm5mlOnufGBFbMsR9seR9flE24JuRvEDZodeWqCj4X3DFETOpC+wkNJ57OeZkN2KD3VpDDiAAAAdElEQVR4nO3N2xWCMADA0B4FBBFUQPCJ7j+lf+lPJ6C5AyRhhz0KlKhwQI0GwaBBgwYNZhs8osUJHXqcccEVBg0aNGgw3+CAERMCbpgTFhg0mGPwjhiMkwcMGtx68IkX3ojBT0IMrjBoMMfgN+GXYNDgxoN/pFKbyN9fg4YAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Chessboard().remove((3, 4)).remove((5, 5)).tile_with_many(DOMINO).solve()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eeb51823", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/display_solutions.ipynb b/examples/display_solutions.ipynb new file mode 100644 index 0000000..fd47e8a --- /dev/null +++ b/examples/display_solutions.ipynb @@ -0,0 +1,134 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "c5f94563", + "metadata": {}, + "outputs": [], + "source": [ + "from polyomino.board import Chessboard, Irregular\n", + "from polyomino.constant import TETROMINOS, ALL_PENTOMINOS" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a990b439", + "metadata": {}, + "outputs": [], + "source": [ + "solution = Chessboard().tile_with(ALL_PENTOMINOS + [TETROMINOS['Square']]).solve()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "24061091", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAMAAAC5zwKfAAAAKlBMVEUAAADHAmpXhq27nv0P09O+aOhDO+yTXJ/3b0Je9l31av2rSvJGyVnRuL77hKSIAAAAYklEQVR4nO3Mtw3AMBAEQULekOq/XGXP5HCQ0udOAVOmMP+2CIWQMHmorBYh4TjhFnyzh8MiJMweertFSJg9PIXrI0LCccI7qLqG3rTwCISE2UNV97BZhIQjhtVSDSFh8vAFbS7Jkaszv9kAAAAASUVORK5CYII=\n", + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "solution" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "076e18a7", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "+-+-+-+-+-+-+-+-+\n", + "| | | |\n", + "+ +-+-+-+-+-+ +\n", + "| | | |\n", + "+-+ +-+ +-+-+-+-+\n", + "| | | | | |\n", + "+ +-+-+-+ +-+-+ +\n", + "| | | |\n", + "+-+-+-+-+-+-+ + +\n", + "| | | | |\n", + "+-+ +-+-+ +-+-+-+\n", + "| | | | | | |\n", + "+ +-+ + +-+ +-+ +\n", + "| | | | | |\n", + "+ +-+ +-+ +-+ +\n", + "| | | | |\n", + "+-+-+-+-+-+-+-+-+\n" + ] + } + ], + "source": [ + "print(solution.display())" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "45085dd3", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAHgAAACgCAMAAADw11iiAAAAk1BMVEUAAACBwY3ac/hk0/mbAEoJ9QGTXJ7ydEoWUaeNs7AXWTtcDgONxtiUs2IrEdqBgtgbhHCs6SGq72YpD6d4r8iWYLJ1mXWi2op3zdOIlOALFnivsiUhhdoFzLH3PGk3RDY4tweuj5Rka49aRhpf1ElPVhqFkRlyJ7oUG+/958OetdD6VNMXulL7EKfxKRLFMco+FIFhhjlHAAABL0lEQVR4nO3OSVJCURQEUcNeOhUUBLEDsRdk/5tzVjWpeIMfd5i5gZMHh82O1HGzE3WqzpodAAMDA3eGz1Xi3IVKXILTQk8BAwMDV8CurxLsBirBQ9ULAQMDA1fA/WbmRmoQGoYuFTAwMHAFfNXsWo1C5sZqohLsgIGBgbvDNypx7lYZHocmIXNTBQwMDFwBu5lKsLtTCZ6raQgYGBi4Al6oWShxqXnoXgEDAwNXwIuQuaV6aPaonlSCHTAwMHB3+FkZXoZeVOIS7MytFDAwMHAF7NYqwe5VJXijViFgYGDgCnjdLHGpTehNAQMDA1fA780+1GezL/WtEuyAgYGBu8M/KnHuVyUuwc7cVgEDAwNXwG6nEuz+VIL3ahsCBgYGroB3zRKX2jcDBgYGLoD/ASi7eOqIz7E2AAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "board = Irregular([(i, j) for i in range(0, 12) for j in range(0, 16)])\n", + "problem = board.tile_with_many(TETROMINOS['T'])\n", + "problem.solve()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba1efa04", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/more.md b/examples/more.md index aa30f08..e310041 100644 --- a/examples/more.md +++ b/examples/more.md @@ -5,7 +5,7 @@ ``` ```python ->>> board = Irregular([(i, j) for i in range(0, 12) for j in range(0, 16)]) +>>> board = Rectangle(12, 16) >>> problem = board.tile_with_many(TETROMINOS['T']) >>> solution = problem.solve() >>> print(solution.display()) @@ -105,4 +105,4 @@ | | | | +-+ +-+ -``` \ No newline at end of file +``` diff --git a/polyomino/board.py b/polyomino/board.py index 155a186..91c9011 100644 --- a/polyomino/board.py +++ b/polyomino/board.py @@ -2,6 +2,7 @@ from pretty_poly import make_ascii +from .jupyter import board_to_png from .problem import TilingProblem from .tileset import exactly, many from .transform import rotations @@ -77,6 +78,9 @@ def format_tiling(self, pieces): remainder = set(self.squares) - set(squares_in_pieces) return make_ascii(pieces + [remainder]) + def _repr_png_(self): + return board_to_png(self) + class Irregular(Shape): _adjusted = None diff --git a/polyomino/jupyter.py b/polyomino/jupyter.py new file mode 100644 index 0000000..2ab159a --- /dev/null +++ b/polyomino/jupyter.py @@ -0,0 +1,20 @@ +import io + +from pretty_poly.image import make_colored_blocks, make_lines +from pretty_poly.png import _make_png_writer + + +def solution_to_png(solution): + data, palette = make_colored_blocks(solution.tiling) + writer = _make_png_writer(data, palette) + f = io.BytesIO() + writer.write(f, data) + return f.getvalue() + + +def board_to_png(board): + data, palette = make_lines([board.squares]) + writer = _make_png_writer(data, palette) + f = io.BytesIO() + writer.write(f, data) + return f.getvalue() diff --git a/polyomino/solution.py b/polyomino/solution.py index d7a55b1..d6b4f24 100644 --- a/polyomino/solution.py +++ b/polyomino/solution.py @@ -1,5 +1,7 @@ from pretty_poly import make_ascii +from .jupyter import solution_to_png + class Solution(object): def __init__(self, tiling, board): @@ -14,3 +16,6 @@ def python(self): def _gen_python(self): yield "TILING = %s" % (repr(self.tiling),) + + def _repr_png_(self): + return solution_to_png(self) diff --git a/pyproject.toml b/pyproject.toml index 112ac61..930df47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "polyomino" -version = "0.7.0" +version = "0.7.1a0" description = "Solve polyomino tiling problems." readme = "README.md" authors = ["Jack Grahl "] @@ -9,21 +9,22 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.7.1" exact-cover = "1.3.0" -pretty-poly = "0.2.0" - -[tool.poetry.dev-dependencies] -pytest = "^6.2.1" -hypothesis = "^6.0.2" -pytest-profiling = "^1.7.0" -black = "^21.6b0" +pretty-poly = "0.5.1" [tool.poetry.scripts] test = 'run_tests:run_tests' doctest = 'run_tests:run_doctests' [tool.poetry.group.dev.dependencies] +pytest = "^6.2.1" +hypothesis = "^6.0.2" +pytest-profiling = "^1.7.0" +black = "^21.6b0" pytest-fail-slow = ">=0.2.0,<0.3.0" +[tool.poetry.group.jupyter.dependencies] +jupyter = "^1.0.0" + [build-system] requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api"