Exemple #1
0
class App(object):
    # Implements the app class, the GUI
    def __init__(self, master, **kwargs):
        self.master = master
        self.type = NUMBERS
        self.n = 4
        self.w = 500
        self.h = self.w + 100
        self.imagePath = "textures/tartan.jpg"
        # app state
        self.started = False
        self.onPauseMenu = False
        self.onMenu = True
        self.icons = {}
        # setup canvas
        self.canvas = tk.Canvas(self.master, width=self.w, height=self.h)
        self.canvas.pack()
        # bind mouse and keys to the tkinter
        self.bind()
        self.loadSources()
        # main menu
        self.setUpMenu()
        self.showMenu()

    def loadSources(self):
        # loads images and sets on the class variables
        self.openImages()
        self.setIcons()

    def startGame(self):
        # starts game
        # change app state
        self.started = True
        self.gameOver = False
        self.robotSolved = False
        self.model = GameLogic(self.n)
        self.seed = []
        self.numberOfMoves = 0
        self.t = Timer(self.master, self.canvas)
        # run draw loop
        self.master.after(0, self.draw)
        # start shuffling the board
        self.shuffleBoard()

    def shuffleBoard(self):
        # prepares state of game and draws shuffle animation
        self.onPause = True
        self.seed = self.model.getShuffleSeed()
        self.shuffleAnimaion(self.seed)

    def shuffleAnimaion(self, seed):
        # draws shuffle animation
        self.draw()
        if not seed:
            self.onPause = False
            return
        self.model.moveDirection(seed[0])
        seed = seed[1:]
        self.master.after(50, lambda seed=seed: self.shuffleAnimaion(seed))

    def openImages(self):
        # opens icons and images and saves as variables in the app
        im = Image.open("textures/bg/" + "gyr.jpg")
        self.bg = ImageTk.PhotoImage(im)
        # TO BE CHANGEEEEEEEED
        bgButton = Image.open("textures/bg/button.png").convert("RGBA")
        self.bgButtonTrans = ImageTk.PhotoImage(
            self.reduce_opacity(bgButton, 0.95))
        self.bgButton = ImageTk.PhotoImage(bgButton)
        self.run = {}
        run = Image.open("textures/icons/run.png").convert("RGBA")
        run = run.resize((320, 70), Image.ANTIALIAS)
        self.run["off"] = ImageTk.PhotoImage(self.reduce_opacity(run, 0.95))
        self.run["on"] = ImageTk.PhotoImage(run)

        bgPause = Image.open("textures/bg/15class.png").convert("RGBA")
        self.bgPause = ImageTk.PhotoImage(self.reduce_opacity(bgPause, 0.96))
        self.iconList = ["home", "pause", "restart", "bot", "play"]
        for iconName in self.iconList:
            icon = Image.open("textures/icons/" + iconName +
                              ".png").convert("RGBA")
            if iconName == "play":
                icon = icon.resize((120, 120), Image.ANTIALIAS)
            self.icons[iconName] = {}
            self.icons[iconName]["on"] = ImageTk.PhotoImage(icon)
            self.icons[iconName]["off"] = ImageTk.PhotoImage(
                self.reduce_opacity(icon, 0.5))
            self.icons[iconName]["state"] = False

    def setBG(self):
        # sets bg of the app
        self.canvas.create_image(0, 0, image=self.bg, anchor='nw')

    def setIcons(self):
        # Draw icons at the bottom of canvas
        begin = 40
        margin = 120
        height = 510
        self.icons["home"]["tag"] = self.canvas.create_image(
            begin, height, image=self.icons["home"]["off"], anchor="nw")
        self.icons["restart"]["tag"] = self.canvas.create_image(
            begin + margin,
            height,
            image=self.icons["restart"]["off"],
            anchor="nw")
        self.icons["pause"]["tag"] = self.canvas.create_image(
            begin + 2 * margin,
            height,
            image=self.icons["pause"]["off"],
            anchor="nw")
        self.icons["bot"]["tag"] = self.canvas.create_image(
            begin + 3 * margin,
            height,
            image=self.icons["bot"]["off"],
            anchor="nw")
        self.icons["play"]["tag"] = 0

    def loadTextures(self, infile):
        if self.type == NUMBERS:
            # im = Image.open("textures/gray.jpg")
            alpha = int(0.5 * 255)
            fill = "#BBBCB6"
            fill = self.master.winfo_rgb(fill) + (alpha, )
            im = Image.new('RGBA', (self.size - 2, self.size - 2), fill)
            # im = im.putalpha(128)
            ph = ImageTk.PhotoImage(im)
            number = self.n * self.n
            return [ph] * number
        else:
            textures = [0]
            pieces = self.crop(infile)
            for i in range(1, self.n * self.n):
                im = pieces[i - 1]
                im = im.resize((self.size - 2, self.size - 2))
                ph = ImageTk.PhotoImage(im)
                textures.append(ph)
            return textures

    def bind(self):
        # bonds input to the tkinter
        self.master.bind("<Right>", self.arrowKey)
        self.master.bind("<Left>", self.arrowKey)
        self.master.bind("<Up>", self.arrowKey)
        self.master.bind("<Down>", self.arrowKey)
        self.master.bind("<Key>", self.key)
        self.canvas.bind("<Button-1>", self.mouse)
        self.canvas.bind("<Motion>", self.moved)

    def handleGameOver(self):
        # in case game is over and user solved
        if not self.robotSolved and self.gameOver:
            if tk.messagebox.askyesno(title="Congrats!!!",
                                      message="You solved the puzzle in " +
                                      str(self.t.getTotal()) +
                                      " seconds. Using " +
                                      str(self.numberOfMoves) +
                                      " moves. Do you want to play again?"):
                self.model.reInit()
                self.gameOver = False
                self.onPause = True
                # self.shuffleBoard()
                self.t.stop()
                self.t.delete()
                self.startGame()
                return

    def draw(self):
        # draws objects on the canvas
        if self.onMenu:
            return
        else:
            self.tiles = self.getTiles(self.n)
            self.drawTiles()
        if self.model.isGameOver() and not self.onPause:
            self.gameOver = True
            self.t.stop()
            self.handleGameOver()


