tinysim 0.0.1__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.
- tinysim-0.0.1/PKG-INFO +56 -0
- tinysim-0.0.1/README.md +30 -0
- tinysim-0.0.1/pyproject.toml +50 -0
- tinysim-0.0.1/setup.cfg +4 -0
- tinysim-0.0.1/tinysim/__init__.py +11 -0
- tinysim-0.0.1/tinysim/_tk_base.py +54 -0
- tinysim-0.0.1/tinysim/flappy/__init__.py +103 -0
- tinysim-0.0.1/tinysim/flappy/sim.js +118 -0
- tinysim-0.0.1/tinysim/flappy/tk.py +94 -0
- tinysim-0.0.1/tinysim/flappy/widget.py +53 -0
- tinysim-0.0.1/tinysim/frogger/__init__.py +145 -0
- tinysim-0.0.1/tinysim/frogger/sim.js +110 -0
- tinysim-0.0.1/tinysim/frogger/tk.py +95 -0
- tinysim-0.0.1/tinysim/frogger/widget.py +60 -0
- tinysim-0.0.1/tinysim/mountain_car/__init__.py +56 -0
- tinysim-0.0.1/tinysim/mountain_car/sim.js +158 -0
- tinysim-0.0.1/tinysim/mountain_car/styles.css +5 -0
- tinysim-0.0.1/tinysim/mountain_car/tk.py +141 -0
- tinysim-0.0.1/tinysim/mountain_car/widget.py +56 -0
- tinysim-0.0.1/tinysim/simple_amr/__init__.py +0 -0
- tinysim-0.0.1/tinysim/simple_amr/example_maps.py +121 -0
- tinysim-0.0.1/tinysim/simple_amr/sim.js +430 -0
- tinysim-0.0.1/tinysim/simple_amr/styles.css +54 -0
- tinysim-0.0.1/tinysim/simple_amr/widget.py +73 -0
- tinysim-0.0.1/tinysim/topdown_driving/__init__.py +190 -0
- tinysim-0.0.1/tinysim/topdown_driving/sim.js +136 -0
- tinysim-0.0.1/tinysim/topdown_driving/tk.py +180 -0
- tinysim-0.0.1/tinysim/topdown_driving/track_0.json +753 -0
- tinysim-0.0.1/tinysim/topdown_driving/widget.py +60 -0
- tinysim-0.0.1/tinysim.egg-info/PKG-INFO +56 -0
- tinysim-0.0.1/tinysim.egg-info/SOURCES.txt +36 -0
- tinysim-0.0.1/tinysim.egg-info/dependency_links.txt +1 -0
- tinysim-0.0.1/tinysim.egg-info/requires.txt +6 -0
- tinysim-0.0.1/tinysim.egg-info/top_level.txt +2 -0
- tinysim-0.0.1/tinysim_warp/cart_pole/__init__.py +259 -0
- tinysim-0.0.1/tinysim_warp/quadruped/__init__.py +203 -0
- tinysim-0.0.1/tinysim_warp/simple_quadruped/__init__.py +168 -0
- tinysim-0.0.1/tinysim_warp/simple_quadruped/simple_quadruped.urdf +247 -0
tinysim-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tinysim
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: small modular simulation environments
|
|
5
|
+
Author-email: Matthew Taylor <matthew.taylor.andre@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/MatthewAndreTaylor/TinySim
|
|
7
|
+
Keywords: jupyter,simulation
|
|
8
|
+
Classifier: Framework :: Jupyter
|
|
9
|
+
Classifier: Framework :: Jupyter :: JupyterLab
|
|
10
|
+
Classifier: Framework :: Jupyter :: JupyterLab :: 4
|
|
11
|
+
Classifier: Framework :: Jupyter :: JupyterLab :: Extensions
|
|
12
|
+
Classifier: Programming Language :: Python
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: anywidget
|
|
23
|
+
Requires-Dist: jupyter-ui-poll
|
|
24
|
+
Provides-Extra: warp
|
|
25
|
+
Requires-Dist: warp-lang==1.8.1; extra == "warp"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# TinySim
|
|
29
|
+
|
|
30
|
+
[](https://pypi.org/project/coderbot-sim)
|
|
31
|
+
[](https://matthewandretaylor.github.io/CoderBots/lab?path=example.ipynb)
|
|
32
|
+
|
|
33
|
+
The goal of this project is to create more minimal simulation environments for robotics and machine learning.
|
|
34
|
+
|
|
35
|
+
Existing solutions like [ROS 2](https://github.com/ros2) and [Gymnasium](https://gymnasium.farama.org/) come with many heavy dependencies.
|
|
36
|
+
|
|
37
|
+
Additionally they are usually difficult to get setup and may require domain specific knowledge to use.
|
|
38
|
+
Simulation environments should be able to run in any notebook environment and require fewer dependencies.
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Get Started 🚀
|
|
42
|
+
|
|
43
|
+
To use the baseline Python only simulation environments.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install tinysim
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
To use the [warp](https://github.com/NVIDIA/warp) simulation environments.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install tinysim[warp]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
tinysim-0.0.1/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
# TinySim
|
|
3
|
+
|
|
4
|
+
[](https://pypi.org/project/coderbot-sim)
|
|
5
|
+
[](https://matthewandretaylor.github.io/CoderBots/lab?path=example.ipynb)
|
|
6
|
+
|
|
7
|
+
The goal of this project is to create more minimal simulation environments for robotics and machine learning.
|
|
8
|
+
|
|
9
|
+
Existing solutions like [ROS 2](https://github.com/ros2) and [Gymnasium](https://gymnasium.farama.org/) come with many heavy dependencies.
|
|
10
|
+
|
|
11
|
+
Additionally they are usually difficult to get setup and may require domain specific knowledge to use.
|
|
12
|
+
Simulation environments should be able to run in any notebook environment and require fewer dependencies.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
## Get Started 🚀
|
|
16
|
+
|
|
17
|
+
To use the baseline Python only simulation environments.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install tinysim
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
To use the [warp](https://github.com/NVIDIA/warp) simulation environments.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install tinysim[warp]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "tinysim"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "small modular simulation environments"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Matthew Taylor", email = "matthew.taylor.andre@gmail.com"},
|
|
7
|
+
]
|
|
8
|
+
requires-python = ">=3.9"
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Framework :: Jupyter",
|
|
11
|
+
"Framework :: Jupyter :: JupyterLab",
|
|
12
|
+
"Framework :: Jupyter :: JupyterLab :: 4",
|
|
13
|
+
"Framework :: Jupyter :: JupyterLab :: Extensions",
|
|
14
|
+
"Programming Language :: Python",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.9",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
]
|
|
22
|
+
urls = {Homepage = "https://github.com/MatthewAndreTaylor/TinySim"}
|
|
23
|
+
keywords = ["jupyter", "simulation"]
|
|
24
|
+
|
|
25
|
+
dependencies = [
|
|
26
|
+
"numpy",
|
|
27
|
+
"anywidget",
|
|
28
|
+
"jupyter-ui-poll"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
warp = ["warp-lang==1.8.1"]
|
|
33
|
+
|
|
34
|
+
[project.readme]
|
|
35
|
+
file = "README.md"
|
|
36
|
+
content-type = "text/markdown"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools]
|
|
43
|
+
packages = ["tinysim", "tinysim_warp"]
|
|
44
|
+
include-package-data = true
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.package-data]
|
|
47
|
+
tinysim = ["**/*"]
|
|
48
|
+
tinysim_warp = ["**/*"]
|
|
49
|
+
|
|
50
|
+
|
tinysim-0.0.1/setup.cfg
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import tkinter as tk
|
|
2
|
+
import threading
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TkBaseFrontend(ABC):
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self._root = None
|
|
10
|
+
self._canvas = None
|
|
11
|
+
self._thread = None
|
|
12
|
+
|
|
13
|
+
def render(self):
|
|
14
|
+
if self._thread is not None:
|
|
15
|
+
return
|
|
16
|
+
self._thread = threading.Thread(target=self._window_hook, daemon=True)
|
|
17
|
+
self._thread.start()
|
|
18
|
+
|
|
19
|
+
def _window_hook(self):
|
|
20
|
+
root = tk.Tk()
|
|
21
|
+
root.protocol("WM_DELETE_WINDOW", self._on_close)
|
|
22
|
+
self._create_window(root)
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def _create_window(self, root):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
def bring_to_front(self, root):
|
|
29
|
+
root.lift()
|
|
30
|
+
root.attributes("-topmost", True)
|
|
31
|
+
root.after_idle(root.attributes, "-topmost", False)
|
|
32
|
+
root.focus_force()
|
|
33
|
+
|
|
34
|
+
def _on_close(self):
|
|
35
|
+
if self._root:
|
|
36
|
+
try:
|
|
37
|
+
self._root.destroy()
|
|
38
|
+
except tk.TclError:
|
|
39
|
+
pass
|
|
40
|
+
self._root = None
|
|
41
|
+
self._canvas = None
|
|
42
|
+
|
|
43
|
+
def _pump(self):
|
|
44
|
+
if not self._root:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
self._root.update_idletasks()
|
|
49
|
+
self._root.update()
|
|
50
|
+
except tk.TclError:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
if self._root:
|
|
54
|
+
self._root.after(20, self._pump)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .. import SimEnvironment
|
|
3
|
+
|
|
4
|
+
WIDTH, HEIGHT = 800, 600
|
|
5
|
+
GRAVITY = 900.0
|
|
6
|
+
FLAP_STRENGTH = -300.0
|
|
7
|
+
PIPE_SPEED = -200.0
|
|
8
|
+
PIPE_WIDTH = 80
|
|
9
|
+
PIPE_GAP = 200
|
|
10
|
+
PIPE_INTERVAL = 1.6
|
|
11
|
+
|
|
12
|
+
BIRD_X = 200
|
|
13
|
+
BIRD_SIZE = 35
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FlappyEnv(SimEnvironment):
|
|
17
|
+
def __init__(self, num_envs: int = 1):
|
|
18
|
+
self.num_envs = num_envs
|
|
19
|
+
self.reset()
|
|
20
|
+
|
|
21
|
+
def reset(self):
|
|
22
|
+
# birds (vectorized)
|
|
23
|
+
self.bird_y = np.full(self.num_envs, HEIGHT / 2, dtype=np.float32)
|
|
24
|
+
self.bird_vel = np.zeros(self.num_envs, dtype=np.float32)
|
|
25
|
+
self.done = np.zeros(self.num_envs, dtype=bool)
|
|
26
|
+
|
|
27
|
+
# shared pipes
|
|
28
|
+
self.pipes_x = np.empty(0, dtype=np.float32)
|
|
29
|
+
self.pipes_y = np.empty(0, dtype=np.float32)
|
|
30
|
+
self.time_since_pipe = PIPE_INTERVAL
|
|
31
|
+
return self.step(np.zeros(self.num_envs, dtype=np.int32), dt=0.0)
|
|
32
|
+
|
|
33
|
+
def _step_physics(self, action, dt):
|
|
34
|
+
flap_mask = (action == 1) & (~self.done)
|
|
35
|
+
self.bird_vel[flap_mask] = FLAP_STRENGTH
|
|
36
|
+
self.bird_vel += GRAVITY * dt
|
|
37
|
+
self.bird_y += self.bird_vel * dt
|
|
38
|
+
|
|
39
|
+
def _spawn_pipe(self):
|
|
40
|
+
pipe_y = np.random.randint(120, HEIGHT - 120 - PIPE_GAP)
|
|
41
|
+
self.pipes_x = np.append(self.pipes_x, WIDTH)
|
|
42
|
+
self.pipes_y = np.append(self.pipes_y, pipe_y)
|
|
43
|
+
|
|
44
|
+
def _update_pipes(self, dt):
|
|
45
|
+
self.time_since_pipe += dt
|
|
46
|
+
if self.time_since_pipe > PIPE_INTERVAL:
|
|
47
|
+
self.time_since_pipe = 0.0
|
|
48
|
+
self._spawn_pipe()
|
|
49
|
+
|
|
50
|
+
self.pipes_x += PIPE_SPEED * dt
|
|
51
|
+
|
|
52
|
+
# keep pipes on screen
|
|
53
|
+
keep = self.pipes_x > -PIPE_WIDTH
|
|
54
|
+
self.pipes_x = self.pipes_x[keep]
|
|
55
|
+
self.pipes_y = self.pipes_y[keep]
|
|
56
|
+
|
|
57
|
+
def _check_collisions(self): # world bounds
|
|
58
|
+
hit_bounds = (self.bird_y < 0) | (self.bird_y + BIRD_SIZE > HEIGHT)
|
|
59
|
+
bx = BIRD_X
|
|
60
|
+
by = self.bird_y[:, None]
|
|
61
|
+
px = self.pipes_x[None, :]
|
|
62
|
+
upper_y = np.zeros_like(self.pipes_y)[None, :]
|
|
63
|
+
upper_h = self.pipes_y[None, :]
|
|
64
|
+
lower_y = (self.pipes_y + PIPE_GAP)[None, :]
|
|
65
|
+
lower_h = (HEIGHT - (self.pipes_y + PIPE_GAP))[None, :]
|
|
66
|
+
x_overlap = (bx < px + PIPE_WIDTH) & (bx + BIRD_SIZE > px)
|
|
67
|
+
upper_hit = x_overlap & (by < upper_y + upper_h) & (by + BIRD_SIZE > upper_y)
|
|
68
|
+
lower_hit = x_overlap & (by < lower_y + lower_h) & (by + BIRD_SIZE > lower_y)
|
|
69
|
+
hit_pipe = (upper_hit | lower_hit).any(axis=1)
|
|
70
|
+
self.done |= hit_bounds | hit_pipe
|
|
71
|
+
|
|
72
|
+
def step(self, action, dt=0.02):
|
|
73
|
+
if np.isscalar(action):
|
|
74
|
+
action = np.full(self.num_envs, action, dtype=np.float32)
|
|
75
|
+
else:
|
|
76
|
+
action = np.asarray(action, dtype=np.float32)
|
|
77
|
+
if action.shape[0] != self.num_envs:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
f"Expected actions of shape ({self.num_envs},), got {action.shape}"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# mask done environments to have no action
|
|
83
|
+
action = action * (~self.done)
|
|
84
|
+
self._step_physics(action, dt)
|
|
85
|
+
self._update_pipes(dt)
|
|
86
|
+
self._check_collisions()
|
|
87
|
+
|
|
88
|
+
bird_y = self.bird_y.tolist()
|
|
89
|
+
bird_vel = self.bird_vel.tolist()
|
|
90
|
+
done = self.done.tolist()
|
|
91
|
+
|
|
92
|
+
if self.num_envs == 1:
|
|
93
|
+
bird_y = self.bird_y[0]
|
|
94
|
+
bird_vel = self.bird_vel[0]
|
|
95
|
+
done = bool(self.done[0])
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
"bird_y": bird_y,
|
|
99
|
+
"bird_vel": bird_vel,
|
|
100
|
+
"pipes_x": self.pipes_x.tolist(),
|
|
101
|
+
"pipes_y": self.pipes_y.tolist(),
|
|
102
|
+
"done": done,
|
|
103
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
let simState = {};
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
initialize({ model }) {
|
|
5
|
+
model.on("change:sim_state", () => {
|
|
6
|
+
simState = model.get("sim_state") || {};
|
|
7
|
+
});
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
async render({ model, el }) {
|
|
11
|
+
const [width, height] = model.get("_viewport_size") || [800, 600];
|
|
12
|
+
|
|
13
|
+
const container = document.createElement("div");
|
|
14
|
+
container.style.position = "relative";
|
|
15
|
+
container.style.width = width + "px";
|
|
16
|
+
container.style.height = height + "px";
|
|
17
|
+
el.appendChild(container);
|
|
18
|
+
|
|
19
|
+
const canvas = document.createElement("canvas");
|
|
20
|
+
canvas.width = width;
|
|
21
|
+
canvas.height = height;
|
|
22
|
+
container.appendChild(canvas);
|
|
23
|
+
const ctx = canvas.getContext("2d");
|
|
24
|
+
|
|
25
|
+
const WORLD_WIDTH = 800;
|
|
26
|
+
const WORLD_HEIGHT = 600;
|
|
27
|
+
const BIRD_X = 200;
|
|
28
|
+
const BIRD_SIZE = 35;
|
|
29
|
+
const PIPE_WIDTH = 80;
|
|
30
|
+
const PIPE_GAP = 200;
|
|
31
|
+
const GROUND_HEIGHT = 80;
|
|
32
|
+
|
|
33
|
+
// Scale world to canvas (in case viewport differs from 800×600)
|
|
34
|
+
const scaleX = width / WORLD_WIDTH;
|
|
35
|
+
const scaleY = height / WORLD_HEIGHT;
|
|
36
|
+
|
|
37
|
+
function draw() {
|
|
38
|
+
const state = simState || {};
|
|
39
|
+
const birdY = state.bird_y ?? WORLD_HEIGHT / 2;
|
|
40
|
+
const pipes_x = state.pipes_x || [];
|
|
41
|
+
const pipes_y = state.pipes_y || [];
|
|
42
|
+
const done = state.done || false;
|
|
43
|
+
|
|
44
|
+
ctx.clearRect(0, 0, width, height);
|
|
45
|
+
|
|
46
|
+
// Background sky
|
|
47
|
+
ctx.fillStyle = "#70C5CE";
|
|
48
|
+
ctx.fillRect(0, 0, width, height);
|
|
49
|
+
|
|
50
|
+
// Ground
|
|
51
|
+
const groundH = GROUND_HEIGHT * scaleY;
|
|
52
|
+
ctx.fillStyle = "#DED895";
|
|
53
|
+
ctx.fillRect(0, height - groundH, width, groundH);
|
|
54
|
+
|
|
55
|
+
// Bird
|
|
56
|
+
const birdScreenX = BIRD_X * scaleX;
|
|
57
|
+
const birdScreenY = birdY * scaleY;
|
|
58
|
+
const birdSizeX = BIRD_SIZE * scaleX;
|
|
59
|
+
const birdSizeY = BIRD_SIZE * scaleY;
|
|
60
|
+
|
|
61
|
+
ctx.fillStyle = "#FFD700";
|
|
62
|
+
ctx.strokeStyle = "#000000";
|
|
63
|
+
ctx.lineWidth = 1;
|
|
64
|
+
|
|
65
|
+
if (!done) {
|
|
66
|
+
ctx.beginPath();
|
|
67
|
+
ctx.ellipse(
|
|
68
|
+
birdScreenX + birdSizeX / 2,
|
|
69
|
+
birdScreenY + birdSizeY / 2,
|
|
70
|
+
birdSizeX / 2,
|
|
71
|
+
birdSizeY / 2,
|
|
72
|
+
0,
|
|
73
|
+
0,
|
|
74
|
+
Math.PI * 2
|
|
75
|
+
);
|
|
76
|
+
ctx.fill();
|
|
77
|
+
ctx.stroke();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Pipes
|
|
81
|
+
ctx.fillStyle = "#228B22";
|
|
82
|
+
ctx.strokeStyle = "#4a8d34";
|
|
83
|
+
ctx.lineWidth = 2 * ((scaleX + scaleY) / 2);
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < pipes_x.length; i++) {
|
|
86
|
+
const pipeX = pipes_x[i];
|
|
87
|
+
const pipeY = pipes_y[i];
|
|
88
|
+
|
|
89
|
+
const pw = PIPE_WIDTH * scaleX;
|
|
90
|
+
const ux = pipeX * scaleX;
|
|
91
|
+
const uy = 0; // Upper pipe starts at top of screen
|
|
92
|
+
const uh = pipeY * scaleY; // Upper pipe height extends down to pipeY
|
|
93
|
+
const lx = pipeX * scaleX;
|
|
94
|
+
const ly = (pipeY + PIPE_GAP) * scaleY; // Lower pipe starts after gap
|
|
95
|
+
const lh = (WORLD_HEIGHT - (pipeY + PIPE_GAP)) * scaleY; // Lower pipe extends to bottom
|
|
96
|
+
|
|
97
|
+
// Upper pipe
|
|
98
|
+
ctx.beginPath();
|
|
99
|
+
ctx.rect(ux, uy, pw, uh);
|
|
100
|
+
ctx.fill();
|
|
101
|
+
ctx.stroke();
|
|
102
|
+
|
|
103
|
+
// Lower pipe
|
|
104
|
+
ctx.beginPath();
|
|
105
|
+
ctx.rect(lx, ly, pw, lh);
|
|
106
|
+
ctx.fill();
|
|
107
|
+
ctx.stroke();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
requestAnimationFrame(draw);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
draw();
|
|
114
|
+
|
|
115
|
+
model.set("_view_ready", true);
|
|
116
|
+
model.save_changes();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
from .. import _tk_base
|
|
6
|
+
except ImportError:
|
|
7
|
+
raise ImportError("tkinter is required for FlappyTkFrontend")
|
|
8
|
+
|
|
9
|
+
from . import (
|
|
10
|
+
FlappyEnv,
|
|
11
|
+
WIDTH,
|
|
12
|
+
HEIGHT,
|
|
13
|
+
PIPE_WIDTH,
|
|
14
|
+
BIRD_SIZE,
|
|
15
|
+
BIRD_X,
|
|
16
|
+
PIPE_GAP,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class FlappyTkFrontend(_tk_base.TkBaseFrontend):
|
|
21
|
+
|
|
22
|
+
def __init__(self, viewport_size=(800, 600), sim_env=None):
|
|
23
|
+
super().__init__()
|
|
24
|
+
if sim_env is None:
|
|
25
|
+
sim_env = FlappyEnv()
|
|
26
|
+
|
|
27
|
+
self.sim_env = sim_env
|
|
28
|
+
self._viewport_size = viewport_size
|
|
29
|
+
|
|
30
|
+
async def step(self, action, dt=0.02):
|
|
31
|
+
state = self.sim_env.step(action, dt=dt)
|
|
32
|
+
if self._root:
|
|
33
|
+
self._root.after(0, lambda: self._draw_state(state))
|
|
34
|
+
|
|
35
|
+
await asyncio.sleep(dt)
|
|
36
|
+
return state
|
|
37
|
+
|
|
38
|
+
async def reset(self):
|
|
39
|
+
state = self.sim_env.reset()
|
|
40
|
+
if self._root:
|
|
41
|
+
self._draw_state(state)
|
|
42
|
+
return state
|
|
43
|
+
|
|
44
|
+
def _create_window(self, root):
|
|
45
|
+
w, h = self._viewport_size
|
|
46
|
+
root.title("Flappy Bird")
|
|
47
|
+
canvas = tk.Canvas(root, width=w, height=h, bg="#1E1E1E")
|
|
48
|
+
canvas.pack(fill="both", expand=True)
|
|
49
|
+
self._root = root
|
|
50
|
+
self._canvas = canvas
|
|
51
|
+
self.bring_to_front(root)
|
|
52
|
+
self._draw_state(self.sim_env.reset())
|
|
53
|
+
self._pump()
|
|
54
|
+
root.mainloop()
|
|
55
|
+
|
|
56
|
+
def _draw_state(self, state):
|
|
57
|
+
if not self._canvas:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
canvas = self._canvas
|
|
61
|
+
canvas.delete("all")
|
|
62
|
+
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill="#70C5CE", outline="")
|
|
63
|
+
canvas.create_rectangle(
|
|
64
|
+
0, HEIGHT - 80, WIDTH, HEIGHT, fill="#DED895", outline=""
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# bird
|
|
68
|
+
by = state["bird_y"]
|
|
69
|
+
|
|
70
|
+
if not state.get("done", False):
|
|
71
|
+
canvas.create_oval(
|
|
72
|
+
BIRD_X,
|
|
73
|
+
by,
|
|
74
|
+
BIRD_X + BIRD_SIZE,
|
|
75
|
+
by + BIRD_SIZE,
|
|
76
|
+
fill="#FFD700",
|
|
77
|
+
outline="#000",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# pipes
|
|
81
|
+
for x, y in zip(state["pipes_x"], state["pipes_y"]):
|
|
82
|
+
# Upper pipe
|
|
83
|
+
canvas.create_rectangle(
|
|
84
|
+
x, 0, x + PIPE_WIDTH, y, fill="#228B22", outline="#4a8d34"
|
|
85
|
+
)
|
|
86
|
+
# Lower pipe
|
|
87
|
+
canvas.create_rectangle(
|
|
88
|
+
x,
|
|
89
|
+
y + PIPE_GAP,
|
|
90
|
+
x + PIPE_WIDTH,
|
|
91
|
+
HEIGHT,
|
|
92
|
+
fill="#228B22",
|
|
93
|
+
outline="#4a8d34",
|
|
94
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import anywidget
|
|
3
|
+
import traitlets
|
|
4
|
+
import asyncio
|
|
5
|
+
from IPython.display import display
|
|
6
|
+
from jupyter_ui_poll import ui_events
|
|
7
|
+
|
|
8
|
+
from . import FlappyEnv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FlappySim(anywidget.AnyWidget):
|
|
12
|
+
_esm = pathlib.Path(__file__).parent / "sim.js"
|
|
13
|
+
|
|
14
|
+
sim_state = traitlets.Dict(default_value={}).tag(sync=True)
|
|
15
|
+
_viewport_size = traitlets.Tuple(
|
|
16
|
+
traitlets.Int(), traitlets.Int(), default_value=(800, 600)
|
|
17
|
+
).tag(sync=True)
|
|
18
|
+
_manual_control = traitlets.Bool(default_value=False).tag(sync=True)
|
|
19
|
+
_view_ready = traitlets.Bool(default_value=False).tag(sync=True)
|
|
20
|
+
|
|
21
|
+
def __init__(self, viewport_size=(800, 600), manual_control=False, sim_env=None):
|
|
22
|
+
super().__init__()
|
|
23
|
+
self._viewport_size = viewport_size
|
|
24
|
+
self._manual_control = manual_control
|
|
25
|
+
if sim_env is None:
|
|
26
|
+
sim_env = FlappyEnv()
|
|
27
|
+
if sim_env.num_envs != 1:
|
|
28
|
+
raise ValueError("FlappySim currently only supports single environment.")
|
|
29
|
+
|
|
30
|
+
self.sim_env = sim_env
|
|
31
|
+
self.sim_state = self.sim_env.reset()
|
|
32
|
+
|
|
33
|
+
def render(self):
|
|
34
|
+
display(self)
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
with ui_events() as ui_poll:
|
|
38
|
+
while not self._view_ready:
|
|
39
|
+
ui_poll(100)
|
|
40
|
+
except Exception:
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
async def step(self, action, dt=0.02):
|
|
44
|
+
state = self.sim_env.step(action, dt=dt)
|
|
45
|
+
self.sim_state = state
|
|
46
|
+
await asyncio.sleep(dt)
|
|
47
|
+
return state
|
|
48
|
+
|
|
49
|
+
async def reset(self):
|
|
50
|
+
state = self.sim_env.reset()
|
|
51
|
+
self.sim_state = state
|
|
52
|
+
await asyncio.sleep(0)
|
|
53
|
+
return state
|