Browse Source

add gamepad/keyboard handler and simple snake game

Thomas Buck 1 year ago
parent
commit
d846d60b6f
5 changed files with 284 additions and 3 deletions
  1. 3
    0
      README.md
  2. 5
    0
      camp_small.py
  3. 6
    3
      draw.py
  4. 103
    0
      gamepad.py
  5. 167
    0
      snake.py

+ 3
- 0
README.md View File

@@ -17,6 +17,9 @@ You always need:
17 17
     pip install pil
18 18
     pip install bdfparser
19 19
     pip install "qrcode[pil]"
20
+    pip install evdev
21
+
22
+For evdev to find all devices you may need to add your user to the `input` group or run the scripts as root.
20 23
 
21 24
 The rest depends on the output device chosen.
22 25
 For debugging on your host PC you can use the TestGUI interface with pygame:

+ 5
- 0
camp_small.py View File

@@ -18,12 +18,15 @@ if __name__ == "__main__":
18 18
     from net import CheckHTTP
19 19
     from image import ImageScreen
20 20
     from qr import QRScreen
21
+    from snake import Snake
22
+    from gamepad import InputWrapper
21 23
     from manager import Manager
22 24
 
23 25
     url = "http://ubabot.frubar.net"
24 26
 
25 27
     import util
26 28
     t = util.getTarget()
29
+    i = InputWrapper()
27 30
 
28 31
     # Loading fonts and graphics takes a while.
29 32
     # So show a splash screen while the user waits.
@@ -60,6 +63,8 @@ if __name__ == "__main__":
60 63
     m.add(Solid(t, 1.0))
61 64
     m.add(d) # HTTP Check, either "success" or "fail"
62 65
     m.add(Solid(t, 1.0))
66
+    m.add(Snake(t, i, camp_pink, camp_green))
67
+    m.add(Solid(t, 1.0))
63 68
     m.add(ScrollText(t, "Your advertisement could appear here. Open a Pull Request on git.xythobuz.de/thomas/rgb-matrix-visualizer or send an e-mail to thomas@xythobuz.de", "iv18x16u", 2, 70, camp_green))
64 69
     m.add(Solid(t, 1.0))
65 70
 

+ 6
- 3
draw.py View File

@@ -122,14 +122,17 @@ class ScrollText:
122 122
     def __init__(self, g, t, f, i = 1, s = 75, fg = (255, 255, 255), bg = (0, 0, 0)):
123 123
         self.gui = g
124 124
         self.drawer = DrawText(self.gui, fg, bg)
125
-        self.text = t
126
-        self.font = f
127 125
         self.iterations = i
128 126
         self.speed = 1.0 / s
129 127
 
130
-        self.width = self.drawer.text(self.text, self.font, 0, False)
128
+        self.setText(t, f)
131 129
         self.restart()
132 130
 
131
+    def setText(self, t, f):
132
+        self.text = t
133
+        self.font = f
134
+        self.width = self.drawer.text(self.text, self.font, 0, False)
135
+
133 136
     def restart(self):
134 137
         self.offset = -self.gui.width
135 138
         self.last = time.time()

+ 103
- 0
gamepad.py View File