#  Move input handlers

    def key(self, event):
        c = event.char.upper()
        if c in ["W", "A", "S", "D"]:
            dirs = {"A": "L", "S": "D", "W": "U", "D": "R"}
            if self.drawMoveDirection(dirs[c]):
                # run timer only after first move
                if self.numberOfMoves == 0:
                    self.t.run()
                self.numberOfMoves += 1
                if self.model.isGameOver() and not self.onPause:
                    self.gameOver = True
                    self.t.stop()

    def arrowKey(self, event):
        d = event.keysym[0]
        if d in ["U", "D", "L", "R"]:
            if self.drawMoveDirection(d):
                if self.numberOfMoves == 0:
                    print("run")
                    self.t.run()
                self.numberOfMoves += 1
                if self.model.isGameOver() and not self.onPause:
                    self.gameOver = True
                    self.t.stop()

    def getPos(self, x):
        pos = math.floor(x / self.size)
        if pos < 0:
            pos = 0
        if pos >= self.n:
            pos -= 1
        return pos

    def drawMoveDirection(self, direction):
        if self.onPause:
            return
        res = self.model.moveDirection(direction)
        self.master.after(0, self.draw)
        return res

    def mouse(self, event):
        # Mouse click handler general
        if self.onMenu:
            self.menuClick(event)
            return
        else:
            this = self.canvas.find_withtag(tk.CURRENT)
            foundButton = [
                name for name in self.iconList
                if self.icons[name]["tag"] == this[0]
            ]
            if foundButton:
                self.iconClickHandle(foundButton[0])
                return
        # transposing when getting point

        y = self.getPos(event.x)
        x = self.getPos(event.y) if event.y < self.w else -1
        if x < 0:
            return
        if not self.onPause and not self.onPauseMenu and (x, y !=
                                                          self.model.empty):
            if self.model.moveByBlock(x, y):
                if self.numberOfMoves == 0:
                    self.t.run()
                self.numberOfMoves += 1
            self.master.after(0, self.draw)

    def getTiles(self, n):
        board = self.model.getBoard()
        tiles = copy.deepcopy(board)  # copying to have exact same dimensions

        for i in range(len(board)):
            for j in range(len(board[i])):
                newTile = Tile(self.canvas, board[i][j], self.type,
                               j * self.size, i * self.size, self.size,
                               self.textures)
                tiles[i][j] = newTile
        return tiles

    def drawTiles(self):
        self.canvas.delete("all")
        self.setBG()
        self.setIcons()
        for row in self.tiles:
            for tile in row:
                tile.display()

    def moved(self, event):
        # Event hover handler on mouse move
        if self.onMenu:
            return
        this = self.canvas.find_withtag(tk.CURRENT)
        if this:
            found = [
                name for name in self.iconList
                if self.icons[name]["tag"] == this[0]
            ]
            if found:
                name = found[0]
                self.icons[name]["state"] = True
                self.canvas.itemconfig(self.icons[name]["tag"],
                                       image=self.icons[name]["on"])
            else:
                for name in self.iconList:
                    if self.icons[name]["state"]:
                        self.canvas.itemconfig(self.icons[name]["tag"],
                                               image=self.icons[name]["off"])
            # add here main menu icon handler

    def iconClickHandle(self, name):
        # down menu handlers
        if name == "restart":
            self.model.reInit()
            self.gameOver = False
            self.onPause = True
            # self.shuffleBoard()
            self.t.stop()
            self.t.delete()
            self.startGame()
            return
        if name == "bot":
            self.robotSolved = True
            s = Solver(self.model, self.n)
            self.loading = True
            solution, time = s.getSolution()
            self.loading = False
            print(time)
            self.shuffleAnimaion(solution)
        if name == "home":
            self.onMenu = True
            self.showMenu()
            self.t.stop()
        if name == "pause":
            self.onPauseMenu = True
            self.showPause()
            self.t.stop()
        if name == "play":
            self.onPauseMenu = False
            self.t.resume()
            self.draw()
            # self.t.resume()

    def setUpMenu(self):
        # creates menu objects
        self.menu = {}
        self.mi = {}
        self.mi["3"] = Icon(3, "3on", offPath="3off", resize=(90, 90))
        self.mi["4"] = Icon(4,
                            "4on",
                            offPath="4off",
                            resize=(90, 90),
                            state=True)
        self.mi["5"] = Icon(5, "5on", offPath="5off", resize=(90, 90))

        self.mi["C"] = Icon(1,
                            "onC",
                            offPath="offC",
                            resize=(130, 65),
                            state=True)
        self.mi["I"] = Icon(2, "Ion", offPath="Ioff", resize=(130, 65))

    def showMenu(self):
        # displays menu
        self.setBG()
        x = 50
        y = 50
        s = 400
        self.canvas.create_text(250,
                                y + 40,
                                text="Choose the size of the board",
                                font="Helvetica 16")
        x = 50
        y = y + 70
        size = 90
        padding = 50
        margin = (s - 3 * size - 2 * padding) // 2
        font = "Helvetica 40 bold"

        x0, y0 = x + padding, y
        cur = self.mi["3"]
        cur.setTag(
            self.canvas.create_image(x0, y0, image=cur.get(), anchor='nw'))

        x0, y0 = x + padding + margin + size, y
        cur = self.mi["4"]
        cur.setTag(
            self.canvas.create_image(x0, y0, image=cur.get(), anchor='nw'))

        x0, y0 = x + padding + 2 * (margin + size), y
        cur = self.mi["5"]
        cur.setTag(
            self.canvas.create_image(x0, y0, image=cur.get(), anchor='nw'))

        y = y + size + padding
        self.canvas.create_text(250,
                                y - 20,
                                text="Choose the regime of the game",
                                font="Helvetica 16")

        size = 130
        margin = s - 2 * size - 2 * padding

        x0, y0 = x + padding, y
        cur = self.mi["C"]
        cur.setTag(
            self.canvas.create_image(x0, y0, image=cur.get(), anchor='nw'))
        x0, y0 = x + padding + margin + size, y
        cur = self.mi["I"]
        cur.setTag(
            self.canvas.create_image(x0, y0, image=cur.get(), anchor='nw'))
        self.menu["start"] = self.canvas.create_image(90,
                                                      370,
                                                      image=self.run["on"],
                                                      anchor='nw')

    def menuClick(self, event):
        # handle menu clicks using tags in canvas
        this = self.canvas.find_withtag(tk.CURRENT)
        if this:
            # handle number button clicks
            if this[0] == self.mi["3"].getTag():
                self.mi["3"].toggle()
                self.n = 3
            if this[0] == self.mi["4"].getTag():
                self.mi["4"].toggle()
                self.n = 4
            if this[0] == self.mi["5"].getTag():
                self.mi["5"].toggle()
                self.n = 5
            for i in ["3", "4", "5"]:
                if str(self.n) != i:
                    self.mi[i].turnOff()
            if this[0] == self.mi["C"].getTag():
                print("num")
                self.type = NUMBERS
                self.mi["I"].turnOff()
                self.mi["C"].turnOn()
            elif this[0] == self.mi["I"].getTag():
                if self.getImagePath():
                    self.type = NUMBERS + 1
                    self.mi["I"].turnOn()
                    self.mi["C"].turnOff()
            elif this[0] == self.menu["start"]:
                print("starting")
                self.onMenu = False
                self.size = self.w // self.n
                self.textures = self.loadTextures(self.imagePath)
                self.startGame()
                return
            self.showMenu()

    def getImagePath(self):
        # promt image dialog
        f = tkinter.filedialog.askopenfilename(
            parent=self.master,
            initialdir='C:/Users/User/Desktop/FALL 2020/15112/fifteen-puzzle>',
            title='Choose file',
            filetypes=[('png images', '.png'), ('gif images', '.gif'),
                       ('jpg images', '.jpg')])
        if f:
            self.imagePath = f
            print(self.imagePath)
            return True
        else:
            print("You didn't choose the image.")
            return False

    def showPause(self):
        # draws pause menu
        self.menu["bg"] = self.canvas.create_image(0,
                                                   0,
                                                   image=self.bgPause,
                                                   anchor='nw')
        self.icons["play"]["tag"] = self.canvas.create_image(
            195, 195, image=self.icons["play"]["off"], anchor="nw")

    # Image utils

    def crop(self, infile):
        # crops an image into n^2 parts and return array of images
        im = Image.open(infile)
        width, height = im.size
        imgwidth = self.w
        imgheight = imgwidth * height // width
        im = im.resize((imgwidth, imgheight), Image.ANTIALIAS)
        cropped = []
        for i in range(self.n):
            for j in range(self.n):
                box = (j * self.size, i * self.size, (j + 1) * self.size,
                       (i + 1) * self.size)
                piece = im.crop(box)
                img = Image.new('RGB', (self.size, self.size), 255)
                img.paste(piece)
                cropped.append(img)
        return cropped

    @staticmethod
    def reduce_opacity(im, opacity):
        """Returns an image with reduced opacity."""
        # from https://stackoverflow.com/questions/61271072/how-can-i-solve-python-3-pil-putalpha-problem
        assert opacity >= 0 and opacity <= 1
        if im.mode != 'RGBA':
            im = im.convert('RGBA')
        else:
            im = im.copy()
        alpha = im.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
        im.putalpha(alpha)
        return im

    # from https://stackoverflow.com/questions/44099594/how-to-make-a-tkinter-canvas-rectangle-with-rounded-corners
    def round_rect(self, x1, y1, x2, y2, fill="red", radius=25, **kw):
        # draws rounded rectangles
        points = [
            x1 + radius, y1, x1 + radius, y1, x2 - radius, y1, x2 - radius, y1,
            x2, y1, x2, y1 + radius, x2, y1 + radius, x2, y2 - radius, x2,
            y2 - radius, x2, y2, x2 - radius, y2, x2 - radius, y2, x1 + radius,
            y2, x1 + radius, y2, x1, y2, x1, y2 - radius, x1, y2 - radius, x1,
            y1 + radius, x1, y1 + radius, x1, y1
        ]
        return self.canvas.create_polygon(points,
                                          fill=fill,
                                          width=2,
                                          smooth=True,
                                          **kw)
