{"id":15767244,"url":"https://github.com/briancaffey/redis-light-green-light-dev-to-hackathon","last_synced_at":"2025-04-21T04:31:28.998Z","repository":{"id":58024258,"uuid":"523515633","full_name":"briancaffey/redis-light-green-light-dev-to-hackathon","owner":"briancaffey","description":"My submission for the Redis Hackathon on DEV! \"Red Light, Green Light\" built with Python, JavaScript and Redis","archived":false,"fork":false,"pushed_at":"2022-08-29T02:46:21.000Z","size":1703,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-04T13:21:56.580Z","etag":null,"topics":["celery","celerybeat","flask","flask-socketio","nuxt","nuxt3","python","python3","redis","redis-om","redis-stack","socket-io","socket-io-client"],"latest_commit_sha":null,"homepage":"","language":"Python","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/briancaffey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-10T22:36:46.000Z","updated_at":"2023-07-04T08:14:45.000Z","dependencies_parsed_at":"2023-01-16T19:15:18.529Z","dependency_job_id":null,"html_url":"https://github.com/briancaffey/redis-light-green-light-dev-to-hackathon","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":"briancaffey/flask-redis-ecs-terraform","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/briancaffey%2Fredis-light-green-light-dev-to-hackathon","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/briancaffey%2Fredis-light-green-light-dev-to-hackathon/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/briancaffey%2Fredis-light-green-light-dev-to-hackathon/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/briancaffey%2Fredis-light-green-light-dev-to-hackathon/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/briancaffey","download_url":"https://codeload.github.com/briancaffey/redis-light-green-light-dev-to-hackathon/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249996095,"owners_count":21358061,"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":["celery","celerybeat","flask","flask-socketio","nuxt","nuxt3","python","python3","redis","redis-om","redis-stack","socket-io","socket-io-client"],"created_at":"2024-10-04T13:21:19.705Z","updated_at":"2025-04-21T04:31:28.583Z","avatar_url":"https://github.com/briancaffey.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Redis Light, Green Light\n\nThis project is an online, multiplayer implementation of \"Red Light, Green Light\" from Squid Game built with Python, Javascript and Redis. This is my submission for the 2022 [Redis Hackathon on DEV](https://dev.to/devteam/announcing-the-redis-hackathon-on-dev-3248)!\n\n### Gameplay\n\n![Redis Light, Green Light Gameplay](/images/gameplay.png)\n\n### Game event log built with redis streams\n\n![Redis Stream data](/images/events.png)\n\n### Architecture Overview\n\n![Project Architecture Diagram](/images/rlgl.drawio.png)\n\n## Code Overview with `cloc` (count lines of code)\n\n```\nmake cloc\n\ngithub.com/AlDanial/cloc v 1.94  T=0.03 s (1102.7 files/s, 67098.7 lines/s)\n-------------------------------------------------------------------------------\nLanguage                     files          blank        comment           code\n-------------------------------------------------------------------------------\nVuejs Component                 13            104             14            528\nPython                           3            178            104            410\nMarkdown                         5            139              0            272\nYAML                             3             10              0             83\nmake                             1             10              2             34\nSVG                              2              0              0             22\nTypeScript                       1              1              1             22\nCSS                              1              4              0             18\nJavaScript                       1              0              1             18\nText                             2              0              0             13\nDockerfile                       1              8              0             12\n-------------------------------------------------------------------------------\nSUM:                            33            454            122           1432\n-------------------------------------------------------------------------------\n```\n\n# Overview video\n\nHere's a short video that explains the project and how it uses Redis:\n\n\u003ciframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/BoalZKmgoEU\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen\u003e\u003c/iframe\u003e\n\n## How it works\n\n### How the data is stored:\n\nReal-time data for game state is stored in hashes called `Room` and `Position`.\n\n```py\nclass Room(HashModel):\n    room: uuid.UUID = Field(index=True)\n    light: str # red or green\n    changed: int # timestamp\n    created: int # timestamp\n\n\nclass Position(HashModel):\n    pos: int # represents how many steps a player has taken\n    state: str # alive or dead\n    player: uuid.UUID = Field(index=True)\n    room: uuid.UUID = Field(index=True)\n```\n\nRedis OM is used to perform CRUD (create, read, update and delete) operations on these hashes in API requests, websocket handlers and celery tasks. Here are some examples:\n\n**Creating a new room**: `CREATE operation` on `Room` hash\n\n```py\nroom = Room(\n    room=new_room_id, light=LightState.RED, changed=timestamp, created=timestamp\n)\nroom.save()\n```\n\n**Changing the color of a light for a room**: `UPDATE` operation on `Room` hash\n\n```py\nom_room = Room.find(Room.room == room).first()\nom_room.update(light=state, changed=timestamp)\n```\n\nIn Squid Game, Jun-ho learns that the games have been running for over 30 years, and that his elder brother Hwang In-ho was the winner in 2015. To keep a permanent historical record all of the events from a room I use **Redis streams** and the `XADD` command.\n\nThere are **eight** types of events that can happen in the lifecycle of a game:\n\n```py\nclass EventType:\n    CREATED = \"created\" # new game room is created\n    LIGHT = \"light\" # the light color is updated\n    JOIN = \"join\" # player joins a room\n    MOVE = \"move\" # player moves successfully when the light is green\n    WIN = \"win\" # player wins by moving 100 steps\n    DIE = \"die\" # player tries to move when the light is read\n    LEAVE = \"leave\" # player leaves or is disconnected from the game\n    END = \"end\" # game ends because there are no more players in the room\n```\n\nI use streams as append-only logs to persist very action that happens during the course of a game. Each event in the stream has an `event` property that stores one of the `EventType`s listed above. Here are some examples of how I store game event data in streams:\n\n**Record a room creation event**\n\n```py\nom_redis_conn.xadd(f\"stream:room:{new_room_id}\", {\"event\": EventType.CREATED})\n```\n\n**Record when a player moves successfully**\n\n```py\nom_redis_conn.xadd(\n    f\"stream:room:{room}\",\n    {\n        \"event\": EventType.MOVE,\n        \"player\": player,\n        \"pos\": new_pos,\n    },\n)\n```\n\n**Record when a player wins**\n\n```py\nom_redis_conn.xadd(\n    f\"stream:room:{room}\",\n    {\"event\": EventType.WIN, \"player\": player, \"pos\": value},\n)\n```\n\n### How the data is accessed:\n\nData is accessed using a combination of Redis OM queries and raw Redis commands. When a user joins a room, the web socket handler fetches all players currently in the give room with the following query:\n\n```py\npositions = Position.find(Position.room == room).all()\n```\n\nTo display all events for a given room, the `XRANGE` command is used to fetch all events which are then sent back to the client:\n\n```py\nevents = om_redis_conn.xrange(f\"stream:room:{room}\", min=\"-\", max=\"+\")\n```\n\n### Indirect usage of Redis\n\nIn addition to storing temporary game state and append-only event logs, Redis also supports the application as a message broker for the celery worker and scheduler, and it supports the SocketIO as a message queue which is required when there are multiple servers process (Flask, celery, celerybeat). Main application data is stored on DB index `0`, and these other services use other DB indexes for isolation and separation of concerns to the extent that it makes sense.\n\n## How to run it locally?\n\nRunning the application in a local development environment involves starting the web client and also starting multiple backend services. Backend services can be brought up using a `docker-compose.yml` file or they can be started by running commands in a Python virtual environment.\n\n### Prerequisites\n\nTo run the client locally you will need:\n\n- Node 16.16.0\n\nTo run the backend locally with docker and docker-compose you will need:\n\n- docker 20.10.14+\n- docker-compose 1.29.2\n- Python 3.9+ (if not using docker)\n\nRun the following command to check your local versions:\n\n```\nmake check\n```\n\nIt should show something like:\n\n```\nDocker version 20.10.14, build a224086\n\ndocker-compose version 1.29.2, build 5becea4c\n\nPython 3.10.2\n\nNode v16.16.0\n```\n\n### Local installation\n\nTo start the backend services (Flask API, celery worker, celerybeat scheduler and Redis Stack), you can run:\n\n```\ndocker-compose up\n```\n\nMake sure that you do not have any local instances of redis using port `6379` before running the above command, or it will fail to start.\n\nTo start the client, run the follow commands:\n\n```\ncd client\nnpm i\nnpm run dev\n```\n\nThe backend can also be ran locally using virtual environments. You can run the `redis-stack` docker image with:\n\n```\ndocker-compose -f redis-stack.yml up\n```\n\nCreate a virtual environment in the `/app` directory and active it:\n\n```\npython3 -m venv .env\nsource .env/bin/activate\n```\n\nThen install pip requirements:\n\n```\npip install -r requirements.txt\npip install -r requirements_dev.txt\n```\n\nNext you can start the three services in different windows. Before starting each service, make sure to activate the virtual environment with:\n\n```\nsource .env/bin/activate\n```\n\nFor advanced usage, please see the [`Makefile`](/Makefile) which has some helpful targets for starting different parts of the application (redis-stack, backend services and client).\n\n**Start the Flask API server**\n\n```\ngunicorn -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker -w 1 wsgi:app --reload\n```\n\nYou should see:\n\n```\n[2022-08-27 11:19:29 -0400] [17881] [INFO] Starting gunicorn 20.1.0\n[2022-08-27 11:19:29 -0400] [17881] [INFO] Listening at: http://127.0.0.1:8000 (17881)\n[2022-08-27 11:19:29 -0400] [17881] [INFO] Using worker: geventwebsocket.gunicorn.workers.GeventWebSocketWorker\n[2022-08-27 11:19:29 -0400] [17882] [INFO] Booting worker with pid: 17882\n```\n\n**Start the celery worker**\n\n```\ncelery --app app.celery worker --loglevel=info\n```\n\n```\ncelery@Brians-MacBook-Pro.local v5.2.3 (dawn-chorus)\n\nmacOS-12.3.1-arm64-arm-64bit 2022-08-27 11:21:05\n\n[config]\n.\u003e app:         app:0x105e63a30\n.\u003e transport:   redis://localhost:6379/2\n.\u003e results:     redis://localhost:6379/3\n.\u003e concurrency: 8 (prefork)\n.\u003e task events: OFF (enable -E to monitor tasks in this worker)\n\n[queues]\n.\u003e celery           exchange=celery(direct) key=celery\n\n\n[tasks]\n  . update_light\n  . update_lights\n\n[2022-08-27 11:21:06,094: INFO/MainProcess] Connected to redis://localhost:6379/2\n[2022-08-27 11:21:06,149: INFO/MainProcess] mingle: searching for neighbors\n[2022-08-27 11:21:07,234: INFO/MainProcess] mingle: all alone\n[2022-08-27 11:21:07,356: INFO/MainProcess] celery@Brians-MacBook-Pro.local ready.\n```\n\n**Start the celerybeat service**\n\n```\ncelery --app app.celery beat --loglevel=info\n```\n\nYou should see:\n\n```\ncelery beat v5.2.3 (dawn-chorus) is starting.\n__    -    ... __   -        _\nLocalTime -\u003e 2022-08-27 11:24:58\nConfiguration -\u003e\n    . broker -\u003e redis://localhost:6379/2\n    . loader -\u003e celery.loaders.app.AppLoader\n    . scheduler -\u003e celery.beat.PersistentScheduler\n    . db -\u003e celerybeat-schedule\n    . logfile -\u003e [stderr]@%INFO\n    . maxinterval -\u003e 5.00 minutes (300s)\n[2022-08-27 11:24:58,824: INFO/MainProcess] beat: Starting...\n[2022-08-27 11:25:00,847: INFO/MainProcess] Scheduler: Sending due task update_lights (update_lights)\n```\n\nBefore starting `celerybeat`, make sure that you have deleted a file called `celerybeat-schedule.db`.\n\nThe client runs on `http://localhost:3000`. It makes API and websocket connections with the backend which runs on `http://localhost:8000`.\n\nPlease see the [Makefile](/Makefile) for a full list of commands for running the application locally.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbriancaffey%2Fredis-light-green-light-dev-to-hackathon","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbriancaffey%2Fredis-light-green-light-dev-to-hackathon","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbriancaffey%2Fredis-light-green-light-dev-to-hackathon/lists"}