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.
Related
I have a code that makes multiple canvases on Python tkinter (9 canvas). With this function (I destroy all item on window with destroy function at start):
def show_board(self):
self.destroy_start()
canvas_list = []
for i in range(9):
dist = {
f"canvas{i + 1}": Canvas(height=150, width=150, bg=BOARD_COLOR),
}
canvas_list.append(dist)
for n in range(len(canvas_list)):
canvas_dict = canvas_list[n]
for (key, val) in canvas_dict.items():
if n == 0 or n <= 2:
row = 0
if n == 0:
col = 0
val.grid(row=row, column=col, padx=(125, 0))
elif n == 1:
col = 1
val.grid(row=row, column=col)
elif n == 2:
col = 2
val.grid(row=row, column=col)
elif 2 < n <= 5:
row = 1
if n == 3:
col = 0
val.grid(row=row, column=col, padx=(125, 0))
elif n == 4:
col = 1
val.grid(row=row, column=col)
elif n == 5:
col = 2
val.grid(row=row, column=col)
elif n > 5:
row = 2
if n == 6:
col = 0
val.grid(row=row, column=col, padx=(125, 0))
elif n == 7:
col = 1
val.grid(row=row, column=col)
elif n == 8:
col = 2
val.grid(row=row, column=col)
# loop in canvas and get x and y of the click and set the image when user click
for item in canvas_list:
for (key, val) in item.items():
canvas_x, canvas_y = val.winfo_rootx(), val.winfo_rooty()
print(canvas_x, canvas_y)
self.check_click(can=val)
self.pl1_score_label.grid(column=0, row=3, padx=(100, 0))
self.pl2_score_label.grid(column=2, row=3)
and I want to when user clicks on window on one of these canvases for the canvas to create an image (just than canvas)
I try to do this with last loop in upper function
def display_mouse_pos(self, event):
self.window.update()
x = event.x
y = event.y
print(x, y)
def check_click(self, can):
self.window.bind("<Button-1>", self.display_mouse_pos)
can.create_image(100, 100, image=self.x_image)
self.window.update()`
but I get that image on all canvas I know problem is my function and for loop I can't find the way to check if screen get clicked or not and if its get clicked which canvas get clicked and who can I make a picture in that canvas
Doing a minesweeper on python using tkinter. I'm trying to make a square white when you click on it but it only makes the first square white.
import random
import tkinter as tk
import time
def init_grille(m, n, n_mines): # Creates the grid with the mines and the numbers
grille = []
for i in range(m) :
ligne = []
for i in range(n) :
ligne.append(0)
grille.append(ligne)
for i in range(n_mines) :
rm = random.randint(0, m - 1)
rn = random.randint(0, n - 1)
while grille[rm][rn] == 'X' :
rm = random.randint(0, m - 1)
rn = random.randint(0, n - 1)
grille[rm][rn] = 'X'
for em in range(m) :
for en in range(n) :
if grille[em][en] == 0 :
m_proches = 0
if en > 0 and grille[em][en-1] == 'X' :
m_proches += 1
if en < n - 1 and grille[em][en+1] == 'X' :
m_proches += 1
if em < m - 1 and grille[em+1][en] == 'X' :
m_proches += 1
if em < m - 1 and en < n - 1 and grille[em+1][en+1] == 'X' :
m_proches += 1
if em < m - 1 and en > 0 and grille[em+1][en-1] == 'X' :
m_proches += 1
if em > 0 and grille[em-1][en] == 'X' :
m_proches += 1
if em > 0 and en < n - 1 and grille[em-1][en+1] == 'X' :
m_proches += 1
if em > 0 and en > 0 and grille[em-1][en-1] == 'X' :
m_proches += 1
grille[em][en] = m_proches
return grille
def init_grille_joueur(m, n): # Creates the grid the player interacts with (digging and flagging)
grille_j = []
for i in range(m) :
ligne = []
for i in range(n) :
ligne.append(0)
grille_j.append(ligne)
return grille_j
root = tk.Tk()
M = 10
N = 10
LARGEUR_CASE = 50
HAUTEUR_CASE = 50 # Just some values used for testing the program
def fenetre_principale(): # Configurates the window
label = tk.Label(root, text = "GO !", background = "white")
label.pack()
root.title("Démineur")
root.geometry(str(M * LARGEUR_CASE) + 'x' + str(N * HAUTEUR_CASE + 90))
def init_canvas(m, n): # Creates the visual grid
rect_id = []
canvas = tk.Canvas(root, width = M * LARGEUR_CASE + 25, height = N * HAUTEUR_CASE + 25)
for i in range(m):
for j in range(n):
rect_id.append(canvas.create_rectangle(j * LARGEUR_CASE, i * HAUTEUR_CASE, (j + 1) * LARGEUR_CASE, (i + 1) * HAUTEUR_CASE, outline = "lightgray", fill = "gray", width = 2))
canvas.bind('<Button-1>', clic_gauche)
canvas.bind('<Button-3>', clic_droit)
canvas.pack()
return rect_id, canvas
def get_case(x, y): # Locates a square on the visual grid
case = canvas.find_overlapping(x, y, x, y)
if len(case) == 0 :
return -1, -1
else :
x = (case[0] - 1) // M
y = (case[0] - 1) % N
return x, y
def creuser(i, j): # The digging mechanic
if not grille[i][j] == -1 :
grille_j[i][j] = -1
C = (j * LARGEUR_CASE + LARGEUR_CASE / 2, i * HAUTEUR_CASE + HAUTEUR_CASE / 2)
case_id = canvas.find_overlapping(j, i, j, i)
case = case_id[0]
canvas.itemconfig(case, fill = "white")
if grille[i][j] == 'X' :
print("Perdu!")
canvas.create_text(C, font = "Wingdings 20 bold", text = chr(77), fill = "purple")
elif grille[i][j] == 1 :
canvas.create_text(C, font = "Arial 20 bold", text = str(grille[i][j]), fill = "blue")
elif grille[i][j] == 2 :
canvas.create_text(C, font = "Arial 20 bold", text = str(grille[i][j]), fill = "green")
elif grille[i][j] > 2 :
canvas.create_text(C, font = "Arial 20 bold", text = str(grille[i][j]), fill = "red")
canvas.pack()
def clic_gauche(event): # Left click
x, y = get_case(event.x, event.y)
creuser(x, y)
def drapeau(i, j): # The flag mechanic, unfinished
C = (j * LARGEUR_CASE + LARGEUR_CASE / 2, i * HAUTEUR_CASE + HAUTEUR_CASE / 2)
if grille_j[i][j] == 0 :
grille_j[i][j] = 1
canvas.create_text(C, font = "Wingdings 20 bold", text = chr(80), fill = "orange")
elif grille_j[i][j] == 1 :
grille_j[i][j] = 0
canvas.delete()
def clic_droit(event): # Right click
x, y = get_case(event.x, event.y)
drapeau(x, y)
'''def maj_labels():''' # Not finished yet
grille = init_grille(M, N, 5)
grille_j = init_grille_joueur(M, N)
fenetre_principale()
rect_id, canvas = init_canvas(M, N)
'''root.after(500, maj_labels)''' # It's supposed to update the timer which isn't implemented yet
root.mainloop()
I tried using find_overlapping to get the square's id when I click on it and then change its color with itemconfig, I tried to change the lines' order to see if they contradicted eachother but it's still the same problem
You passed incorrect region (j, i, j, i) to canvas.find_overlapping() inside creuser(), region (*C, *C) should be used instead:
def creuser(i, j):
...
#case_id = canvas.find_overlapping(j, i, j, i) # incorrect region
case_id = canvas.find_overlapping(*C, *C) # correct region
...
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')
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)))
from tkinter import *
import random
tk = Tk()
tk.wm_title("Battleship")
switch = True
game_over = False
labels = []
class player:
def __init__(self):
self.switchuser = False
self.placedships = []
self.bombed = []
self.sunk = []
self.ship_sizes = [5, 4, 3, 3, 2]
self.direction = 'v'
self.player_ocean = []
temp = []
for i in range(10):
for y in range(10):
temp += [0]
self.player_ocean += [temp]
temp = []
self.selectedCoord = [0, 0] # [0] = x coord, [1] = y coord
self.attack_selection = [0, 0]
self.hits = []
self.misses = []
def selectPlacement(self, event, key):
'''
initialise column and row index
condition for different directions
check if ship placed is off grid or clashes with another ship
place ship
add to self.placedships
remove ship from availiable ships to cycle to next ship
'''
clear = True
col = self.selectedCoord[0]
row = self.selectedCoord[1]
print(self.selectedCoord)
if self.direction == 'v':
v_range_start = row - self.ship_sizes[0] + 1
if v_range_start >= 0: # check if ship will be off the grid
for cell in range(v_range_start, row + 1): # check if the ship clashes with existing ships
if [cell, col] in self.placedships:
clear = False
break
if clear == True:
for y in range(v_range_start, row + 1):
self.player_ocean[y][col] = 1
self.placedships.append([y, col])
self.ship_sizes.remove(self.ship_sizes[0])
refresh_ocean('place')
elif self.direction == 'h':
h_range_end = col + self.ship_sizes[0]
if 10 > h_range_end:
for cell in range(col, h_range_end):
if [row, cell] in self.placedships:
clear = False
break
if clear == True:
for x in range(col, h_range_end):
self.player_ocean[row][x] = 1
self.placedships.append([row, x])
self.ship_sizes.remove(self.ship_sizes[0])
refresh_ocean('place')
def selectAttack(self, event):
col = self.attack_selection[0]
row = self.attack_selection[1]
if [row, col] in ai.ai_placedships:
if [row, col] not in self.hits:
self.hits.append([row, col])
print('hit')
else:
if [row, col] not in self.misses:
self.misses.append([row, col])
# if str(type(event)) == 'tkinter.Event':
# return True
refresh_ocean('attackselection')
ai.ai_attack()
# forming tkinter window
def baseGrid():
gridLabel_player = Label(tk,
text="Your grid \nA B C D E F G H I J ")
gridLabel_player.grid(row=0, column=0)
gridLabel_ai = Label(tk,
text="AI's grid \nA B C D E F G H I J ")
gridLabel_ai.grid(row=0, column=1)
tk.player_canvas = Canvas(tk, height=300, width=300, highlightbackground='black', highlightthickness=0.5)
tk.ai_canvas = Canvas(tk, height=300, width=300, highlightbackground='black', highlightthickness=0.5)
tk.player_canvas.grid(row=1, column=0, padx=50)
tk.ai_canvas.grid(row=1, column=1, padx=50)
for x in range(10):
for y in range(10):
tk.player_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill='white')
tk.ai_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill='white')
def moveShip(event, key):
print(player1.selectedCoord)
if event.keysym == 'Down':
if key == 'place':
if player1.selectedCoord[1] != 9:
player1.selectedCoord[1] += 1
elif key == 'attackselection':
if player1.attack_selection[1] != 9:
player1.attack_selection[1] += 1
elif event.keysym == 'Up':
if key == 'place':
if player1.selectedCoord[1] != 0:
player1.selectedCoord[1] -= 1
elif key == 'attackselection':
if player1.attack_selection[1] != 0:
player1.attack_selection[1] -= 1
elif event.keysym == 'Left':
if key == 'place':
if player1.selectedCoord[0] != 0:
player1.selectedCoord[0] -= 1
elif key == 'attackselection':
if player1.attack_selection[0] != 0:
player1.attack_selection[0] -= 1
elif event.keysym == 'Right':
if key == 'place':
if player1.selectedCoord[0] != 9:
player1.selectedCoord[0] += 1
elif key == 'attackselection':
if player1.attack_selection[0] != 9:
player1.attack_selection[0] += 1
for y in range(10):
for x in range(10):
if key == 'place':
if [y, x] == player1.selectedCoord or [x, y] in player1.placedships:
player1.player_ocean[x][y] = 1
else:
player1.player_ocean[x][y] = 0
# elif key == 'attackselection':
# if [y,x] == player1.attack_selection or [x,y] in player1.hits:
# ai.ai_ocean[x][y] = 1
# else:
# ai.ai_ocean[x][y] = 0
refresh_ocean(key)
def changedir(event):
if player1.direction == 'v':
player1.direction = 'h'
elif player1.direction == 'h':
player1.direction = 'v'
def refresh_ocean(key):
print('function call')
for y in range(10):
for x in range(10):
colour = 'white'
if key == 'place':
if [y, x] in player1.placedships:
colour = 'green'
if [x, y] == player1.selectedCoord:
colour = 'yellow'
tk.player_canvas.itemconfig(
tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30, fill=colour))
elif key == 'attackselection':
# print('miss',ai.AI_miss,'\nhits',ai.destroyed_cords,'\nships',player1.placedships)
if [y, x] in player1.placedships:
tk.player_canvas.itemconfig(
tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30), fill='green')
if [y, x] in ai.AI_miss:
tk.player_canvas.itemconfig(
tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30), fill='gray')
if [y, x] in ai.destroyed_cords:
tk.player_canvas.itemconfig(
tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30), fill='red')
if [y, x] in player1.hits:
colour = 'red'
if [y, x] in player1.misses:
colour = 'gray'
if [x, y] == player1.attack_selection:
colour = 'yellow'
tk.ai_canvas.itemconfig(
tk.ai_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30), fill=colour)
# else:
# tk.player_canvas.itemconfig(
# tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30, fill='white'))
def attackGui():
tk.bind("<Key>", lambda event: moveShip(event, 'attackselection'))
tk.bind("<Return>", lambda event: player1.selectAttack(event))
class Ai(player):
def __init__(self):
# = [[[9,0], [9, 1], [9, 2], [9, 3], [9, 4]], [[9, 5], [9, 6]]]
# player1.placedships = [[[9, 0], [8, 0], [7, 0], [6, 0], [5, 0]], [[4, 0], [3, 0]]]
self.destroyed_ships = []
self.destroyed_cords = []
self.AI_miss = []
self.fmove_check = True
self.mdirection = 1
self.ai_placedships = []
self.directions = ['v', 'h']
self.ship_sizes = [5, 4, 3, 3, 2]
self.currentcoord = [0,0]
def ai_attack(self):
def first_move(self):
self.missed = 0
self.AIx = random.randint(0, 9)
self.AIy = random.randint(0, 9)
self.AIsel_cords = [self.AIy, self.AIx]
while self.AIsel_cords in self.AI_miss or self.AIsel_cords in self.destroyed_cords:
self.AIx = random.randint(0, 9)
self.AIy = random.randint(0, 9)
self.AIsel_cords = [self.AIy, self.AIx]
self.currentcoord = self.AIsel_cords
if self.AIsel_cords in player1.placedships:
# self.ship_check = ship
self.destroyed_cords.append(player1.placedships.pop(player1.placedships.index(self.AIsel_cords)))
self.fmove_check = False
else:
self.AI_miss.append(self.AIsel_cords)
def fol_move(self):
checked = False
self.entered = False
# direction check
if self.mdirection > 4:
self.mdirection = 1
# up
elif self.mdirection == 1 and self.AIy - 1 >= 0:
self.entered = True
self.AIy -= 1
self.currentcoord[0] -= 1
# to find whether the cords are part of a ship
if [self.AIy, self.AIx] in player1.placedships:
# if [self.AIx, self.AIy] in self.ship_check:
# self.destroyed_cords.append(ship.pop(ship.index([self.AIx, self.AIy])))
# else:
# self.destroyed_cords +=
self.destroyed_cords.append(
player1.placedships.pop(player1.placedships.index([self.AIy, self.AIx])))
checked = True
# add the cords that are not part of a ship into AI_miss
else:
self.AI_miss.append([self.AIy, self.AIx])
self.AIy, self.AIx = self.AIsel_cords[0], self.AIsel_cords[1]
self.mdirection += 1
self.missed += 1
# left
elif self.mdirection == 2 and self.AIx - 1 >= 0:
self.entered = True
self.AIx -= 1
self.currentcoord[1] -= 1
# to find whether the cords are part of a ship
if [self.AIy, self.AIx] in player1.placedships:
self.destroyed_cords.append(
player1.placedships.pop(player1.placedships.index([self.AIy, self.AIx])))
checked = True
# add the cords that are not part of a ship into AI_miss
else:
self.AI_miss.append([self.AIy, self.AIx])
self.AIy, self.AIx = self.AIsel_cords[0], self.AIsel_cords[1]
self.mdirection += 1
self.missed += 1
# down
elif self.mdirection == 3 and self.AIy + 1 <= 9:
self.entered = True
self.AIy += 1
self.currentcoord[0] += 1
# to find whether the cords are part of a ship
if [self.AIy, self.AIx] in player1.placedships:
self.destroyed_cords.append(
player1.placedships.pop(player1.placedships.index([self.AIy, self.AIx])))
checked = True
# add the cords that are not part of a ship into AI_miss
else:
self.AI_miss.append([self.AIy, self.AIx])
self.AIy, self.AIx = self.AIsel_cords[0], self.AIsel_cords[1]
self.mdirection += 1
self.missed += 1
# right
elif self.mdirection == 4 and self.AIx + 1 <= 9:
self.entered = True
self.AIx += 1
self.currentcoord[1] += 1
# to find whether the cords are part of a ship
if [self.AIy, self.AIx] in player1.placedships:
self.destroyed_cords.append(
player1.placedships.pop(player1.placedships.index([self.AIy, self.AIx])))
checked = True
# add the cords that are not part of a ship into AI_miss
else:
self.AI_miss.append([self.AIy, self.AIx])
self.AIy, self.AIx = self.AIsel_cords[0], self.AIsel_cords[1]
self.mdirection += 1
self.missed += 1
elif not self.entered:
self.AIy, self.AIx = self.AIsel_cords[0], self.AIsel_cords[1]
self.mdirection += 1
if self.missed == 2:
self.missed = 0
self.fmove_check = True
if self.fmove_check:
first_move(self)
elif not self.fmove_check:
fol_move(self)
if not self.entered:
fol_move(self)
# print('\n', self.destroyed_cords)
# print(player1.placedships)
# print(self.AI_miss)
def ai_place(self):
print(len(ai.ship_sizes))
ai_dir = random.choice(self.directions)
col = random.randint(0, 9)
row = random.randint(0, 9)
clear = True
if ai_dir == 'v':
v_range_start = row - self.ship_sizes[0] + 1
if v_range_start >= 0: # check if ship will be off the grid
for cell in range(v_range_start, row + 1): # check if the ship clashes with existing ships
if [cell, col] in self.ai_placedships:
clear = False
break
if clear == True:
for y in range(v_range_start, row + 1):
self.ai_placedships.append([y, col])
self.ship_sizes.remove(self.ship_sizes[0])
elif ai_dir == 'h':
h_range_end = col + self.ship_sizes[0]
if 10 > h_range_end:
for cell in range(col, h_range_end):
if [row, cell] in self.ai_placedships:
clear = False
break
if clear == True:
for x in range(col, h_range_end):
self.ai_placedships.append([row, x])
self.ship_sizes.remove(self.ship_sizes[0])
attackinstructionLabel = Label(tk, text="Arrow keys to move selection"
"\nEnter to shoot at selected cell"
"\nGrey cells are missed ships"
"\nRed cells are hit ships")
attackinstructionLabel.grid(row=2)
instructionLabel = Label(tk, text="Arrow keys to move selection"
"\nRight shift to change ship orientation"
"\nEnter to place ship"
"\nYellow cells means that you are selecting a cell to place the next ship in"
"\nGreen cells show placed ships")
instructionLabel.grid(row=2)
tk.bind("<Key>", lambda event: moveShip(event, 'place'))
tk.bind("<Shift_R>", changedir)
tk.bind("<Return>", lambda event: player1.selectPlacement(event, 'place'))
base = baseGrid()
player1 = player()
ai = Ai()
while True:
tk.update()
tk.update_idletasks()
if len(player1.ship_sizes) != 0:
if len(ai.ship_sizes) != 0:
ai_place = ai.ai_place()
refresh_ocean('place')
else:
if (sorted(player1.hits) != sorted(ai.ai_placedships)) and len(player1.placedships) != 0:
attack = attackGui()
instructionLabel.destroy()
refresh_ocean('attackselection')
else:
popup = Tk()
p_label = Label(popup,text='GAME OVER')
popup.after(5000,p_label.destroy())
tk.destroy()
i tried taking out all the code run during the while loop that creates additional things in the program but it still gets noticeably laggy. the only thing i can identify right now which may be the problem is the refresh_ocean function which is the only thing that deals with the gui in the while loop but it uses the itemconfig method which shouldnt add on anything to the gui
I think you are using:
tk.player_canvas.itemconfig in bad way.
tk.player_canvas.itemconfig(
tk.player_canvas.create_rectangle(x * 30, y * 30, (x + 1) * 30, (y + 1) * 30), fill='green')
You are creating more and more rectangles.
This is my usual way of handling an a grid on tkinter canvas
grid = [[0 for x in range(10)] for y in range(10)]
for x in range(10):
for y in range(10):
grid[x][y] = tk.player_canvas.create_rectangle(x * 30, y * 30, 300, 300, fill='white')
tk.player_canvas.itemconfig( grid[x][y], fill="blue")