Skip to content

Commit

Permalink
Merge branch 'develop' into sync-server
Browse files Browse the repository at this point in the history
  • Loading branch information
rk1a committed Mar 3, 2023
2 parents 5b9663f + c1a86f0 commit e734599
Show file tree
Hide file tree
Showing 21 changed files with 213 additions and 173 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ log/*
# Python
**/__pycache__/
__pycache__/
*.egg-info

newworld/
## Non-static Minetest directories or symlinks to these
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
EleutherAI Alignment Minetest
=============================
This is a fork of the Minetest Voxel Engine, designed to support and OAI gym like environment. The library supports
This is a fork of the Minetest Voxel Engine, designed to support an OAI gym like environment. The library supports

Modes of Operation
------------------
Expand All @@ -11,6 +11,17 @@ The model provides 4 modes of operation for players and agents
4. Real time recording of player actions


Minetester
==========
Minetester is the Python package that exposes Minetest environments via the `gym(nasium)` interface.
After building the minetest executable you can install it with:
```
pip install -e .
```
To verify the installation run
```
python -m minetester.scripts.test_loop
```

Minetest
========
Expand Down
2 changes: 0 additions & 2 deletions hacking_testing/compile_proto.sh

This file was deleted.

2 changes: 0 additions & 2 deletions hacking_testing/record_client.sh

This file was deleted.

2 changes: 0 additions & 2 deletions hacking_testing/server.sh

This file was deleted.

1 change: 1 addition & 0 deletions minetester/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from minetester.minetest_env import Minetest
169 changes: 8 additions & 161 deletions hacking_testing/minetest_env.py → minetester/minetest_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,168 +3,16 @@
import os
import random
import shutil
import subprocess
import uuid
from typing import Any, Dict, List, Optional, Tuple

import gym
import matplotlib.pyplot as plt
import numpy as np
import proto_python.objects_pb2 as pb_objects
import psutil
import zmq
from proto_python.objects_pb2 import KeyType

# Define default keys / buttons
KEY_MAP = {
"FORWARD": KeyType.FORWARD,
"BACKWARD": KeyType.BACKWARD,
"LEFT": KeyType.LEFT,
"RIGHT": KeyType.RIGHT,
"JUMP": KeyType.JUMP,
"SNEAK": KeyType.SNEAK, # shift key in menus
"DIG": KeyType.DIG, # left mouse button
"MIDDLE": KeyType.MIDDLE, # middle mouse button
"PLACE": KeyType.PLACE, # right mouse button
"DROP": KeyType.DROP,
"HOTBAR_NEXT": KeyType.HOTBAR_NEXT, # mouse wheel up
"HOTBAR_PREV": KeyType.HOTBAR_PREV, # mouse wheel down
"SLOT_1": KeyType.SLOT_1,
"SLOT_2": KeyType.SLOT_2,
"SLOT_3": KeyType.SLOT_3,
"SLOT_4": KeyType.SLOT_4,
"SLOT_5": KeyType.SLOT_5,
"SLOT_6": KeyType.SLOT_6,
"SLOT_7": KeyType.SLOT_7,
"SLOT_8": KeyType.SLOT_8,
"INVENTORY": KeyType.INVENTORY,
}
INV_KEY_MAP = {value: key for key, value in KEY_MAP.items()}

# Define noop action
NOOP_ACTION = {key: 0 for key in KEY_MAP.keys()}
NOOP_ACTION.update({"MOUSE": np.zeros(2, dtype=int)})


def unpack_pb_obs(received_obs: str):
pb_obs = pb_objects.Observation()
pb_obs.ParseFromString(received_obs)
obs = np.frombuffer(pb_obs.image.data, dtype=np.uint8).reshape(
pb_obs.image.height,
pb_obs.image.width,
3,
)
last_action = unpack_pb_action(pb_obs.action) if pb_obs.action else None
rew = pb_obs.reward
done = pb_obs.terminal
# TODO receive extra infos
info = {}
return obs, rew, done, info, last_action


def unpack_pb_action(pb_action: pb_objects.Action):
action = dict(NOOP_ACTION)
action["MOUSE"] = [pb_action.mouseDx, pb_action.mouseDy]
for key_event in pb_action.keyEvents:
if key_event.key in INV_KEY_MAP and key_event.eventType == pb_objects.PRESS:
key_name = INV_KEY_MAP[key_event.key]
action[key_name] = 1
return action


