Python Bounce Ball Game in functional programming - python

This is a common arcade game, the goal is to hit all targets as many times as you can to earn the highest score. Every time the ball hits a "stone" you earn 1 point, and if it hits 2 you earn 2 points, respectively. If the player fails to catch the ball on the bar or "pole" then the game is over.
My assignment is to turn this game written in object-oriented programming into functional programming.
import time
class Ball:
def __init__(self, cvs, pole, stones, scre):
self.stones = stones
self.cvs = cvs
self.pole = pole
self.scre = scre
self.bottom_hit = False
self.hit = 0
self.id = canvas.create_oval(10, 10, 25, 25, fill="cadetblue", width=1)
self.cvs.move(self.id, 230, 461)
self.a = 3
self.b = -3
self.cvs.move(self.id, self.a, self.b)
self.cvs_height = canvas.winfo_height()
self.cvs_width = canvas.winfo_width()
def stone_strike(self, push):
for stone_line in self.stones:
for stone in stone_line:
stone_push = self.cvs.coords(stone.id)
try:
if push[2] >= stone_push[0] and push[0] <= stone_push[2]:
if push[3] >= stone_push[1] and push[1] <= stone_push[3]:
canvas.bell()
self.hit += 1
self.scre.configure(text="Score: " + str(self.hit))
self.cvs.delete(stone.id)
return True
except:
continue
return False
def pole_strike(self, push):
pole_push = self.cvs.coords(self.pole.id)
if push[2] >= pole_push[0] and push[0] <= pole_push[2]:
if push[3] >= pole_push[1] and push[1] <= pole_push[3]:
return True
return False
def draw(self):
self.cvs.move(self.id, self.a, self.b)
push = self.cvs.coords(self.id)
if self.stone_strike(push):
self.b = 3
if push[1] <= 0:
self.b = 3
if push[3] >= self.cvs_height:
self.bottom_hit = True
if push[0] <= 0:
self.a = 3
if push[2] >= self.cvs_width:
self.a = -3
if self.pole_strike(push):
self.b = -3
class Pole:
def __init__(self, cvs):
self.cvs = cvs
self.id = canvas.create_rectangle(0, 0, 100, 10, fill="darkgoldenrod")
self.cvs.move(self.id, 200, 485)
self.a = 0
self.cvs_width = canvas.winfo_width()
self.cvs.bind_all("<Left>", self.turn_left)
self.cvs.bind_all("<Right>", self.turn_right)
def draw(self):
push = self.cvs.coords(self.id)
if push[0] + self.a <= 0:
self.a = 0
if push[2] + self.a >= self.cvs_width:
self.a = 0
self.cvs.move(self.id, self.a, 0)
def turn_left(self, event):
self.a = -5
def turn_right(self, event):
self.a = 5
class Stone:
def __init__(self):
self.id = canvas.create_rectangle(5, 5, 25, 25, fill="firebrick")
def start_game(event):
score.configure(text="Score: 00")
canvas.delete("all")
pole = Pole(canvas)
stones = []
for i in range(0, 5):
b = []
for j in range(0, 19):
tmp = Stone()
b.append(tmp)
stones.append(b)
for i in range(0, 5):
for j in range(0, 19):
canvas.move(stones[i][j].id, 25 * j, 25 * i)
ball = Ball(canvas, pole, stones, score)
root.update()
time.sleep(1)
while 1:
if not ball.bottom_hit:
ball.draw()
pole.draw()
root.update()
time.sleep(0.01)
if ball.hit == 95:
canvas.create_text(250, 250, text="YOU WON!!", fill="darkolivegreen", font="Calibri 24")
break
else:
canvas.create_text(250, 250, text="GAME OVER!!", fill="darkolivegreen", font="Calibri 24")
break
root = Tk()
root.title("Bounce Ball Game")
root.geometry("500x570")
root.resizable(0, 0)
canvas = Canvas(root, width=500, height=500, bd=0, bg="black")
canvas.pack(padx=10, pady=10)
canvas.create_text(250, 250, text="Press Enter to start Game!!", fill="darkolivegreen", font="Calibri 18")
score = Label(height=50, width=80, text="Score: 00", font="Calibri 14 italic")
score.pack(side="left")
root.bind_all("<Return>", start_game)
root.mainloop()
Here is the code I have written.. There must be logical errors since it's not working. Any ideas??
import time
def startgame(event):
score.configure(text="score:00")
canvas.delete("all")
canvas_width = canvas.winfo_width()
canvas_height = canvas.winfo_height()
pole = canvas.create_rectangle(0, 0, 100, 10, fill="darkgoldenrod")
canvas.move(pole, 200, 425)
pa = 0
def turn_left(event):
pa = -5
def turn_right(event):
pa = 5
def stone_strike(push):
for stone_line in stones:
for stone in stone_line:
stone_push = canvas.coords(stone)
try:
if push[2] >= stone_push[0] and push[0] <= stone_push[2]:
if push[3] >= stone_push[1] and push[1] <= stone_push[3]:
canvas.bell()
hit += 1
score.configure(text="score:" + str(hit))
stonaki.delete()
return True
except:
continue
return False
def pole_strike(push_b):
pole_push = canvas.coords(pole)
if push_b[2] >= pole_push[0] and push_b[0] <= pole_push[2]:
if push_b[3] >= pole_push[1] and push_b[1] <= pole_push[3]:
return True
return False
canvas.bind_all("<Left>", turn_left)
canvas.bind_all("<Right>", turn_right)
push_p = canvas.coords(pole)
if push_p[0] + pa <= 0:
pa = 0
if push_p[2] + pa >= canvas_width:
pa = 0
stones = []
for i in range(0, 5):
lista = []
for j in range(0, 19):
stonaki = canvas.create_rectangle(5, 5, 25, 25, fill="firebrick")
lista.append(stonaki)
stones.append(lista)
for i in range(0, 5):
for j in range(0, 19):
canvas.move(stones[i][j], 25 * j, 25 * i)
root.update()
bottom_hit = False
hit = 0
ball = canvas.create_oval(10, 10, 25, 25, fill="cadetblue", width=1)
canvas.move(ball, 230, 461)
ba = 3
bb = -3
canvas.move(ball, ba, bb)
push_b = canvas.coords(ball)
if stone_strike(push_b):
bb = 3
if push_b[1] <= 0:
bb = 3
if push_b[3] >= canvas_height:
bottom_hit = True
if push_b[0] <= 0:
ba = 3
if push_b[2] >= canvas_width:
ba = -3
if pole_strike(push_b):
bb = -3
time.sleep(1)
while 1:
if not bottom_hit:
root.update()
time.sleep(0.01)
if hit == 95:
canvas.create_text(250, 250, text="YOU WON!!", fill="darkolivegreen", font="Calibri 24")
break
else:
canvas.create_text(250, 250, text="GAME OVER!!", fill="darkolivegreen", font="Calibri 24")
break
root = Tk()
root.title("Bounce Ball Game")
root.geometry("500x570")
root.resizable(0, 0)
canvas = Canvas(root, width=500, height=500, bd=0, bg="black")
canvas.pack(padx=10, pady=10)
canvas.create_text(250, 250, text="Press Enter to start Game!!", fill="darkolivegreen", font="Calibri 18")
score = Label(height=50, width=80, text="Score: 00", font="Calibri 14 italic")
score.pack(side="left")
root.bind_all("<Return>", startgame)
root.mainloop()