@@ -0,0 +1,103 @@
1
+#!/usr/bin/env python3
2
+
3
+# Uses the Python evdev wrapper:
4
+# https://github.com/gvalkov/python-evdev
5
+#
6
+# ----------------------------------------------------------------------------
7
+# "THE BEER-WARE LICENSE" (Revision 42):
8
+# <xythobuz@xythobuz.de> wrote this file.  As long as you retain this notice
9
+# you can do whatever you want with this stuff. If we meet some day, and you
10
+# think this stuff is worth it, you can buy me a beer in return.   Thomas Buck
11
+# ----------------------------------------------------------------------------
12
+
13
+from evdev import InputDevice, list_devices, ecodes, categorize
14
+from selectors import DefaultSelector, EVENT_READ
15
+
16
+class InputWrapper:
17
+    def __init__(self):
18
+        self.devices = []
19
+        self.selector = DefaultSelector()
20
+        self.keys = {
21
+            "left": False,
22
+            "right": False,
23
+            "up": False,
24
+            "down": False,
25
+            "a": False,
26
+            "b": False,
27
+            "x": False,
28
+            "y": False,
29
+        }
30
+
31
+        devices = [InputDevice(path) for path in list_devices()]
32
+        for device in devices:
33
+            c = device.capabilities()
34
+            keep = False
35
+
36
+            # check for key events
37
+            if ecodes.EV_KEY in c:
38
+                # check for gamepad
39
+                if ecodes.BTN_A in c[ecodes.EV_KEY]:
40
+                    print("Gamepad detected: ", device.name)
41
+                    keep = True
42
+
43
+                # check for arrow keys
44
+                elif ecodes.KEY_LEFT in c[ecodes.EV_KEY]:
45
+                    print("Keyboard detected:", device.name)
46
+                    keep = True
47
+
48
+            if not keep:
49
+                continue
50
+
51
+            d = InputDevice(device.path)
52
+            self.devices.append(d)
53
+            self.selector.register(d, EVENT_READ)
54
+
55
+    def get(self, verbose = False):
56
+        for key, mask in self.selector.select(0):
57
+            device = key.fileobj
58
+            for event in device.read():
59
+                if event.type != ecodes.EV_KEY:
60
+                    continue
61
+
62
+                v = False
63
+                if event.value != 0:
64
+                    v = True
65
+
66
+                if (event.code == ecodes.KEY_LEFT) or (event.code == ecodes.BTN_WEST):
67
+                    self.keys["left"] = v
68
+                elif (event.code == ecodes.KEY_RIGHT) or (event.code == ecodes.BTN_EAST):
69
+                    self.keys["right"] = v
70
+                elif (event.code == ecodes.KEY_UP) or (event.code == ecodes.BTN_NORTH):
71
+                    self.keys["up"] = v
72
+                elif (event.code == ecodes.KEY_DOWN) or (event.code == ecodes.BTN_SOUTH):
73
+                    self.keys["down"] = v
74
+                elif (event.code == ecodes.KEY_SPACE) or (event.code == ecodes.BTN_A):
75
+                    self.keys["a"] = v
76
+                elif (event.code == ecodes.KEY_LEFTCTRL) or (event.code == ecodes.BTN_B):
77
+                    self.keys["b"] = v
78
+                elif (event.code == ecodes.KEY_LEFTALT) or (event.code == ecodes.BTN_X):
79
+                    self.keys["x"] = v
80
+                elif (event.code == ecodes.KEY_ENTER) or (event.code == ecodes.BTN_Y):
81
+                    self.keys["y"] = v
82
+                else:
83
+                    return self.keys
84
+
85
+                if verbose:
86
+                    print(categorize(event), event)
87
+
88
+        return self.keys
89
+
90
+if __name__ == "__main__":
91
+    from pprint import pprint
92
+    import sys
93
+
94
+    if len(sys.argv) > 1:
95
+        devices = [InputDevice(path) for path in list_devices()]
96
+        for device in devices:
97
+            print(device.path, device.name, device.phys)
98
+            pprint(device.capabilities(verbose=True))
99
+            print()
100
+    else:
101
+        i = InputWrapper()
102
+        while True:
103
+            i.get(True)

+ 167
- 0
snake.py View File

