Pong game in Python with tkinter

This is a pong game I created some time ago, but it doesn't have methods to store scores yet and also might have some bugs. It is also very slow.

The code:

from tkinter import Tk
from random import choice
import os

def clear():
os.system('cls' if os.name == 'nt' else 'clear')

class Pong:
def __init__(self):
self.tk = Tk()

self.p1 = [5, 4]
self.p2 = [5, 58]

self.move = [-1, choice([1, -1])]

self.l = 5

self.ball = [7, 31]

self.terminal = [
list(' ------------------------------------------------------------- '),
list('|                                                             |'),
list('|                                                             |'),
list('|                                                             |'),
list('|                                                             |'),
list('|   |                                                     |   |'),
list('|   |                                                     |   |'),
list('|   |                          O                          |   |'),
list('|   |                                                     |   |'),
list('|   |                                                     |   |'),
list('|                                                             |'),
list('|                                                             |'),
list('|                                                             |'),
list('|                                                             |'),
list(' ------------------------------------------------------------- ')
]

def moveUp(self, p):
i, j = p
l = self.l

if i == 1:
return

self.terminal[i + l - 1][j] = ' '

for x in range(i - 1, i + l - 1):
self.terminal[x][j] = '|'

p[0] -= 1

def moveDown(self, p):
i, j = p
l = self.l

if i + l == len(self.terminal) - 1:
return

self.terminal[i][j] = ' '

for x in range(i + 1, i + l + 1):
self.terminal[x][j] = '|'

p[0] += 1

def moveComputer(self):
if self.move[0] == -1:
self.moveUp(self.p2)
if self.move[0] == 1:
self.moveDown(self.p2)

def moveBall(self):
try:
if self.terminal[self.ball[0] + self.move[0]][self.ball[1]] == '-':
self.move[0] *= -1

if self.terminal[self.ball[0]][self.ball[1] + self.move[1]] == '|':
self.move[1] *= -1
elif self.terminal[self.ball[0] + self.move[0]][self.ball[1] + self.move[1]] == '|':
self.move[0] *= -1
self.move[1] *= -1

self.terminal[self.ball[0]][self.ball[1]] = ' '

self.ball[0] += self.move[0]
self.ball[1] += self.move[1]

self.terminal[self.ball[0]][self.ball[1]] = 'O'

if ' ' in self.terminal[0][1:-1]:
i = self.terminal[0][1:-1].index(' ')

self.terminal[i] = '-'

if ' ' in self.terminal[-1][1:-1]:
i = self.terminal[-1][1:-1].index(' ')

self.terminal[i] = '-'

self.print()

if self.move[1] == 1:
self.moveComputer()
except:
if (self.terminal[self.ball[0] - 1] == '-') and (self.terminal[self.ball[0] + 1] == '|') or \
(self.terminal[self.ball[0] + 1] == '-') and (self.terminal[self.ball[0] - 1] == '|'):
self.move[0] *= -1
else:
self.move[0] *= -1
self.move[1] *= -1

self.tk.after(25, self.moveBall)

def play(self):
self.tk.bind('<Up>', lambda x: self.moveUp(self.p1))
self.tk.bind('<Down>', lambda x: self.moveDown(self.p1))

self.moveBall()

self.tk.mainloop()

def print(self):
clear()
print('\n'.join(''.join(i) for i in self.terminal))

pong = Pong()
pong.play()


How do I make it faster, neater, and shorter?

Thanks for your help!

• Why are you using tkinter? The game appears as text in the terminal. Nov 14 '19 at 15:11
• @BryanOakley To use tk.bind and tk.after
– Sriv
Nov 14 '19 at 17:35

Reserved Name

print is a reserved name in python, so having a method with the same name isn't the best idea. I would use something like print_grid or print_game, something a little more descriptive, and also doesn't conflict with print.

Naming Conventions

Method and variable names should be in snake_case, not camelCase.

Main Guard

You should use a main guard when you have outside code. Take a look:

if __name__ == '__main__':
pong = Pong()
pong.play()


This prevents the two lines from being run if you decide to import this game from another module.

Keystrokes

It seems a little unnecessary to use tkinter for a console game. Consider using something like pynput or curses to listen to keyboard strokes.

Unnecessary Code

This

if (self.terminal[self.ball[0] - 1] == '-') and (self.terminal[self.ball[0] + 1] == '|') or (self.terminal[self.ball[0] + 1] == '-') and (self.terminal[self.ball[0] - 1] == '|'):
self.move[0] *= -1
else:
self.move[0] *= -1
self.move[1] *= -1


is unnecessarily complicated. Since self.move[0] *= -1 is run anyway, you only need to check if this condition is False:

if not ((self.terminal[self.ball[0] - 1] == '-') and (self.terminal[self.ball[0] + 1] == '|') or (self.terminal[self.ball[0] + 1] == '-') and (self.terminal[self.ball[0] - 1] == '|')):
self.move[1] *= -1
self.move[0] *= -1


tkinter conventions

When using tkinter, it's recommended to pass tk into the class's constructor. Take a look:

def __init__(self, master):
self.tk = master


Then your initialization code looks like this:

if __name__ == '__main__':
root = Tk()
pong = Pong(root)
pong.play()


Type Hinting

You can use type hints to make it clear what types of parameters are passed to functions, and what types are returned by functions. Lets take a look at your moveDown (should be move_down) function header:

def moveDown(self, p):


Now consider this

from typing import List

def moveDown(self, p: List[int]) -> None:


This says that moveDown (again, move_down) should take a list of integers and returns None. This becomes more useful as you write functions with more parameters.

Docstrings

Lets expand upon your moveDown function. This can be even more descriptive by using a function docstring. This will allow you to put in words what the function is supposed to do. Take a look:

def moveDown(self, p: List[int]) -> None:
"""
Moves the bars down. What player moves depends on the list passed.

:param p -> List[int]: Player to move

:return: None
"""


Now there is more description to the method, and also allows you to describe the parameters accepted, what values are returned, and what happens in the function. You can also provide when you should use this function.