def pack_pb_action(action: Dict[str, Any]):
pb_action = pb_objects.Action()
pb_action.mouseDx, pb_action.mouseDy = action["MOUSE"]
for key, v in action.items():
if key == "MOUSE":
continue
pb_action.keyEvents.append(
pb_objects.KeyboardEvent(
key=KEY_MAP[key],
eventType=pb_objects.PRESS if v else pb_objects.RELEASE,
),
)
return pb_action


def start_minetest_server(
minetest_path: str = "bin/minetest",
config_path: str = "minetest.conf",
log_path: str = "log/{}.log",
server_port: int = 30000,
world_dir: str = "newworld",
sync_port: int = None,
sync_dtime: float = 0.001,
game_id: str = "minetest",
):
cmd = [
minetest_path,
"--server",
"--world",
world_dir,
"--gameid",
game_id,
"--config",
config_path,
"--port",
str(server_port),
]
if sync_port:
cmd.extend(["--sync-port", str(sync_port)])
cmd.extend(["--sync-dtime", str(sync_dtime)])
stdout_file = log_path.format("server_stdout")
stderr_file = log_path.format("server_stderr")
with open(stdout_file, "w") as out, open(stderr_file, "w") as err:
server_process = subprocess.Popen(cmd, stdout=out, stderr=err)
return server_process


def start_minetest_client(
minetest_path: str = "bin/minetest",
config_path: str = "minetest.conf",
log_path: str = "log/{}.log",
client_port: int = 5555,
server_port: int = 30000,
cursor_img: str = "cursors/mouse_cursor_white_16x16.png",
client_name: str = "MinetestAgent",
xvfb_headless: bool = False,
sync_port: int = None,
):
cmd = [
minetest_path,
"--name",
client_name,
"--password",
"1234",
"--address",
"0.0.0.0", # listen to all interfaces
"--port",
str(server_port),
"--go",
"--dumb",
"--client-address",
"tcp://localhost:" + str(client_port),
"--record",
"--noresizing",
"--config",
config_path,
]
if xvfb_headless:
# hide window
cmd.insert(0, "-a") # allow restarts
cmd.insert(0, "xvfb-run")
# don't render to screen
cmd.append("--headless")
if cursor_img:
cmd.extend(["--cursor-image", cursor_img])
if sync_port:
cmd.extend(["--sync-port", str(sync_port)])

stdout_file = log_path.format("client_stdout")
stderr_file = log_path.format("client_stderr")
with open(stdout_file, "w") as out, open(stderr_file, "w") as err:
client_process = subprocess.Popen(cmd, stdout=out, stderr=err)
return client_process
from minetester.utils import (KEY_MAP, pack_pb_action, start_minetest_client,
start_minetest_server, unpack_pb_obs)


class Minetest(gym.Env):
Expand Down Expand Up @@ -514,13 +362,6 @@ def step(self, action: Dict[str, Any]):
return next_obs, rew, done, info

def render(self, render_mode: str = "human"):
if render_mode is None:
gym.logger.warn(
"You are calling render method without specifying any render mode. "
"You can specify the render_mode at initialization, "
f'e.g. gym("{self.spec.id}", render_mode="rgb_array")',
)
return
if render_mode == "human":
if self.render_img is None:
# Setup figure
Expand All @@ -540,6 +381,12 @@ def render(self, render_mode: str = "human"):
plt.draw(), plt.pause(1e-3)
elif render_mode == "rgb_array":
return self.last_obs
else:
raise NotImplementedError(
"You are calling 'render()' with an unsupported"
f" render mode: '{render_mode}'. "
f"Supported modes: {self.metadata['render.modes']}"
)

def close(self):
if self.render_fig is not None:
Expand Down
File renamed without changes.
Empty file added minetester/scripts/__init__.py
Empty file.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
from minetest_env import Minetest
from minetester import Minetest

env = Minetest(
seed=42,
Expand All @@ -9,7 +9,7 @@
sync_dtime=0.05,
)

render = False
render = True
obs = env.reset()
done = False
while not done:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import Any, Dict, Optional

from gym.wrappers import TimeLimit
from minetest_env import Minetest
from minetester import Minetest
from stable_baselines3.common.vec_env import DummyVecEnv, SubprocVecEnv

if __name__ == "__main__":
Expand Down
Loading

0 comments on commit e734599

Please sign in to comment.