Skip to content

Commit

Permalink
Move Digger into its own class, export getdigdat API and
Browse files Browse the repository at this point in the history
use it to end game as soon as we die.
  • Loading branch information
sobomax committed Jan 17, 2024
1 parent 86ad296 commit 75d8395
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 97 deletions.
89 changes: 89 additions & 0 deletions python/Digger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from ctypes import c_bool, c_ubyte, c_int, POINTER, Structure, c_int16, c_int32

from . import _digger

class DiggerControls(Structure):
_fields_ = [
("leftpressed", c_bool),
("rightpressed", c_bool),
("uppressed", c_bool),
("downpressed", c_bool),
("f1pressed", c_bool),
("left2pressed", c_bool),
("right2pressed", c_bool),
("up2pressed", c_bool),
("down2pressed", c_bool),
("f12pressed", c_bool)
]

class DiggerState(Structure):
_fields_ = [
("h", c_int16),
("v", c_int16),
("rx", c_int16),
("ry", c_int16),
("mdir", c_int16),
("bagtime", c_int16),
("rechargetime", c_int16),
("deathstage", c_int16),
("deathbag", c_int16),
("deathani", c_int16),
("deathtime", c_int16),
("emocttime", c_int16),
("emn", c_int16),
("msc", c_int16),
("lives", c_int16),
("ivt", c_int16),
("notfiring", c_bool),
("firepressed", c_bool),
("dead", c_bool),
("levdone", c_bool),
("invin", c_bool),
]

class Digger:
def __init__(self):
self.gamestep = _digger.gamestep
self.gamestep.restype = c_bool
self.maininit = _digger.maininit
self.maininit.restype = None
self.initgame = _digger.initgame
self.startlevel = _digger.startlevel
self.startlevel.restype = None
self.getscreen = _digger.getscreen
self.getscreen.argtypes = [POINTER(c_ubyte), c_int]
self.getscreen.restype = None
self.getscore = _digger.gettscore
self.getscore.argtypes = [c_int]
self.getscore.restype = c_int32
self._getdigdat = _digger.getdigdat
self._getdigdat.argtypes = [c_int]
self._getdigdat.restype = POINTER(DiggerState)
self.digger_controls = DiggerControls.in_dll(_digger, "digger_controls")
self.soundflag = c_bool.in_dll(_digger, 'soundflag')
self.musicflag = c_bool.in_dll(_digger, 'musicflag')

def game(self, steps=None):
self.maininit()
self.initgame()
self.startlevel()
while (steps is None or steps > 0) and self.gamestep(): steps -= 1
return steps

def screenshot(self):
buffer_size = 640 * 400
buffer = (c_ubyte * buffer_size)()
self.getscreen(buffer, buffer_size)
return buffer

def showscreen(self):
from PIL import Image
import numpy as np
s = self.screenshot()
image_data = np.frombuffer(s, dtype=np.uint8).reshape((400, 640))
image = Image.fromarray(image_data, 'P')
image.putpalette(generate_vga_palette())
image.show()

def getdigdat(self, player):
return self._getdigdat(player).contents
24 changes: 20 additions & 4 deletions python/DiggerGym.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import gymnasium as gym
import numpy as np

from . import Digger
from .Digger import Digger

class DiggerGym(gym.Env):
lastscore:int
Expand All @@ -22,17 +22,33 @@ def step(self, action):
self.digger.digger_controls.downpressed = (action == 3)
self.digger.digger_controls.f1pressed = (action == 4)
if action not in range(5): raise ValueError(f"Invalid action: {action}")
done = not self.digger.gamestep()
dead = self.digger.getdigdat(0).deathstage > 1
done = not self.digger.gamestep() or dead
reward = (newscore:=self.digger.getscore(0)) - self.lastscore
assert reward >= 0
self.lastscore = newscore
return self.digger.getscreenrgb(), reward, done, False, {}
return self.getscreenrgb(), reward, done, dead, {}

def reset(self):
self.digger.initgame()
self.digger.startlevel()
self.lastscore = self.digger.getscore(0)
return self.digger.getscreenrgb(), {}
return self.getscreenrgb(), {}

def getscreenrgb(self):
s = self.digger.screenshot()
@lru_cache(maxsize=1)
def generate_vga_palette():
palette = np.zeros((256, 3), dtype=np.uint8)
for i in range(256):
r = (i & 0b0011) * 85
g = ((i & 0b1100) >> 2) * 85
b = ((i & 0b110000) >> 4) * 85
palette[i] = [r, g, b]
return palette
image_data = np.frombuffer(s, dtype=np.uint8).reshape((400, 640))
palette = generate_vga_palette()
return palette[image_data]