In the process of turning this game written in object-oriented programming into functional programming, we need to take care of variables/widgets which are being used by every function. If we are making changes to some variable in a local function and want to retain the changes made after the function call ends, so I will be making those variables/widgets global.
Also, in the OOP code, changes are being made every time in the canvas using functions draw() for both "ball" and "pole". Hence making two functions for the same drawB() and drawP():
from tkinter import *
import time
def startgame(event):
score.configure(text="score:00")
canvas.delete("all")
canvas_width = canvas.winfo_width()
canvas_height = canvas.winfo_height()
global pole,pa, ba,bb,bottom_hit, hit,ball
pole = canvas.create_rectangle(0, 0, 100, 10, fill="darkgoldenrod")
canvas.move(pole, 200, 425)
pa = 0
def turn_left(event):
global pa
pa = -5
def turn_right(event):
global pa
pa = 5
def stone_strike(push):
global stones, hit
for stone_line in stones:
for stone in stone_line:
stone_push = canvas.coords(stone)
try:
if push[2] >= stone_push[0] and push[0] <= stone_push[2]:
if push[3] >= stone_push[1] and push[1] <= stone_push[3]:
canvas.bell()
hit += 1
score.configure(text="score:" + str(hit))
canvas.delete(stone) #what to delete
return True
except:
continue
return False
def pole_strike(push_b):
global pole
pole_push = canvas.coords(pole)
if push_b[2] >= pole_push[0] and push_b[0] <= pole_push[2]:
if push_b[3] >= pole_push[1] and push_b[1] <= pole_push[3]:
return True
return False
canvas.bind_all("<Left>", turn_left)
canvas.bind_all("<Right>", turn_right)
def drawP():
global pa,pole
push_p = canvas.coords(pole)
if push_p[0] + pa <= 0:
pa = 0
if push_p[2] + pa >= canvas_width:
pa = 0
canvas.move(pole, pa, 0)
global stones
stones = []
for i in range(0, 5):
lista = []
for j in range(0, 19):
stonaki = canvas.create_rectangle(5, 5, 25, 25, fill="firebrick")
lista.append(stonaki)
stones.append(lista)
for i in range(0, 5):
for j in range(0, 19):
canvas.move(stones[i][j], 25 * j, 25 * i)
bottom_hit = False
hit = 0
ball = canvas.create_oval(10, 10, 25, 25, fill="cadetblue", width=1)
canvas.move(ball, 230, 461)
ba = 3
bb = -3
root.update()
def drawB():
global ba,bb,bottom_hit, hit,ball,pole
canvas.move(ball, ba, bb)
push_b = canvas.coords(ball)
if stone_strike(push_b):
bb = 3
if push_b[1] <= 0:
bb = 3
if push_b[3] >= canvas_height:
bottom_hit = True
if push_b[0] <= 0:
ba = 3
if push_b[2] >= canvas_width:
ba = -3
if pole_strike(push_b):
bb = -3
time.sleep(1)
while 1:
if not bottom_hit:
drawB()
drawP()
root.update()
time.sleep(0.01)
if hit == 95:
canvas.create_text(250, 250, text="YOU WON!!", fill="darkolivegreen", font="Calibri 24")
break
else:
canvas.create_text(250, 250, text="GAME OVER!!", fill="darkolivegreen", font="Calibri 24")
break
root = Tk()
root.title("Bounce Ball Game")
root.geometry("500x570")
root.resizable(0, 0)
canvas = Canvas(root, width=500, height=500, bd=0, bg="black")
canvas.pack(padx=10, pady=10)
canvas.create_text(250, 250, text="Press Enter to start Game!!", fill="darkolivegreen", font="Calibri 18")
score = Label(height=50, width=80, text="Score: 00", font="Calibri 14 italic")
score.pack(side="left")
root.bind_all("<Return>", startgame)
root.mainloop()
You may further optimize this code.

