Local version release

pull/2/head
kaqu 2020-11-26 18:19:17 +01:00
parent 444f10de52
commit 4955a1b732
6 changed files with 117 additions and 41 deletions

6
.vscode/launch.json vendored
View File

@ -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"
}
]

58
README.md Normal file
View File

@ -0,0 +1,58 @@
![Showtime](pictures/gamepads.png)
# 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 ;).
![Showtime](pictures/screenshot.png)
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.

View File

@ -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:

BIN
pictures/gamepads.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
pictures/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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):
# --------------------------------------------------------------