@@ -0,0 +1,167 @@
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# "THE BEER-WARE LICENSE" (Revision 42):
5
+# <xythobuz@xythobuz.de> wrote this file.  As long as you retain this notice
6
+# you can do whatever you want with this stuff. If we meet some day, and you
7
+# think this stuff is worth it, you can buy me a beer in return.   Thomas Buck
8
+# ----------------------------------------------------------------------------
9
+
10
+from draw import ScrollText
11
+import time
12
+import random
13
+
14
+class Snake:
15
+    def __init__(self, g, i, sc = (0, 255, 0), d = (0, 0, 255), bg = (0, 0, 0), ts = 0.3, su = 0.75, to = 60.0):
16
+        self.gui = g
17
+        self.input = i
18
+        self.colors = [ bg, sc, d ]
19
+        self.timestep = ts
20
+        self.timeout = to
21
+        self.speedup = su
22
+
23
+        self.winText = ScrollText(self.gui, "You Won!", "uushi",
24
+                                  2, 75, (0, 255, 0))
25
+        self.loseText = ScrollText(self.gui, "Game Over!", "uushi",
26
+                                   2, 75, (255, 0, 0))
27
+        self.scoreText = ScrollText(self.gui, "Score:", "uushi",
28
+                                    2, 75, sc)
29
+
30
+        random.seed()
31
+        self.restart()
32
+
33
+    def restart(self):
34
+        self.start = time.time()
35
+        self.last = time.time()
36
+        self.direction = "r"
37
+        self.directionTmp = "r"
38
+        self.score = 0
39
+        self.data = [[0 for y in range(self.gui.height)] for x in range(self.gui.width)]
40
+
41
+        self.player = [ (int(self.gui.width / 2), int(self.gui.height / 2)) ]
42
+        self.data[self.player[0][0]][self.player[0][1]] = 1
43
+
44
+        self.placeDot()
45
+
46
+    def finished(self):
47
+        if self.input == None:
48
+            # backup timeout for "AI"
49
+            if (time.time() - self.start) >= self.timeout:
50
+                return True
51
+
52
+        if self.direction == "":
53
+            # game over screen
54
+            return self.scoreText.finished()
55
+
56
+        return False
57
+
58
+    def placeDot(self):
59
+        d = (random.randrange(0, self.gui.width), random.randrange(0, self.gui.height))
60
+        while self.data[d[0]][d[1]] != 0:
61
+            d = (random.randrange(0, self.gui.width), random.randrange(0, self.gui.height))
62
+        self.data[d[0]][d[1]] = 2
63
+
64
+    def buttons(self):
65
+        keys = self.input.get()
66
+        if keys["left"]:
67
+            self.directionTmp = "l"
68
+        elif keys["right"]:
69
+            self.directionTmp = "r"
70
+        elif keys["up"]:
71
+            self.directionTmp = "u"
72
+        elif keys["down"]:
73
+            self.directionTmp = "d"
74
+
75
+    def step(self):
76
+        player = self.player[len(self.player) - 1]
77
+        if self.direction == "r":
78
+            player = (player[0] + 1, player[1])
79
+        elif self.direction == "l":
80
+            player = (player[0] - 1, player[1])
81
+        elif self.direction == "u":
82
+            player = (player[0], player[1] - 1)
83
+        elif self.direction == "d":
84
+            player = (player[0], player[1] + 1)
85
+
86
+        if (player[0] < 0) or (player[1] < 0) or (player[0] >= self.gui.width) or (player[1] >= self.gui.height):
87
+            return False
88
+
89
+        if self.data[player[0]][player[1]] == 0:
90
+            # remove last tail piece if not on dot
91
+            self.data[self.player[0][0]][self.player[0][1]] = 0
92
+            self.player.pop(0)
93
+        elif self.data[player[0]][player[1]] == 1:
94
+            # snake crashed into itself
95
+            return False
96
+        else:
97
+            # collected a dot
98
+            self.score += 1
99
+            if self.score >= self.gui.width * self.gui.height:
100
+                return False
101
+
102
+            self.timestep = self.timestep * self.speedup
103
+            self.placeDot()
104
+
105
+        self.player.append(player)
106
+        self.data[player[0]][player[1]] = 1
107
+        return True
108
+
109
+    def finishedEndScreen(self):
110
+        if self.score >= self.gui.width * self.gui.height:
111
+            return self.winText.finished()
112
+        else:
113
+            return self.loseText.finished()
114
+
115
+    def drawEndScreen(self):
116
+        if self.score >= self.gui.width * self.gui.height:
117
+            self.winText.draw()
118
+        else:
119
+            self.loseText.draw()
120
+
121
+    def drawScoreScreen(self):
122
+        self.scoreText.draw()
123
+
124
+    def draw(self):
125
+        if self.direction == "":
126
+            if self.finishedEndScreen():
127
+                self.drawScoreScreen()
128
+            else:
129
+                self.drawEndScreen()
130
+                self.scoreText.restart()
131
+            return
132
+
133
+        if self.input != None:
134
+            self.buttons()
135
+        else:
136
+            # TODO "AI"
137
+            pass
138
+
139
+        if (time.time() - self.last) >= self.timestep:
140
+            self.last = time.time()
141
+
142
+            # only allow valid inputs
143
+            tmp = self.direction + self.directionTmp
144
+            if (tmp != "lr") and (tmp != "rl") and (tmp != "ud") and (tmp != "du"):
145
+                self.direction = self.directionTmp
146
+
147
+            cont = self.step()
148
+            if cont == False:
149
+                self.direction = ""
150
+                self.scoreText.setText("Score: " + str(self.score), "uushi")
151
+                self.winText.restart()
152
+                self.loseText.restart()
153
+                self.scoreText.restart()
154
+
155
+        for x in range(0, self.gui.width):
156
+            for y in range(0, self.gui.height):
157
+                self.gui.set_pixel(x, y, self.colors[self.data[x][y]])
158
+
159
+if __name__ == "__main__":
160
+    import util
161
+    t = util.getTarget()
162
+
163
+    from gamepad import InputWrapper
164
+    i = InputWrapper()
165
+
166
+    d = Snake(t, i)
167
+    t.debug_loop(d.draw)

Loading…
Cancel
Save