Related

I made a minesweeper game with tkinter

I'm a total beginner who just started learning to code with the book "Head First Learn to Code". I've just finished the book and coded a minesweeper game with python3 on my mac. I hope to get some advice on the game I made. If you feel like the code is too long to read, here are some specific questions I'd like to ask:
What does this: flag = flag.resize((12, 12), Image.ANTIALIAS) actually do, why do I need to assign it a variable instead of just doing this: flag.resize((12, 12), Image.ANTIALIAS) as I'd do with other objects
Is there a better way to create a stopwatch? Mine is the function update_time.
Did I make any mistake regarding the conventions of python? Please point out some for me.
Any help would be much appreciated!
Here's my code:
from tkinter import *
import random as rd
from tkinter import messagebox
from PIL import ImageTk, Image
import sys
class GameGrid(Frame): #the game
def __init__(self, master, height, width, mines_count, player):
Frame.__init__(self, master)
self.grid(row=0)
self.master = master
if sys.platform == 'win32': #checking os
self.platform = 'windows'
else:
self.platform = 'macos'
self.height = height #storing height, width, mines_count, and player's name
self.width = width
self.mines_count = mines_count
self.player_name = player
self.play_time = 0 #initiating play_time and other values
self.lost = False
self.won = False
self.notmine = height * width - mines_count #calculate the number of tiles that are not mines
flag = Image.open('flag.png') #creating and storing flag and bomb images
flag = flag.resize((12, 12), Image.ANTIALIAS)
bomb = Image.open('bomb.png')
bomb = bomb.resize((12, 12), Image.ANTIALIAS)
self.flag = ImageTk.PhotoImage(flag)
self.bomb = ImageTk.PhotoImage(bomb)
grid_model = [[0]*width for item in [0]*height] #creating a list to hold 1's and 0's
while mines_count > 0: #1 is mine, 0 is normal
randi = rd.randint(0, height-1) #putting mines into the list by generating random coordinates
randj = rd.randint(0, width-1) #and storing mine in the corresponding place
if grid_model[randi][randj] == 0:
grid_model[randi][randj] = 1
mines_count -= 1
self.tiles = {} #creating Tiles and storing them using dictionary
for i in range(height):
for j in range(width):
if grid_model[i][j] == 1:
self.tiles[i, j] = Tile(self, i, j, True)
else:
mine_neighbors = 0 #counting nearby mines if Tile in creation is not a mine
if i - 1 >= 0:
if grid_model[i-1][j] == 1:
mine_neighbors += 1
if i - 1 >= 0 and j - 1 >= 0:
if grid_model[i-1][j-1] == 1:
mine_neighbors += 1
if i - 1 >= 0 and j + 1 < width:
if grid_model[i-1][j+1] == 1:
mine_neighbors += 1
if j - 1 >= 0:
if grid_model[i][j-1] == 1:
mine_neighbors += 1
if j + 1 < width:
if grid_model[i][j+1] == 1:
mine_neighbors += 1
if i + 1 < height:
if grid_model[i+1][j] == 1:
mine_neighbors += 1
if i + 1 < height and j - 1 >= 0:
if grid_model[i+1][j-1] == 1:
mine_neighbors += 1
if i + 1 < height and j + 1 < width:
if grid_model[i+1][j+1] == 1:
mine_neighbors += 1
self.tiles[i, j] = Tile(self, i, j, False, mine_neighbors)
def reveal_surroundings(self, i, j): #reveal nearby tiles
revealing = []
width = self.width
height = self.height
if i - 1 >= 0:
revealing.append(self.tiles[i-1, j])
if i - 1 >= 0 and j - 1 >= 0:
revealing.append(self.tiles[i-1, j-1])
if i - 1 >= 0 and j + 1 < width:
revealing.append(self.tiles[i-1, j+1])
if j - 1 >= 0:
revealing.append(self.tiles[i, j-1])
if j + 1 < width:
revealing.append(self.tiles[i, j+1])
if i + 1 < height:
revealing.append(self.tiles[i+1, j])
if i + 1 < height and j - 1 >= 0:
revealing.append(self.tiles[i+1, j-1])
if i + 1 < height and j + 1 < width:
revealing.append(self.tiles[i+1, j+1])
for tile in revealing:
tile.reveal()
def lose(self): #show if lost, stop the clock
global stp
stp = True
self.lost = True
if self.platform == 'windows':
for tile in self.tiles:
if self.tiles[tile].mine:
self.tiles[tile].config(bg='red')
else:
for tile in self.tiles:
if self.tiles[tile].mine:
self.tiles[tile].config(image=self.bomb, padx=9, pady=4, bg='red')
self.tiles[tile].unbind('<Button-1>')
self.tiles[tile].unbind('<Button-2>')
messagebox.showerror(message='Boom, Game Over!!')
self.score = ScoreBoard(self.master)
def win(self): #show if won, stop the clock, creating a window recording scores
global mn, sc, stp
stp = True
self.won = True
for tile in self.tiles:
if self.tiles[tile].mine:
self.tiles[tile].config(image=self.bomb, padx=9, pady=4, bg='red')
self.tiles[tile].unbind('<Button-1>')
self.tiles[tile].unbind('<Button-2>')
messagebox.showinfo(message='Congrats, You Survived ;)')
play_time = str(m) + ' mins, ' + str(s) + ' secs'
self.score = ScoreBoard(self.master, self.player_name, play_time)
class ScoreBoard(Toplevel): #for score recording
def __init__(self, master, name=None, time=None):
Toplevel.__init__(self, master)
self.title('Hall of Fame')
fin_text = ''
if name != None: #writing in the record if there is one
self.board = open('ScoreBoard.txt', 'r') #assigning the text inside ScoreBoard.txt to board_text
board_text = '' #and writing it into ScoreBoard.txt
for line in self.board:
board_text = board_text + line
self.board = open('ScoreBoard.txt', 'w')
self.record = name + ' ' + time
self.board.write(board_text + '\n' + self.record)
self.board = open('ScoreBoard.txt', 'r') #reading text in ScoreBoard and put it on the window
for line in self.board:
fin_text = fin_text + line
self.lbl = Label(self, text=fin_text)
self.lbl.pack()
self.geometry('300x300')
self.board.close()
class Tile(Label): #the Tile
def __init__(self, master, i, j, mine, mine_neighbors=None):
Label.__init__(self, master, width=2, relief=RAISED)
self.grid(row=i, column=j)
self.game = master #storing row, column, is mine or not, count of nearby mines
self.mine = mine
self.row = i
self.col = j
self.mine_neighbors = mine_neighbors
self.revealed = False
self.marked = False
self.bind('<Button-1>', self.reveal) #bind Tile: reveal(left click), mark(right click)
self.bind('<Button-2>', self.mark)
def reveal(self, event=None): #revealing tile
if self.mine:
self.game.lose()
return
else:
if not self.revealed:
self.revealed = True
self.mark()
self.unbind('<Button-1>')
self.unbind('<Button-2>')
if self.mine_neighbors == 0: #if no nearby mines, reveal nearby tiles
self.config(text='', relief=SUNKEN, bg='lightgrey', image='', padx=1, pady=1)
self.game.reveal_surroundings(self.row, self.col)
else:
self.config(text=self.mine_neighbors, relief=SUNKEN, bg='lightgrey', image='', padx=1, pady=1)
self.game.notmine -= 1
if self.game.notmine == 0:
self.game.win()
def mark(self,event=None): #marking tile
if self.game.platform == 'windows':
if not self.marked:
self.config(text='*')
self.marked = True
else:
self.config(text='')
self.marked = False
else:
if not self.marked:
self.config(image=self.game.flag, padx=9, pady=4)
self.marked = True
else:
self.config(image='', padx=1, pady=1)
self.marked = False
stp = False #used to stop the clock when lost or won
def update_time(): #a stopwatch
global m, s, timer, stp
if stp != True:
s = s + 1
if s == 60:
m = m + 1
s = 0
mn = str(m) #making the clock look better by adding a 0 when the number
sc = str(s) #of second or minute is just one digit, e.g. 01, 06, 09..
if len(sc) == 1 and len(mn) == 1:
sc = '0' + sc
mn = '0' + mn
timer.config(text=mn+':'+sc)
elif len(mn) == 1 and len(sc) != 1:
mn = '0' + str(m)
timer.config(text=mn+':'+str(s))
elif len(sc) == 1 and len(mn) != 1:
sc = '0' + sc
timer.config(text=mn+':'+sc)
timer.after(1000, update_time)
def play(height, width, mines_count, player): #initiating the game
global s, m, timer
m = 0
s = -1
time = str(m) + ':' + str(s)
root = Tk()
root.title('MineSweeper')
root.resizable(False, False)
timer = Label(root, text='%i:%i'%(m,s)) #creating stopwatch and update it every second
timer.grid(row=1)
update_time()
game = GameGrid(root, height, width, mines_count, player)
root.mainloop()
if __name__ == '__main__':
play(10, 10, 10, 'Harley')

