class GameScreen(Screen): """ Main screen of the program. Contains the game's grid, a frame to show flag numbers, a frame to show the timer of the match, a frame showing the match's state, one button to start over/play again and one to return to the SetUpScreen. """ def __init__(self, window): super().__init__(window) #gridsize:fontsize for button based on grid's sizes self.gsize_fsize = { 4: 100, 5: 80, 6: 65, 7: 55, 8: 45, 9: 40, 10: 35, 11: 30, 12: 25, 13: 21, 14: 18, 15: 15, 16: 12, 17: 10, 18: 8, 19: 7, 20: 6 } #additional left pady for the col=0 buttons based on the grid's width #works fine with squared grids, however fails with rectangular ones self.w_padyleft = { 4: 50, 5: 40, 6: 35, 7: 30, 8: 25, 9: 15, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0 } self.frm_grid = tk.Frame(self.master, width=self.master_w * 0.8, height=self.master_h, bg=self.master["bg"]) self.frm_info = tk.Frame(self.master, width=self.master_w * 0.2, height=self.master_h, bg=self.master["bg"]) self.frm_flag = tk.Frame(self.frm_info, width=self.master_w * 0.2, height=self.master_h * 0.2, bg=self.master["bg"]) self.lab_flag = tk.Label(self.frm_flag, width=2, height=1, font=("Ubuntu Mono", 50), text="\u2691", bg=self.master["bg"], fg="black") self.lab_n_flags = tk.Label(self.frm_flag, width=7, height=1, font=("Ubuntu Mono", 40), text="000/000", bg=self.master["bg"]) self.frm_clock = tk.Frame(self.frm_info, width=self.master_w * 0.2, height=self.master_h * 0.2, bg=self.master["bg"]) self.lab_clock = tk.Label(self.frm_clock, width=2, height=1, font=("Ubuntu Mono", 55), text="\u231a", bg=self.master["bg"], fg="black") self.lab_time = tk.Label(self.frm_clock, width=5, height=1, font=("Ubuntu Mono", 40), text="00:00", bg=self.master["bg"]) self.frm_match = tk.Frame(self.frm_info, width=self.master_w * 0.2, height=self.master_h * 0.1, bg=self.master["bg"]) self.lab_match = tk.Label(self.frm_match, width=8, height=1, font=("Ubuntu Mono", 33, "bold"), text="WAITING", bg=self.master["bg"], fg="#f1c232", highlightbackground="black", highlightthickness=3) self.frm_options = tk.Frame(self.frm_info, width=self.master_w * 0.2, height=self.master_h * 0.2, bg=self.master["bg"]) self.but_overagain = tk.Button(self.frm_options, width=10, height=1, state="disabled", disabledforeground="black", font=("Ubuntu Mono", 26, "bold"), bg="#ffd966", highlightbackground="black", highlightthickness=2) self.but_cancel = tk.Button(self.frm_options, width=10, height=1, font=("Ubuntu Mono", 26, "bold"), text="Cancel", bg="#c63d3d", fg="black", highlightbackground="black", highlightthickness=2, activebackground="#c67474") self.frames = [self.frm_grid, self.frm_info] def overAgain(self): self.destroy() self.setNewGame(self.grid.getHeight(), self.grid.getWidth(), self.grid.getNMines()) self.show() def setNewGame(self, height, width, n_mines): self.grid = Grid(height, width, n_mines) self.time_start = 0 #serve as a control variable too self.lab_n_flags["text"] = "0/{}".format(n_mines) self.lab_time["text"] = "00:00" self.lab_match.config(fg="#f1c232", text="WAITING") self.but_overagain.config(bg="#ffd966", text="Start over", state="disabled", disabledforeground="black", command=self.overAgain) def show(self): self.frm_grid.pack(side="left") self.frm_info.pack(side="right") fsize = min(self.gsize_fsize[self.grid.getHeight()], +self.gsize_fsize[self.grid.getWidth()]) for r in range(self.grid.getHeight()): for c in range(self.grid.getWidth()): b = tk.Button(self.frm_grid, width=2, height=1, font=("Ubuntu Mono", fsize), fg="black", text=" ", activeforeground="black", bg="#6fa8dc", activebackground="#9fc5e8", highlightbackground="black", highlightthickness=2, state="normal") padx = (0, 0) pady = (0, 0) if r == 0: pady = (20, 0) elif r == self.grid.getHeight() - 1: pady = (0, 20) if c == 0: padx = (20 + self.w_padyleft[self.grid.getWidth()], 0) elif c == self.grid.getWidth() - 1: padx = (0, 20) b.grid(row=r, column=c, padx=padx, pady=pady) b.bind("<Button-1>", self.start) b.bind("<Button-3>", self.flag) self.grid.getSquare(r, c).setButton(b) self.frm_flag.pack(pady=(0, 15)) self.frm_clock.pack(pady=(15, 30)) self.frm_match.pack(pady=(30, 30)) self.frm_options.pack(pady=(30, 0)) self.lab_flag.pack() self.lab_n_flags.pack() self.lab_clock.pack() self.lab_time.pack() self.lab_match.pack(padx=2, ipadx=4, ipady=5) self.but_overagain.pack(padx=4, pady=(0, 2), ipady=5) self.but_cancel.pack(padx=4, pady=(2, 0), ipady=5) def updateTime(self): time_now = int(monotonic()) passed = time_now - self.time_start m = passed // 60 s = passed % 60 m = str(m) if m > 9 else "0" + str(m) s = str(s) if s > 9 else "0" + str(s) self.lab_time["text"] = m + ":" + s self.id_time = self.lab_time.after(995, self.updateTime) def start(self, event): self.is_start = True self.time_start = int(monotonic()) #start time self.updateTime() self.lab_match.config(fg="black", text="IN GAME") self.but_overagain.config(state="normal", fg="black", bg="#f1c232", activebackground="#ffd966") row = event.widget.grid_info()["row"] col = event.widget.grid_info()["column"] self.grid.setMines(row, col) for r in self.grid.getGrid(): for sq in r: sq.getButton().bind("<Button-1>", self.play) self.play(event) def play(self, event): """ Called when user clicks the mouse's left button. If the square clicked is a flag or is revealed, does nothing. If the square is a mine, the player loses, the grid becomes disabled and the timer stops. If the square is anything else, the game expands the grid from this square. Check if, after the expansion, the player wins. """ row = event.widget.grid_info()["row"] col = event.widget.grid_info()["column"] if self.grid.getSquare(row, col).getState() == 1 \ or self.grid.getSquare(row, col).getFlag() == "\u2691" \ and not self.is_start: return #nothing happens if self.grid.getSquare(row, col).getValue() == "\u2620": #mine self.lab_time.after_cancel(self.id_time) self.lab_match.config(fg="#c63d3d", text="YOU LOSE") self.but_overagain.config(bg="#17c651", activebackground="#65c680", text="Play again") self.grid.showAll(lose=True) self.grid.getSquare( row, col).getButton().config(disabledforeground="red") return else: #normal play self.grid.expandPosition(row, col) self.updateNFlaggedSquares() if self.hadVictory(): #victory self.lab_time.after_cancel(self.id_time) self.time_start = 0 self.grid.showAll(win=True) self.updateNFlaggedSquares() self.lab_match.config(fg="#17c651", text="YOU WIN") self.but_overagain.config(bg="#17c651", activebackground="#65c680", text="Play again") if self.is_start: self.is_start = False def updateNFlaggedSquares(self): self.lab_n_flags["text"] = "{}/{}".format( self.grid.getNFlaggedSquares(), self.grid.getNMines()) def flag(self, event): """ Called when player clicks mouse's right button. The square must be unrevealed to be flagged. If square has no flag, it gets a flag and the number of flagged squares increases. If square has a flag, it gets a question mark, and the number of flagged squares decreases. If square has a question mark, it has its flag removed. """ row = event.widget.grid_info()["row"] col = event.widget.grid_info()["column"] if self.grid.getSquare(row, col).getState() == 0: f = self.grid.getNFlaggedSquares() n = self.grid.getNMines() if self.grid.getSquare(row, col).getFlag() == "": if f < n: self.grid.getSquare(row, col).setFlag("\u2691") self.grid.addFlaggedSquare() else: self.grid.getSquare(row, col).setFlag("?") elif self.grid.getSquare(row, col).getFlag() == "\u2691": self.grid.getSquare(row, col).setFlag("?") self.grid.removeFlaggedSquare() elif self.grid.getSquare(row, col).getFlag() == "?": self.grid.getSquare(row, col).setFlag("") self.updateNFlaggedSquares() def hadVictory(self): for r in self.grid.getGrid(): for sq in r: if sq.getState() == 0 and sq.getValue() == " ": return False return True def destroy(self): if self.time_start != 0: #game started self.lab_time.after_cancel(self.id_time) for r in self.grid.getGrid(): for sq in r: sq.getButton().grid_forget() super().destroy()