A new reworked version of pong!
This commit is contained in:
parent
09cf44ccd1
commit
7cf15a8e6b
|
@ -1,183 +1,309 @@
|
||||||
#!/usr/bin/env python
|
# !/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Pong
|
||||||
|
|
||||||
|
One of the most important video games.
|
||||||
|
Pong was created by Al Alcorn and it did not use a microprocessor
|
||||||
|
|
||||||
|
This demo is based on some initial code by Siddharth Natamai
|
||||||
|
|
||||||
|
In 2021, it was reworked by Jay Nabaonne into this version you see today.
|
||||||
|
|
||||||
|
A big
|
||||||
|
|
||||||
|
###### ## ## ###### ## ## ## ## ## ## ###### ## ## ##
|
||||||
|
## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ##
|
||||||
|
## ###### ## ## ###### #### ## ## ## ## ## ## ##
|
||||||
|
## ## ## ###### ## ### #### ###### ## ## ## ## ##
|
||||||
|
## ## ## ## ## ## ## ## ## ## ## ## ## ##
|
||||||
|
## ## ## ## ## ## ## ## ## #### ###### ###### ##
|
||||||
|
|
||||||
|
to Jay for making it a smooth playing game.
|
||||||
|
@jaynabonne https://github.com/jaynabonne
|
||||||
|
|
||||||
|
Copyright 2021 PySimpleGUI, Jay Nabonne
|
||||||
|
"""
|
||||||
|
|
||||||
import PySimpleGUI as sg
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import time
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
"""
|
GAMEPLAY_SIZE = (700, 400)
|
||||||
Pong code supplied by Daniel Young (Neonzz)
|
BAT_SIZE = (20, 110)
|
||||||
Modified. Original code: https://www.pygame.org/project/3649/5739
|
STARTING_BALL_POSITION = (327, 200)
|
||||||
"""
|
BALL_RADIUS = 12
|
||||||
|
BACKGROUND_COLOR = 'black'
|
||||||
|
BALL_COLOR = 'green1'
|
||||||
|
BALL_SPEED = 300
|
||||||
|
BAT_SPEED = 400
|
||||||
|
|
||||||
|
UP_ARROW = 38
|
||||||
|
DOWN_ARROW = 40
|
||||||
|
|
||||||
|
player1_up_keycode = ord('W')
|
||||||
|
player1_down_keycode = ord('S')
|
||||||
|
player2_up_keycode = UP_ARROW
|
||||||
|
player2_down_keycode = DOWN_ARROW
|
||||||
|
|
||||||
|
num_rounds = 10
|
||||||
|
|
||||||
|
|
||||||
|
class Bat:
|
||||||
|
def __init__(self, graph: sg.Graph, colour, x, field_height):
|
||||||
|
self.graph = graph
|
||||||
|
self.field_height = field_height
|
||||||
|
self.width = BAT_SIZE[0]
|
||||||
|
self.height = BAT_SIZE[1]
|
||||||
|
self.current_x = x
|
||||||
|
self.current_y = self.field_height / 2 - self.height / 2
|
||||||
|
self.id = graph.draw_rectangle(
|
||||||
|
(self.current_x, self.current_y),
|
||||||
|
(self.current_x + self.width, self.current_y + self.height),
|
||||||
|
fill_color=colour
|
||||||
|
)
|
||||||
|
self.vy = 0
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.vy = 0
|
||||||
|
|
||||||
|
def up(self):
|
||||||
|
self.vy = -BAT_SPEED
|
||||||
|
|
||||||
|
def down(self):
|
||||||
|
self.vy = BAT_SPEED
|
||||||
|
|
||||||
|
def is_hit_by(self, pos):
|
||||||
|
bat_p0 = (self.current_x, self.current_y)
|
||||||
|
bat_p1 = (bat_p0[0] + self.width, bat_p0[1] + self.height)
|
||||||
|
return bat_p0[0] <= pos[0] <= bat_p1[0] and bat_p0[1] <= pos[1] <= bat_p1[1]
|
||||||
|
|
||||||
|
def update(self, delta: float):
|
||||||
|
new_y = self.current_y + self.vy * delta
|
||||||
|
if new_y <= 0:
|
||||||
|
new_y = 0
|
||||||
|
self.stop()
|
||||||
|
if new_y + self.height >= self.field_height:
|
||||||
|
new_y = self.field_height - self.height
|
||||||
|
self.stop()
|
||||||
|
self.current_y = new_y
|
||||||
|
|
||||||
|
self.graph.relocate_figure(self.id, self.current_x, self.current_y)
|
||||||
|
|
||||||
|
|
||||||
class Ball:
|
class Ball:
|
||||||
def __init__(self, canvas, bat, bat2, color):
|
def __init__(self, graph: sg.Graph, bat_1: Bat, bat_2: Bat, colour):
|
||||||
self.canvas = canvas
|
self.graph = graph # type: sg.Graph
|
||||||
self.bat = bat
|
self.bat_1 = bat_1
|
||||||
self.bat2 = bat2
|
self.bat_2 = bat_2
|
||||||
self.playerScore = 0
|
self.id = self.graph.draw_circle(
|
||||||
self.player1Score = 0
|
STARTING_BALL_POSITION, BALL_RADIUS, line_color=colour, fill_color=colour)
|
||||||
self.drawP1 = None
|
self.current_x, self.current_y = STARTING_BALL_POSITION
|
||||||
self.drawP = None
|
self.vx = random.choice([-BALL_SPEED, BALL_SPEED])
|
||||||
self.id = self.canvas.create_oval(10, 10, 35, 35, fill=color)
|
self.vy = -BALL_SPEED
|
||||||
self.canvas.move(self.id, 327, 220)
|
|
||||||
self.canvas_height = self.canvas.winfo_height()
|
|
||||||
self.canvas_width = self.canvas.winfo_width()
|
|
||||||
self.x = random.choice([-2.5, 2.5])
|
|
||||||
self.y = -2.5
|
|
||||||
|
|
||||||
def checkwin(self):
|
def hit_left_bat(self):
|
||||||
winner = None
|
return self.bat_1.is_hit_by((self.current_x - BALL_RADIUS, self.current_y))
|
||||||
if self.playerScore >= 10:
|
|
||||||
winner = 'Player left wins'
|
|
||||||
if self.player1Score >= 10:
|
|
||||||
winner = 'Player Right'
|
|
||||||
return winner
|
|
||||||
|
|
||||||
def updatep(self, val):
|
def hit_right_bat(self):
|
||||||
self.canvas.delete(self.drawP)
|
return self.bat_2.is_hit_by((self.current_x + BALL_RADIUS, self.current_y))
|
||||||
self.drawP = self.canvas.create_text(170, 50, font=(
|
|
||||||
'freesansbold.ttf', 40), text=str(val), fill='white')
|
|
||||||
|
|
||||||
def updatep1(self, val):
|
def update(self, delta: float):
|
||||||
self.canvas.delete(self.drawP1)
|
self.current_x += self.vx * delta
|
||||||
self.drawP1 = self.canvas.create_text(550, 50, font=(
|
self.current_y += self.vy * delta
|
||||||
'freesansbold.ttf', 40), text=str(val), fill='white')
|
if self.current_y <= BALL_RADIUS: # see if hit top or bottom of play area. If so, reverse y direction
|
||||||
|
self.vy = -self.vy
|
||||||
|
self.current_y = BALL_RADIUS
|
||||||
|
if self.current_y >= GAMEPLAY_SIZE[1] - BALL_RADIUS:
|
||||||
|
self.vy = -self.vy
|
||||||
|
self.current_y = GAMEPLAY_SIZE[1] - BALL_RADIUS
|
||||||
|
if self.hit_left_bat():
|
||||||
|
self.vx = abs(self.vx)
|
||||||
|
if self.hit_right_bat():
|
||||||
|
self.vx = -abs(self.vx)
|
||||||
|
|
||||||
def hit_bat(self, pos):
|
self.position_to_current()
|
||||||
bat_pos = self.canvas.coords(self.bat.id)
|
|
||||||
if pos[2] >= bat_pos[0] and pos[0] <= bat_pos[2]:
|
|
||||||
if pos[3] >= bat_pos[1] and pos[3] <= bat_pos[3]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def hit_bat2(self, pos):
|
def position_to_current(self):
|
||||||
bat_pos = self.canvas.coords(self.bat2.id)
|
self.graph.relocate_figure(self.id, self.current_x - BALL_RADIUS, self.current_y - BALL_RADIUS)
|
||||||
if pos[2] >= bat_pos[0] and pos[0] <= bat_pos[2]:
|
|
||||||
if pos[3] >= bat_pos[1] and pos[3] <= bat_pos[3]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def draw(self):
|
def restart(self):
|
||||||
self.canvas.move(self.id, self.x, self.y)
|
self.current_x, self.current_y = STARTING_BALL_POSITION
|
||||||
pos = self.canvas.coords(self.id)
|
self.position_to_current()
|
||||||
if pos[1] <= 0:
|
|
||||||
self.y = 4
|
|
||||||
if pos[3] >= self.canvas_height:
|
|
||||||
self.y = -4
|
|
||||||
if pos[0] <= 0:
|
|
||||||
self.player1Score += 1
|
|
||||||
self.canvas.move(self.id, 327, 220)
|
|
||||||
self.x = 4
|
|
||||||
self.updatep1(self.player1Score)
|
|
||||||
if pos[2] >= self.canvas_width:
|
|
||||||
self.playerScore += 1
|
|
||||||
self.canvas.move(self.id, -327, -220)
|
|
||||||
self.x = -4
|
|
||||||
self.updatep(self.playerScore)
|
|
||||||
if self.hit_bat(pos):
|
|
||||||
self.x = 4
|
|
||||||
if self.hit_bat2(pos):
|
|
||||||
self.x = -4
|
|
||||||
|
|
||||||
|
|
||||||
class pongbat():
|
class Scores:
|
||||||
def __init__(self, canvas, color):
|
def __init__(self, graph: sg.Graph):
|
||||||
self.canvas = canvas
|
self.player_1_score = 0
|
||||||
self.id = self.canvas.create_rectangle(40, 200, 25, 310, fill=color)
|
self.player_2_score = 0
|
||||||
self.canvas_height = self.canvas.winfo_height()
|
self.score_1_element = None
|
||||||
self.canvas_width = self.canvas.winfo_width()
|
self.score_2_element = None
|
||||||
self.y = 0
|
self.graph = graph
|
||||||
|
|
||||||
def up(self, evt):
|
self.draw_player1_score()
|
||||||
self.y = -5
|
self.draw_player2_score()
|
||||||
|
|
||||||
def down(self, evt):
|
def draw_player1_score(self):
|
||||||
self.y = 5
|
if self.score_1_element:
|
||||||
|
self.graph.delete_figure(self.score_1_element)
|
||||||
|
self.score_1_element = self.graph.draw_text(
|
||||||
|
str(self.player_1_score), (170, 50), font='Courier 40', color='white')
|
||||||
|
|
||||||
def draw(self):
|
def draw_player2_score(self):
|
||||||
self.canvas.move(self.id, 0, self.y)
|
if self.score_2_element:
|
||||||
pos = self.canvas.coords(self.id)
|
self.graph.delete_figure(self.score_2_element)
|
||||||
if pos[1] <= 0:
|
self.score_2_element = self.graph.draw_text(
|
||||||
self.y = 0
|
str(self.player_2_score), (550, 50), font='Courier 40', color='white')
|
||||||
if pos[3] >= 400:
|
|
||||||
self.y = 0
|
def win_loss_check(self):
|
||||||
|
if self.player_1_score >= num_rounds:
|
||||||
|
return 'Left player'
|
||||||
|
if self.player_2_score >= num_rounds:
|
||||||
|
return 'Right player'
|
||||||
|
return None
|
||||||
|
|
||||||
|
def increment_player_1(self):
|
||||||
|
self.player_1_score += 1
|
||||||
|
self.draw_player1_score()
|
||||||
|
|
||||||
|
def increment_player_2(self):
|
||||||
|
self.player_2_score += 1
|
||||||
|
self.draw_player2_score()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.player_1_score = 0
|
||||||
|
self.player_2_score = 0
|
||||||
|
self.draw_player1_score()
|
||||||
|
self.draw_player2_score()
|
||||||
|
|
||||||
|
|
||||||
class pongbat2():
|
def check_ball_exit(ball: Ball, scores: Scores):
|
||||||
def __init__(self, canvas, color):
|
if ball.current_x <= 0:
|
||||||
self.canvas = canvas
|
scores.increment_player_2()
|
||||||
self.id = self.canvas.create_rectangle(680, 200, 660, 310, fill=color)
|
ball.restart()
|
||||||
self.canvas_height = self.canvas.winfo_height()
|
if ball.current_x >= GAMEPLAY_SIZE[0]:
|
||||||
self.canvas_width = self.canvas.winfo_width()
|
scores.increment_player_1()
|
||||||
self.y = 0
|
ball.restart()
|
||||||
|
|
||||||
def up(self, evt):
|
|
||||||
self.y = -5
|
|
||||||
|
|
||||||
def down(self, evt):
|
def goto_menu(window):
|
||||||
self.y = 5
|
window['-MAIN_MENU-'].update(visible=True)
|
||||||
|
window['-GAME-'].update(visible=False)
|
||||||
def draw(self):
|
|
||||||
self.canvas.move(self.id, 0, self.y)
|
|
||||||
pos = self.canvas.coords(self.id)
|
|
||||||
if pos[1] <= 0:
|
|
||||||
self.y = 0
|
|
||||||
if pos[3] >= 400:
|
|
||||||
self.y = 0
|
|
||||||
|
|
||||||
|
|
||||||
def pong():
|
def pong():
|
||||||
# ------------- Define GUI layout -------------
|
sleep_time = 10
|
||||||
layout = [[sg.Canvas(size=(700, 400),
|
|
||||||
background_color='black',
|
|
||||||
key='canvas')],
|
|
||||||
[sg.Text(''), sg.Button('Quit')]]
|
|
||||||
# ------------- Create window -------------
|
|
||||||
window = sg.Window('The Classic Game of Pong', layout,
|
|
||||||
return_keyboard_events=True, finalize=True)
|
|
||||||
|
|
||||||
canvas = window['canvas'].TKCanvas
|
inner_layout = [[sg.Graph(GAMEPLAY_SIZE,
|
||||||
|
(0, GAMEPLAY_SIZE[1]),
|
||||||
|
(GAMEPLAY_SIZE[0], 0),
|
||||||
|
background_color=BACKGROUND_COLOR,
|
||||||
|
key='-GRAPH-')],
|
||||||
|
[sg.Button('Back to Menu', key="-MENU-")]]
|
||||||
|
|
||||||
# ------------- Create line down center, the bats and ball -------------
|
main_menu_layout = [[sg.Text("Pong", font="Courier 40", justification="center", size=(None, 1))],
|
||||||
canvas.create_line(350, 0, 350, 400, fill='white')
|
[sg.Text("-- Instructions --", font="Courier 16")],
|
||||||
bat1 = pongbat(canvas, 'white')
|
[sg.Text("Left player controls: W and S", font="Courier 12")],
|
||||||
bat2 = pongbat2(canvas, 'white')
|
[sg.Text("Right player controls: \u2191 and \u2193", font="Courier 12")],
|
||||||
ball1 = Ball(canvas, bat1, bat2, 'green')
|
[sg.Text("Escape to pause game", font="Courier 12")],
|
||||||
|
[sg.Text("", font="Courier 8")],
|
||||||
|
[sg.Text("Winner is first to 10 points", font="Courier 12")],
|
||||||
|
[sg.Text("", font="Courier 8")],
|
||||||
|
[sg.Button("Start", key='-START-', font="Courier 24"),
|
||||||
|
sg.Button("Quit", key='-QUIT-', font="Courier 24")]]
|
||||||
|
|
||||||
|
layout = [[sg.pin(sg.Column(main_menu_layout, key='-MAIN_MENU-', size=GAMEPLAY_SIZE)),
|
||||||
|
sg.pin(sg.Column(inner_layout, key='-GAME-', visible=False))]]
|
||||||
|
|
||||||
|
window = sg.Window('Pong', layout, finalize=True, use_default_focus=False)
|
||||||
|
|
||||||
|
window.bind("<Key>", "+KEY+")
|
||||||
|
window.bind("<KeyRelease>", "-KEY-")
|
||||||
|
|
||||||
|
graph_elem = window['-GRAPH-'] # type: sg.Graph
|
||||||
|
|
||||||
|
scores = Scores(graph_elem)
|
||||||
|
bat_1 = Bat(graph_elem, 'red', 30, GAMEPLAY_SIZE[1])
|
||||||
|
bat_2 = Bat(graph_elem, 'blue', GAMEPLAY_SIZE[0] - 30 - BAT_SIZE[0], GAMEPLAY_SIZE[1])
|
||||||
|
ball_1 = Ball(graph_elem, bat_1, bat_2, 'green1')
|
||||||
|
|
||||||
|
start = datetime.datetime.now()
|
||||||
|
last_post_read_time = start
|
||||||
|
|
||||||
|
game_playing = False
|
||||||
|
|
||||||
# ------------- Event Loop -------------
|
|
||||||
while True:
|
while True:
|
||||||
# ------------- Draw ball and bats -------------
|
pre_read_time = datetime.datetime.now()
|
||||||
ball1.draw()
|
processing_time = (pre_read_time - last_post_read_time).total_seconds()
|
||||||
bat1.draw()
|
time_to_sleep = sleep_time - int(processing_time*1000)
|
||||||
bat2.draw()
|
time_to_sleep = max(time_to_sleep, 0)
|
||||||
|
|
||||||
# ------------- Read the form, get keypresses -------------
|
event, values = window.read(time_to_sleep)
|
||||||
event, values = window.read(timeout=0)
|
now = datetime.datetime.now()
|
||||||
# ------------- If quit -------------
|
delta = (now-last_post_read_time).total_seconds()
|
||||||
if event == sg.WIN_CLOSED or event == 'Quit':
|
# read_delta = (now-pre_read_time).total_seconds()
|
||||||
|
last_post_read_time = now
|
||||||
|
# print("**", event, delta, time_to_sleep, processing_time, read_delta)
|
||||||
|
if event in (sg.WIN_CLOSED, "-QUIT-"):
|
||||||
break
|
break
|
||||||
# ------------- Keypresses -------------
|
elif event == "-START-":
|
||||||
if event is not None:
|
scores.reset()
|
||||||
if event.startswith('Up'):
|
ball_1.restart()
|
||||||
bat2.up(2)
|
window['-MAIN_MENU-'].update(visible=False)
|
||||||
elif event.startswith('Down'):
|
window['-GAME-'].update(visible=True)
|
||||||
bat2.down(2)
|
sg.popup('\nPress a key to begin.\n',
|
||||||
elif event == 'w':
|
no_titlebar=True,
|
||||||
bat1.up(1)
|
font="Courier 12",
|
||||||
elif event == 's':
|
text_color=sg.BLUES[0],
|
||||||
bat1.down(1)
|
background_color=sg.YELLOWS[1],
|
||||||
|
any_key_closes=True,
|
||||||
|
button_type=sg.POPUP_BUTTONS_NO_BUTTONS)
|
||||||
|
last_post_read_time = datetime.datetime.now()
|
||||||
|
game_playing = True
|
||||||
|
elif event == "-MENU-":
|
||||||
|
game_playing = False
|
||||||
|
goto_menu(window)
|
||||||
|
elif game_playing:
|
||||||
|
if event == "+KEY+":
|
||||||
|
if window.user_bind_event.keycode == player1_up_keycode:
|
||||||
|
bat_1.up()
|
||||||
|
elif window.user_bind_event.keycode == player1_down_keycode:
|
||||||
|
bat_1.down()
|
||||||
|
elif window.user_bind_event.keycode == player2_up_keycode:
|
||||||
|
bat_2.up()
|
||||||
|
elif window.user_bind_event.keycode == player2_down_keycode:
|
||||||
|
bat_2.down()
|
||||||
|
elif event == "-KEY-":
|
||||||
|
if window.user_bind_event.keycode in [player1_up_keycode, player1_down_keycode]:
|
||||||
|
bat_1.stop()
|
||||||
|
elif window.user_bind_event.keycode in [player2_up_keycode, player2_down_keycode]:
|
||||||
|
bat_2.stop()
|
||||||
|
elif window.user_bind_event.keycode == 27:
|
||||||
|
sg.popup('\nPaused. Press a key to resume.\n',
|
||||||
|
no_titlebar=True,
|
||||||
|
font="Courier 12",
|
||||||
|
text_color=sg.BLUES[0],
|
||||||
|
background_color=sg.YELLOWS[1],
|
||||||
|
any_key_closes=True,
|
||||||
|
button_type=sg.POPUP_BUTTONS_NO_BUTTONS)
|
||||||
|
last_post_read_time = datetime.datetime.now()
|
||||||
|
|
||||||
if ball1.checkwin():
|
if game_playing:
|
||||||
sg.popup('Game Over', ball1.checkwin() + ' won!!')
|
ball_1.update(delta)
|
||||||
break
|
bat_1.update(delta)
|
||||||
|
bat_2.update(delta)
|
||||||
|
|
||||||
# ------------- Bottom of loop, delay between animations -------------
|
check_ball_exit(ball_1, scores)
|
||||||
# time.sleep(.01)
|
|
||||||
canvas.after(10)
|
winner = scores.win_loss_check()
|
||||||
|
if winner is not None:
|
||||||
|
sg.popup('Game Over', winner + ' won!!', no_titlebar=True)
|
||||||
|
game_playing = False
|
||||||
|
goto_menu(window)
|
||||||
|
|
||||||
window.close()
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
pong()
|
pong()
|
||||||
|
|
Loading…
Reference in New Issue