#!/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