Skip to content

Commit

Permalink
Add type hints, Clean up repository (#5)
Browse files Browse the repository at this point in the history
Changes:
- Add type hints in `.pyi` file
- Make workflow run on MacOS, Windows, and Linux, for Python 3.9 and 3.13
- Specify demo dependencies in `pyproject.toml`
- Move demos to `demo` directory
- Remove `init` function
- Add correct compilation flags for MacOS
  • Loading branch information
wojciech-graj authored Oct 21, 2024
1 parent 735b4e4 commit 5045c36
Show file tree
Hide file tree
Showing 16 changed files with 435 additions and 340 deletions.
9 changes: 3 additions & 6 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python package
name: cydoomgeneric

on:
push:
Expand All @@ -15,8 +12,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.11"]
os: [ubuntu-latest, windows-latest]
python-version: ["3.9", "3.13"]
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v3
Expand Down
13 changes: 8 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
*.DS_Store
*.WAD
*.egg-info/
*.o
*.obj
*.DS_Store
*.so
build/
.savegame/
*.WAD
*.so.map
./cydoomgeneric/cydoomgeneric.c
.ropeproject/
.savegame/
__pycache__/
build/
cydoomgeneric/cydoomgeneric.c
57 changes: 24 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@ cyDoomGeneric should run on Linux, MacOS, and Windows.

You must implement the `draw_frame` and `get_key` functions.

```
```python
import cydoomgeneric as cdg

resx = 640
resy = 400

# Required functions
def draw_frame(pixels: np.ndarray) -> None:
def get_key() -> Optional[Tuple[int, int]]:
def get_key() -> Optional[tuple[int, int]]:

# Optional functions
def init() -> None:
def sleep_ms(ms: int) -> None:
def set_window_title(t: str) -> None:
def get_ticks_ms() -> int:
Expand All @@ -32,7 +31,6 @@ cdg.init(resx,
resy,
draw_frame,
get_key,
init=init,
sleep_ms=sleep_ms,
get_ticks_ms=get_ticks_ms,
set_window_title=set_window_title)
Expand All @@ -49,8 +47,8 @@ Some additional documentation can be found in `cydoomgeneric/cydoomgeneric.pyx`.

To build and install cydoomgeneric, run the following command:

```
$ pip install .
```sh
pip install .
```

## Demo Screenshots
Expand All @@ -76,18 +74,14 @@ $ pip install .

#### Pyplot

Ensure that the `matplotlib` python package is installed.

```
$ cd cydoomgeneric
$ python demopyplot.py
```sh
pip install '.[pyplot]'
python demo/demopyplot.py
```

#### Minecraft: Pi Edition

Ensure that the `mcpi scikit-image` packages are installed.

Before running the script, launch Minecraft: Pi Edition and join a world. The `SCALE` variable in `demominepi.py` can be adjusted to change the display size.
Before running the script, launch Minecraft: Pi Edition and join a world. The `SCALE` variable in `demo/demominepi.py` can be adjusted to change the display size.

To move, step on the appropriate block on the platform that the player is standing on. To press the fire, use, enter, or escape keys, hit (`RMB`) the appropriate block with the sword:
```
Expand All @@ -97,49 +91,46 @@ NETHER_REACTOR_CORE: ENTER
NETHER_REACTOR_CORE(active): ESCAPE
```

```
$ cd cydoomgeneric
$ python demominepi.py
```sh
pip install '.[minepi]'
python demo/demominepi.py
```

#### MS Paint

Ensure that the `pyautogui pywinctl scikit-image` packages are installed, and that the Windows XP version of mspaint is installed, which can be done by running `winetricks mspaint`.
Ensure that the Windows XP version of mspaint is installed, which can be done by running `winetricks mspaint`.

If you have not installed mspaint using wine, you'll have to edit the `PAINT_COMMAND` variable in `demomspaint.py` to contain the command for launching paint.
If you have not installed mspaint using wine, you'll have to edit the `PAINT_COMMAND` variable in `demo/demomspaint.py` to contain the command for launching paint.

If you wish to free your mouse in the middle of a frame being drawn, you should drag it to the top-left corner of the screen, which will free it, at which point you can kill the python script. Once a frame has been drawn, you will be able to send an input by flood-filling the appropriate "key" drawn under the frame.

```
$ cd cydoomgeneric
$ python demomspaint.py
```sh
pip install '.[mspaint]'
python demo/demomspaint.py
```

#### LibreOffice Calc

Ensure that the libreoffice SDK (`libreoffice-dev` on Debian) is installed, and that you're using the system python installation instead of a virtual environment.

The `SCALE` variable in `democalc.py` can be adjusted in the range `[0,5]` to change the display size, idealy either 1 or 2. Lower scales will exponentially increase the setup time required prior to starting the game. Expect to wait a few minutes.
The `SCALE` variable in `demo/democalc.py` can be adjusted in the range `[0,5]` to change the display size, idealy either 1 or 2. Lower scales will exponentially increase the setup time required prior to starting the game. Expect to wait a few minutes.

Sometimes the window will be tiny, so maximize it if neccessary. Also, you may experience unexpected issues while attempting to run this demo, and there's not much I can do because the UNO API has virtually no documentation and the code here has been pieced together from 10 year old forum posts for the Java or C++ version of the API.

Only run the following command once, unless the libreoffice process is killed:
```
$ libreoffice --nofirststartwizard --nologo --norestore --accept='socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext' &
```sh
libreoffice --nofirststartwizard --nologo --norestore --accept='socket,host=localhost,port=2002,tcpNoDelay=1;urp;StarOffice.ComponentContext' &
```

```
$ cd cydoomgeneric
$ python democalc.py
```sh
python demo/democalc.py
```

#### Pygame

Ensure that the `pygame` python package is installed.

```
$ cd cydoomgeneric
$ python demopygame.py
```sh
pip install '.[pygame]'
python demo/demopygame.py
```

## License
Expand Down
119 changes: 119 additions & 0 deletions cydoomgeneric/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
Copyright(C) 2024 Wojciech Graj
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
"""

from enum import IntEnum
from typing import Callable, Optional, Sequence

import numpy as np

def init(resx: int,
resy: int,
draw_frame: Callable[[np.ndarray], None],
get_key: Callable[[], Optional[tuple[int, int]]],
sleep_ms: Optional[Callable[[int], None]] = None,
get_ticks_ms: Optional[Callable[[], int]] = None,
set_window_title: Optional[Callable[[str], None]] = None) -> None:
"""
Initializes the doom context.
:param resx:
:param resy:
:param draw_frame: Called every frame. Takes framebuffer as np.ndarray in
shape [resy, resx, 4]. Pixels are BGR.
:param get_key: Called multiple times every frame until input is exhausted.
Return None when input is exhausted. Otherwise, return
(is pressed ~0/1~, key).
:param sleep_ms:
:param get_ticks_ms:
:param set_window_title:
"""


def main(argv: Optional[Sequence[str]] = None) -> int:
"""
main(argv) -> int
Run doom. Must be called after init.
:param Optional[Sequence[str]] argv:
"""


class Keys(IntEnum):
RIGHTARROW: int
LEFTARROW: int
UPARROW: int
DOWNARROW: int
STRAFE_L: int
STRAFE_R: int
USE: int
FIRE: int
ESCAPE: int
ENTER: int
TAB: int
F1: int
F2: int
F3: int
F4: int
F5: int
F6: int
F7: int
F8: int
F9: int
F10: int
F11: int
F12: int

BACKSPACE: int
PAUSE: int

EQUALS: int
MINUS: int

RSHIFT: int
RCTRL: int
RALT: int

LALT: int

CAPSLOCK: int
NUMLOCK: int
SCRLCK: int
PRTSCR: int

HOME: int
END: int
PGUP: int
PGDN: int
INS: int
DEL: int

P_0: int
P_1: int
P_2: int
P_3: int
P_4: int
P_5: int
P_6: int
P_7: int
P_8: int
P_9: int

P_DIVIDE: int
P_PLUS: int
P_MINUS: int
P_MULTIPLY: int
P_PERIOD: int
P_EQUALS: int
P_ENTER: int
1 change: 0 additions & 1 deletion cydoomgeneric/cydoomgeneric.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ cdef extern from "doomgeneric.h":

void dg_Create(uint32_t resx,
uint32_t resy,
void (*pDG_Init)() except *,
void (*pDG_DrawFrame)() noexcept,
void (*pDG_SleepMs)(uint32_t) noexcept,
uint32_t (*pDG_GetTicksMs)() noexcept,
Expand Down
37 changes: 3 additions & 34 deletions cydoomgeneric/cydoomgeneric.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from enum import IntEnum
import time
import sys
from typing import Callable, Optional, Tuple, Sequence
from typing import Callable, Optional, Sequence


from cpython.mem cimport PyMem_Malloc, PyMem_Free
Expand All @@ -25,20 +25,14 @@ import numpy as np
cimport numpy as np


__init_f: Optional[Callable[None, None]]
__draw_frame_f: Callable[[np.ndarray], None]
__sleep_ms_f: Optional[Callable[[int], None]]
__get_ticks_ms_f: Optional[Callable[None, int]]
__get_key_f: Callable[None, Optional[Tuple[int, int]]]
__get_key_f: Callable[None, Optional[tuple[int, int]]]
__set_window_title_f: Optional[Callable[[str], None]]
__start_time: int


cdef void __init() except *:
if __init_f:
__init_f()


cdef void __draw_frame() noexcept:
try:
__draw_frame_f(np.asarray(<uint8_t[:cdg.DOOMGENERIC_RESY, :cdg.DOOMGENERIC_RESX, :4]><uint8_t*>&cdg.DG_ScreenBuffer[0]))
Expand Down Expand Up @@ -89,34 +83,17 @@ def init(resx: int,
resy: int,
draw_frame: Callable[[np.ndarray], None],
get_key: Callable[[int], str],
init: Optional[Callable[None, None]]=None,
sleep_ms: Optional[Callable[[int], None]]=None,
get_ticks_ms: Optional[Callable[None, int]]=None,
get_ticks_ms: Optional[Callable[[], int]]=None,
set_window_title: Optional[Callable[[str], None]]=None
) -> None:
"""
init(resx, resx, init, draw_frame, sleep_ms, get_ticks_ms, get_key, set_window_title) -> None

Initializes the doom context.

:param int resx:
:param int resy:
:param Callable[[np.ndarray], None] draw_frame: Called every frame. Takes framebuffer as np.ndarray in shape [resy, resx, 4]. Pixels are BGR.
:param Callable[[int], Optional[Tuple[int, int]]] get_key: Called multiple times every frame until input is exhausted. Return None when input is exhausted. Otherwise, return (is pressed ~0/1~, key).
:param Optional[Callable[None, None]] init: Initialization function called immediately after this function terminates
:param Optional[Callable[[int], None]] sleep_ms:
:param Optional[Callable[None, int]] get_ticks_ms:
:param Optional[Callable[[str], None]] set_window_title:
"""
global __init_f
global __draw_frame_f
global __sleep_ms_f
global __get_ticks_ms_f
global __get_key_f
global __set_window_title_f
global __start_time

__init_f = init
__draw_frame_f = draw_frame
__sleep_ms_f = sleep_ms
__get_ticks_ms_f = get_ticks_ms
Expand All @@ -126,7 +103,6 @@ def init(resx: int,

cdg.dg_Create(resx,
resy,
&__init,
&__draw_frame,
&__sleep_ms,
&__get_ticks_ms,
Expand All @@ -135,13 +111,6 @@ def init(resx: int,


def main(argv: Optional[Sequence[str]]=None) -> int:
"""
main(argv) -> int

Run doom. Must be called after init.

:param Optional[Sequence[str]] argv:
"""
if argv is None:
return cdg.dg_main(0, NULL)

Expand Down
Empty file added cydoomgeneric/py.typed
Empty file.
Loading

0 comments on commit 5045c36

Please sign in to comment.