tinysim 0.0.1__py3-none-any.whl
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/__init__.py +11 -0
- tinysim/_tk_base.py +54 -0
- tinysim/flappy/__init__.py +103 -0
- tinysim/flappy/sim.js +118 -0
- tinysim/flappy/tk.py +94 -0
- tinysim/flappy/widget.py +53 -0
- tinysim/frogger/__init__.py +145 -0
- tinysim/frogger/sim.js +110 -0
- tinysim/frogger/tk.py +95 -0
- tinysim/frogger/widget.py +60 -0
- tinysim/mountain_car/__init__.py +56 -0
- tinysim/mountain_car/sim.js +158 -0
- tinysim/mountain_car/styles.css +5 -0
- tinysim/mountain_car/tk.py +141 -0
- tinysim/mountain_car/widget.py +56 -0
- tinysim/simple_amr/__init__.py +0 -0
- tinysim/simple_amr/example_maps.py +121 -0
- tinysim/simple_amr/sim.js +430 -0
- tinysim/simple_amr/styles.css +54 -0
- tinysim/simple_amr/widget.py +73 -0
- tinysim/topdown_driving/__init__.py +190 -0
- tinysim/topdown_driving/sim.js +136 -0
- tinysim/topdown_driving/tk.py +180 -0
- tinysim/topdown_driving/track_0.json +753 -0
- tinysim/topdown_driving/widget.py +60 -0
- tinysim-0.0.1.dist-info/METADATA +56 -0
- tinysim-0.0.1.dist-info/RECORD +33 -0
- tinysim-0.0.1.dist-info/WHEEL +5 -0
- tinysim-0.0.1.dist-info/top_level.txt +2 -0
- tinysim_warp/cart_pole/__init__.py +259 -0
- tinysim_warp/quadruped/__init__.py +203 -0
- tinysim_warp/simple_quadruped/__init__.py +168 -0
- tinysim_warp/simple_quadruped/simple_quadruped.urdf +247 -0
tinysim/frogger/tk.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from . import FroggerEnv, WIDTH, HEIGHT, CELL, ROWS, COLS
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import tkinter as tk
|
|
6
|
+
from .. import _tk_base
|
|
7
|
+
except ImportError:
|
|
8
|
+
raise ImportError("tkinter is required for FroggerTkFrontend")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FroggerTkFrontend(_tk_base.TkBaseFrontend):
|
|
12
|
+
|
|
13
|
+
def __init__(self, viewport_size=(800, 600), sim_env=None):
|
|
14
|
+
super().__init__()
|
|
15
|
+
if sim_env is None:
|
|
16
|
+
sim_env = FroggerEnv()
|
|
17
|
+
if sim_env.num_envs != 1:
|
|
18
|
+
raise ValueError(
|
|
19
|
+
"FroggerTkFrontend currently only supports single environment."
|
|
20
|
+
)
|
|
21
|
+
self.sim_env = sim_env
|
|
22
|
+
self._viewport_size = viewport_size
|
|
23
|
+
|
|
24
|
+
async def step(self, action, dt=0.01):
|
|
25
|
+
state = self.sim_env.step(action, dt=dt)
|
|
26
|
+
if self._root:
|
|
27
|
+
self._root.after(0, lambda: self._draw_state(self.sim_env))
|
|
28
|
+
|
|
29
|
+
await asyncio.sleep(dt)
|
|
30
|
+
return state
|
|
31
|
+
|
|
32
|
+
async def reset(self):
|
|
33
|
+
state = self.sim_env.reset()
|
|
34
|
+
if self._canvas:
|
|
35
|
+
self._draw_state(self.sim_env)
|
|
36
|
+
return state
|
|
37
|
+
|
|
38
|
+
def _create_window(self, root):
|
|
39
|
+
w, h = self._viewport_size
|
|
40
|
+
root.title("Frogger")
|
|
41
|
+
canvas = tk.Canvas(root, width=w, height=h, bg="#1E1E1E")
|
|
42
|
+
canvas.pack(fill="both", expand=True)
|
|
43
|
+
self._root = root
|
|
44
|
+
self._canvas = canvas
|
|
45
|
+
self.bring_to_front(root)
|
|
46
|
+
self._draw_state(self.sim_env)
|
|
47
|
+
self._pump()
|
|
48
|
+
root.mainloop()
|
|
49
|
+
|
|
50
|
+
def _draw_state(self, sim_env: FroggerEnv):
|
|
51
|
+
if not self._canvas:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
canvas = self._canvas
|
|
55
|
+
canvas.delete("all")
|
|
56
|
+
|
|
57
|
+
# safe zones
|
|
58
|
+
canvas.create_rectangle(0, 0, WIDTH, CELL, fill="#000050", outline="")
|
|
59
|
+
canvas.create_rectangle(
|
|
60
|
+
0, (ROWS - 1) * CELL, WIDTH, HEIGHT, fill="#004000", outline=""
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# cars
|
|
64
|
+
for lane_rects in sim_env.car_rects: # lane_rects shape: (num_cars_per_lane, 4)
|
|
65
|
+
for x, y, w, h in lane_rects:
|
|
66
|
+
canvas.create_rectangle(x, y, x + w, y + h, fill="#B43232", outline="")
|
|
67
|
+
|
|
68
|
+
# frog
|
|
69
|
+
fx, fy, fw, fh = (
|
|
70
|
+
sim_env.frog_pos[0, 0] * CELL,
|
|
71
|
+
sim_env.frog_pos[0, 1] * CELL,
|
|
72
|
+
CELL,
|
|
73
|
+
CELL,
|
|
74
|
+
)
|
|
75
|
+
canvas.create_oval(
|
|
76
|
+
fx + 5, fy + 5, fx + fw - 5, fy + fh - 5, fill="#32DC32", outline=""
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# grid
|
|
80
|
+
for r in range(ROWS):
|
|
81
|
+
y = r * CELL
|
|
82
|
+
canvas.create_line(0, y, WIDTH, y, fill="#282828")
|
|
83
|
+
for c in range(COLS):
|
|
84
|
+
x = c * CELL
|
|
85
|
+
canvas.create_line(x, 0, x, HEIGHT, fill="#282828")
|
|
86
|
+
|
|
87
|
+
# score
|
|
88
|
+
canvas.create_text(
|
|
89
|
+
10,
|
|
90
|
+
10,
|
|
91
|
+
anchor="nw",
|
|
92
|
+
text=f"Score: {sim_env.score[0]:.2f}",
|
|
93
|
+
fill="white",
|
|
94
|
+
font=("Arial", 16),
|
|
95
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import pathlib
|
|
2
|
+
import anywidget
|
|
3
|
+
import traitlets
|
|
4
|
+
import asyncio
|
|
5
|
+
import numpy as np
|
|
6
|
+
from IPython.display import display
|
|
7
|
+
from jupyter_ui_poll import ui_events
|
|
8
|
+
|
|
9
|
+
from . import FroggerEnv
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class FroggerWidget(anywidget.AnyWidget):
|
|
13
|
+
_esm = pathlib.Path(__file__).parent / "sim.js"
|
|
14
|
+
|
|
15
|
+
sim_state = traitlets.Dict(default_value={}).tag(sync=True)
|
|
16
|
+
car_positions = traitlets.List(default_value=[]).tag(sync=True)
|
|
17
|
+
_viewport_size = traitlets.Tuple(
|
|
18
|
+
traitlets.Int(), traitlets.Int(), default_value=(800, 600)
|
|
19
|
+
).tag(sync=True)
|
|
20
|
+
_view_ready = traitlets.Bool(default_value=False).tag(sync=True)
|
|
21
|
+
|
|
22
|
+
def get_car_positions(self):
|
|
23
|
+
return np.vstack(self.sim_env.car_rects).flatten().tolist()
|
|
24
|
+
|
|
25
|
+
def __init__(self, viewport_size=(800, 600), sim_env=None):
|
|
26
|
+
self._viewport_size = viewport_size
|
|
27
|
+
super().__init__()
|
|
28
|
+
if sim_env is None:
|
|
29
|
+
sim_env = FroggerEnv()
|
|
30
|
+
|
|
31
|
+
if sim_env.num_envs != 1:
|
|
32
|
+
raise ValueError(
|
|
33
|
+
"FroggerWidget currently only supports single environment."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
self.sim_env = sim_env
|
|
37
|
+
self.sim_state = self.sim_env.reset()
|
|
38
|
+
self.car_positions = self.get_car_positions()
|
|
39
|
+
|
|
40
|
+
def render(self):
|
|
41
|
+
display(self)
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
with ui_events() as ui_poll:
|
|
45
|
+
while not self._view_ready:
|
|
46
|
+
ui_poll(100)
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
async def step(self, action: int, dt: float = 0.01) -> dict:
|
|
51
|
+
sim_state = self.sim_env.step(action)
|
|
52
|
+
self.sim_state = sim_state
|
|
53
|
+
self.car_positions = self.get_car_positions()
|
|
54
|
+
await asyncio.sleep(dt)
|
|
55
|
+
return sim_state
|
|
56
|
+
|
|
57
|
+
async def reset(self) -> dict:
|
|
58
|
+
sim_state = self.sim_env.reset()
|
|
59
|
+
self.sim_state = sim_state
|
|
60
|
+
return sim_state
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .. import SimEnvironment
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MountainCarEnv(SimEnvironment):
|
|
6
|
+
def __init__(self, num_envs: int = 1):
|
|
7
|
+
self.num_envs = num_envs
|
|
8
|
+
# environment constants
|
|
9
|
+
self.min_position = -1.2
|
|
10
|
+
self.max_position = 0.6
|
|
11
|
+
self.max_speed = 0.07
|
|
12
|
+
self.force = 0.001
|
|
13
|
+
self.gravity = 0.0025
|
|
14
|
+
self.goal_position = 0.5
|
|
15
|
+
|
|
16
|
+
# initial state
|
|
17
|
+
self.position = np.full(num_envs, -0.5, dtype=np.float32)
|
|
18
|
+
self.velocity = np.zeros(num_envs, dtype=np.float32)
|
|
19
|
+
|
|
20
|
+
def step(self, action) -> dict:
|
|
21
|
+
if np.isscalar(action):
|
|
22
|
+
action = np.full(self.num_envs, action, dtype=np.float32)
|
|
23
|
+
else:
|
|
24
|
+
action = np.asarray(action, dtype=np.float32)
|
|
25
|
+
if action.shape[0] != self.num_envs:
|
|
26
|
+
raise ValueError(
|
|
27
|
+
f"Expected actions of shape ({self.num_envs},), got {action.shape}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
force_term = (action - 1.0) * self.force
|
|
31
|
+
self.velocity += force_term + np.cos(3 * self.position) * (-self.gravity)
|
|
32
|
+
self.velocity = np.clip(self.velocity, -self.max_speed, self.max_speed)
|
|
33
|
+
|
|
34
|
+
self.position += self.velocity
|
|
35
|
+
self.position = np.clip(self.position, self.min_position, self.max_position)
|
|
36
|
+
|
|
37
|
+
# handle collision with left wall
|
|
38
|
+
wall_mask = (self.position == self.min_position) & (self.velocity < 0)
|
|
39
|
+
self.velocity[wall_mask] = 0.0
|
|
40
|
+
done = self.position >= self.goal_position
|
|
41
|
+
|
|
42
|
+
if self.num_envs == 1:
|
|
43
|
+
self.position = float(self.position[0])
|
|
44
|
+
self.velocity = float(self.velocity[0])
|
|
45
|
+
done = bool(done[0])
|
|
46
|
+
|
|
47
|
+
return {"position": self.position, "velocity": self.velocity, "done": done}
|
|
48
|
+
|
|
49
|
+
def reset(self) -> dict:
|
|
50
|
+
self.position = -0.5
|
|
51
|
+
self.velocity = 0.0
|
|
52
|
+
return {
|
|
53
|
+
"position": self.position,
|
|
54
|
+
"velocity": self.velocity,
|
|
55
|
+
"done": self.position >= self.goal_position,
|
|
56
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
async render({ model, el }) {
|
|
3
|
+
const [width, height] = model.get("_viewport_size") || [600, 400];
|
|
4
|
+
const container = document.createElement("div");
|
|
5
|
+
container.style.position = "relative";
|
|
6
|
+
el.appendChild(container);
|
|
7
|
+
|
|
8
|
+
const canvas = document.createElement("canvas");
|
|
9
|
+
canvas.width = width;
|
|
10
|
+
canvas.height = height;
|
|
11
|
+
container.appendChild(canvas);
|
|
12
|
+
const ctx = canvas.getContext("2d");
|
|
13
|
+
|
|
14
|
+
const sim_state = model.get("sim_state") || {};
|
|
15
|
+
const minPosition = -1.2;
|
|
16
|
+
const maxPosition = 0.6;
|
|
17
|
+
const maxSpeed = 0.07;
|
|
18
|
+
const force = 0.001;
|
|
19
|
+
const gravity = 0.0025;
|
|
20
|
+
let position = sim_state.position || -0.5;
|
|
21
|
+
let velocity = sim_state.velocity || 0.0;
|
|
22
|
+
|
|
23
|
+
if (model.get("_manual_control")) {
|
|
24
|
+
const controls = document.createElement("div");
|
|
25
|
+
controls.className = "controls";
|
|
26
|
+
controls.innerHTML = `
|
|
27
|
+
<button id="left">←</button>
|
|
28
|
+
<button id="right">→</button>
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
const controlsObj = { action: 1 }; // 0=left, 1=idle, 2=right
|
|
32
|
+
container.appendChild(controls);
|
|
33
|
+
|
|
34
|
+
controls.addEventListener("mousedown", (e) => {
|
|
35
|
+
const id = e.target.id;
|
|
36
|
+
if (id === "left") {
|
|
37
|
+
controlsObj.action = 0;
|
|
38
|
+
} else if (id === "right") {
|
|
39
|
+
controlsObj.action = 2;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
controls.addEventListener("mouseup", (e) => {
|
|
44
|
+
const id = e.target.id;
|
|
45
|
+
if (id === "left" || id === "right") {
|
|
46
|
+
controlsObj.action = 1;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let lastTime = new Date();
|
|
51
|
+
const step = () => {
|
|
52
|
+
let ms = new Date();
|
|
53
|
+
if (ms - lastTime < 20) {
|
|
54
|
+
requestAnimationFrame(step);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
lastTime = ms;
|
|
58
|
+
|
|
59
|
+
const action = controlsObj.action; // 0=left, 1=idle, 2=right
|
|
60
|
+
const forceTerm = (action - 1) * force;
|
|
61
|
+
velocity += forceTerm + Math.cos(3 * position) * -gravity;
|
|
62
|
+
velocity = Math.max(Math.min(velocity, maxSpeed), -maxSpeed);
|
|
63
|
+
position += velocity;
|
|
64
|
+
position = Math.max(Math.min(position, maxPosition), minPosition);
|
|
65
|
+
if (position === minPosition && velocity < 0) velocity = 0;
|
|
66
|
+
requestAnimationFrame(step);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
step();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const goalPosition = 0.5;
|
|
73
|
+
const worldWidth = maxPosition - minPosition;
|
|
74
|
+
const scale = width / worldWidth;
|
|
75
|
+
const heightFn = (x) => Math.sin(3 * x) * 0.45 + 0.55;
|
|
76
|
+
|
|
77
|
+
const terrain = [];
|
|
78
|
+
for (let i = 0; i < 200; i++) {
|
|
79
|
+
const t = i / 199;
|
|
80
|
+
const xWorld = minPosition + t * worldWidth;
|
|
81
|
+
const yWorld = heightFn(xWorld);
|
|
82
|
+
terrain.push({
|
|
83
|
+
x: (xWorld - minPosition) * scale,
|
|
84
|
+
y: height - yWorld * scale
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const carWidth = 40;
|
|
89
|
+
const carHeight = 20;
|
|
90
|
+
const clearance = 10;
|
|
91
|
+
|
|
92
|
+
model.on("change:sim_state", () => {
|
|
93
|
+
const newState = model.get("sim_state") || {};
|
|
94
|
+
position = newState.position;
|
|
95
|
+
velocity = newState.velocity;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const draw = () => {
|
|
99
|
+
ctx.clearRect(0, 0, width, height);
|
|
100
|
+
ctx.fillStyle = "#eeeeee";
|
|
101
|
+
ctx.beginPath();
|
|
102
|
+
ctx.moveTo(terrain[0].x, terrain[0].y);
|
|
103
|
+
for (const p of terrain) ctx.lineTo(p.x, p.y);
|
|
104
|
+
ctx.lineTo(width, height);
|
|
105
|
+
ctx.lineTo(0, height);
|
|
106
|
+
ctx.closePath();
|
|
107
|
+
ctx.fill();
|
|
108
|
+
|
|
109
|
+
ctx.strokeStyle = "#444";
|
|
110
|
+
ctx.lineWidth = 2;
|
|
111
|
+
ctx.stroke();
|
|
112
|
+
const xScreen = (position - minPosition) * scale;
|
|
113
|
+
const yScreen = height - heightFn(position) * scale - clearance;
|
|
114
|
+
const slope = Math.cos(3 * position);
|
|
115
|
+
const angle = Math.atan(slope);
|
|
116
|
+
|
|
117
|
+
ctx.save();
|
|
118
|
+
ctx.translate(xScreen, yScreen);
|
|
119
|
+
ctx.rotate(-angle);
|
|
120
|
+
|
|
121
|
+
ctx.fillStyle = "#000";
|
|
122
|
+
ctx.fillRect(-carWidth / 2, -carHeight, carWidth, carHeight);
|
|
123
|
+
|
|
124
|
+
ctx.fillStyle = "#777";
|
|
125
|
+
const wheelR = carHeight * 0.4;
|
|
126
|
+
ctx.beginPath();
|
|
127
|
+
ctx.arc(carWidth / 3, 0, wheelR, 0, Math.PI * 2);
|
|
128
|
+
ctx.fill();
|
|
129
|
+
ctx.beginPath();
|
|
130
|
+
ctx.arc(-carWidth / 3, 0, wheelR, 0, Math.PI * 2);
|
|
131
|
+
ctx.fill();
|
|
132
|
+
ctx.restore();
|
|
133
|
+
|
|
134
|
+
const fx = (goalPosition - minPosition) * scale;
|
|
135
|
+
const fy = height - heightFn(goalPosition) * scale;
|
|
136
|
+
ctx.strokeStyle = "#000";
|
|
137
|
+
ctx.beginPath();
|
|
138
|
+
ctx.moveTo(fx, fy);
|
|
139
|
+
ctx.lineTo(fx, fy - 40);
|
|
140
|
+
ctx.stroke();
|
|
141
|
+
|
|
142
|
+
ctx.fillStyle = "#ff0";
|
|
143
|
+
ctx.beginPath();
|
|
144
|
+
ctx.moveTo(fx, fy - 40);
|
|
145
|
+
ctx.lineTo(fx + 25, fy - 35);
|
|
146
|
+
ctx.lineTo(fx, fy - 30);
|
|
147
|
+
ctx.closePath();
|
|
148
|
+
ctx.fill();
|
|
149
|
+
|
|
150
|
+
requestAnimationFrame(draw);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
draw();
|
|
154
|
+
|
|
155
|
+
model.set("_view_ready", true);
|
|
156
|
+
model.save_changes();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import math
|
|
3
|
+
import numpy as np
|
|
4
|
+
from . import MountainCarEnv
|
|
5
|
+
|
|
6
|
+
try:
|
|
7
|
+
import tkinter as tk
|
|
8
|
+
from .. import _tk_base
|
|
9
|
+
except ImportError:
|
|
10
|
+
raise ImportError("tkinter is required for MountainCarTkFrontend")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MountainCarTkFrontend(_tk_base.TkBaseFrontend):
|
|
14
|
+
|
|
15
|
+
def __init__(self, viewport_size=(600, 400), sim_env=None):
|
|
16
|
+
super().__init__()
|
|
17
|
+
if sim_env is None:
|
|
18
|
+
sim_env = MountainCarEnv()
|
|
19
|
+
self.sim_env = sim_env
|
|
20
|
+
self._viewport_size = viewport_size
|
|
21
|
+
|
|
22
|
+
async def step(self, action, dt=0.01):
|
|
23
|
+
state = self.sim_env.step(action)
|
|
24
|
+
|
|
25
|
+
if self._root:
|
|
26
|
+
self._root.after(0, lambda s=state: self._draw_state(s))
|
|
27
|
+
|
|
28
|
+
await asyncio.sleep(dt)
|
|
29
|
+
return state
|
|
30
|
+
|
|
31
|
+
async def reset(self):
|
|
32
|
+
state = self.sim_env.reset()
|
|
33
|
+
if self._canvas:
|
|
34
|
+
self._draw_state(state)
|
|
35
|
+
return state
|
|
36
|
+
|
|
37
|
+
def _create_window(self, root):
|
|
38
|
+
w, h = self._viewport_size
|
|
39
|
+
root.title("Mountain Car")
|
|
40
|
+
canvas = tk.Canvas(root, width=w, height=h, bg="#eeeeee")
|
|
41
|
+
canvas.pack(fill="both", expand=True)
|
|
42
|
+
self.bring_to_front(root)
|
|
43
|
+
self._root = root
|
|
44
|
+
self._canvas = canvas
|
|
45
|
+
self._draw_state(self.sim_env.reset())
|
|
46
|
+
self._pump()
|
|
47
|
+
root.mainloop()
|
|
48
|
+
|
|
49
|
+
def _draw_state(self, state: dict):
|
|
50
|
+
if not self._canvas:
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
c = self._canvas
|
|
54
|
+
w = int(c.winfo_width() or self._viewport_size[0])
|
|
55
|
+
h = int(c.winfo_height() or self._viewport_size[1])
|
|
56
|
+
c.delete("all")
|
|
57
|
+
|
|
58
|
+
min_x = self.sim_env.min_position
|
|
59
|
+
max_x = self.sim_env.max_position
|
|
60
|
+
world_width = max_x - min_x
|
|
61
|
+
scale = w / world_width
|
|
62
|
+
clearance = 10
|
|
63
|
+
|
|
64
|
+
def heightFn(x):
|
|
65
|
+
return np.sin(3 * x) * 0.45 + 0.55
|
|
66
|
+
|
|
67
|
+
# Terrain
|
|
68
|
+
terrain_pts = []
|
|
69
|
+
for px in range(w):
|
|
70
|
+
x_world = min_x + px / w * world_width
|
|
71
|
+
y_world = heightFn(x_world)
|
|
72
|
+
y_screen = h - y_world * scale
|
|
73
|
+
terrain_pts.append((px, y_screen))
|
|
74
|
+
for i in range(len(terrain_pts) - 1):
|
|
75
|
+
c.create_line(*terrain_pts[i], *terrain_pts[i + 1], fill="#444444", width=2)
|
|
76
|
+
|
|
77
|
+
# Goal
|
|
78
|
+
gx = self.sim_env.goal_position
|
|
79
|
+
gy = heightFn(gx)
|
|
80
|
+
goal_x = (gx - min_x) * scale
|
|
81
|
+
goal_y = h - gy * scale
|
|
82
|
+
c.create_line(goal_x, goal_y, goal_x, goal_y - 40, fill="#000", width=2)
|
|
83
|
+
c.create_polygon(
|
|
84
|
+
goal_x,
|
|
85
|
+
goal_y - 40,
|
|
86
|
+
goal_x + 25,
|
|
87
|
+
goal_y - 35,
|
|
88
|
+
goal_x,
|
|
89
|
+
goal_y - 30,
|
|
90
|
+
fill="#ffff00",
|
|
91
|
+
outline="",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Draw each car
|
|
95
|
+
positions = np.atleast_1d(state["position"])
|
|
96
|
+
car_width, car_height = 40, 20
|
|
97
|
+
wheel_r = car_height * 0.4
|
|
98
|
+
|
|
99
|
+
def rot(px, py, ang):
|
|
100
|
+
s, c0 = math.sin(ang), math.cos(ang)
|
|
101
|
+
return px * c0 - py * s, px * s + py * c0
|
|
102
|
+
|
|
103
|
+
colors = ["#ff0000", "#00aa00", "#0000ff", "#ffaa00"]
|
|
104
|
+
|
|
105
|
+
for i, x_world in enumerate(positions):
|
|
106
|
+
y_world = heightFn(x_world)
|
|
107
|
+
x_screen = (x_world - min_x) * scale
|
|
108
|
+
y_screen = h - y_world * scale - clearance
|
|
109
|
+
|
|
110
|
+
slope = math.cos(3 * x_world)
|
|
111
|
+
angle = -math.atan(slope)
|
|
112
|
+
|
|
113
|
+
# body
|
|
114
|
+
body_local = [
|
|
115
|
+
(-car_width / 2, -car_height),
|
|
116
|
+
(car_width / 2, -car_height),
|
|
117
|
+
(car_width / 2, 0),
|
|
118
|
+
(-car_width / 2, 0),
|
|
119
|
+
]
|
|
120
|
+
body_screen = []
|
|
121
|
+
for px, py in body_local:
|
|
122
|
+
rx, ry = rot(px, py, angle)
|
|
123
|
+
body_screen.append((x_screen + rx, y_screen + ry))
|
|
124
|
+
|
|
125
|
+
c.create_polygon(
|
|
126
|
+
body_screen, fill=colors[i % len(colors)], outline="", stipple="gray75"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# wheels
|
|
130
|
+
for wx in (-car_width / 3, car_width / 3):
|
|
131
|
+
rx, ry = rot(wx, 0, angle)
|
|
132
|
+
cx = x_screen + rx
|
|
133
|
+
cy = y_screen + ry
|
|
134
|
+
c.create_oval(
|
|
135
|
+
cx - wheel_r,
|
|
136
|
+
cy - wheel_r,
|
|
137
|
+
cx + wheel_r,
|
|
138
|
+
cy + wheel_r,
|
|
139
|
+
fill="#777777",
|
|
140
|
+
outline="",
|
|
141
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
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 MountainCarEnv
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MountainCarWidget(anywidget.AnyWidget):
|
|
12
|
+
_esm = pathlib.Path(__file__).parent / "sim.js"
|
|
13
|
+
_css = pathlib.Path(__file__).parent / "styles.css"
|
|
14
|
+
|
|
15
|
+
sim_state = traitlets.Dict(default_value={}).tag(sync=True)
|
|
16
|
+
_viewport_size = traitlets.Tuple(
|
|
17
|
+
traitlets.Int(), traitlets.Int(), default_value=(600, 400)
|
|
18
|
+
).tag(sync=True)
|
|
19
|
+
_view_ready = traitlets.Bool(default_value=False).tag(sync=True)
|
|
20
|
+
_manual_control = traitlets.Bool(default_value=False).tag(sync=True)
|
|
21
|
+
|
|
22
|
+
def __init__(self, manual_control=False, viewport_size=(600, 400), sim_env=None):
|
|
23
|
+
self._manual_control = manual_control
|
|
24
|
+
self._viewport_size = viewport_size
|
|
25
|
+
super().__init__()
|
|
26
|
+
if sim_env is None:
|
|
27
|
+
sim_env = MountainCarEnv()
|
|
28
|
+
|
|
29
|
+
if sim_env.num_envs != 1:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"MountainCarWidget currently only supports single environment."
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
self.sim_env = sim_env
|
|
35
|
+
self.sim_state = self.sim_env.reset()
|
|
36
|
+
|
|
37
|
+
def render(self):
|
|
38
|
+
display(self)
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
with ui_events() as ui_poll:
|
|
42
|
+
while not self._view_ready:
|
|
43
|
+
ui_poll(100)
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
async def step(self, action: int, dt: float = 0.01) -> dict:
|
|
48
|
+
sim_state = self.sim_env.step(action)
|
|
49
|
+
self.sim_state = sim_state
|
|
50
|
+
await asyncio.sleep(dt)
|
|
51
|
+
return sim_state
|
|
52
|
+
|
|
53
|
+
async def reset(self) -> dict:
|
|
54
|
+
sim_state = self.sim_env.reset()
|
|
55
|
+
self.sim_state = sim_state
|
|
56
|
+
return sim_state
|
|
File without changes
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import random
|
|
2
|
+
import math
|
|
3
|
+
import copy
|
|
4
|
+
|
|
5
|
+
empty_map = {
|
|
6
|
+
"name": "rect_triangle_map",
|
|
7
|
+
"robot": {
|
|
8
|
+
"pos": [500, 300],
|
|
9
|
+
"angle": 0,
|
|
10
|
+
}, # speed: 0.01, turn_speed: 0.04, lidar: { numBeams: 30, fov: 6.28, maxRange: 1000}
|
|
11
|
+
"map": [
|
|
12
|
+
{
|
|
13
|
+
"type": "rectangle",
|
|
14
|
+
"x": 400,
|
|
15
|
+
"y": 0,
|
|
16
|
+
"width": 800,
|
|
17
|
+
"height": 30,
|
|
18
|
+
"bodyInfo": {"angle": 0, "render": {"fillStyle": "#2f6f8f"}},
|
|
19
|
+
"label": "Wall",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"type": "rectangle",
|
|
23
|
+
"x": 400,
|
|
24
|
+
"y": 590,
|
|
25
|
+
"width": 800,
|
|
26
|
+
"height": 20,
|
|
27
|
+
"bodyInfo": {"angle": 0, "render": {"fillStyle": "#2f6f8f"}},
|
|
28
|
+
"label": "Wall",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "rectangle",
|
|
32
|
+
"x": 10,
|
|
33
|
+
"y": 500,
|
|
34
|
+
"width": 20,
|
|
35
|
+
"height": 1000,
|
|
36
|
+
"bodyInfo": {"angle": 0, "render": {"fillStyle": "#2f6f8f"}},
|
|
37
|
+
"label": "Wall",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"type": "rectangle",
|
|
41
|
+
"x": 790,
|
|
42
|
+
"y": 500,
|
|
43
|
+
"width": 20,
|
|
44
|
+
"height": 1000,
|
|
45
|
+
"bodyInfo": {"angle": 0, "render": {"fillStyle": "#2f6f8f"}},
|
|
46
|
+
"label": "Wall",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def gen_simple_map():
|
|
53
|
+
map_data = copy.deepcopy(empty_map)
|
|
54
|
+
width = 800
|
|
55
|
+
height = 600
|
|
56
|
+
|
|
57
|
+
# Generate static obstacles
|
|
58
|
+
for _ in range(6):
|
|
59
|
+
w = 60 + random.random() * 120
|
|
60
|
+
h = 40 + random.random() * 100
|
|
61
|
+
x = 120 + random.random() * (width - 240)
|
|
62
|
+
y = 120 + random.random() * (height - 240)
|
|
63
|
+
angle = random.random() * math.pi
|
|
64
|
+
rect = {
|
|
65
|
+
"type": "rectangle",
|
|
66
|
+
"x": x,
|
|
67
|
+
"y": y,
|
|
68
|
+
"width": w,
|
|
69
|
+
"height": h,
|
|
70
|
+
"bodyInfo": {
|
|
71
|
+
"angle": angle,
|
|
72
|
+
"render": {"fillStyle": "#2f6f8f"},
|
|
73
|
+
"label": "Obstacle",
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
map_data["map"].append(rect)
|
|
77
|
+
|
|
78
|
+
# Generate dynamic pushable boxes
|
|
79
|
+
for _ in range(3):
|
|
80
|
+
size = 40 + random.random() * 20
|
|
81
|
+
x = 100 + random.random() * (width - 200)
|
|
82
|
+
y = 100 + random.random() * (height - 200)
|
|
83
|
+
box = {
|
|
84
|
+
"type": "rectangle",
|
|
85
|
+
"x": x,
|
|
86
|
+
"y": y,
|
|
87
|
+
"width": size,
|
|
88
|
+
"height": size,
|
|
89
|
+
"bodyInfo": {
|
|
90
|
+
"isStatic": False,
|
|
91
|
+
"frictionAir": 0.1,
|
|
92
|
+
"friction": 0.5,
|
|
93
|
+
"restitution": 0.2,
|
|
94
|
+
"density": 0.002,
|
|
95
|
+
"render": {"fillStyle": "#b2df8a"},
|
|
96
|
+
"label": "Pushable Box",
|
|
97
|
+
},
|
|
98
|
+
}
|
|
99
|
+
map_data["map"].append(box)
|
|
100
|
+
|
|
101
|
+
for i in range(2):
|
|
102
|
+
size = 40 + random.random() * 20
|
|
103
|
+
x = 100 + random.random() * (width - 200)
|
|
104
|
+
y = 100 + random.random() * (height - 200)
|
|
105
|
+
angle = random.random() * math.pi
|
|
106
|
+
box = {
|
|
107
|
+
"type": "rectangle",
|
|
108
|
+
"x": x,
|
|
109
|
+
"y": y,
|
|
110
|
+
"width": size,
|
|
111
|
+
"height": size,
|
|
112
|
+
"bodyInfo": {
|
|
113
|
+
"isStatic": True,
|
|
114
|
+
"angle": angle,
|
|
115
|
+
"label": "Goal",
|
|
116
|
+
"render": {"fillStyle": "#ffffff"},
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
map_data["map"].append(box)
|
|
120
|
+
|
|
121
|
+
return map_data
|