{"id":13594275,"url":"https://github.com/mordechaim/Mosaic","last_synced_at":"2025-04-09T07:31:29.104Z","repository":{"id":63901530,"uuid":"85984929","full_name":"mordechaim/Mosaic","owner":"mordechaim","description":"Play and solve Fill-A-Pix puzzles (called \"Mosaic\" here).","archived":false,"fork":false,"pushed_at":"2017-08-20T17:39:04.000Z","size":196,"stargazers_count":7,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-06-12T11:11:44.347Z","etag":null,"topics":["fill-a-pix","grid","java","mosaic","puzzle"],"latest_commit_sha":null,"homepage":"https://puzzling.stackexchange.com/users/34420/mordechai","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mordechaim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-23T18:38:03.000Z","updated_at":"2024-05-31T13:46:52.000Z","dependencies_parsed_at":"2023-01-14T12:45:45.575Z","dependency_job_id":null,"html_url":"https://github.com/mordechaim/Mosaic","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mordechaim%2FMosaic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mordechaim%2FMosaic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mordechaim%2FMosaic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mordechaim%2FMosaic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mordechaim","download_url":"https://codeload.github.com/mordechaim/Mosaic/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":213549345,"owners_count":15603930,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fill-a-pix","grid","java","mosaic","puzzle"],"created_at":"2024-08-01T16:01:31.161Z","updated_at":"2024-08-01T16:16:11.645Z","avatar_url":"https://github.com/mordechaim.png","language":"Java","funding_links":[],"categories":["Java"],"sub_categories":[],"readme":"**PLEASE NOTE: This document is outdated. New commits changed the way you use the API, and a complete JavaFX front-end has been added.**\n\n**Mosaic** \u0026nbsp; \u0026mdash; \u0026nbsp; \nAlso known as:\nArtMosaico, Count and Darken, Cuenta Y Sombrea, Fill-a-Pix, Fill-In, Komsu Karala, Magipic, Majipiku, Mosaico, Mosaik, Mozaiek, Nampre Puzzle, Nurie-Puzzle, Oekaki-Pix, Voisimage.\n\nMosaic is a Minesweeper-like puzzle based on a grid with a pixel-art picture hidden inside. Using logic alone, the solver determines which squares are painted and which should remain empty until the hidden picture is completely exposed.\n\nEach puzzle consists of a grid containing clues in various places. The object is to reveal a hidden picture by painting the squares around each clue so that the number of painted squares, including the square with the clue, matches the value of the clue.\n\nOriginally created by [Trevor Truran](https://en.wikipedia.org/wiki/Trevor_Truran), after inspiration of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life). \n\nThe puzzle was later developed by [ConceptisPuzzles](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix) under the name __Fill-a-Pix__, following the \"a-pix\" pixel art series:\n\n- [**Pic-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/pic-a-pix) \u0026nbsp; \u0026mdash; \u0026nbsp; Nonogram\n- [**Sym-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/sym-a-pix)\n- [**Link-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/link-a-pix)\n\n- [**Maze-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/maze-a-pix)\n- [**Dot-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/dot-a-pix)\n- [**Cross-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/cross-a-pix)\n- [**Block-a-Pix**](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/block-a-pix)\n\nI'll stick to the name \"Mosaic\" as the general name of this puzzle.\n\nHere are some links to understand the rules of the puzzle \u0026mdash; all from ConceptisPuzzles:\n\n- [Mosaic Rules](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/rules)\n- [Animated Solving](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/tutorial) \u0026nbsp; \u0026mdash; \u0026nbsp; Requires Flash\n- [Basic Logic and Advanced Logic Techniques](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/techniques)\n- [Puzzle Tips](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/tips)\n- [Puzzle History](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/history)\n\n----------\n\nCreating your own Mosaic\n-\n\nThere are 3 rules that must be met when creating a Mosaic. Trying to solve them using the API will result in its appropriate exception to be thrown.\n\n- **Every cell must have at-least one clue, either surrounding or in itself.**\u003cbr\u003e\n[![no-clue][1]][1]\n \n As you can see, the 3 right cells will never be solvable.\n Trying to solve this with the API will throw:\n\n        Exception in thread \"main\" com.stackexchange.puzzling.user.mordechai.mosaic.solvers.NoClueException: x: 3, y: 0\n            at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.AbstractSolveAlgorithm.\u003cinit\u003e(AbstractSolveAlgorithm.java:37)\n            at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.\u003cinit\u003e(RecursionSolver.java:32)\n            at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.\u003cinit\u003e(RecursionSolver.java:28)\n            at com.stackexchange.puzzling.user.mordechai.mosaic.TestPuzzle.main(TestPuzzle.java:14)\n\n\n\n- **Clues must not contradict.**\u003cbr\u003e\n[![contradiction][2]][2]\n\n Since 9 means all cells painted, the 2 will have 3 painted cells, which contradicts with the clue.\n Trying to solve this with the API will throw:\n\n        Exception in thread \"main\" com.stackexchange.puzzling.user.mordechai.mosaic.solvers.ContradictionException: x: 3, y: 1\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.takeStepImpl(RecursionSolver.java:151)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.takeStep(RecursionSolver.java:127)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.run(RecursionSolver.java:90)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.start(RecursionSolver.java:56)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.TestPuzzle.main(TestPuzzle.java:23)\n\n\n- **Must have a unique solution.** \u003cbr\u003e If not enough clues are given there may be cases where 2 or more possible solutions are all correct according to the clues.\n[![unique][3]][3]\n\n Once again, trying to solve a puzzle like this with this with `checkAmbiguity(true)`, will throw:\n\n        Exception in thread \"main\" com.stackexchange.puzzling.user.mordechai.mosaic.solvers.AmbigiousException: x: 3, y: 1\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.lambda$5(RecursionSolver.java:224)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.AbstractSolveAlgorithm.fire(AbstractSolveAlgorithm.java:175)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.AbstractSolveAlgorithm.setState(AbstractSolveAlgorithm.java:82)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.setState(RecursionSolver.java:283)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.takeStep(RecursionSolver.java:119)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.takeStep(RecursionSolver.java:104)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.takeStep(RecursionSolver.java:104)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.run(RecursionSolver.java:90)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.solvers.RecursionSolver.start(RecursionSolver.java:56)\n\t        at com.stackexchange.puzzling.user.mordechai.mosaic.TestPuzzle.main(TestPuzzle.java:23)\n\n\nUsing the Application Programming Interface (API)\n-\n\n\u003ch3\u003e1. Kickstarter \u003c/h3\u003e\n\nThe entry point is the class `Mosaic` that takes a `java.awt.Image`, URL or a `Grid\u003cClue\u003e` - the logical representation of a grid used internally in `Mosaic` - as its constructor's parameters. The `Grid` will be filled with `Clues`. Here's the `Clue` class:\n\n\n    package com.stackexchange.puzzling.user.mordechai.mosaic;\n    \n    public class Clue {\n    \n    \tprivate Fill fill = Fill.EMPTY;\n    \tprivate int clue = -1;\n    \tprivate boolean isPixel;\n    \n    \tpublic Clue(Fill fill, int clue, boolean isPixel) {\n    \t\tthis.fill = fill;\n    \t\tthis.clue = clue;\n    \t\tthis.isPixel = isPixel;\n    \t}\n    \n    \tpublic Clue(boolean isPixel) {\n    \t\tthis.isPixel = isPixel;\n    \t}\n    \n    \tpublic Clue(Clue other) {\n    \t\tif (other == null)\n    \t\t\treturn;\n    \n    \t\tthis.fill = other.fill;\n    \t\tthis.clue = other.clue;\n    \t\tthis.isPixel = other.isPixel;\n    \t}\n    \n    \tpublic Clue() {\n    \t}\n    \n    \tpublic Fill getFill() {\n    \t\treturn fill;\n    \t}\n    \n    \tpublic int getClue() {\n    \t\treturn clue;\n    \t}\n    \n    \tpublic boolean isPixel() {\n    \t\treturn isPixel;\n    \t}\n    \n    \tpublic void setFill(Fill fill) {\n    \t\tthis.fill = fill;\n    \t}\n    \n    \tpublic void setClue(int clue) {\n    \t\tthis.clue = clue;\n    \t}\n    \n    \tpublic void setIsPixel(boolean b) {\n    \t\tisPixel = b;\n    \t}\n    \n    }\n\n\n`Fill` is an enum with values `FILLED, EMPTY, X;`\n\n**1.1 Filling Clues**\n\nThe `Mosaic` class itself just sets the `isPixel` of each clue according to the image, but the actual clues is up to the programmer to do. `Mosaic` has a method:\n\n    public void putClues(ClueGenerator generator)\n\nWhich helps fill them up neatly.\n\n`ClueGenerator` is a functional interface:\n\n    package com.stackexchange.puzzling.user.mordechai.mosaic;\n\n    import com.stackexchange.puzzling.user.mordechai.grid.Grid;\n\n    public interface ClueGenerator {\n    \n        boolean shouldGenerate(Grid\u003cClue\u003e grid, int x, int y, int iteration);\n    \t\n    \tdefault int iterations() {\n            return 0;\n    \t}\n    }\n\nHere's an example of creating and filling the clues with a checkerboard design:\n\n\u003cpre\u003eMosaic m = new Mosaic(getClass().getResource(\"empty-8x8.png\"));\n// empty-50x50.png is just a white empty image of size 8,8\n\n\u003cb\u003em.putClues((grid, x, y, iteration) -\u003e (x+y) % 2 == 0);\u003c/b\u003e\n\nSystem.out.println(m.getGrid().toGridString(\n                   clue -\u003e \" \" + (clue.getClue() == -1 ? \" \" : clue.getClue()),\n                   false)); // true would return with grid-lines\u003c/pre\u003e\n\nThe above code prints to the console:\n\n     0   0   0   0  \n       0   0   0   0\n     0   0   0   0  \n       0   0   0   0\n     0   0   0   0  \n       0   0   0   0\n     0   0   0   0  \n       0   0   0   0\n\n\nYou can as well do random fills or even make the clues be an image itself:\n\n   \n\u003cpre\u003eGrid\u003cClue\u003e grid = new Grid\u003c\u003e(20, 20);\ngrid.fill(Clue::new);\n\nMosaic m = new Mosaic(grid);\n\nBufferedImage image = new BufferedImage(20, 20, BufferedImage.TYPE_INT_RGB);\nGraphics g = image.getGraphics();\n\ng.setColor(Color.WHITE);\ng.fillRect(0, 0, 20, 20);\ng.setColor(Color.BLACK);\ng.setFont(new Font(\"Dialog\", Font.BOLD, 25));\ng.drawString(\"?\", 2, 19);\n\ng.dispose();\n\nMosaic q = new Mosaic(image);\nm.putClues((gr, x, y, iteration) -\u003e \u003cb\u003eq.getGrid().get(x, y).isPixel()\u003c/b\u003e || (x+y) % 4 == 0);\n\nSystem.out.println(m.getGrid().toGridString(\n                   clue -\u003e \" \" + (clue.getClue() == -1 ? \" \" : clue.getClue()),\n                   false));\u003c/pre\u003e\n\nResults:\n\n     0       0       0       0       0      \n           0       0 0 0 0 0       0       0\n         0     0 0 0 0 0 0 0 0 0 0       0  \n       0     0 0 0 0 0 0 0 0 0 0 0     0    \n     0     0 0 0 0   0       0 0 0 0 0      \n           0 0 0   0       0   0 0 0       0\n         0       0       0     0 0 0     0  \n       0       0       0     0 0 0 0   0    \n     0       0       0     0 0 0 0   0      \n           0       0     0 0 0 0   0       0\n         0       0     0 0 0 0   0       0  \n       0       0     0 0 0 0   0       0    \n     0       0       0 0 0   0       0      \n           0       0 0 0 0 0       0       0\n         0       0       0       0       0  \n       0       0       0       0       0    \n     0       0       0 0 0   0       0      \n           0       0 0 0 0 0       0       0\n         0       0   0 0 0       0       0  \n       0       0       0       0       0    \n\nIdeas are virtually endless, show your creativity here.\n\n**1.2 Iterations**\n\nIn some cases you will want to repeatedly iterate over the grid when filling the clues until some precondition is met (e.g. Fill randomly, then repeat and check if a cell has a missing clue).\n\nThere's an overloaded version of `putClues()`:\n  \n    public void putClues(ClueGenerator generator, int iterations)\n\nThe process will repeat \u003cem\u003en\u003c/em\u003e times according to `iterations`. You can get the current iteration in the last argument of the clue generator.\n\nIn the event that you want to write a general, full concrete implementation of `ClueGenerator` that heavily relies in the amount of iterations, you can override the default `iterations()` method. Any number greater that zero will always have precedence over the iterations parameter in `putClues()`. Zero or below will either use the `iterations` param or default to 1 if the first overload is used.\n\n\u003ch3\u003e2. Solving\u003c/h3\u003e\n\nTo the current time, I've implemented one solve algorithm, namely `RecursionSolver`. As its name tells, it uses recursion. The regular case doesn't really require recursion to solve; it's the [Advanced Logic](http://www.conceptispuzzles.com/index.aspx?uri=puzzle/fill-a-pix/techniques#advancedlogic) problems that recursion will be used for.\n\nUsing it is easy, just create an instance, pass the `Mosaic` as constructor argument and start.\n\n    Mosaic m = new Mosaic(...);\n    RecursionSolver rs = new RecursionSolver(m);\n    rs.start();\n\nYou can then output the results to the console:\n\n\n    System.out.println(m.getGrid().toGridString(clue -\u003e \" \" + clue.getFill(), false));\n\nAnd a report:\n\n    System.out.println(rs.getReport());\n\n**2.1 Uniqueness**\n\nAs explained earlier, puzzles must follow some rules to be valid. Contradictions and missing clues are detected easily, but uniqueness requires to try all valid solutions and check if more than one is found.\n\nFor this reason, I've designed the solver that uniqueness detection is off by default. You can toggle that behaviour:\n\n    rs.checkAmbiguity(aBoolean);\n\n\u003ch3\u003e3. Exporting to Excel\u003c/h3\u003e\n\nAfter you've created a Mosaic but you want to put it into Excel, you have to print a CSV version and just copy-paste it:\n\n\u003cpre\u003eSystem.out.println(m.getGrid().toGridString(\n                   clue -\u003e (clue.getClue() == -1 ? \" \" : clue.getClue()) \u003cb\u003e+ \"\\t\"\u003c/b\u003e ,\n                   false));\u003c/pre\u003e\n\nMy experience is that you should size the cells: Width: 1.86, Height: 13.5\n\n\u003ch3\u003e4. Attribution\u003c/h3\u003e\n\nMuch hard work has gone to develop this API. You are free to use it without limit and even create your own user interface version. If you publish a puzzle created by this API publicly on the Internet (intentionally excludes for private use), you should link this  repository.\n\n\n    \n\n\n  [1]: https://i.stack.imgur.com/79JJE.png\n  [2]: https://i.stack.imgur.com/tNld9.png\n  [3]: https://i.stack.imgur.com/5nW7A.png\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmordechaim%2FMosaic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmordechaim%2FMosaic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmordechaim%2FMosaic/lists"}