Why do the x and y coordinates not line up to where your clicking if I put the tkinter object inside a function?

I don't understand why this happens, but the x and y coordinates won't properly line up if I put the tkinter object inside a function.
here's the code
from tkinter import *
import random
root = Tk()
root.title("Treasure Hunt")
root.geometry("400x300")
root.iconbitmap("treasure2.ico")
class Treasure_game:
count = 0
search = 1
def __init__(self, main, num1, num2):
self.num1 = num1
self.num2 = num2
frame = Frame(main)
frame.pack()
self.treasure_map = Canvas(frame, bg="#f3c474", width=200, height=200)
self.treasure_map.create_rectangle(2, 2, 201, 201)
self.treasure_map.bind("<Button-1>", self.game)
self.top_label = Label(frame, text="Click on the map to find the treasure")
self.coord_label = Label(frame)
self.parrot_button = Button(frame, text="Search", state=DISABLED, command=self.parrot_search)
self.top_label.pack()
self.treasure_map.pack()
self.coord_label.pack()
self.parrot_button.pack()
def game(self, event):
text = (f"You clicked at x: {event.x} y: {event.y}. You are {abs(event.x-x_coord)} away from x and {abs(event.y-y_coord)} from y.")
self.coord_label.config(text=text)
self.count += 1
if abs(event.x-x_coord) < 5 and abs(event.y-y_coord) < 5:
self.coord_label.config(text="You have found the treasure!")
self.top_label.config(text="You have won!!")
self.treasure_map.create_line(self.num1-5, self.num2-5, self.num1+5, self.num2+5, fill="red", width=2)
self.treasure_map.create_line(self.num1-5, self.num2+5, self.num1+5, self.num2-5, fill="red", width=2)
print(f"It took {self.count} tries to find the treasure")
self.playing = False
if self.count >= 10 and self.search == 1:
self.parrot_button["state"] = NORMAL
def parrot_search(self):
if self.search == 1:
self.treasure_map.create_oval(self.num1-random.randint(20, 40), self.num2-random.randint(20, 40), self.num1+random.randint(20, 40), self.num2+random.randint(20, 40))
self.count += 1
self.search -= 1
def main_game():
pass
x_coord = random.randint(3, 200)
y_coord = random.randint(3, 200)
game = Treasure_game(root, x_coord, y_coord)
main_game()
root.mainloop()
If I put the x_coord, y_coord and game variables inside the main_game function, the num1 and num2 of the Treasure_game won't line up to where your clicking.
So since you are passing x_coord and y_coord as parameters in your Treasure_game().
We change the main_game
def main_game():
x_coord = random.randint(3, 200)
y_coord = random.randint(3, 200)
game = Treasure_game(root, x_coord, y_coord)
We change our if statement because when we initialized the Class we set self.num1 = num1 Which num1 is x_coord
if abs(event.x-self.num1) < 5 and abs(event.y-self.num2) < 5:
#continue with the other code

