0
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

  • Side note. Use `.append` instead of `+=` for stuff like `temp += [0]`. Even better would be to use listcomp such as `self.player_ocean = [[0 for _ in range(10)] for _ in range(10)]` – Error - Syntactical Remorse Nov 15 '19 at 14:58
  • `tkinter` should use only one `Tk()` - to create main window. For other windows you should use `Toplevel()` – furas Nov 15 '19 at 15:09
  • you could use `time.time()` to get time in different moment to see which part takes more time and makes your problem. OR learn how to use `profiler` (in any `IDE`) – furas Nov 15 '19 at 15:11
  • i dont think the second window is the problem tho the problem occurs from the start of the program – youdecipher Nov 15 '19 at 15:14
  • im not using pycharm pro so i think there isnt profiling – youdecipher Nov 15 '19 at 15:17
  • @youdecipher You are using `tk.update() ; tk.update_idletasks()` which is doubled. Read [Tkinter understanding mainloop](https://stackoverflow.com/questions/29158220/tkinter-understanding-mainloop). Also consider to change some `for ... for ...` to a `.after(...` version. – stovfl Nov 15 '19 at 15:27
  • i used tk.update() because i wanted the while true loop to continue going through the if else code – youdecipher Nov 15 '19 at 15:34
  • i changed the last part to a function called checkstate 1 line before tk.mainloop which runs the if else that was in the while loop previously, and at the end of it runs a tk.after() to run checkstate again but its still laggy after a while – youdecipher Nov 15 '19 at 16:02
  • as i said before - use `time.time()` to see which part makes problem. If you will know which part take more time than another then you will know which part you have to change. – furas Nov 15 '19 at 16:38

1 Answers1

0

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")
JensJ
  • 71
  • 4