def screenshot(self):
s = self.digger.screenshot()
Expand Down
2 changes: 1 addition & 1 deletion python/Symbol.map
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DIGGER_2ba68bed631b {
global: maininit; initgame; startlevel; gamestep; digger_controls; dgstate; getscreen; gettscore;
soundint; soundflag; musicflag;
soundint; soundflag; musicflag; getdigdat;
local: *;
};
87 changes: 1 addition & 86 deletions python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from ctypes import cdll, c_bool, c_ubyte, c_int, POINTER, Structure, c_int32
from ctypes import cdll
import os, site, sysconfig

from .env import DIGGER_MOD_NAME
Expand All @@ -46,88 +46,3 @@
break
#else:
# raise ImportError("Cannot find %s" % DIGGER_MOD_NAME + _esuf)

class DiggerControls(Structure):
_fields_ = [
("leftpressed", c_bool),
("rightpressed", c_bool),
("uppressed", c_bool),
("downpressed", c_bool),
("f1pressed", c_bool),
("left2pressed", c_bool),
("right2pressed", c_bool),
("up2pressed", c_bool),
("down2pressed", c_bool),
("f12pressed", c_bool)
]

class Digger:
def __init__(self):
self.gamestep = _digger.gamestep
self.gamestep.restype = c_bool
self.maininit = _digger.maininit
self.maininit.restype = None
self.initgame = _digger.initgame
self.startlevel = _digger.startlevel
self.startlevel.restype = None
self.getscreen = _digger.getscreen
self.getscreen.argtypes = [POINTER(c_ubyte), c_int]
self.getscreen.restype = None
self.getscore = _digger.gettscore
self.getscore.argtypes = [c_int]
self.getscore.restype = c_int32
self.digger_controls = DiggerControls.in_dll(_digger, "digger_controls")
self.soundflag = c_bool.in_dll(_digger, 'soundflag')
self.musicflag = c_bool.in_dll(_digger, 'musicflag')

def game(self, steps=None):
self.maininit()
self.initgame()
self.startlevel()
while (steps is None or steps > 0) and self.gamestep(): steps -= 1
return steps

def screenshot(self):
buffer_size = 640 * 400
buffer = (c_ubyte * buffer_size)()
self.getscreen(buffer, buffer_size)
return buffer

def showscreen(self):
from PIL import Image
import numpy as np
def generate_vga_palette():
palette = []
for i in range(256):
r = (i & 0b0011) * 85
g = ((i & 0b1100) >> 2) * 85
b = ((i & 0b110000) >> 4) * 85
palette.extend([r, g, b])
return palette
s = self.screenshot()
image_data = np.frombuffer(s, dtype=np.uint8).reshape((400, 640))
image = Image.fromarray(image_data, 'P')
image.putpalette(generate_vga_palette())
image.show()

def getscreenrgb(self):
import numpy as np
from functools import lru_cache
@lru_cache(maxsize=1)
def generate_vga_palette():
palette = np.zeros((256, 3), dtype=np.uint8)
for i in range(256):
r = (i & 0b0011) * 85
g = ((i & 0b1100) >> 2) * 85
b = ((i & 0b110000) >> 4) * 85
palette[i] = [r, g, b]
return palette

s = self.screenshot()
image_data = np.frombuffer(s, dtype=np.uint8).reshape((400, 640))
palette = generate_vga_palette()

# Convert indexed image to RGB format using NumPy's advanced indexing
rgb_image = palette[image_data]

return rgb_image
14 changes: 8 additions & 6 deletions tests/test_python.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from digger import Digger
from digger.Digger import Digger
from digger.DiggerGym import DiggerGym

if __name__ == '__main__':
#d= Digger()
#r = d.game(1)
#assert r == 0
#while True:
# if not d.gamestep(): break
d= Digger()
r = d.game(1)
assert r == 0
while True:
if not d.gamestep(): break
print(f'{d.getdigdat(0).deathstage=}')
#d.showscreen()
exit(0)
import numpy as np
#rgb = np.frombuffer(d.screenshot(), dtype=np.uint8).reshape((400, 640))
#print(f'{rgb.shape=} {np.unique(rgb)=}')
Expand Down

0 comments on commit 75d8395

Please sign in to comment.