python code for tic tac toe, but need to add a visible scoreboard that shows how many times player X and O wins, and how many ties

I need help figuring out how to visibly show a scoreboard for the amount of times player X has won, player O has won, and how many draws or ties there has been. Here is the code I am using, I just can't figure out how to add a scoreboard.
I added a # that says where my scoreboard is going to go and I started it, but don't think its correct and need help with the entire scoreboard.
from tkinter import *
#screen dimensions
winsize = 500
gridwidth = 2
symbol = winsize/12
symsize = 0.5
#colors of players
player_x = 'black'
player_o = 'gray'
#screen colors
screencolor = 'red'
gridcolor = 'black'
bg_color = 'white'
player_one = 1
sizeof_cell = winsize / 3
screen_title = 0
turn_x = 1
turn_o = 2
gameover = 3
score_x = 0
score_o = 0
zero = 0
X = 1
O = 2
class Game(Tk):
def __init__(self):
Tk.__init__(self)
self.canvas = Canvas(
height=winsize, width=winsize,
bg=bg_color)
self.canvas.pack()
self.bind('<x>', self.exit)
self.canvas.bind('<Button-1>', self.mouse)
self.gamestate = screen_title
self.title_screen()
self.board = [
[zero, zero, zero],
[zero, zero, zero],
[zero, zero, zero]]
#ADD HIGH SCORE
def display_score(score):
score = 0
if score(grid):
score += 1
#creating title screen
def title_screen(self):
self.canvas.delete('all')
self.canvas.create_rectangle(
0, 0,
winsize, winsize,
fill='black',
outline='black')
self.canvas.create_rectangle(
int(winsize/15), int(winsize/15),
int(winsize*14/15), int(winsize*14/15),
width=int(winsize/20),
outline='black')
self.canvas.create_rectangle(
int(winsize/10), int(winsize/10),
int(winsize*9/10), int(winsize*9/10),
fill='black',
outline='black')
#adding names, R number, intructions
self.canvas.create_text(
winsize/2,
winsize/3,
text='TIC TAC TOE', fill='white',
font=('Calibri', int(-winsize/10), 'bold'))
self.canvas.create_text(
winsize/2,
winsize/2,
text='Game By: Yanely Ramos (R11529473)', fill='white',
font=('Arial', int(-winsize/25)))
self.canvas.create_text(
winsize/2,
winsize/1.8,
text='& Maria Tan de Castro (R11512696)', fill='white',
font=('Arial', int(-winsize/25)))
self.canvas.create_text(
winsize/2,
winsize/1.3,
text='Directions: Players take turn putting marks on empty' + '\n' + 'squares first player to get 3 in a row is a winner.' + "\n" + 'If all 9 squares are full and there is no winner, it is a draw.', fill='white',
font=('Times New Roman', int(-winsize/35)))
self.canvas.create_text(
int(winsize/2),
int(winsize/2.5),
text='[Click here to play]', fill='white',
font=('Arial', int(-winsize/25)))
#clicking of the game using the mouse
def mouse(self, event):
x = self.pixel_grid(event.x)
y = self.pixel_grid(event.y)
if self.gamestate == screen_title:
self.new_gameboard()
self.gamestate = player_one
elif (self.gamestate == turn_x and
self.board[y][x] == zero):
self.next_shift(X, x, y)
if self.winner(X):
self.gamestate = gameover
self.gameover_title('X WINS')
elif self.game_draw():
self.gamestate = gameover
self.gameover_title('DRAW')
else:
self.gamestate = turn_o
elif (self.gamestate == turn_o and
self.board[y][x] == zero):
self.next_shift(O, x, y)
if self.winner(O):
self.gamestate = gameover
self.gameover_title('O WINS')
elif self.game_draw():
self.gamestate = gameover
self.gameover_title('DRAW')
else:
self.gamestate = turn_x
elif self.gamestate == gameover:
self.new_gameboard()
self.gamestate = player_one
#starting new game
def new_gameboard(self):
self.canvas.delete('all')
self.board = [
[zero, zero, zero],
[zero, zero, zero],
[zero, zero, zero]]
for n in range(1, 3):
self.canvas.create_line(
sizeof_cell*n, 0,
sizeof_cell*n, winsize,
width=gridwidth, fill=gridcolor)
self.canvas.create_line(
0, sizeof_cell*n,
winsize, sizeof_cell*n,
width=gridwidth, fill=gridcolor)
#what pops up when the game ends
def gameover_title(self, outcome):
self.canvas.delete('all')
if outcome == 'X WINS':
wintext = 'X wins'
wincolor = player_x
elif outcome == 'O WINS':
wintext = 'O wins'
wincolor = player_o
elif outcome == 'DRAW':
wintext = 'Draw'
wincolor = screencolor
self.canvas.create_rectangle(
0, 0,
winsize, winsize,
fill=wincolor, outline='')
self.canvas.create_text(
int(winsize/2), int(winsize/2),
text=wintext, fill='white',
font=('Calibri', int(-winsize/6), 'bold'))
self.canvas.create_text(
int(winsize/2), int(winsize/1.65),
text='[click to play again]', fill='white',
font=('Arial', int(-winsize/25)))
#next players turn
def next_shift(self, player, grid_x, grid_y):
if player == X:
self.drawx(grid_x, grid_y)
self.board[grid_y][grid_x] = X
elif player == O:
self.drawo(grid_x, grid_y)
self.board[grid_y][grid_x] = O
#creating X player
def drawx(self, grid_x, grid_y):
x = self.grid_pixel(grid_x)
y = self.grid_pixel(grid_y)
delta = sizeof_cell/1.5*symsize
self.canvas.create_line(
x-delta, y-delta,
x+delta, y+delta,
width=symbol, fill='red')
self.canvas.create_line(
x+delta, y-delta,
x-delta, y+delta,
width=symbol, fill='red')
#creating O player
def drawo(self, grid_x, grid_y):
x = self.grid_pixel(grid_x)
y = self.grid_pixel(grid_y)
delta = sizeof_cell/1.5*symsize
self.canvas.create_oval(
x-delta, y-delta,
x+delta, y+delta,
width=symbol, outline='black')
#how to win
def winner(self, symbol):
for y in range(3):
if self.board[y] == [symbol, symbol, symbol]:
return True
for x in range(3):
if self.board[0][x] == self.board[1][x] == self.board[2][x] == symbol:
return True
if self.board[0][0] == self.board[1][1] == self.board[2][2] == symbol:
return True
elif self.board[0][2] == self.board[1][1] == self.board[2][0] == symbol:
return True
return False
#if 9 squares fill up, go to draw screen
def game_draw(self):
for row in self.board:
if zero in row:
return False
return True
def grid_pixel(self, grid_coord):
pixel_coord = grid_coord * sizeof_cell + sizeof_cell / 2
return pixel_coord
def pixel_grid(self, pixel_coord):
if pixel_coord >= winsize:
pixel_coord = winsize - 1
grid_coord = int(pixel_coord / sizeof_cell)
return grid_coord
def exit(self, event):
self.destroy()
def main():
root = Game()
root.mainloop()
root.title('TIC TAC TOE')
You can save players statistics in the mouse method and show them when you need them.
Try the following example
Add self.stats = {'X': 0, 'O': 0, 'DRAW': 0} in Game init method.
In mouse method:
  When self.winner(X) increment X (self.stats['X']+=1)
  When self.winner(O) increment O
  When self.game_draw() increment DRAW