class App(tk.Toplevel):
    def __init__(self, master, seed, friend, comm):
        super().__init__(master)
        self.master = master
        self.type = 1
        self.n = 4
        self.w = 500
        self.h = self.w + 100
        self.canvas = tk.Canvas(self, width=self.w, height=self.h)
        self.seed = seed
        self.comm = comm
        self.friend = friend
        self.canvas.pack()
        self.loadSources()
        self.f = False
        self.startGame()

    def loadSources(self):
        self.icons = {}
        self.textures = self.loadTextures("")
        backgrounds = ["gyr.jpg", "gyr.jpg"]
        bgPath = random.choice(backgrounds)
        im = Image.open("textures/bg/" + bgPath)
        self.bg = ImageTk.PhotoImage(im)

    def startGame(self):
        self.started = True
        self.model = GameLogic(self.n)
        self.gameOver = False
        self.onPause = True
        self.master.after(0, self.draw)
        self.numberOfMoves = 0
        self.shuffleBoard()
        self.canvas.bind("<Button-1>", self.mouse)
        self.t = Timer(self.master, self.canvas)

    def setBG(self):
        self.canvas.create_image(0, 0, image=self.bg, anchor='nw')

    def crop(self, infile):
        im = Image.open(infile)
        width, height = im.size
        imgwidth = self.w
        imgheight = imgwidth * height // width
        im = im.resize((imgwidth, imgheight), Image.ANTIALIAS)
        cropped = []
        for i in range(self.n):
            for j in range(self.n):
                box = (j * 125, i * 125, (j + 1) * 125, (i + 1) * 125)
                piece = im.crop(box)
                img = Image.new('RGB', (125, 125), 255)
                img.paste(piece)
                cropped.append(img)
        return cropped

    @staticmethod
    def reduce_opacity(im, opacity):
        """Returns an image with reduced opacity."""
        # from https://stackoverflow.com/questions/61271072/how-can-i-solve-python-3-pil-putalpha-problem
        assert opacity >= 0 and opacity <= 1
        if im.mode != 'RGBA':
            im = im.convert('RGBA')
        else:
            im = im.copy()
        alpha = im.split()[3]
        alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
        im.putalpha(alpha)
        return im

    def loadTextures(self, infile):
        infile = "textures/wood.jpg"
        alpha = int(0.5 * 255)
        fill = "#0269A4"
        fill = self.master.winfo_rgb(fill) + (alpha, )
        im = Image.new('RGBA', (123, 123), fill)
        # im = im.putalpha(128)
        ph = ImageTk.PhotoImage(im)
        number = self.n * self.n
        return [ph] * number

    def shuffleBoard(self):
        # self.seed = self.model.getShuffleSeed()
        self.shuffleAnimaion(self.seed)

    def shuffleAnimaion(self, seed):
        self.draw()
        if not seed:
            self.onPause = False
            return
        self.model.moveDirection(seed[0])
        seed = seed[1:]
        self.master.after(50, lambda seed=seed: self.shuffleAnimaion(seed))

    def handleGameOver(self):
        # actions when game is over
        if self.gameOver:
            self.timeFinished = round(self.t.getTotal(), 2)
            self.comm.sendMessage(self.friend, "W" + str(self.timeFinished))
            tk.messagebox.showinfo(message="CONGRATSSSSSSS")
            self.destroy()

    def draw(self):
        self.tiles = self.getTiles(self.n)
        self.drawTiles()
        self.putNumberOfMoves()
        if self.model.isGameOver() and not self.onPause:
            self.gameOver = True
            self.t.stop()
            self.handleGameOver()

    def getPos(self, x):
        pos = math.floor(x / 125)
        if pos < 0:
            pos = 0
        if pos >= self.n:
            pos -= 1
        return pos

    def drawMoveDirection(self, direction):
        if self.onPause:
            return
        res = self.model.moveDirection(direction)
        self.master.after(0, self.draw)
        return res

    def putNumberOfMoves(self):
        # puts number of moves into the screen
        self.canvas.create_text(100,
                                520,
                                text="Your #moves:" + str(self.numberOfMoves),
                                font="Helvetica 16")
        self.canvas.create_text(100,
                                540,
                                text="Opponent #moves:",
                                font="Helvetica 16")

    def setFriendStat(self, s):
        # get's number of moves from friend
        if self.f:
            try:
                self.canvas.delete(self.f)
            except Exception:
                pass
        self.f = self.canvas.create_text(200, 540, text=s, font="Helvetica 16")

    def mouse(self, event):
        # handles mouse input
        y = self.getPos(event.x)
        x = self.getPos(event.y) if event.y < self.w else -1
        if x < 0:
            return
        if not self.onPause and (x, y != self.model.empty):
            if self.model.moveByBlock(x, y):
                if self.numberOfMoves == 0:
                    self.t.run()
                self.numberOfMoves += 1
                self.putNumberOfMoves()
            self.master.after(0, self.draw)

    def getTiles(self, n):
        # returns tiles as a class objects
        board = self.model.getBoard()
        tiles = copy.deepcopy(board)  # copying to have exact same dimensions

        for i in range(len(board)):
            for j in range(len(board[i])):
                newTile = Tile(self.canvas, board[i][j], self.type, j * 125,
                               i * 125, 125, self.textures)
                tiles[i][j] = newTile
        return tiles

    def drawTiles(self):
        # draws tiles on the screen
        self.canvas.delete("all")
        for row in self.tiles:
            for tile in row:
                tile.display()

    def getMoves(self):
        # returns number of moves
        return self.numberOfMoves

    def getCanvas(self):
        # return canvas object
        return self.canvas