motioninput-tui 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- motioninput_tui-0.1.0/LICENSE +21 -0
- motioninput_tui-0.1.0/PKG-INFO +106 -0
- motioninput_tui-0.1.0/README.md +85 -0
- motioninput_tui-0.1.0/pyproject.toml +134 -0
- motioninput_tui-0.1.0/pyproject.toml.orig +142 -0
- motioninput_tui-0.1.0/src/motioninput_tui/__init__.py +9 -0
- motioninput_tui-0.1.0/src/motioninput_tui/__main__.py +179 -0
- motioninput_tui-0.1.0/src/motioninput_tui/config.py +246 -0
- motioninput_tui-0.1.0/src/motioninput_tui/constants.py +41 -0
- motioninput_tui-0.1.0/src/motioninput_tui/controls/__init__.py +1 -0
- motioninput_tui-0.1.0/src/motioninput_tui/controls/buttons.py +131 -0
- motioninput_tui-0.1.0/src/motioninput_tui/controls/gamepad.py +256 -0
- motioninput_tui-0.1.0/src/motioninput_tui/controls/layouts.py +549 -0
- motioninput_tui-0.1.0/src/motioninput_tui/controls/source.py +125 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/__init__.py +1 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/buffer.py +114 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/motions.py +801 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/notation.py +207 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/recognizer.py +377 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/ruleset.py +159 -0
- motioninput_tui-0.1.0/src/motioninput_tui/engine/session.py +295 -0
- motioninput_tui-0.1.0/src/motioninput_tui/gamepad_probe.py +216 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/__init__.py +1 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/hsf2.json +1903 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/kof2001.json +9076 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/kof98.json +7700 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/lb2.json +2972 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/sfa3.json +6920 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/sfiii3.json +5456 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/ssii.json +2366 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/ssvsp.json +4306 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/data/usfiv.json +10064 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/loader.py +86 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/models.py +137 -0
- motioninput_tui-0.1.0/src/motioninput_tui/games/rulesets.py +406 -0
- motioninput_tui-0.1.0/src/motioninput_tui/notation_styles.py +379 -0
- motioninput_tui-0.1.0/src/motioninput_tui/py.typed +0 -0
- motioninput_tui-0.1.0/src/motioninput_tui/settings.py +73 -0
- motioninput_tui-0.1.0/src/motioninput_tui/terminal/__init__.py +6 -0
- motioninput_tui-0.1.0/src/motioninput_tui/terminal/detect.py +143 -0
- motioninput_tui-0.1.0/src/motioninput_tui/terminal/kitty.py +143 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/__init__.py +5 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/app.py +251 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/keyboard_driver.py +105 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/__init__.py +6 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/gamepad_bind.py +155 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/input_display.py +199 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/input_picker.py +243 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/keyboard_bind.py +154 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/notation.py +152 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/settings.py +87 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/setup.py +228 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/screens/training.py +222 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/__init__.py +7 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/input_strip.py +57 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/move_feed.py +75 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/movelist.py +92 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/panel.py +110 -0
- motioninput_tui-0.1.0/src/motioninput_tui/tui/widgets/settings_list.py +99 -0
- motioninput_tui-0.1.0/src/motioninput_tui/utils/__init__.py +1 -0
- motioninput_tui-0.1.0/src/motioninput_tui/utils/logger.py +78 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kieran Gee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: motioninput-tui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A terminal trainer for fighting game motion inputs, tuned per game.
|
|
5
|
+
Author: Kieran Gee
|
|
6
|
+
Author-email: Kieran Gee <kieran.lost.the.game@gmail.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Topic :: Games/Entertainment
|
|
14
|
+
Requires-Dist: rich>=15.0.0
|
|
15
|
+
Requires-Dist: textual>=8.2.8
|
|
16
|
+
Requires-Dist: pygame-ce>=2.5.8
|
|
17
|
+
Requires-Python: >=3.14.0, <4
|
|
18
|
+
Project-URL: Repository, https://github.com/kism/motioninput-tui
|
|
19
|
+
Project-URL: Documentation, https://motioninput-tui.readthedocs.io/
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# motioninput-tui
|
|
23
|
+
|
|
24
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/check.yml)
|
|
25
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml)
|
|
26
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/test.yml)
|
|
27
|
+
[](https://codecov.io/gh/kism/motioninput-tui)
|
|
28
|
+
[](https://motioninput-tui.readthedocs.io/en/latest/?badge=latest)
|
|
29
|
+
|
|
30
|
+
A terminal trainer for fighting game motion inputs. Pick a game and a
|
|
31
|
+
character, press inputs, and see which move the game would have given you.
|
|
32
|
+
The input handling has been tuned per game: hold down and
|
|
33
|
+
double tap forward in 3rd Strike and you get a dragon punch; do it in Super
|
|
34
|
+
Turbo or Alpha 3 and you get nothing.
|
|
35
|
+
|
|
36
|
+
Ships with Hyper Street Fighter II, Street Fighter Alpha 3, 3rd Strike, The
|
|
37
|
+
King of Fighters '98 and 2001, and Samurai Shodown V Special.
|
|
38
|
+
|
|
39
|
+
**Full documentation: <https://motioninput-tui.readthedocs.io/>**
|
|
40
|
+
|
|
41
|
+
## Install and run
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv sync --all-groups # omit --all-groups for a plain install
|
|
45
|
+
uv run motioninput-tui
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
See the docs for the full command line reference, controls, settings and move
|
|
49
|
+
notation.
|
|
50
|
+
|
|
51
|
+
## Technical Information and AI Disclaimr
|
|
52
|
+
|
|
53
|
+
### How it's created
|
|
54
|
+
|
|
55
|
+
This is my first AI-heavy project, the workflow is
|
|
56
|
+
|
|
57
|
+
- Get claude to add a game based on a gamefaqs guide
|
|
58
|
+
- sha256 of original guide is verified to avoid possibility of claude editing the reference data
|
|
59
|
+
- skill to make a briefing of each guide, each brief gets made in the game-brief skill
|
|
60
|
+
- claude writes a parser for each guide
|
|
61
|
+
- The moves from the guide are parsed with hard logic, not interpereted by claude
|
|
62
|
+
- The game logic is interpreted by claude based on the guide
|
|
63
|
+
|
|
64
|
+
- The game engine has many control layouts and motion inputs that are applied per game
|
|
65
|
+
|
|
66
|
+
### How I ensure code quality
|
|
67
|
+
|
|
68
|
+
For each numberd release I do the following
|
|
69
|
+
|
|
70
|
+
- Manually read all the changed code from the previous release
|
|
71
|
+
|
|
72
|
+
- In this register of games/characters I personally test and re-verify
|
|
73
|
+
- This is not frame perfect, I just open 3SX/Mame/whatever and see if it feels the same.
|
|
74
|
+
- The tests that claude writes will reflect these, but absolutely needs to be checked by a human
|
|
75
|
+
|
|
76
|
+
- Register (this will later be a separate file)
|
|
77
|
+
- SFA3
|
|
78
|
+
- Ken
|
|
79
|
+
- Shouryuu Ken feels too strict
|
|
80
|
+
- Sakura
|
|
81
|
+
- Shou'ou Ken feels too strict
|
|
82
|
+
- Sakura Otoshi punch timing is relaxed?
|
|
83
|
+
- SFIII
|
|
84
|
+
- Ken
|
|
85
|
+
- Elana
|
|
86
|
+
- Hugo
|
|
87
|
+
- I can't do Gigas Breaker
|
|
88
|
+
- USFIV
|
|
89
|
+
- Ken
|
|
90
|
+
- Sakura
|
|
91
|
+
- Sakura Otoshi timing is relaxed
|
|
92
|
+
- KOF2001
|
|
93
|
+
- Yuri Sakazaki
|
|
94
|
+
|
|
95
|
+
## Contributing
|
|
96
|
+
|
|
97
|
+
- [Adding a game](https://motioninput-tui.readthedocs.io/en/latest/adding-a-game.html)
|
|
98
|
+
- [Development setup](https://motioninput-tui.readthedocs.io/en/latest/development.html)
|
|
99
|
+
|
|
100
|
+
## Credit
|
|
101
|
+
|
|
102
|
+
Move list guides by:
|
|
103
|
+
|
|
104
|
+
- [Kao Megura / Chris MacDonald](https://gamefaqs.gamespot.com/community/Kao_Megura/contributions/faqs) [Rest In Peace](https://web.archive.org/web/20040520095719/http://cgfm2.emuviews.com/).
|
|
105
|
+
- x_MJ_x (Hyper Street Fighter II)
|
|
106
|
+
- THEMCD / Damon M. McDaniel (Ultimate Street Fighter IV)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# motioninput-tui
|
|
2
|
+
|
|
3
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/check.yml)
|
|
4
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/check_types.yml)
|
|
5
|
+
[](https://github.com/kism/motioninput-tui/actions/workflows/test.yml)
|
|
6
|
+
[](https://codecov.io/gh/kism/motioninput-tui)
|
|
7
|
+
[](https://motioninput-tui.readthedocs.io/en/latest/?badge=latest)
|
|
8
|
+
|
|
9
|
+
A terminal trainer for fighting game motion inputs. Pick a game and a
|
|
10
|
+
character, press inputs, and see which move the game would have given you.
|
|
11
|
+
The input handling has been tuned per game: hold down and
|
|
12
|
+
double tap forward in 3rd Strike and you get a dragon punch; do it in Super
|
|
13
|
+
Turbo or Alpha 3 and you get nothing.
|
|
14
|
+
|
|
15
|
+
Ships with Hyper Street Fighter II, Street Fighter Alpha 3, 3rd Strike, The
|
|
16
|
+
King of Fighters '98 and 2001, and Samurai Shodown V Special.
|
|
17
|
+
|
|
18
|
+
**Full documentation: <https://motioninput-tui.readthedocs.io/>**
|
|
19
|
+
|
|
20
|
+
## Install and run
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv sync --all-groups # omit --all-groups for a plain install
|
|
24
|
+
uv run motioninput-tui
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the docs for the full command line reference, controls, settings and move
|
|
28
|
+
notation.
|
|
29
|
+
|
|
30
|
+
## Technical Information and AI Disclaimr
|
|
31
|
+
|
|
32
|
+
### How it's created
|
|
33
|
+
|
|
34
|
+
This is my first AI-heavy project, the workflow is
|
|
35
|
+
|
|
36
|
+
- Get claude to add a game based on a gamefaqs guide
|
|
37
|
+
- sha256 of original guide is verified to avoid possibility of claude editing the reference data
|
|
38
|
+
- skill to make a briefing of each guide, each brief gets made in the game-brief skill
|
|
39
|
+
- claude writes a parser for each guide
|
|
40
|
+
- The moves from the guide are parsed with hard logic, not interpereted by claude
|
|
41
|
+
- The game logic is interpreted by claude based on the guide
|
|
42
|
+
|
|
43
|
+
- The game engine has many control layouts and motion inputs that are applied per game
|
|
44
|
+
|
|
45
|
+
### How I ensure code quality
|
|
46
|
+
|
|
47
|
+
For each numberd release I do the following
|
|
48
|
+
|
|
49
|
+
- Manually read all the changed code from the previous release
|
|
50
|
+
|
|
51
|
+
- In this register of games/characters I personally test and re-verify
|
|
52
|
+
- This is not frame perfect, I just open 3SX/Mame/whatever and see if it feels the same.
|
|
53
|
+
- The tests that claude writes will reflect these, but absolutely needs to be checked by a human
|
|
54
|
+
|
|
55
|
+
- Register (this will later be a separate file)
|
|
56
|
+
- SFA3
|
|
57
|
+
- Ken
|
|
58
|
+
- Shouryuu Ken feels too strict
|
|
59
|
+
- Sakura
|
|
60
|
+
- Shou'ou Ken feels too strict
|
|
61
|
+
- Sakura Otoshi punch timing is relaxed?
|
|
62
|
+
- SFIII
|
|
63
|
+
- Ken
|
|
64
|
+
- Elana
|
|
65
|
+
- Hugo
|
|
66
|
+
- I can't do Gigas Breaker
|
|
67
|
+
- USFIV
|
|
68
|
+
- Ken
|
|
69
|
+
- Sakura
|
|
70
|
+
- Sakura Otoshi timing is relaxed
|
|
71
|
+
- KOF2001
|
|
72
|
+
- Yuri Sakazaki
|
|
73
|
+
|
|
74
|
+
## Contributing
|
|
75
|
+
|
|
76
|
+
- [Adding a game](https://motioninput-tui.readthedocs.io/en/latest/adding-a-game.html)
|
|
77
|
+
- [Development setup](https://motioninput-tui.readthedocs.io/en/latest/development.html)
|
|
78
|
+
|
|
79
|
+
## Credit
|
|
80
|
+
|
|
81
|
+
Move list guides by:
|
|
82
|
+
|
|
83
|
+
- [Kao Megura / Chris MacDonald](https://gamefaqs.gamespot.com/community/Kao_Megura/contributions/faqs) [Rest In Peace](https://web.archive.org/web/20040520095719/http://cgfm2.emuviews.com/).
|
|
84
|
+
- x_MJ_x (Hyper Street Fighter II)
|
|
85
|
+
- THEMCD / Damon M. McDaniel (Ultimate Street Fighter IV)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "motioninput-tui"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A terminal trainer for fighting game motion inputs, tuned per game."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.14.0,<4"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"rich>=15.0.0",
|
|
11
|
+
"textual>=8.2.8",
|
|
12
|
+
"pygame-ce>=2.5.8",
|
|
13
|
+
]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: End Users/Desktop",
|
|
18
|
+
"Programming Language :: Python :: 3.14",
|
|
19
|
+
"Topic :: Games/Entertainment",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[[project.authors]]
|
|
23
|
+
name = "Kieran Gee"
|
|
24
|
+
email = "kieran.lost.the.game@gmail.com"
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Repository = "https://github.com/kism/motioninput-tui"
|
|
28
|
+
Documentation = "https://motioninput-tui.readthedocs.io/"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
motioninput-tui = "motioninput_tui.__main__:main"
|
|
32
|
+
motioninput-tui-probe = "motioninput_tui.gamepad_probe:main"
|
|
33
|
+
|
|
34
|
+
[dependency-groups]
|
|
35
|
+
type = ["ty>=0.0.69"]
|
|
36
|
+
lint = ["ruff>=0.16.2"]
|
|
37
|
+
test = [
|
|
38
|
+
"pytest>=9.1.1",
|
|
39
|
+
"coverage>=7.15.4",
|
|
40
|
+
]
|
|
41
|
+
docs = [
|
|
42
|
+
"sphinx>=8.1.0",
|
|
43
|
+
"myst-parser>=4.0.0",
|
|
44
|
+
"sphinx-rtd-theme>=3.0.0",
|
|
45
|
+
]
|
|
46
|
+
guides = [
|
|
47
|
+
"curl-cffi>=0.7.0",
|
|
48
|
+
"beautifulsoup4>=4.12.0",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[build-system]
|
|
52
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
53
|
+
build-backend = "uv_build"
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
cache-dir = "~/.cache/ruff"
|
|
57
|
+
line-length = 120
|
|
58
|
+
|
|
59
|
+
[tool.ruff.lint]
|
|
60
|
+
preview = true
|
|
61
|
+
select = ["ALL"]
|
|
62
|
+
ignore = [
|
|
63
|
+
"DJ",
|
|
64
|
+
"AIR",
|
|
65
|
+
"FAST",
|
|
66
|
+
"DTZ",
|
|
67
|
+
"missing-todo-author",
|
|
68
|
+
"missing-todo-link",
|
|
69
|
+
"single-line-implicit-string-concatenation",
|
|
70
|
+
"missing-trailing-comma",
|
|
71
|
+
"D",
|
|
72
|
+
"DOC",
|
|
73
|
+
"missing-copyright-notice",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint.per-file-ignores]
|
|
77
|
+
"tests/*.py" = [
|
|
78
|
+
"ARG",
|
|
79
|
+
"FBT",
|
|
80
|
+
"ANN",
|
|
81
|
+
"D",
|
|
82
|
+
"DOC",
|
|
83
|
+
"TC",
|
|
84
|
+
"no-self-use",
|
|
85
|
+
"assert",
|
|
86
|
+
"private-member-access",
|
|
87
|
+
"implicit-namespace-package",
|
|
88
|
+
"pytest-missing-fixture-name-underscore",
|
|
89
|
+
"suspicious-non-cryptographic-random-usage",
|
|
90
|
+
"hardcoded-password-string",
|
|
91
|
+
"too-many-arguments",
|
|
92
|
+
"blind-except",
|
|
93
|
+
"suspicious-non-cryptographic-random-usage",
|
|
94
|
+
"import-private-name",
|
|
95
|
+
]
|
|
96
|
+
"tests/test_notation_styles.py" = ["ambiguous-unicode-character-string"]
|
|
97
|
+
"src/motioninput_tui/notation_styles.py" = ["ambiguous-unicode-character-string"]
|
|
98
|
+
"scripts/*.py" = [
|
|
99
|
+
"print",
|
|
100
|
+
"implicit-namespace-package",
|
|
101
|
+
]
|
|
102
|
+
".claude/skills/**/*.py" = [
|
|
103
|
+
"print",
|
|
104
|
+
"implicit-namespace-package",
|
|
105
|
+
]
|
|
106
|
+
"docs/conf.py" = ["implicit-namespace-package"]
|
|
107
|
+
|
|
108
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
109
|
+
fixture-parentheses = false
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.pydocstyle]
|
|
112
|
+
convention = "google"
|
|
113
|
+
|
|
114
|
+
[tool.ruff.format]
|
|
115
|
+
quote-style = "double"
|
|
116
|
+
indent-style = "space"
|
|
117
|
+
docstring-code-format = true
|
|
118
|
+
docstring-code-line-length = 40
|
|
119
|
+
|
|
120
|
+
[tool.pytest.ini_options]
|
|
121
|
+
testpaths = ["tests"]
|
|
122
|
+
|
|
123
|
+
[tool.coverage.run]
|
|
124
|
+
command_line = ".venv/bin/pytest -q"
|
|
125
|
+
source = ["motioninput_tui"]
|
|
126
|
+
context = "test"
|
|
127
|
+
|
|
128
|
+
[tool.coverage.report]
|
|
129
|
+
show_missing = false
|
|
130
|
+
format = "text"
|
|
131
|
+
sort = "-Miss"
|
|
132
|
+
|
|
133
|
+
[tool.coverage.html]
|
|
134
|
+
show_contexts = true
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "motioninput-tui"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A terminal trainer for fighting game motion inputs, tuned per game."
|
|
5
|
+
authors = [{ name = "Kieran Gee", email = "kieran.lost.the.game@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
requires-python = ">=3.14.0,<4"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"rich>=15.0.0",
|
|
12
|
+
"textual>=8.2.8",
|
|
13
|
+
"pygame-ce>=2.5.8", # Gamepad support, imported only when the gamepad layout is selected.
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: End Users/Desktop",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Topic :: Games/Entertainment",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
# Development only; none of these are published as PyPI extras.
|
|
26
|
+
type = ["ty>=0.0.69"]
|
|
27
|
+
lint = ["ruff>=0.16.2"]
|
|
28
|
+
test = ["pytest>=9.1.1", "coverage>=7.15.4"]
|
|
29
|
+
|
|
30
|
+
# Building the docs/ site. Never imported by the trainer itself.
|
|
31
|
+
docs = ["sphinx>=8.1.0", "myst-parser>=4.0.0", "sphinx-rtd-theme>=3.0.0"]
|
|
32
|
+
|
|
33
|
+
# Only for fetching reference guides, never imported by the trainer itself.
|
|
34
|
+
guides = ["curl-cffi>=0.7.0", "beautifulsoup4>=4.12.0"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Repository = "https://github.com/kism/motioninput-tui"
|
|
38
|
+
Documentation = "https://motioninput-tui.readthedocs.io/"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["uv_build>=0.12.3,<0.13.0"]
|
|
42
|
+
build-backend = "uv_build"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
motioninput-tui = "motioninput_tui.__main__:main"
|
|
46
|
+
motioninput-tui-probe = "motioninput_tui.gamepad_probe:main"
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
cache-dir = "~/.cache/ruff"
|
|
50
|
+
line-length = 120
|
|
51
|
+
|
|
52
|
+
[tool.ruff.lint]
|
|
53
|
+
preview = true # Optional, these can be unstable, many of these rules exist.
|
|
54
|
+
select = ["ALL"] # Include absolutely all of Ruff's rules.
|
|
55
|
+
ignore = [
|
|
56
|
+
# Modules
|
|
57
|
+
"DJ", # Django not required
|
|
58
|
+
"AIR", # Airflow not required
|
|
59
|
+
"FAST", # FastAPI not required
|
|
60
|
+
"DTZ", # no need to specify timezone for time objects
|
|
61
|
+
|
|
62
|
+
# Specific rules
|
|
63
|
+
"missing-todo-author", # Verbose TODO, only I work on this project
|
|
64
|
+
"missing-todo-link", # Verbose TODO, only I work on this project
|
|
65
|
+
"single-line-implicit-string-concatenation", # conflict in the formatter
|
|
66
|
+
"missing-trailing-comma", # conflict in the formatter
|
|
67
|
+
"D", # Don't care about docstrings
|
|
68
|
+
"DOC", # Don't care about docstrings
|
|
69
|
+
"missing-copyright-notice", # Copyright lives in pyproject.toml
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.ruff.lint.per-file-ignores]
|
|
73
|
+
"tests/*.py" = [
|
|
74
|
+
# Modules
|
|
75
|
+
"ARG", # Unused function args -> fixtures nevertheless are functionally relevant
|
|
76
|
+
"FBT", # Don't care about booleans as positional arguments in tests, e.g. via @pytest.mark.parametrize()
|
|
77
|
+
"ANN", # Don't care about annotations in tests
|
|
78
|
+
"D", # Don't care about docstrings in tests
|
|
79
|
+
"DOC", # Don't care about docstrings in tests
|
|
80
|
+
"TC", # Test doubles are used at runtime; no need to guard their imports
|
|
81
|
+
|
|
82
|
+
# Specific rules
|
|
83
|
+
"no-self-use", # Test doubles mirror a real API, even when a method ignores self
|
|
84
|
+
"assert", # asserts allowed in tests.
|
|
85
|
+
"private-member-access", # Accessing private members is needed for testing.
|
|
86
|
+
"implicit-namespace-package", # __init__.py not needed for PyTest.
|
|
87
|
+
"pytest-missing-fixture-name-underscore", # monkeypatch doesn't return.
|
|
88
|
+
"suspicious-non-cryptographic-random-usage", # I'll assume no real crypto will be done in PyTest.
|
|
89
|
+
"hardcoded-password-string", # Hardcoded fake passwords are fine in tests.
|
|
90
|
+
"too-many-arguments", # Tests can have as many arguments as they want.
|
|
91
|
+
"blind-except", # Tests can use BaseException.
|
|
92
|
+
"suspicious-non-cryptographic-random-usage", # Regular RNG is fine in tests.
|
|
93
|
+
"import-private-name", # Tests can import private members.
|
|
94
|
+
]
|
|
95
|
+
"tests/test_notation_styles.py" = [
|
|
96
|
+
"ambiguous-unicode-character-string", # The glyphs are what it is testing.
|
|
97
|
+
]
|
|
98
|
+
"src/motioninput_tui/notation_styles.py" = [
|
|
99
|
+
# A module of glyphs to pick between: ⋃ really is meant to be a cup, not a U,
|
|
100
|
+
# and ×2 is the multiplication sign the guides use.
|
|
101
|
+
"ambiguous-unicode-character-string",
|
|
102
|
+
]
|
|
103
|
+
"scripts/*.py" = [
|
|
104
|
+
"print", # Command line tools; printing is how they report.
|
|
105
|
+
"implicit-namespace-package", # Standalone scripts, not a package.
|
|
106
|
+
]
|
|
107
|
+
".claude/skills/**/*.py" = [
|
|
108
|
+
"print", # Skill scripts are command line tools; printing is how they report.
|
|
109
|
+
"implicit-namespace-package", # Standalone scripts, not a package.
|
|
110
|
+
]
|
|
111
|
+
"docs/conf.py" = [
|
|
112
|
+
"implicit-namespace-package", # Sphinx config, not part of the motioninput_tui package.
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
117
|
+
fixture-parentheses = false
|
|
118
|
+
|
|
119
|
+
[tool.ruff.lint.pydocstyle]
|
|
120
|
+
convention = "google"
|
|
121
|
+
|
|
122
|
+
[tool.ruff.format]
|
|
123
|
+
quote-style = "double"
|
|
124
|
+
indent-style = "space"
|
|
125
|
+
docstring-code-format = true
|
|
126
|
+
docstring-code-line-length = 40
|
|
127
|
+
|
|
128
|
+
[tool.pytest.ini_options]
|
|
129
|
+
testpaths = ["tests"]
|
|
130
|
+
|
|
131
|
+
[tool.coverage.run]
|
|
132
|
+
command_line = ".venv/bin/pytest -q"
|
|
133
|
+
source = ["motioninput_tui"]
|
|
134
|
+
context = "test"
|
|
135
|
+
|
|
136
|
+
[tool.coverage.report]
|
|
137
|
+
show_missing = false
|
|
138
|
+
format = "text"
|
|
139
|
+
sort = "-Miss"
|
|
140
|
+
|
|
141
|
+
[tool.coverage.html]
|
|
142
|
+
show_contexts = true
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Main entrypoint."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from rich import traceback
|
|
8
|
+
|
|
9
|
+
from .config import Config, config_path
|
|
10
|
+
from .constants import PROGRAM_NAME, PROGRAM_NAME_WITH_FULL_VERSION, PROGRAM_NAME_WITH_VERSION
|
|
11
|
+
from .controls.layouts import DEFAULT_LAYOUT, available_layouts
|
|
12
|
+
from .engine.recognizer import BufferPolicy
|
|
13
|
+
from .games.loader import GameDataMissingError, available_games, load_game
|
|
14
|
+
from .games.rulesets import GAME_SPECS
|
|
15
|
+
from .terminal import detect, query_support
|
|
16
|
+
from .utils.logger import get_logger, setup_logger_cli
|
|
17
|
+
|
|
18
|
+
traceback.install(extra_lines=2)
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _get_args() -> argparse.Namespace:
|
|
23
|
+
parser = argparse.ArgumentParser(prog=PROGRAM_NAME, description="Fighting game motion input trainer.")
|
|
24
|
+
parser.add_argument("--game", choices=sorted(GAME_SPECS), help="Skip the game picker.")
|
|
25
|
+
parser.add_argument("--character", help="Skip the character picker. Needs --game.")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--layout",
|
|
28
|
+
choices=sorted(layout.key for layout in available_layouts()),
|
|
29
|
+
default=None,
|
|
30
|
+
help=f"Control layout (default: last used, or {DEFAULT_LAYOUT}).",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--no-key-release",
|
|
34
|
+
action="store_true",
|
|
35
|
+
help="Do not ask the terminal for key release reporting; infer holds from auto-repeat instead.",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--loose-buffer",
|
|
39
|
+
action=argparse.BooleanOptionalAction,
|
|
40
|
+
default=None,
|
|
41
|
+
help="Do not spend inputs when a move comes out, so one motion can feed several moves. "
|
|
42
|
+
"Not how the games behave; it is also in the trainer's settings, ctrl+b. Default: last used.",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--config",
|
|
46
|
+
type=Path,
|
|
47
|
+
default=None,
|
|
48
|
+
help=f"Config file holding the last used selection (default: {config_path()}).",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument("--list", action="store_true", help="List games and characters, then exit.")
|
|
51
|
+
parser.add_argument("--check-terminal", action="store_true", help="Report terminal suitability, then exit.")
|
|
52
|
+
parser.add_argument("--version", action="version", version=PROGRAM_NAME_WITH_FULL_VERSION)
|
|
53
|
+
parser.add_argument("-v", action="count", default=0, help="Increase verbosity (can be used multiple times).")
|
|
54
|
+
return parser.parse_args()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _print_terminal() -> int:
|
|
58
|
+
info = detect()
|
|
59
|
+
logger.info("Terminal: %s (%s, %s)", info.name, info.speed, info.detail)
|
|
60
|
+
if info.warning():
|
|
61
|
+
logger.warning("%s", info.warning())
|
|
62
|
+
else:
|
|
63
|
+
logger.info("Should be fast enough for accurate input timing.")
|
|
64
|
+
|
|
65
|
+
releases = query_support()
|
|
66
|
+
if releases is None:
|
|
67
|
+
logger.info("Key releases: could not ask, no terminal attached.")
|
|
68
|
+
elif releases:
|
|
69
|
+
logger.info("Key releases: supported. Holds will be tracked exactly.")
|
|
70
|
+
else:
|
|
71
|
+
logger.warning(
|
|
72
|
+
"Key releases: not supported. Holds will be inferred from auto-repeat, "
|
|
73
|
+
"so charge moves depend on your keyboard repeat delay. "
|
|
74
|
+
"Terminals that do support this: Ghostty, Alacritty, WezTerm, kitty, foot, Contour, Rio."
|
|
75
|
+
)
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _print_roster() -> int:
|
|
80
|
+
for game in available_games():
|
|
81
|
+
logger.info("%s - %s", game.key, game.name)
|
|
82
|
+
for character in game.characters:
|
|
83
|
+
# The input display's "characters" are button sets, with no moves
|
|
84
|
+
# to count, so they say what they are instead.
|
|
85
|
+
if character.moves:
|
|
86
|
+
logger.info(" %-22s %d trainable moves", character.key, len(character.trainable_moves))
|
|
87
|
+
else:
|
|
88
|
+
logger.info(" %-22s %s", character.key, character.name)
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def main() -> int:
|
|
93
|
+
"""Main entrypoint."""
|
|
94
|
+
args = _get_args()
|
|
95
|
+
setup_logger_cli(args.v)
|
|
96
|
+
logger.debug("%s", PROGRAM_NAME_WITH_VERSION)
|
|
97
|
+
|
|
98
|
+
if args.check_terminal:
|
|
99
|
+
return _print_terminal()
|
|
100
|
+
if args.list:
|
|
101
|
+
return _print_roster()
|
|
102
|
+
|
|
103
|
+
if args.character and not args.game:
|
|
104
|
+
logger.error("--character needs --game as well")
|
|
105
|
+
return 2
|
|
106
|
+
|
|
107
|
+
config = Config.load(args.config)
|
|
108
|
+
_apply_overrides(config, args)
|
|
109
|
+
|
|
110
|
+
if not _resolve_selection(config, from_cli=bool(args.character)):
|
|
111
|
+
return 1
|
|
112
|
+
|
|
113
|
+
info = detect()
|
|
114
|
+
if info.should_warn:
|
|
115
|
+
logger.warning("%s", info.warning())
|
|
116
|
+
|
|
117
|
+
# Ask before the interface takes over the terminal, so holds are tracked
|
|
118
|
+
# exactly from the very first keystroke rather than from the first release.
|
|
119
|
+
key_release = False if args.no_key_release else bool(query_support())
|
|
120
|
+
if key_release:
|
|
121
|
+
logger.debug("Terminal reports key releases; holds will be tracked exactly")
|
|
122
|
+
else:
|
|
123
|
+
logger.info("Terminal does not report key releases; holds will be inferred from auto-repeat")
|
|
124
|
+
|
|
125
|
+
from .tui import MotionInputApp # ruff: ignore[import-outside-top-level] - importing textual is slow, only do it when running the app
|
|
126
|
+
|
|
127
|
+
MotionInputApp(
|
|
128
|
+
config,
|
|
129
|
+
key_release=key_release,
|
|
130
|
+
skip_setup=bool(args.game and args.character),
|
|
131
|
+
).run()
|
|
132
|
+
return 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _resolve_selection(config: Config, *, from_cli: bool) -> bool:
|
|
136
|
+
"""Check the selection against the rosters. False means do not start.
|
|
137
|
+
|
|
138
|
+
A remembered character can simply be gone: rosters are regenerated, and a
|
|
139
|
+
name override in ``motioninput_tui_datagen/names.py`` renames the key with the character.
|
|
140
|
+
That is no reason to refuse to start, so the selection is dropped and the
|
|
141
|
+
picker opens on it instead. A character named on the command line is
|
|
142
|
+
different, and still gets an error.
|
|
143
|
+
"""
|
|
144
|
+
if not config.game:
|
|
145
|
+
return True
|
|
146
|
+
try:
|
|
147
|
+
game = load_game(config.game)
|
|
148
|
+
except GameDataMissingError as exc:
|
|
149
|
+
logger.error("%s", exc) # ruff: ignore[error-instead-of-exception] - a traceback helps nobody here
|
|
150
|
+
return False
|
|
151
|
+
if not config.character:
|
|
152
|
+
return True
|
|
153
|
+
try:
|
|
154
|
+
config.character = game.character(config.character).key
|
|
155
|
+
except KeyError as exc:
|
|
156
|
+
if from_cli:
|
|
157
|
+
logger.error("%s", exc) # ruff: ignore[error-instead-of-exception] - a traceback helps nobody here
|
|
158
|
+
return False
|
|
159
|
+
logger.info("Forgetting the saved character, it is not in the roster any more: %s", exc)
|
|
160
|
+
config.character = None
|
|
161
|
+
return True
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _apply_overrides(config: Config, args: argparse.Namespace) -> None:
|
|
165
|
+
"""Let command line arguments win over what was remembered."""
|
|
166
|
+
if args.game:
|
|
167
|
+
config.game = args.game
|
|
168
|
+
# Not the character from whatever game was being played last: whoever
|
|
169
|
+
# was last trained on this one. --character always wins, and cannot be
|
|
170
|
+
# given without --game.
|
|
171
|
+
config.character = args.character or config.characters.get(args.game)
|
|
172
|
+
if args.layout:
|
|
173
|
+
config.layout = args.layout
|
|
174
|
+
if args.loose_buffer is not None:
|
|
175
|
+
config.buffer_policy = BufferPolicy.LOOSE if args.loose_buffer else BufferPolicy.CONSUME
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
sys.exit(main()) # pragma: no cover
|