{"id":16651807,"url":"https://github.com/fourjr/elevator-simulator","last_synced_at":"2026-06-04T19:31:07.687Z","repository":{"id":157610259,"uuid":"622514009","full_name":"fourjr/elevator-simulator","owner":"fourjr","description":"Simulates an elevator with various algorithms","archived":false,"fork":false,"pushed_at":"2024-10-12T07:09:23.000Z","size":5211,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-12T22:42:46.630Z","etag":null,"topics":["python","simulator"],"latest_commit_sha":null,"homepage":"","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/fourjr.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":"2023-04-02T10:49:43.000Z","updated_at":"2025-10-26T18:06:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"2348aceb-cc28-4922-8abb-f4d69dd70b54","html_url":"https://github.com/fourjr/elevator-simulator","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fourjr/elevator-simulator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fourjr%2Felevator-simulator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fourjr%2Felevator-simulator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fourjr%2Felevator-simulator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fourjr%2Felevator-simulator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fourjr","download_url":"https://codeload.github.com/fourjr/elevator-simulator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fourjr%2Felevator-simulator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33917183,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"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":["python","simulator"],"created_at":"2024-10-12T09:26:39.379Z","updated_at":"2026-06-04T19:31:07.579Z","avatar_url":"https://github.com/fourjr.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"simulating elevators - [inspiration](https://youtu.be/xOayymoIl8U)\n\n# Contents\n- [Features](#features)\n- [Python GUI](#python-gui)\n    - [End User](#end-user)\n    - [Development](#development)\n- [Web GUI](#web-gui)\n    - [End User](#end-user-1)\n    - [Development](#development-1)\n- [Test Suite](#test-suite)\n    - [End User](#end-user-1)\n    - [Development](#development-1)\n        - [Benchmark Example](#benchmark-example)\n\n## Features\n- Fully featured test suite to run automated tests\n- Easy to use GUI to allow for manual testing and bug fixing\n- Extensible framework to implement new algorithms and tests\n- Ability to export and import artefacts for debugging or reproducible testing\n- Adjustable simulation speed*\n\n\u003e *Simulation speed might not be as relative as one might expect. For example, the jump from 100 to 200 might not be double the speed as there might be other factors such as code computation affecting the slower execution.\n\n## Python GUI\n\n### End User\n\nThe `gui` module exposes a [WXPython](https://www.wxpython.org/) GUI to allow end users to interact with the elevator simulator.\n\nRun `python -m gui`\n\n![preview](docs/preview.gif)\n\n### Development\n\nThe GUI uses a multithreaded approach as per the following explanation:\n| Thread | Description |\n| --- | --- |\n| Main Thread | Manages the GUI and user input |\n| Manager Thread | Manages the elevators and all backend related tasks |\n\nUpon any changes in the manager thread, a [wx event](https://docs.wxpython.org/events_overview.html) is fired to allow for the main thread to update the GUI. This can happen *very very* often (multiple times per tick) hence it is important to keep the event handlers as lightweight as possible and perform as little layout changes.\n\n\n## Web GUI\n\n### End User\n\nThe [`web/frontend`](/web/frontend) folder contains a web interface to interact with the elevator simulator. The web interface is built using [next.js](https://nextjs.org/) and [Material-UI](https://material-ui.com/).\n\nTo run the frontend, run `yarn build` \u003e `yarn start` in the `web/frontend` folder.\n\nThe `web.backend` module exposes an asynchronous websocket server to allow for communication between the web interface and the elevator simulator.\n\nTo run the backend, run `python -m web.backend`.\n\n### Development\n\nThe web interface and backend communicate through websockets with packets. The web frontend essentially takes data from the websocket and simply presents it. The backend is responsible for managing the elevators and algorithms.\n\nEach manager pool has a min and max manager count. This can be adjusted based on computer hardware as spinning up more manager will allow more users to connect concurrently but will also consume more resources.\n- min_manager: The initial number of managers to start and keep active\n- max_manager: The maximum number of managers to allow to be active concurrently. This should be kept higher in case of sudden load.\n\nA rough outline of the packet structure is as follows:\n```\n4 bytes: header\n2 bytes: opcode\n2 bytes: length (n)\n[\n    n bytes: data --\u003e integer (2 bytes short endian), string (ascii)\n]\n2 bytes: checksum (length, data)\n4 bytes: tail\n```\n\nA basic simulation is done by sending the below packets. After each client packet, the server will respond with the same packet (possibly with additional data).\n![packets](docs/packets.svg)\n\nThe next.js frontend can be run with `yarn dev` in the [`web/frontend`](/web/frontend) folder.\n\n## Test Suite\n\n### End User\n\nThe test suite exposes a `TestSuite` class that takes in `TestSettings`. This allows the end user to create reproducible tests and feed them in programmatically. Refer to the source code for exact arguments.\n\nFurther options can also be fed into the `TestSuite` class. Refer to the source code for exact arguments.\n\nIt is recommended for the `name` to not be distinguishable to the algorithm and multiple tests (with different algorithms) to have identical names.\n\n`python -m tests` will run all the tests in the `tests` folder. Tests must contain a  `run_test` function\n\nSource Code: [suite.py](/suite.py)    \nExamples: [test_json.py](/test_json.py) ([test.example.json](/test.example.json)), [test_benchmark.py](/test_benchmark.py)\n\n### Development\n\nThe test suite runs using a multiprocess approach as per the following explanation:\n| Process | Description |\n| --- | --- |\n| Main Process | Manages all processes and does final saving of results |\n| Background Process | Handles errors raised by test processes and exports artefacts |\n| `N` Test Processes | Runs the test and raises errors to the background process, reports back to main process |\n\nThe number of test processes (`N`) are determined by the following formula:\n- \u003c= the given `max_processes` kwarg\n- \u003c= (CPU Count - 1)\n- \u003c= Number of total iterations\n\nThe processes are then spawned and iterations are run concurrently. Upon any errors raised by the algorithm, it will be passed to the Background Process and the iteration will be skipped. A new process will be spawned to continue the test suite.\n\nTests are *mostly replicable* with the given seed. The initial state should be the same but there might be small kinks that could result in slightly varied outcomes. Note that for each seed, the iteration count is also attached to it.\n\n#### Benchmark Example\n\nRough example of what the test suite is capable of. This ran in under 3 minutes (10 iterations each) on a 4 physical core CPU.\n\n```\nSLOW                  NUM  TICK                 WAIT               TIL              OCC\n--------------------  ---  -------------------  -----------------  ---------------  -------------\nRolling               10   714.90 (697.00)      295.20 (285.35)    44.19 (38.95)    58.40 (67.33)\nDestination Dispatch  10   889.00 (913.50)      353.88 (334.30)    55.40 (50.35)    57.22 (62.00)\nScatter               10   955.10 (956.50)      387.54 (384.25)    74.25 (59.65)    75.16 (84.00)\nLOOK                  10   1267.40 (1311.50)    519.34 (504.15)    102.95 (88.60)   78.52 (88.67)\nNStepLOOK             10   1343.70 (1400.50)    525.12 (478.45)    68.43 (58.35)    49.47 (51.33)\nFCFS                  10   1552.20 (1569.50)    680.54 (670.45)    75.26 (69.75)    46.25 (46.00)\n\nBUSY                  NUM  TICK                 WAIT               TIL              OCC\n--------------------  ---  -------------------  -----------------  ---------------  -------------\nRolling               10   5161.20 (5125.00)    2197.29 (2105.40)  183.15 (155.40)  68.51 (92.00)\nDestination Dispatch  10   5364.70 (5459.00)    2159.20 (2063.25)  172.26 (159.20)  60.34 (68.00)\nScatter               10   8144.80 (8166.50)    3453.09 (3427.00)  354.35 (242.15)  85.22 (98.67)\nLOOK                  10   8264.10 (8197.00)    3609.78 (3591.35)  367.68 (306.55)  88.06 (98.67)\nNStepLOOK             10   12005.00 (12145.50)  4507.09 (4395.95)  183.98 (157.45)  29.77 (14.67)\nFCFS                  10   13627.00 (13976.50)  5957.50 (5818.15)  243.40 (213.35)  34.09 (24.67)\n```\n\n## Development\n\nBoth the GUIs and the Test Suite control the same managers and algorithms in the backend. However, there are wrappers to allow for the difference in concurrency type (threading/multiprocessing/asyncio).\n\n![overview](docs/overview.svg)\n\n| File | Description |\n| --- | --- |\n| [gui.py](/gui.py) | GUI handler |\n| [gui](/gui) | GUI backend |\n| [web](/web) | Web interface |\n| [suite](/suite) | Test suite |\n| [models](/models) | Data models for custom classes |\n| [constants.py](/constants.py) | Various enums and constants |\n| [utils.py](/utils.py) | Utility functions |\n| [errors.py](/errors.py) | Custom errors |\n\n### Dependencies\n- wxPython===4.2.1 ([PyPi](https://pypi.org/project/wxPython/4.2.1/), [official website](https://wxpython.org/pages/downloads/index.html))\n- tqdm===4.65.0 [test suite only] ([PyPi](https://pypi.org/project/tqdm/4.65.0/))\n- colorama===0.4.6 [test suite only] ([PyPi](https://pypi.org/project/colorama/0.4.6/))\n\n### Custom Algorithms\n\nA custom algorithm can be made by subclassing [ElevatorAlgorithm](/models.py) in a file in the `algorithms` folder.\n\nThe name of the file is unimportant. 2 attributes need to be defined in the file,  `__algorithm__` (object) as shown. `name` must be defined in the subclass.\n\n```python\nclass MyAlgorithm(ElevatorAlgorithm):\n    name = \"My Custom Algorithm\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        # custom init code here\n\n    def get_new_destination(self, elevator: Elevator) -\u003e int:\n        # return a integer for the new destination floor\n        pass\n\n\n__algorithm__ = MyAlgorithm\n```\n\nThere are various events exposed for subclasses but the only required function is `get_new_destination`. Exposed events are listed below.\n\n```python\ndef pre_loop(self):\ndef post_loop(self):\ndef on_load_load(self, load, elevator):\ndef on_load_unload(self, load, elevator):\ndef on_elevator_move(self, elevator):\ndef on_elevator_added(self, elevator):\ndef on_elevator_removed(self, elevator):\ndef on_floors_changed(self):\ndef on_load_added(self, load):\ndef on_load_removed(self, load):\ndef on_simulation_end(self, load):\n```\n\nThere are also 2 check functions that should return a boolean. If the check fails, the load will not be loaded/unloaded.\n```python\ndef pre_load_check(self, load, elevator) -\u003e bool:\ndef pre_unload_check(self, load, elevator) -\u003e bool:\n```\n\n### The Loop\n\nThe loop is managed by the [ActionQueue](/models/action.py) which prioritises the actions to be carried out on an elevator. The loop is called once every tick and actions are executed until `ADD_TICK`.\n\nIf there are no actions to be carried out, the elevator will carry out `RUN_CYCLE`.\n\n| Action | Description |\n| --- | --- |\n| ADD_TICK | Adds a tick to the elevator |\n| RUN_CYCLE | Runs the elevator [cycle](#the-cycle) |\n| MOVE_ELEVATOR | Moves the elevator to the next floor |\n| LOAD_LOAD | Loads the load into the elevator |\n| UNLOAD_LOAD | Unloads the load from the elevator |\n\n\nTicks are only physically added in the algorithm, after the entire elevator's cycle executes and it exits with a `ADD_TICK` action. This repeats again in the next tick.\n\nFor example, a typical action call could be as follows:\n```python\nRUN_CYCLE\nADD_TICK - 3  # moving elevator\nMOVE_ELEVATOR\n\nRUN_CYCLE\nADD_TICK - 3  # door open\nLOAD_LOAD\nLOAD_LOAD\nLOAD_LOAD\nADD_TICK - 1  # 3 loads\nLOAD_LOAD\nLOAD_LOAD\nLOAD_UNLOAD\nADD_TICK - 1  # 3 loads\nLOAD_UNLOAD\nADD_TICK - 1  # 3 loads (part thereof)\nADD_TICK - 3   # door close\nADD_TICK - 3  # moving elevator\nMOVE_ELEVATOR\n```\n\n![loop](docs/loop.svg)\n\n\u003e GUI events will be triggered upon every configuration change and every tick. Refer to [GUI](#gui) and [ElevatorManager](/models.py) \u003e `send_event` for more information.\n\n#### Ticks\nA tick is represented by 1 second (assuming 1x speed). The following are rough guidelines of how many ticks each action should take\n\n| Action | Ticks | Remarks\n| --- | --- | --- |\n| MOVE_ELEVATOR | 3 | |\n| LOAD_LOAD/UNLOAD_LOAD | 1 per 3 loads (or part thereof) | The counter is combined for both loading and unloading loads. |\n| OPEN_DOOR | 3 | This is only called when there is at least 1 load to load/unload. |\n| CLOSE_DOOR | 3 | This is only called when there is at least 1 load to load/unload. |\n\n\n#### The Cycle\nThe cycle is the main event loop of the elevator. It can be broken down into 3 major parts:\n1. Remove existing loads\n2. Add new loads\n3. Move the elevator\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffourjr%2Felevator-simulator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffourjr%2Felevator-simulator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffourjr%2Felevator-simulator/lists"}