{"id":20385583,"url":"https://github.com/rgson/lyner","last_synced_at":"2025-08-11T19:10:11.271Z","repository":{"id":77576991,"uuid":"84994369","full_name":"rgson/Lyner","owner":"rgson","description":"Lyner solves LYNE puzzles","archived":false,"fork":false,"pushed_at":"2017-04-16T14:54:57.000Z","size":364,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-04T23:15:54.557Z","etag":null,"topics":["bot","lyne","puzzle-solver"],"latest_commit_sha":null,"homepage":null,"language":"Python","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/rgson.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-14T20:31:55.000Z","updated_at":"2019-07-01T14:33:54.000Z","dependencies_parsed_at":null,"dependency_job_id":"6fa58120-de3d-46df-b22a-f7ebdd69b79f","html_url":"https://github.com/rgson/Lyner","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/rgson/Lyner","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgson%2FLyner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgson%2FLyner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgson%2FLyner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgson%2FLyner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rgson","download_url":"https://codeload.github.com/rgson/Lyner/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rgson%2FLyner/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":269939998,"owners_count":24500387,"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","status":"online","status_checked_at":"2025-08-11T02:00:10.019Z","response_time":75,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["bot","lyne","puzzle-solver"],"created_at":"2024-11-15T02:34:51.219Z","updated_at":"2025-08-11T19:10:11.220Z","avatar_url":"https://github.com/rgson.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LYNER\n\n**Lyner** solves [LYNE](http://store.steampowered.com/app/266010/) puzzles.\n\nPrimarily, Lyner features a small program for solving puzzles. It can solve puzzles from textual input or interact directly with the game.\n\nAdditionally, Lyner sports a modular and extensible design to support experimentation with other solver algorithms. It should also be fairly easy to implement new input sources and output targets for various purposes and platforms.\n\n## How is it used?\n\nLyner features two modes: a manual mode with manual puzzle input, and an automatic mode which interacts directly with the LYNE game.\n\nThe available options can be seen by running `lyner.py -h`. Mode-specific instructions are found through `lyner.py manual -h` and `lyner.py auto -h`.\n\n### Manual mode\n\nManual mode solves puzzles from manually specified input, given either as text or as an image.\n\nA LYNE puzzle is described textually as follows:\n\n- Letters define colored nodes. Each unique letter represents a separate color.\n- Uppercase letters (A-Z) denote start and goal nodes. There should be exactly two of the same kind.\n- Lowercase letters (a-z) denote regular colored nodes.\n- Digits (1-9) denote uncolored nodes of a specific capacity.\n- Zero (0) denotes a missing node.\n- Forward slash (/) denotes a line break\n\nSome examples:\n\nLevel                      | Text\n---------------------------|-----\n[A  3](test-images/a03.png) | `A0A/aaa`\n[A  6](test-images/a06.png) | `Aaa/B0A/bbB`\n[A 17](test-images/a17.png) | `AB/2b/AB`\n[B  9](test-images/b09.png) | `ABC/abc/ABC`\n[B 18](test-images/b18.png) | `BAC/022/0c2/CAB`\n[B 25](test-images/b25.png) | `ACB/2C2/acb/B2A`\n\nIn LYNE, puzzles include at most three colors (A, B, C) and uncolored nodes have a capacity of at most four (1, 2, 3, 4). Values outside these ranges should work, but have not been actively tested.\n\nInput can also be passed in the form of a picture, in which case it should be a screenshot of LYNE using the default color scheme. Refer to the `test-images` directory (or the links in the table above) for examples.\n\n### Automatic mode\n\nAutomatic mode solves puzzles directly on a running instance of the LYNE game. It relies on screenshots and automated mouse actions.\n\nAutomatic mode currently only works on Linux. It requires the programs [timeout](https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html#timeout-invocation), [import](https://www.imagemagick.org/script/import.php) and [xdotool](http://www.semicomplete.com/projects/xdotool) to function. All of the programs are available in the Ubuntu software repositories. For non-Ubuntu-based Linux distributions, refer to your distrobution's package manager or the programs' websites directly.\n\n*A Windows version of automatic mode would be great. Pull requrests are warmly welcomed.*\n\n## How does it work?\n\nThe default solver (`GuidedDepthFirstSolver`) models the puzzle as a graph and finds solutions using recursive depth first searches with backtracking, guided by a simple heuristic.\n\nThe graph consists of nodes and edges (as usual). Each node has a type, a capacity, and edges to adjacent nodes. Edges also have a capacity and diagonal edges are linked to their crossing edge.\n\nA start node for one color is arbitrarily selected. A depth-first search is performed to find a path to the corresponding goal node which passes through all nodes of that color. The procedure then repeats using another arbitrarily selected start node for another color. When there are no remaining colors, the solution is checked to make sure all uncolored nodes have also been filled. Otherwise, it backtracks and tries another solution.\n\nThe search is guided by a simple heuristic to prioritize uncolored nodes, particularly those with a high remaining capacity. This speeds up the search by reducing the risk of ending up with an incomplete solution after going through all of the colors.\n\n## How can it be extended?\n\nLyner can be extended to use a different solver algorithm or to support new input sources and output targets.\n\nPull requests with new solvers or sources/targets to support automatic mode on other platforms are warmly welcomed.\n\n### New solver algorithms\n\nIf you have an idea for another solver algorithm, please do implement it! The `GuidedDepthFirstSolver` is pretty good, but it struggles with a few puzzles with particularly vast search spaces. It would be great if you managed to out-perform it.\n\nTo implement a new solver, extend `lyner.Solver` and override `solve_puzzle(self, puzzle)`. The `puzzle` parameter is a textual description of the puzzle and `solve_puzzle` should return a list of lists, one for each color, containing the zero-based coordinates of the nodes in the order in which they are visited. For example, if `puzzle` is `A0A/aaa`, the returned value should be `[[(0, 0), (1, 0), (1, 1), (1, 2), (0, 2)]]`.\n\n### New sources/targets\n\nLyner can also use other input sources/output targets to support new platforms or use cases.\n\nTo support a new input source or output target, extend `lyner.Source` or `lyner.Target` and override `get_puzzle(self)` or `put_solution(self, solution)`, respectively. The `get_puzzle` function should return a textual representation of a puzzle, while `put_solution` is passed a solution (in the format returned by the solver) to do with as you please.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgson%2Flyner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frgson%2Flyner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frgson%2Flyner/lists"}