248 lines
13 KiB
Python
248 lines
13 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
|
|
pong_viewer.py
|
|
|
|
Pandemic Pong view client only
|
|
|
|
"""
|
|
|
|
import sys, time
|
|
|
|
from PyQt5.QtGui import QPixmap
|
|
from PyQt5.QtWidgets import *
|
|
from PyQt5.QtCore import Qt, QTimer, pyqtSlot
|
|
from PyQt5.QtGui import QPainter, QBrush, QPen, QFont, QFontMetrics
|
|
|
|
from playsound import playsound
|
|
|
|
import game_objects.pong_constants as pgc # Pong global constants
|
|
import game_objects.pong_globalvars as pgv # Pong global variables
|
|
from game_objects.pong_player import PongPlayer
|
|
from game_objects.pong_object import PongObject
|
|
from game_objects.pong_game import PongGame
|
|
import game_objects.pong_viewserver as vs # Pong TCP view server (threads)
|
|
|
|
def draw_buttonstate(x, y, player, painter, scale_x, scale_y):
|
|
"""View function: Indicate last button pressed (not strictly necessary ...)"""
|
|
|
|
if (player.state & pgc.BTN_TRIGGER) == pgc.BTN_TRIGGER:
|
|
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern)) # Fill
|
|
painter.drawEllipse(int(x * scale_x), int(y * scale_y), int(20 * scale_x), int(20 * scale_y)) # Blue button
|
|
if (player.state & pgc.BTN_TOP) == pgc.BTN_TOP:
|
|
painter.setPen(QPen(Qt.green, 1, Qt.SolidLine)) # Green
|
|
painter.setBrush(QBrush(Qt.green, Qt.SolidPattern))
|
|
painter.drawEllipse(int((x + 30) * scale_x), int(y * scale_y), int(20 * scale_x), int(20 * scale_y))
|
|
if (player.state & pgc.BTN_THUMB) == pgc.BTN_THUMB:
|
|
painter.setPen(QPen(Qt.red, 1, Qt.SolidLine)) # Red
|
|
painter.setBrush(QBrush(Qt.red, Qt.SolidPattern))
|
|
painter.drawEllipse(int((x + 60) * scale_x), int(y * scale_y), int(20 * scale_x), int(20 * scale_y))
|
|
if (player.state & pgc.BTN_THUMB2) == pgc.BTN_THUMB2:
|
|
painter.setPen(QPen(Qt.yellow, 1, Qt.SolidLine)) # Yellow
|
|
painter.setBrush(QBrush(Qt.yellow, Qt.SolidPattern))
|
|
painter.drawEllipse(int((x + 90) * scale_x), int(y * scale_y), int(20 * scale_x), int(20 * scale_y))
|
|
|
|
class pongWindow(QMainWindow):
|
|
"""Qt5 base class"""
|
|
|
|
def __init__(self, debug_x, debug_y, viewserver, game, ball, player1, player2):
|
|
super().__init__()
|
|
self.title = "Pandemic Pong"
|
|
if pgv.bIsLocal:
|
|
self.title = "Pandemic Pong (Local)"
|
|
if pgv.bIsViewer:
|
|
self.title = "Pandemic Pong (Viewer)"
|
|
self.top = 0
|
|
self.left = 0
|
|
self.win_width = 1920
|
|
self.win_height = 960
|
|
self.viewserver = viewserver
|
|
self.game = game
|
|
self.ball = ball
|
|
self.player1 = player1
|
|
self.player2 = player2
|
|
self.InitWindow(debug_x, debug_y) # s.b. !
|
|
|
|
def InitWindow(self, debug_x, debug_y):
|
|
"""Prepare window for later drawing"""
|
|
|
|
self.setWindowTitle(self.title)
|
|
self.setGeometry(self.top, self.left, self.win_width, self.win_height)
|
|
if pgv.bSizeable:
|
|
if pgc.DEVLOCAL == True: # Small windows for convenience ...
|
|
self.resize(int(self.win_width * 2 / 3 / 3), int(self.win_height * 2 / 2.8 / 3))
|
|
#self.move(int(self.win_width / 2.5), int(self.win_height / 16))
|
|
if pgc.DEVLOCAL == True:
|
|
self.move(debug_x, debug_y)
|
|
else:
|
|
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")
|
|
|
|
self.show()
|
|
|
|
# Don't annoy developers (except for local play - to test game mechanics !) ...
|
|
if (pgc.DEVLOCAL == False) and (pgv.bIsServer == False):
|
|
# Enforce caching ...
|
|
playsound(pgc.sounds[pgc.PLAYERCONTACTSOUND])
|
|
playsound(pgc.sounds[pgc.PLAYERMISSSOUND])
|
|
playsound(pgc.sounds[pgc.WALLCONTACTSOUND])
|
|
playsound(pgc.sounds[pgc.GAMEEXITSOUND])
|
|
time.sleep(0.5)
|
|
playsound(pgc.sounds[pgc.GAMESPLASHSOUND])
|
|
|
|
self.timer = QTimer() # Start processing 'loop'
|
|
self.timer.setInterval(25) # 25ms
|
|
self.timer.setTimerType(Qt.PreciseTimer)
|
|
self.timer.timeout.connect(self.on_timer)
|
|
self.timer.start()
|
|
|
|
def paintEvent(self, event):
|
|
"""Called whenever drawing is necessary ..."""
|
|
|
|
scale_x = self.width() / pgc.GAMEAREA_MAX_X
|
|
scale_y = self.height() / (pgc.GAMEAREA_MAX_Y + 20) # Compensate header
|
|
|
|
painter = QPainter(self) # Painter need's to be freshly picked ...
|
|
painter.setPen(QPen(Qt.white, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.white, Qt.SolidPattern)) # Fill
|
|
|
|
painter.drawPixmap(self.rect(), self.pic) # Game background
|
|
|
|
# Font metrics assume font is monospaced!
|
|
fntLarge = QFont("Monospace", int(120 * min(scale_x, scale_y)))
|
|
fntLarge.setKerning(False)
|
|
fntLarge.setFixedPitch(True)
|
|
fm = QFontMetrics(fntLarge)
|
|
fntMedium = QFont("Monospace", int(40 * min(scale_x, scale_y)))
|
|
fntMedium.setKerning(False)
|
|
fntMedium.setFixedPitch(True)
|
|
|
|
if self.game.state == pgc.STATE_WELCOME: # Welcome screen
|
|
painter.drawPixmap(self.rect(), QPixmap("pictures/pandemic_pong.png"))
|
|
painter.setFont(fntLarge)
|
|
painter.drawText(int(50 * scale_x), int((pgc.GAMEAREA_MAX_Y / 2 - 100) * scale_y), "PANDEMIC")
|
|
painter.drawText(int((pgc.GAMEAREA_MAX_X / 2 + 300) * scale_x), int((pgc.GAMEAREA_MAX_Y / 2 + 100) * scale_y), "PONG")
|
|
|
|
elif self.game.state == pgc.STATE_START: # Wait for start button pressed
|
|
painter.setFont(fntMedium)
|
|
painter.drawText(int(150 * scale_x), int((pgc.GAMEAREA_MAX_Y / 2 - 100) * scale_y), "Press [Start] ...")
|
|
painter.drawText(int(50 * scale_x), int((pgc.GAMEAREA_MAX_Y / 2 + 100) * scale_y), "Press [Select] to abort")
|
|
painter.drawText(int((pgc.GAMEAREA_MAX_X / 2 + 100) * scale_x), int(pgc.GAMEAREA_MAX_Y / 5 * scale_y), "Press [color] button")
|
|
painter.drawText(int((pgc.GAMEAREA_MAX_X / 2 + 100) * scale_x), int(pgc.GAMEAREA_MAX_Y / 5 * 2 * scale_y), "to infect player,")
|
|
painter.drawText(int((pgc.GAMEAREA_MAX_X / 2 + 100) * scale_x), int(pgc.GAMEAREA_MAX_Y / 5 * 3 * scale_y), "view will pickup virus!")
|
|
painter.drawText(int((pgc.GAMEAREA_MAX_X / 2 + 100) * scale_x), int(pgc.GAMEAREA_MAX_Y / 5 * 4 * scale_y), "Match color to deflect!")
|
|
|
|
elif self.game.state == pgc.STATE_PLAY: # Actual play 'til score reaches 10 points
|
|
# Ball gives NO indication ...
|
|
painter.drawRect(int(self.ball.x * scale_x), int(self.ball.y * scale_y), int(self.ball.w * scale_x), int(self.ball.h * scale_y)) # Ball
|
|
|
|
if self.player1.color == pgc.COL_WHITE:
|
|
painter.setPen(QPen(Qt.white, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.white, Qt.SolidPattern)) # Fill
|
|
elif self.player1.color == pgc.COL_BLUE:
|
|
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern)) # Fill
|
|
elif self.player1.color == pgc.COL_GREEN:
|
|
painter.setPen(QPen(Qt.green, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.green, Qt.SolidPattern)) # Fill
|
|
elif self.player1.color == pgc.COL_RED:
|
|
painter.setPen(QPen(Qt.red, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.red, Qt.SolidPattern)) # Fill
|
|
elif self.player1.color == pgc.COL_YELLOW:
|
|
painter.setPen(QPen(Qt.yellow, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.yellow, Qt.SolidPattern)) # Fill
|
|
painter.drawRect(int(self.player1.x * scale_x), int(self.player1.y * scale_y), int(self.player1.w * scale_x), int(self.player1.h * scale_y)) # Player left
|
|
|
|
if self.player2.color == pgc.COL_WHITE:
|
|
painter.setPen(QPen(Qt.white, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.white, Qt.SolidPattern)) # Fill
|
|
elif self.player2.color == pgc.COL_BLUE:
|
|
painter.setPen(QPen(Qt.blue, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern)) # Fill
|
|
elif self.player2.color == pgc.COL_GREEN:
|
|
painter.setPen(QPen(Qt.green, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.green, Qt.SolidPattern)) # Fill
|
|
elif self.player2.color == pgc.COL_RED:
|
|
painter.setPen(QPen(Qt.red, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.red, Qt.SolidPattern)) # Fill
|
|
elif self.player2.color == pgc.COL_YELLOW:
|
|
painter.setPen(QPen(Qt.yellow, 1, Qt.SolidLine)) # Color/Linewidth/Pattern
|
|
painter.setBrush(QBrush(Qt.yellow, Qt.SolidPattern)) # Fill
|
|
painter.drawRect(int(self.player2.x * scale_x), int(self.player2.y * scale_y), int(self.player2.w * scale_x), int(self.player2.h * scale_y)) # Player right
|
|
|
|
painter.setPen(QPen(Qt.white, 1, Qt.SolidLine)) # Reset standard colour
|
|
painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
|
|
|
|
# Game scores
|
|
painter.setFont(fntLarge)
|
|
# Score player #1
|
|
msg = str(self.player1.score)
|
|
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((pgc.GAMEAREA_MAX_X - 840) * scale_x), int(154 * scale_y), "{}".format(self.player2.score))
|
|
|
|
# Indicate Button states
|
|
draw_buttonstate(60, 50, self.player1, painter, scale_x, scale_y)
|
|
draw_buttonstate(pgc.GAMEAREA_MAX_X - 180, 50, self.player2, painter, scale_x, scale_y)
|
|
elif self.game.state == pgc.STATE_GAMERESULTS: # Display winner of this game
|
|
painter.setFont(fntMedium)
|
|
if self.player1.score > 9:
|
|
msg = "Game player #1 Total score {}:{}".format(self.game.p1_game, self.game.p2_game)
|
|
else:
|
|
msg = "Game player #2 Total score {}:{}".format(self.game.p1_game, self.game.p2_game)
|
|
painter.drawText(int(200 * scale_x), int(pgc.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
|
elif self.game.state == pgc.STATE_FINALRESULTS: # Display set winner
|
|
painter.setFont(fntMedium)
|
|
if self.game.p1_game > 2:
|
|
msg = "Match player #1 Total score {}:{}".format(self.game.p1_game, self.game.p2_game)
|
|
else:
|
|
msg = "Match player #2 Total score {}:{}".format(self.game.p1_game, self.game.p2_game)
|
|
painter.drawText(int(200 * scale_x), int(pgc.GAMEAREA_MAX_Y / 2 * scale_y), msg)
|
|
elif self.game.state == pgc.STATE_EXIT: # Indicate good bye ...
|
|
painter.setFont(fntMedium)
|
|
painter.drawText(int(50 * scale_x), int(pgc.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 runs the game ..."""
|
|
|
|
self.timer.stop() # Block overrun
|
|
|
|
if (pgv.bIsServer == False) and (pgv.bIsLocal == False): # Player 1 or 2?
|
|
rc = self.viewserver.receive_data() # Retrieve server stati
|
|
if rc < 0: # Server down, abort game
|
|
self.player2.exit()
|
|
self.player1.exit()
|
|
sys.exit(0)
|
|
#if rc > 0: # May be useful later ...
|
|
# bChanged = True
|
|
|
|
if self.game.pong_game(self.ball, self.player1, self.player2) == False:
|
|
# Quit gracefully
|
|
self.player2.exit()
|
|
self.player1.exit()
|
|
sys.exit(0)
|
|
|
|
self.update() # Enforce redraw (explicitely)
|
|
self.timer.start() # Re-enable timer event
|
|
|
|
def run_Qt5_viewer(debug_x, debug_y, viewserver, game, ball, player1, player2):
|
|
"""Qt5 viewer"""
|
|
|
|
app = QApplication(sys.argv) # Eval command line args
|
|
window = pongWindow(debug_x, debug_y, viewserver, game, ball, player1, player2) # Create Qt5 GUI
|
|
|
|
return app.exec_() # & run logic
|