Local version release
parent
444f10de52
commit
4955a1b732
|
@ -8,9 +8,9 @@
|
|||
"name": "Python: Current File",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
//"args": ["/dev/input/event6"],
|
||||
"args": ["/dev/input/event6", "/dev/input/event7"],
|
||||
"program": "${file}",
|
||||
//"args": ["/dev/input/event6", "/dev/input/event7", "--sizeable"],
|
||||
"args": ["--sizeable"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||

|
||||
|
||||
# Pandemic Pong, a gamepad usage example #
|
||||
|
||||
I recently obtained Nico's old (chinese) SNES gamepad clones
|
||||
for USB. To bring them to life, I decided to program yet another pong
|
||||
clone.
|
||||
|
||||
## 1. Python libs ##
|
||||
|
||||
As warmup, I chose python and scanned a couple of library
|
||||
documents to decide which one might be a suitable choice.
|
||||
|
||||
I selected **libevdev**, the [reference](https://python-libevdev.readthedocs.io/en/latest/) pages are quite convincing ...
|
||||
|
||||
Also, we will have to play some game typical sounds. I chose the **playsound** [library](https://pypi.org/project/playsound/) as the enabler.
|
||||
|
||||
Unfortunately it does not support async operation w/ linux, yet for short samples that does no significant harm!
|
||||
|
||||
With these installed we're prep'ed & ready!
|
||||
|
||||
## 2. Game usage & capabilities ##
|
||||
|
||||
Run the game fullscreen like:
|
||||
|
||||
python3 pandemic_pong.py
|
||||
|
||||
or have a sizeable window with:
|
||||
|
||||
python3 pandemic_pong.py --sizeable
|
||||
|
||||
The game tries to identify the event inputs for two gamepads automatically (otherwise see troubleshooting section below!).
|
||||
|
||||
### 2.1 Scoring ###
|
||||
A game is won by the player who first reaches 10 points.
|
||||
|
||||
A match is won by the player who first wins 3 games.
|
||||
|
||||
### 2.2 Additional features ###
|
||||
|
||||
The game features two additional capabilites beyond the original pong game (& may lack others ;).
|
||||
|
||||

|
||||
|
||||
1. The player movement permits advancing towards the 'net', i.e. is not limited to vertical movement.
|
||||
|
||||
2. It is possible to 'infect' the ball (hence the name!) by pressing one of the colour buttons. Infectiousness diminishes over time. Within the infection time, the player itself changes to the 'infection' colour.
|
||||
If the player contacts the ball during 'infection' time, it will contract the 'virus' (lifetime: next point!). To make things a little bit more complicated, the ball does not hint it's 'infection' by colour.
|
||||
If a tainted ball approaches a player, he has to vaccinate himself w/ the same colour (press corresponding button!).
|
||||
Yet, the vaccine diminishes over time as well ...
|
||||
If the player chooses the wrong colour or no vaccine at all, the ball passes right through the player. The point is counted as a miss, the opponent gains a point.
|
||||
|
||||
## 3. Troubleshooting ##
|
||||
|
||||
In case you have other (differing) USB gamepads, you may need to identify button usage & other gamepad specifics.
|
||||
For convenience, I copied the sample script (folder 'utilities').
|
||||
|
||||
Source adjustments will have to take place within 'pong_player.py'. Recommended approach: Map events here to their original correspondents.
|
|
@ -12,6 +12,7 @@ History:
|
|||
"""
|
||||
|
||||
import sys, os, fcntl, time, random
|
||||
from glob import glob
|
||||
import os
|
||||
import time
|
||||
|
||||
|
@ -61,22 +62,17 @@ class pongWindow(QMainWindow):
|
|||
def InitWindow(self):
|
||||
self.setWindowTitle(self.title)
|
||||
self.setGeometry(self.top, self.left, self.win_width, self.win_height)
|
||||
self.showFullScreen()
|
||||
if bSizeable:
|
||||
self.resize(int(self.win_width * 2 / 3), int(self.win_height * 2 / 2.8))
|
||||
self.move(int(self.win_width / 6), int(self.win_height / 8))
|
||||
else:
|
||||
self.showFullScreen() # Regular full screen play by default
|
||||
|
||||
self.pic = QPixmap("pictures/pong_background.png")
|
||||
# Font metrics assume font is monospaced!
|
||||
self.fntLarge = QFont("Monospace", 120)
|
||||
self.fntLarge.setKerning(False)
|
||||
self.fntLarge.setFixedPitch(True)
|
||||
self.fm = QFontMetrics(self.fntLarge)
|
||||
self.fntMedium = QFont("Monospace", 40)
|
||||
self.fntMedium.setKerning(False)
|
||||
self.fntMedium.setFixedPitch(True)
|
||||
|
||||
self.show()
|
||||
|
||||
# Enforce caching ...
|
||||
playsound(pgc.game_win)
|
||||
playsound(pgc.match_win)
|
||||
playsound(pgc.player_contact)
|
||||
playsound(pgc.player_miss)
|
||||
playsound(pgc.wall_contact)
|
||||
|
@ -103,22 +99,22 @@ class pongWindow(QMainWindow):
|
|||
painter.drawPixmap(self.rect(), self.pic) # Game background
|
||||
|
||||
# Font metrics assume font is monospaced!
|
||||
self.fntLarge = QFont("Monospace", int(120 * scale_y))
|
||||
self.fntLarge.setKerning(False)
|
||||
self.fntLarge.setFixedPitch(True)
|
||||
self.fm = QFontMetrics(self.fntLarge)
|
||||
self.fntMedium = QFont("Monospace", int(40 * scale_y))
|
||||
self.fntMedium.setKerning(False)
|
||||
self.fntMedium.setFixedPitch(True)
|
||||
|
||||
fntLarge = QFont("Monospace", int(120 * scale_y))
|
||||
fntLarge.setKerning(False)
|
||||
fntLarge.setFixedPitch(True)
|
||||
fm = QFontMetrics(fntLarge)
|
||||
fntMedium = QFont("Monospace", int(40 * scale_y))
|
||||
fntMedium.setKerning(False)
|
||||
fntMedium.setFixedPitch(True)
|
||||
|
||||
if game.state == pgc.STATE_WELCOME: # Welcome screen
|
||||
painter.drawPixmap(self.rect(), QPixmap("pictures/pandemic_pong.png"))
|
||||
painter.setFont(self.fntLarge)
|
||||
painter.setFont(fntLarge)
|
||||
painter.drawText(int(50 * scale_x), int((pgv.GAMEAREA_MAX_Y / 2 - 100) * scale_y), "PANDEMIC")
|
||||
painter.drawText(int((pgv.GAMEAREA_MAX_X / 2 + 300) * scale_x), int((pgv.GAMEAREA_MAX_Y / 2 + 100) * scale_y), "PONG")
|
||||
|
||||
elif game.state == pgc.STATE_START: # Wait for start button pressed
|
||||
painter.setFont(self.fntMedium)
|
||||
painter.setFont(fntMedium)
|
||||
painter.drawText(int(150 * scale_x), int((pgv.GAMEAREA_MAX_Y / 2 - 100) * scale_y), "Press [Start] ...")
|
||||
painter.drawText(int(50 * scale_x), int((pgv.GAMEAREA_MAX_Y / 2 + 100) * scale_y), "Press [Select] to abort")
|
||||
painter.drawText(int((pgv.GAMEAREA_MAX_X / 2 + 100) * scale_x), int(pgv.GAMEAREA_MAX_Y / 5 * scale_y), "Press [color] button")
|
||||
|
@ -168,10 +164,10 @@ class pongWindow(QMainWindow):
|
|||
painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
|
||||
|
||||
# Game scores
|
||||
painter.setFont(self.fntLarge)
|
||||
painter.setFont(fntLarge)
|
||||
# Score player #1
|
||||
msg = str(player1.score)
|
||||
msg_width = self.fm.width(msg)
|
||||
msg_width = fm.width(msg)
|
||||
painter.drawText(int((800 - msg_width) * scale_x), int(154 * scale_y), "{}".format(msg))
|
||||
# Score player #2
|
||||
painter.drawText(int((pgv.GAMEAREA_MAX_X - 840) * scale_x), int(154 * scale_y), "{}".format(player2.score))
|
||||
|
@ -180,23 +176,29 @@ class pongWindow(QMainWindow):
|
|||
draw_buttonstate(60, 50, player1, painter, scale_x, scale_y)
|
||||
draw_buttonstate(pgv.GAMEAREA_MAX_X - 180, 50, player2, painter, scale_x, scale_y)
|
||||
elif game.state == pgc.STATE_GAMERESULTS: # Display winner of this game
|
||||
painter.setFont(self.fntMedium)
|
||||
painter.setFont(fntMedium)
|
||||
if player1.score > 9:
|
||||
msg = "Game player #1"
|
||||
msg = "Game player #1 Total score {}:{}".format(game.p1_game, game.p2_game)
|
||||
else:
|
||||
msg = "Game player #2"
|
||||
painter.drawText(int(50 * scale_x), int(pgv.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
||||
msg = "Game player #2 Total score {}:{}".format(game.p1_game, game.p2_game)
|
||||
painter.drawText(int(200 * scale_x), int(pgv.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
||||
elif game.state == pgc.STATE_FINALRESULTS: # Display set winner
|
||||
painter.setFont(self.fntMedium)
|
||||
painter.setFont(fntMedium)
|
||||
if game.p1_game > 2:
|
||||
msg = "Match player #1"
|
||||
msg = "Match player #1 Total score {}:{}".format(game.p1_game, game.p2_game)
|
||||
else:
|
||||
msg = "Match player #2"
|
||||
painter.drawText(int(50 * scale_x), int(pgv.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
||||
msg = "Match player #2 Total score {}:{}".format(game.p1_game, game.p2_game)
|
||||
painter.drawText(int(200 * scale_x), int(pgv.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
||||
elif game.state == pgc.STATE_EXIT: # Indicate good bye ...
|
||||
painter.setFont(self.fntMedium)
|
||||
painter.setFont(fntMedium)
|
||||
painter.drawText(int(50 * scale_x), int(pgv.GAMEAREA_MAX_Y / 2 * scale_x), "Bye!")
|
||||
|
||||
# Force font memory free (indication GC)
|
||||
del fm
|
||||
del fntMedium
|
||||
del fntLarge
|
||||
|
||||
|
||||
@pyqtSlot()
|
||||
def on_timer(self):
|
||||
'''Qt5 timer event'''
|
||||
|
@ -205,21 +207,36 @@ class pongWindow(QMainWindow):
|
|||
self.timer.stop() # Block overrun
|
||||
|
||||
if game.pong_game(ball, player1, player2) == False:
|
||||
# Quit gracefully
|
||||
player2.exit()
|
||||
player1.exit()
|
||||
sys.exit(0)
|
||||
|
||||
self.update() # Redraw
|
||||
self.timer.start() # Re-enable
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__':
|
||||
bSizeable = False
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "--sizeable":
|
||||
bSizeable = True
|
||||
|
||||
# Figure out 2 highest event queues as they are assumed to be the gamepads
|
||||
maxEvent = 0
|
||||
for l in glob("/dev/input/event?"):
|
||||
if int(l[-1]) > maxEvent:
|
||||
maxEvent = int(l[-1])
|
||||
sEventQueue1 = "/dev/input/event" + str(maxEvent-1)
|
||||
sEventQueue2 = "/dev/input/event" + str(maxEvent)
|
||||
|
||||
try:
|
||||
# Initialize
|
||||
random.seed()
|
||||
game = PongGame()
|
||||
ball = PongObject(pgv.GAMEAREA_MAX_X/2, pgv.GAMEAREA_MAX_Y/2, 20, 20, 8.0, 2.5)
|
||||
player1 = PongPlayer(sys.argv[1], 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
|
||||
player2 = PongPlayer(sys.argv[2], pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
|
||||
|
||||
player1 = PongPlayer(sEventQueue1, 10, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
|
||||
player2 = PongPlayer(sEventQueue2, pgv.GAMEAREA_MAX_X-40, pgv.GAMEAREA_MAX_Y/2-50, 20, 160)
|
||||
except IOError as e:
|
||||
import errno
|
||||
if e.errno == errno.EACCES:
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
|
@ -5,8 +5,9 @@
|
|||
# Reading a USB gamepad & making sense of it ...
|
||||
#
|
||||
#-----------------------------------------------------------------------
|
||||
# lsusb ouput: Bus 002 Device 003: ID 0079:0011 DragonRise Inc. Gamepad
|
||||
# input device: /dev/input/event6
|
||||
# 'lsusb' ouput: Bus 002 Device 003: ID 0079:0011 DragonRise Inc. Gamepad
|
||||
# 'ls /dev/input/event*' lists available event sources, the gamepad(s)
|
||||
# are usually the last ones (highest numbers): /dev/input/event6 /dev/input/event7
|
||||
#
|
||||
# Program output (needs to be inspected w/ each new controller):
|
||||
# --------------------------------------------------------------
|
Loading…
Reference in New Issue