Add a simple text in gameover_title showing stats on screen:
self.canvas.create_text(
int(winsize/2), int(winsize-20),
text='X: {X} O: {O} DRAW: {DRAW}'.format(**self.stats),
fill='white',
font=('Arial', int(-winsize/25)))

issue with filling the correct grid cell in tkinter

im trying to make a battleship game in python using tkinter,
the minimum reproducible example is:
from tkinter import *
from random import randint
import time
tk = Tk()
class player:
def __init__(self, master):
self.master = master
self.placedships = []
self.bombed = []
self.sunk = []
self.ship_sizes = [5, 4, 3]
self.player_canvas = Canvas(master, height=300, width=300, highlightbackground='black', highlightthickness=0.5)
self.ai_canvas = Canvas(master, height=300, width=300, highlightbackground='black', highlightthickness=0.5)
self.player_canvas.grid(row=1, column=0, padx=50)
self.ai_canvas.grid(row=1, column=1, padx=50)
self.direction = 'v'
self.shipChosen = 0
gridLabel_player = Label(master,text="Your grid \nA B C D E F G H I J ")
gridLabel_player.grid(row=0,column=0)
gridLabel_ai = Label(master,text="AI's grid \nA B C D E F G H I J ")
gridLabel_ai.grid(row=0,column=1)
for x in range(10):
for y in range(10):
self.player_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill = 'white')
self.ai_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill = 'white')
#variables to store data for cells on game grid
# # self.player_ocean = 10 * [10 * [0]]
# # self.ai_ocean = 10 * [10 * [0]]
self.player_ocean = []
self.ai_ocean = []
temp = []
for i in range(10):
for y in range(10):
temp += [0]
self.player_ocean += [temp]
self.ai_ocean += [temp]
temp = []
self.selectedCoord = [0,0] # [0] = x coord, [1] = y coord
def placeShip(self):
def moveShip(event):
if event.keysym == 'Down' and self.selectedCoord[1] != 9:
self.selectedCoord[1] += 1
elif event.keysym == 'Up' and self.selectedCoord[1] != 0:
self.selectedCoord[1] -= 1
elif event.keysym == 'Left' and self.selectedCoord[0] != 0:
self.selectedCoord[0] -= 1
elif event.keysym == 'Right' and self.selectedCoord[0] != 9:
self.selectedCoord[0] += 1
print('selected coord:',self.selectedCoord)
def selectPlacement(event):
col = self.selectedCoord[0]
row = self.selectedCoord[1]
if self.direction == 'v':
v_range_start = row - self.ship_sizes[self.shipChosen] + 1
for y in range(v_range_start, row+1):
'''insert validation to reject ship clashing'''
self.player_ocean[y][col] = 1
self.placedships += [y,col]
self.refresh_ocean()
self.master.bind("<Up>", moveShip)
self.master.bind("<Down>", moveShip)
self.master.bind("<Left>", moveShip)
self.master.bind("<Right>", moveShip)
self.master.bind("<Return>", selectPlacement)
def refresh_ocean(self): # 1s turns to green
for y in range(10):
for x in range(10):
if self.player_ocean[y][x] == 1:
self.player_canvas.itemconfig(self.player_canvas.create_rectangle( (x+1) * 30, (y-1) * 30,x * 30, y * 30, fill='green'))
player1 = player(tk)
player1.placeShip()
tk.mainloop()
the problem i have is that lets say if i press the down arrow until my selected coordinate is [0,9], the code should colour the 6th to 10th box from top down, in the first column, but it colours the 5th to 9th box.
i have tried debugging it by checking if the coordinates x and y used in the last function refresh_ocean were wrong, but they were as expected
So the short and simple fix is to change (y-1) to (y+1).
That said I made a few changes:
Rewrote some of your names to follow PEP8 style guide.
Converted your refresh_ocean method to handle the initial draw and all draws afterwords. As well as added a delete('all') to clear the board so we are not draw on top of the previous objects. This will prevent using up more memory as the game progresses.
Cleaned up imports and changed from tkinter import * to import tkinter as tk. This will help prevent overwriting other things in the namespace.
By writing your refresh_ocean to handle all the drawing we can specify what needs to be drawn with tags like 'player1' and 'ai'.
Changed += to .append() for your list generation as append() is twice as fast as '+=. See this post: Difference between “.append()” and “+= []”?
Changed you single label of headers into a loop to create each label evenly. This will allow for a more accurate header then trying to manually set spaces in a single string.
Lastly because you are already checking of the key is one of the 4 arrow keys we can use a single bind to '<Key>' and get the same results as all 4 binds.
Updated code:
import tkinter as tk
class Player(tk.Tk):
def __init__(self):
super().__init__()
self.placed_ships = []
self.player_ocean = []
self.ai_ocean = []
self.bombed = []
self.sunk = []
self.bord_size = list('ABCDEFGHIJ')
self.selected_coord = [0, 0]
self.ship_sizes = [5, 4, 3]
self.direction = 'v'
self.shipChosen = 0
wh = 300
p_label_frame = tk.Frame(self, width=wh, height=30)
a_label_frame = tk.Frame(self, width=wh, height=30)
p_label_frame.grid(row=1, column=0, padx=50)
a_label_frame.grid(row=1, column=1, padx=50)
p_label_frame.grid_propagate(False)
a_label_frame.grid_propagate(False)
for ndex, letter in enumerate(self.bord_size):
p_label_frame.columnconfigure(ndex, weight=1)
a_label_frame.columnconfigure(ndex, weight=1)
tk.Label(p_label_frame, text=letter).grid(row=0, column=ndex)
tk.Label(a_label_frame, text=letter).grid(row=0, column=ndex)
self.player_canvas = tk.Canvas(self, height=wh, width=wh, highlightbackground='black', highlightthickness=0.5)
self.ai_canvas = tk.Canvas(self, height=wh, width=wh, highlightbackground='black', highlightthickness=0.5)
self.player_canvas.grid(row=2, column=0, padx=50)
self.ai_canvas.grid(row=2, column=1, padx=50)
temp = []
for i in range(10):
for y in range(10):
temp.append(0)
self.player_ocean.append(temp)
self.ai_ocean.append(temp)
temp = []
self.refresh_ocean()
self.bind("<Key>", self.move_ship)
self.bind("<Return>", self.select_placement)
def move_ship(self, event):
if event.keysym == 'Down' and self.selected_coord[1] < 9:
self.selected_coord[1] += 1
elif event.keysym == 'Up' and self.selected_coord[1] > 0:
self.selected_coord[1] -= 1
elif event.keysym == 'Left' and self.selected_coord[0] > 0:
self.selected_coord[0] -= 1
elif event.keysym == 'Right' and self.selected_coord[0] < 9:
self.selected_coord[0] += 1
print('selected coord:', self.selected_coord)
def select_placement(self, _=None):
col = self.selected_coord[0]
row = self.selected_coord[1]
if self.direction == 'v':
v_range_start = row - self.ship_sizes[self.shipChosen] + 1
for y in range(v_range_start, row+1):
'''insert validation to reject ship clashing'''
self.player_ocean[y][col] = 1
self.placed_ships += [y, col]
self.refresh_ocean(side='player1')
def refresh_ocean(self, side=None):
self.player_canvas.delete('all')
self.ai_canvas.delete('all')
for y in range(len(self.bord_size)):
for x in range(len(self.bord_size)):
if self.player_ocean[y][x] == 1 and side == 'player1':
self.player_canvas.itemconfig(self.player_canvas.create_rectangle(
(x+1) * 30, (y+1) * 30, x * 30, y * 30, fill='green'))
else:
self.player_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill='white')
if self.ai_ocean[y][x] == 1 and side == 'ai':
self.ai_canvas.itemconfig(self.ai_canvas.create_rectangle(
(x + 1) * 30, (y + 1) * 30, x * 30, y * 30, fill='green'))
else:
self.ai_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill='white')
if __name__ == '__main__':
Player().mainloop()
Results:
your refresh_ocean had an off by 1 error
def refresh_ocean(self): # 1s turns to green
for y in range(10):
for x in range(10):
if self.player_ocean[y][x] == 1:
self.player_canvas.itemconfig(self.player_canvas.create_rectangle( x*30, y * 30, x*30+30, y*30+30, fill='green'))
and select placement was placing ships in with a negative index.
i changed your iteration from range(row-size, row) to range(row, row+size) and added bounds checking, but that should probably be moved to the movement handling.
def selectPlacement(event):
col = self.selectedCoord[0]
row = self.selectedCoord[1]
if self.direction == 'v':
if row + self.ship_sizes[self.shipChosen] > 9:
row = 10 - self.ship_sizes[self.shipChosen]
for y in range(row, row + self.ship_sizes[self.shipChosen]):
'''insert validation to reject ship clashing'''
self.player_ocean[y][col] = 1
self.placedships += [y,col]
self.refresh_ocean()
ps: your drawing new squares on top of the old squares. you might want to clear the ocean before drawing.

