{"id":22856107,"url":"https://github.com/andygeiss/fsm","last_synced_at":"2025-06-27T17:33:47.276Z","repository":{"id":253514404,"uuid":"843740031","full_name":"andygeiss/fsm","owner":"andygeiss","description":"Compute the next state with recursive state functions in Golang using generics and iterators.","archived":false,"fork":false,"pushed_at":"2024-08-19T21:17:55.000Z","size":641,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T21:52:59.608Z","etag":null,"topics":["finite-state-machine","generics","go","golang","iterators","recursion","recursive-functions"],"latest_commit_sha":null,"homepage":"","language":"Go","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/andygeiss.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-08-17T09:18:44.000Z","updated_at":"2024-08-19T21:17:59.000Z","dependencies_parsed_at":"2024-08-18T14:45:48.291Z","dependency_job_id":null,"html_url":"https://github.com/andygeiss/fsm","commit_stats":null,"previous_names":["andygeiss/fsm"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andygeiss%2Ffsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andygeiss%2Ffsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andygeiss%2Ffsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andygeiss%2Ffsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andygeiss","download_url":"https://codeload.github.com/andygeiss/fsm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246436094,"owners_count":20776965,"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":["finite-state-machine","generics","go","golang","iterators","recursion","recursive-functions"],"created_at":"2024-12-13T08:07:08.825Z","updated_at":"2025-03-31T07:45:07.573Z","avatar_url":"https://github.com/andygeiss.png","language":"Go","readme":"\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/andygeiss/fsm/blob/main/logo.png?raw=true\" /\u003e\n\u003c/p\u003e\n\n# FSM - Finite State Machine\n\n[![License](https://img.shields.io/github/license/andygeiss/fsm)](https://github.com/andygeiss/fsm/blob/master/LICENSE)\n[![Releases](https://img.shields.io/github/v/release/andygeiss/fsm)](https://github.com/andygeiss/fsm/releases)\n[![Go Report Card](https://goreportcard.com/badge/github.com/andygeiss/fsm)](https://goreportcard.com/report/github.com/andygeiss/fsm)\n[![Codacy Grade Badge](https://app.codacy.com/project/badge/Grade/57bb148a04154ae8b7ce40cecb78947c)](https://app.codacy.com/gh/andygeiss/fsm/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_grade)\n[![Codacy Cover Badge](https://app.codacy.com/project/badge/Coverage/57bb148a04154ae8b7ce40cecb78947c)](https://app.codacy.com/gh/andygeiss/fsm/dashboard?utm_source=gh\u0026utm_medium=referral\u0026utm_content=\u0026utm_campaign=Badge_coverage)\n\nCompute the next state with recursive state functions in Golang using generics and iterators.\n\n## About\n\nBased on Rob Pike's talk on [lexical scanning](https://www.youtube.com/watch?v=HxaD_trXwRE)\nI thought about a version of a finite state machine (FSM) that uses\n`Generics` from [go1.18](https://go.dev/blog/go1.18) in order to\nGolang's fantastic “batteries included” capabilities.\n\n## Walkthrough\n\nThe best way to demonstrate the use of an FSM is to implement a game like “Super Mario”.\nIn this game, Mario changes his state and behavior depending on certain events,\nas shown in the following illustration from the [Mario Wiki](https://www.mariowiki.com/Super_Mario_World):\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"https://github.com/andygeiss/fsm/blob/main/mario.png?raw=true\" /\u003e\n\u003c/p\u003e\n\nBased on the image above, we could specify the `States` and `Events` as follows:\n\nStates:\n1. Small Mario\n2. Super Mario\n3. Fire Mario\n4. Cape Mario\n\nEvents:\n1. Got Mushroom\n2. Got Fire Flower\n3. Got Feather\n\nIn Object-Oriented Programming (OOP), we would specify Mario\nas an object that manages its internal/private state.\nThe behavior of Mario changes depending on the state\nand is implemented as methods.\nThe game world knows its entities and must emit the events\nbased on player inputs, for example.\n\nIn Golang, however, we could implement each state as a function\nthat operates on data and returns a function (recursively).\n\n### Events, States and the World\n\nLet's implement the events and states as follows:\n\n```go\nconst (\n    EventGotFeather = iota\n    EventGotFireFlower\n    EventGotMushroom\n)\n\nconst (\n    StateMarioCape = iota\n    StateMarioFire\n    StateMarioSmall\n    StateMarioSuper\n    StateMarioUndefined\n)\n```\n\nOur implementation will use a channel to receive events from the game world.\nThe world needs to know the state of Mario.\n\n```go\ntype config struct {\n    event      chan int\n    stateMario int\n}\n```\n\n### Mario got some stuff\n\nWe will implement the state transitions by using a `fsm.StateFn` as follows:\n\n```go\nfunc smallMario(ctx context.Context, cfg *config) fsm.StateFn[*config] {\n    cfg.stateMario = StateMarioSmall\n    select {\n    case event := \u003c-cfg.event:\n        switch event {\n        case EventGotFeather:\n            return capeMario(ctx, cfg)\n        case EventGotFireFlower:\n            return fireMario(ctx, cfg)\n        case EventGotMushroom:\n            return superMario(ctx, cfg)\n        }\n    case \u003c-ctx.Done():\n        return nil\n    }\n    return nil\n}\n```\n\nThis will result in a very clean approach that is easy to maintain (and test).\nA complete example for Mario can be found [here](mario_test.go).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandygeiss%2Ffsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandygeiss%2Ffsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandygeiss%2Ffsm/lists"}