{"id":19839985,"url":"https://github.com/sjbiaga/urru","last_synced_at":"2026-05-08T17:34:51.803Z","repository":{"id":231507923,"uuid":"781928134","full_name":"sjbiaga/urru","owner":"sjbiaga","description":"Three puzzles - Flow, Fold, Fill - with Undo/Redo and more, lanterna or scalaFX UI","archived":false,"fork":false,"pushed_at":"2025-07-06T14:58:19.000Z","size":177,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-06T15:44:42.876Z","etag":null,"topics":["cats-effect","fs2","games","http4s","json","kafka","mongodb","puzzles","scalafx","stochastic-pi-calculus","undo-redo"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sjbiaga.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2024-04-04T10:04:15.000Z","updated_at":"2025-07-06T14:59:14.000Z","dependencies_parsed_at":"2024-06-18T14:53:30.312Z","dependency_job_id":"7ed93426-14e0-40d0-bea2-a0dcdd718a51","html_url":"https://github.com/sjbiaga/urru","commit_stats":null,"previous_names":["sjbiaga/urru"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sjbiaga/urru","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjbiaga%2Furru","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjbiaga%2Furru/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjbiaga%2Furru/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjbiaga%2Furru/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sjbiaga","download_url":"https://codeload.github.com/sjbiaga/urru/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sjbiaga%2Furru/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32790544,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T08:22:46.396Z","status":"ssl_error","status_checked_at":"2026-05-08T08:22:45.650Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["cats-effect","fs2","games","http4s","json","kafka","mongodb","puzzles","scalafx","stochastic-pi-calculus","undo-redo"],"created_at":"2024-11-12T12:25:04.617Z","updated_at":"2026-05-08T17:34:51.791Z","avatar_url":"https://github.com/sjbiaga.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"Undo-Redo-Redo-Undo\n===================\n\nA `Game` is composed of colored \"*items*\" that correspond to:\n\n- lines between pairs of points, in the case of `game-flow`;\n- folding blocks in the case of `game-fold`, and\n- compact blocks in the case of `game-fill`.\n\nBesides the intuitive `UI` that allows the player to use the mouse, there is also\na keyboard interface: for a short description of (some of) the keys, press\n`ESCAPE`. In order to play a certain level, there is a file format per game\ndescribed in, respectively, the files \"`flow.txt`\", \"`fold.txt`\" and\n\"`fill.txt`\": these files should be copied and edited to provide a level. Then,\nthe file names must be programmatically specified in the `Main.scala`\nsource file, in the sub-package `ui.scalafx` (full `JavaFX` graphics).\n\n`Path`s\n-------\n\nPer game, each item holds a \"linked list\" that grows by each move: it is called\na \"path\", because the `case class` - named `Path` for a game - has a `parent`\nfield through which the \"list\" is linked back to `None` - the \"empty\" start of\nthe game. (In fact, there is a `Path` per item with each restart of a game.)\n\nAn instance of `Path` has two fields of type `Option[?]` named \"`undo`\" and\n\"`redo`\": following back through \"`undo`\" is just as following back through\n\"`parent`\"; however, upon multiple \"`undo`s\", not only does the path update\nto the parent, but also a \"`redo`\" list is composed such that, following it\nthrough to the end, the reverse list of \"lost\" `undo`s can be traversed (and\nso, \"`redo`ne\").\n\n                       _\n       ,---------------|-.\n       |       _         |\n       |       | next    |\n       |    ,--|---.     |\n       |  A | undo |     |\n       |    `------'     |\n       |       ^         |\n       /-------|---------'\n      /        |       ^\n     Path      |       | parent\n      \\        |       |\n       \\-------|-------|-.\n       |       | next    |\n       |    ,--|---.     |\n       |  B | undo |\u003c............\n       |    `------'     |      |\n       | ,------.        |      | next\n       | |      |        |   ,--|---.\n       | | redo ------------\u003e| undo | C\n       | |      |        |   `------'\n       | `--|---'        |      ^\n       |    | next       |      |\n       `----|------------'      |\n            v                   |\n         ,------.               | next\n         |      |            ,--|---.\n       E | redo ------------\u003e| undo | D\n         |      |   undo     `------'\n         `--|---'\n            | next\n            _\n\n\nAn \"`undo`\" has a `move` field that holds the corresponding move, i.e., that\ncan be \"undone\". This field is preserved even in a \"`redo`\", because the latter\nholds the \"`undo`\" in its `undo` field, and so the move - via `undo.move`. For\n`game-fill`, a move in the opposite direction as the previous, it is intercepted\nalso an `undo`.\n\nA \"`redo`\" is much like a move. Following a \"`redo`\", a new instance of `Path`\nis created with the `undo` field taken from its now-parent's `redo`, and this\n\"`redo`\" is of no further use, although kept as such in the now-parent `Path`.\n\n                       _\n       ,---------------|-.\n       |       _         |\n       |       | next    |\n       |    ,--|---.     |\n       |  A | undo |     |\n       |    `------'     |\n       |       ^         |\n       `-------|---------'\n               |       ^\n               |       | parent\n               |       |\n       ,-------|-------|-.\n       |       |         |\n       |       | next    |\n       |    ,--|---.     |\n       |  B | undo |     | (\u003c- unused redo)\n       |    `------'     |\n       |       ^         |\n       /-------|---------'\n      /        |       ^\n     Path      |       | parent\n      \\        |       |\n       \\-------|-------|-.\n       |       | next    |\n       |    ,--|---.     |\n       |  C | undo |\u003c............\n       |    `------'     |      |\n       | ,------.        |      | next\n       | |      | E      |   ,--|---.\n       | | redo ------------\u003e| undo | D\n       | |      |   undo |   `------'\n       | `--|---'        |\n       |    | next       |\n       `----|------------'\n            _\n\nWhen a move is performed, the \"`redo`\" list vanishes, unless the move is the same\nwith that of the \"`redo`\". Here, not only the two moves must be the same, but they\nmust also be performed with the same _intensity_ (from the part of the player).\nThis amounts to the local context of the move, that can be quantified with regard to\nthe status of the grid: clues and other items. In the simplest case, the move is free\nabsolutely, when the intensity of the `redo` move (kept in its `undo`) is that this\n`undo` would be just a backtrack (without \"clashes\") from the part of the player.\n\nThe state of a game is a mutable list, with an index for each item. The color is\na negative number, calculated by negating the index (divided by 2, for the `game-flow`)\nand subtracting one. The index is a positive number, obtained from the color by the same\ncalculation: for the `game-flow`, further multiplied by 2 and added either 0 or 1.\n\nJust - The Past Simple\n----------------------\n\nThe \"`undo`s\" are essential because they are added in a mutable list in the parent,\nalternating with the corresponding \"`redo`\": this list becomes (part of) the \"past\"\n(simple) for an instance of `Path` created upon a (new) move. It can be accessed from\nthe current instance of the `Path` (from the game state) through the `parent` field.\nIt holds objects of type `Just`, but in reality, the `Undo` and `Redo` `case class`es\nonly, inherit this trait.\n\nConsider a move which is `undo`ne: this `undo` is added at the end of the mutable\nlist - of the instance of `Path` that has now become current (and was former _the_\n`parent`). Next, consider a `redo`. But this performs the move prior to the `undo`:\nas well, this is also added at the end of the mutable list. Now, the \"same\" `undo`\nagain - added at the end of the mutable list. In general, cycling the `undo`/`redo`\nprior to a new distinct move, will correspond to successive elements, either `undo`\nor `redo`, for the same move, at the end of the mutable list that is (becomes)\nthe \"simple past\" - for any new instance of `Path`.\n\nAn element of the \"simple past\" holds an instance of its `Path` as well. So, consider\na new distinct move: this move will be performed also for all the \"just\" mutable lists\nencountered up through the `parent` field, on each element's own `Path`. A quick\ninductive thinking reveals that there is an invariant for all these \"just\" elements,\nwhich is that they correspond at all times to the actual just one grid, and thus\nneed not store a grid, resulting in a low memory footprint.\n\nThis is not all the advantage, though. After a - say, an - `undo` is added as an\nelement of the \"just\" mutable list, a new distinct move which is `undo`ne, will not\nonly create a subsequent `undo` added after this mentioned one, but also in the\n\"just\" list of the current `Path` of this one. Hence, there appear to be three\ncombinations: the first `undo` by itself, the second `undo` by itself, and the\nfirst `undo` together with the second `undo`. Of course, these correspond to the\nsame item, at the same depth, but distinct moves.\n\nIn this manner, rather than complicatedly generating combinations from a \"just\"\nmutable list, a tree-like structure is obtained, whose simple traversal (albeit\nnot quite so direct) allows to apply a callback on the collected path of multiple\n`undo`s (and `redo`s), and have these analysed in conjunction. Moreover, these\ncan be (de)serialized as `case class`es, thus storing actually the tree.\nHowever, in memory, the tree will fast grow exponentially with each `undo`/`redo`,\nbecause it really captures all possible combinations.\n\nThe memory grows exponentially, because when the immutable `undo`s/`redo`s are\nadded, the sequence of moves, `undo`s and `redo`s, is replayed (or reflected,\nbut not from the very beginning, as a further undo would remove a `Path` together\nwith its \" just\") commencing with a fresh instance of `Path`, in each case: it is\nthis `copy` of an `undo` or `redo` `case class` that is added at the end of the\n\"just\" mutable list. The rest of the fields (i.e., besides the `path: Mutable[?]`\nand `identifier: Mutable[Long]` fields) are ignored when an `undo` or `redo` becomes\n\"part of the past simple\".\n\nSince the methods invoked on the `Path` `case class` are the same even for\nreplaying, an `Option[Long]` parameter `id` tells whether to avoid replaying. The\ninvariant of there existing only a grid is preserved as follows.\n\nFor an `undo`, replaying (when reverting from a current child to its parent) means\nperforming\n\n1. the child's `undo` field's move with a fresh (empty) `Path`;\n2. the moves from the `undo` for each `redo` \"forward\";\n3. the `undo`s for each `redo` \"backward\"; and,\n4. because replaying occurs on the child, also just one more `undo` (reflecting\n   the pass from child to parent).\n\n[The resulting `undo`-as-just is added at the end to the parent's `just` mutable\nlist (to be such simple past for further child `Path`s).]\n\nFor a `redo`, replaying (when creating a child from its current parent) means\nperforming\n\n1. the `undo` field move with a fresh (empty) `Path`;\n2. the moves from the `undo` for each `redo` \"forward\";\n3. the `undo`s for each `redo` \"backward\"; and,\n4. because replaying occurs on the parent, also just one `redo` (reflecting\n   the pass from parent to child).\n\n[The resulting `redo`-as-just is added at the end to the current `just` mutable\nlist, but which becomes a parent `Path`.]\n\nIn either case, the resulting `Path` of the replayed and copied `undo` or `redo`,\nis from now on bi-similar with the returned `Path`; or at most until the instance\nof `Path` to whose `just` mutable list they are added, it is \"gone\" - if `undo`ne.\nThis is when \"repeal\" occurs: TODO.\n\nPending Batch(es)\n-----------------\n\nWith the contingent exception of `game-fill`, a move may require `undo`'s on\nother items, until the move is possible.\n\nFor the `game-flow`, a move is always possible, i.e. other line(s) \"snap\" until\nreducing to at most the pair of clue points (of course, a move _into_ any of the\npair of clue points is impossible); however, when the two halves of the same color\nwould (snap and) join at ninety degrees upon a bridge clue, this move is\nprohibited: not that it couldn't make a pending, but otherwise there might be two\npendings (one \"collinear\", one \"not collinear\"), whereas at most one pending is\nassumed.\n\nFor the `game-fold`, a move may not be possible, because a block can at most fold\nto its initial shape, that cannot be eliminated.\n\nFor the `game-fill`, although `drag` (move into the pad) and `drop` (move out of\nthe pad) are also accounted for in the `undo`/`redo` lists, a cascade of `undo`s of\nother blocks leading to a cascade of `undo`s of yet other moves, may become circular\nbefore the blocks can be \"dragged\" out from the grid onto the pad, so a move\nis *not* always possible.\n\n[The condition when a move is abandoned (not when it is an `undo`) occurs in a\nbreadth-first-search manner. To an item (\"visited node\") there correspond\nseveral blocks that are the moves that need to be `undo`ne. For each such block\nin turn, there are searched the items that require several `undo`s (until the\nformer block does not collide with a latter `undo`ne block). However, if for any\ntwo different items attempted to be `undo`ne (one that has been visited and one\nthat is being visited), there are any blocks to be `undo`ne that have points in\ncommon - collide -, this is the condition for the initial move to be abandoned.\n\nInitially, for the item that is to be moved, there are two blocks that must not\ncollide (with other items' `undo`ne blocks): the block before the move and the\nblock after the move (as with the rest, actually). When dropping a block onto\nthe grid from the pad, the former block is empty - thus, it never collides.\n\nIt is true that a move may be possible, but not with this algorithm.\n\nThe drawback is, of course, that the requirement for any two `undo`ne blocks,\ncorresponding to two different items, not to collide, is too strong (because any\nof these do not occur both at once). Nevertheless, it integrates seamlessly with\nthe `undo`-`redo` mechanism, and can always be ameliorated by previously manually\ndragging out items that otherwise \"stuck\" the move onto the pad.\n\nThe advantage is that impossible moves are not possible.]\n\nFor the cases when `undo`s must precede a possible move in order to make it occur,\nit is resorted to the very methods of `undo` (and `redo`), only which do not\nalter the past simple, nor are considered from the point of view of intensities: they\nare called `batch undo` and `batch redo`.\n\nBecause a batch may be independent of other batches, there is a stack-like\nstructure - the \"`pending`\" - that is cleared under certain conditions (which\nobviously interfere with some \"untouchable\" batch); otherwise, the pending\n`batch undo`s may be `batch redo`ne, if *only* in the _reverse_ order in which\nthey occurred.\n\nA (pending) batch associates an item with other items that are `undo`ne upon the\nitem's move. If possible (\"batchable\", depending on the game), the \"top\"\n(of the stack-list) \"pending\" batch accumulates in a `HashMap` the index of\neach `undo`ne item, mapped to how many `undo`s (a batch will `redo`).\n\nThus, an element of the pending mutable stack-list, is a pair:\n\n- the current move's item's index;\n- a mapping from the indexes of the (batch) `undo`ne items to an `undo` count.\n\nThe conditions under which the \"pending\" is cleared are as follows:\n\n- the current move's item's index already exists in the \"pending\" list,\n  as the first of a pair - and there can be but one;\n\n- the current move's item's index appears in the \"pending\" list,\n  contained in the second `HashMap` of a pair - an interference that cancels\n  some batch(es);\n\n- the \"pending\" stack-list contains one or more elements, but the\n  current `undo`'s is not on the top of the stack; otherwise, a\n  `batch redo` occurs.\n\nA `batch redo` iterates over the set of indexes (of the items that\nwere `undo`ne), and for each index, performs so many `redo`s as\nmapped at the index.\n\nFor the `game-flow`, if the current move's line actually \"snaps\" the\nsame-color paired line, this is not considered a `batch undo`, but the\njoin of the two lines (of the same color).\n\nHave - The Past Perfect\n-----------------------\n\nThere is a single `case class` (per game) that inherits the `Have` (past perfect)\ntrait: a `Board` is the grid without any rules or player, which anything can pile up\non, in no matter what order or how many times: inconsistencies are extensional.\nThe grid of a `Have` (`Board`) is reached by the player, and upon an `undo`,\nit is added to the parent `Path`'s `have` mutable list: it \"has been\", so there\nis no longer any intensity.\n\nWhenever there is a move by the player, this move piles up on every `Board`\nin the `have` mutable list. Nothing extensional corresponds to `undo`/`redo`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjbiaga%2Furru","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjbiaga%2Furru","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjbiaga%2Furru/lists"}