Tkinter Python OOP: Move seperate widgets at once with canvas.move()

I want to translate my procedural bouncing Ball programme to OOP in order to train OOP a bit.
I run into the problem that if I call a function on one instance of the object that contains an infinite loop, the next instance will never call its function. Resulting in only one of the balls moving.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
#Moving the Oval(ov1)
def move(self):
self.xd = random.randint(5,10)
self.yd = random.randint(5,10)
while True:
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
else:
self.yd = -(random.randint(5,10))
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
else:
self.xd = -(random.randint(5,10))
root.update()
time.sleep(.01)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
#Problem is that ov1.move() has a internal loop and ov2.move() will never be called
# ov1.move()
# ov2.move()
# ov3.move()
# ov4.move()
# ov5.move()
tk.mainloop()
Have solved it on my own.
I just took out the While True: loop from the class and called the funcion in a loop bellow.
import tkinter as tk
import time
import random
#Define root windows
root = tk.Tk()
root.geometry("800x800")
root.title("TkInter Animation Test")
#Define canvas that is inside the root window
canvas_width = 700
canvas_height = 700
canvas = tk.Canvas(root, width= canvas_width, height= canvas_height, bg="Black")
canvas.pack()
class Oval():
#Oval creation inside the canvas
def __init__(self, y1, x1, y2, x2, color):
self.y1 = y1
self.x1 = x1
self.y2= y2
self.x2= x2
self.oval = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill=color)
self.xd = random.randint(5,10)
# self.xd = 10
self.yd = random.randint(5,10)
# self.yd = 10
#Moving the Oval(ov1)
def move(self):
canvas.move(self.oval, self.xd, self.yd)
# print(self.yd, self.xd)
self.coords = canvas.coords(self.oval)
# print (self.coords)
if self.coords[3] + self.yd >= 700 or self.coords[1] + self.yd <= 0:
if self.yd < 0:
self.yd = random.randint(5,10)
# self.yd = 10
else:
self.yd = -(random.randint(5,10))
# self.yd = -10
if self.coords[2] + self.xd >= 700 or self.coords[0] + self.xd <= 0:
if self.xd < 0:
self.xd = random.randint(5,10)
# self.xd = 10
else:
self.xd = -(random.randint(5,10))
# self.xd = -10
root.update()
# time.sleep(.000000001)
ov1 = Oval(10,10,40,40, "blue")
ov2 = Oval(80,80,120,120, "red")
ov3 = Oval(240,240,270,270, "Yellow")
ov4 = Oval(360,360,400,400, "Green")
ov5 = Oval(500,500,540,540, "white")
while True:
ov1.move()
ov2.move()
ov3.move()
ov4.move()
ov5.move()
time.sleep(.01)
tk.mainloop()

Categories