---
# System prepended metadata

title: 2024商管程式設計期末專題 - Hello Word

---

# 2024商管程式設計期末專題 - Hello Word

[附加檔案下載](https://drive.google.com/drive/folders/1TgFB0d9UnGrsBaYeqqm4klMoSwT51LvI?usp=sharing)
```
# 0601 Final ver.

# 爬蟲
from bs4 import BeautifulSoup
import requests
from urllib.request import urlopen

# GUI
import tkinter as tk
from tkinter import ttk
from collections import defaultdict
from tkinter import messagebox
from tkinter import simpledialog
from PIL import Image, ImageTk

# 遊戲與其他相關
import random
import pygame
import time
import sys
import io
import subprocess

hand_size = 7
board_size = 15
played_limit = 100
pass_conti_limit = 5
round = 1
scores = {
    'A': 1, 'B': 3, 'C': 3, 'D': 2, 'E': 1, 'F': 4, 'G': 2, 'H': 4,
    'I': 1, 'J': 8, 'K': 5, 'L': 1, 'M': 3, 'N': 1, 'O': 1, 'P': 3,
    'Q': 10, 'R': 1, 'S': 1, 'T': 1, 'U': 1, 'V': 4, 'W': 4, 'X': 8,
    'Y': 4, 'Z': 10
}

double_cnt = 10
triple_cnt = 5
sec_per_round = 45


def createmessage(message_in):
    tk.messagebox.showinfo("Output", message=message_in)

class Card:
    def __init__(self, alphabet):
        self.alphabet = alphabet

    def __str__(self):
        return self.alphabet


class Deck:
    def __init__(self):
        self.cards = []

    def __str__(self):
        cards = []
        for card in self.cards:
            cards.append(card.__str__())
        return ' '.join(cards)

    def shuffle(self):
        for i in range(len(self.cards)):
            index = random.randrange(len(self.cards))
            temp_card = self.cards[i]
            self.cards[i] = self.cards[index]
            self.cards[index] = temp_card

    def deal_a_card(self):  # 發牌
        return self.cards.pop()

    def get_card_cnt(self):
        return len(self.cards)

    def refresh_the_deck(self):
        letter_frequencies = {
            'E': 12.02, 'T': 9.10, 'A': 8.12, 'O': 7.68, 'I': 7.31, 'N': 6.95, 'S': 6.28, 'R': 6.02,
            'H': 5.92, 'D': 4.32, 'L': 3.98, 'U': 2.88, 'C': 2.71, 'M': 2.61, 'F': 2.30, 'Y': 2.11,
            'W': 2.09, 'G': 2.03, 'P': 1.82, 'B': 1.49, 'V': 0.98, 'K': 0.77, 'X': 0.15, 'Q': 0.10, 'J': 0.10, 'Z': 0.07
        }

        self.cards = []
        for alphabet, frequency in letter_frequencies.items():
            for _ in range(int(frequency * 5)):
                self.cards.append(Card(alphabet))

    def remove_a_given_card(self, alphabet):
        for card in self.cards:
            if card.alphabet == alphabet:
                target_card = card
                return target_card
        raise Exception(f'The card {alphabet} is not in the deck.')

    def add_a_card(self, alphabet):
        self.cards.append(Card(alphabet))

    def return_card(self, tmp_list):  # 移除此回合出的牌並回到手牌中
        # tmp_list = [[alphabet, row, col], [alphabet, row, col], ...]
        # 只留下非空的sublist
        tmp_list = [x for x in tmp_list if x]
        for i in range(0, len(tmp_list)):
            self.add_a_card(tmp_list[i][0])


class Board:
    REGULAR_CELL = 1
    DOUBLE_WORD_CELL = 2
    TRIPLE_WORD_CELL = 3

    def __init__(self):
        self.board = []
        for i in range(board_size):
            self.board.append([' '] * board_size)
        self.cell_multipliers = [[0 for _ in range(board_size)] for _ in range(board_size)]
        self.initialize_special_cells()

    def __str__(self):
        board_str = "   " + " ".join(f"{i:02}" for i in range(board_size)) + "\n"
        for row in range(board_size):
            board_str += f"{row:02}"
            for col in range(board_size):
                cell = self.board[row][col]
                board_str += "  " + cell
            board_str += "\n"
        return board_str

    def placeCard(self, card, row, col):
        self.board[row][col] = card.__str__()

    def removeCard(self, row, col):
        self.board[row][col] = ' '

    def find_row_start(self, tmp_list):  # 在固定row上找尋開始column
        if len(tmp_list) == 1:
            row = tmp_list[0][1]
            start = tmp_list[0][2]
        else:
            sorted_list = sorted(tmp_list, key=lambda x: (x[1], x[2]))
            row = sorted_list[0][1]
            start = sorted_list[0][2]
        while start > 0 and self.board[row][start - 1] != ' ':
            start -= 1
        return row, start

    def find_col_start(self, tmp_list):  # 固定column找尋row
        if len(tmp_list) == 1:
            col = tmp_list[0][2]
            start = tmp_list[0][1]
        else:
            sorted_list = sorted(tmp_list, key=lambda x: (x[2], x[1]))
            col = sorted_list[0][2]
            start = sorted_list[0][1]
        while start > 0 and self.board[start - 1][col] != ' ':
            start -= 1
        return col, start

    def checkRowWord(self, row, start):
        row_word = ''
        for i in range(start, board_size):
            row_word += self.board[row][i]
            if self.board[row][i] == ' ':
                break
        return row_word

    def checkColWord(self, col, start):
        col_word = ''
        for i in range(start, board_size):
            col_word += self.board[i][col]
            if self.board[i][col] == ' ':
                break
        return col_word

    def reset_round(self, tmp_list):  # 清空當回合出牌的格子
        tmp_list = [x for x in tmp_list if x]
        for i in range(len(tmp_list)):
            self.board[tmp_list[i][1]][tmp_list[i][2]] = ' '

    def initialize_special_cells(self):
        # 在棋盤上隨機分布 double_cnt 個雙倍格子和 triple_cnt 個三倍格子
        double_word_cells = random.sample(range(board_size * board_size), double_cnt)
        triple_word_cells = random.sample(range(board_size * board_size), triple_cnt)

        for i in range(board_size):
            for j in range(board_size):
                index = i * board_size + j
                if index in double_word_cells:
                    self.cell_multipliers[i][j] = self.DOUBLE_WORD_CELL
                elif index in triple_word_cells:
                    self.cell_multipliers[i][j] = self.TRIPLE_WORD_CELL
                else:
                    self.cell_multipliers[i][j] = self.REGULAR_CELL

    def get_cell_multiplier(self, row, col):
        return self.cell_multipliers[row][col]

    def calculate_word_score(self, word, tmp_list):
        base_score = sum(scores[letter.upper()] for letter in word)
        word_multiplier = 1

        # 檢查這一回合放置的字母所落在的加倍格子類型
        for alphabet, row, col in tmp_list:
            cell_multiplier = self.get_cell_multiplier(row, col)
            if cell_multiplier == self.DOUBLE_WORD_CELL:
                word_multiplier *= 2
            elif cell_multiplier == self.TRIPLE_WORD_CELL:
                word_multiplier *= 3

        return base_score * word_multiplier


class Player:
    def __init__(self, name):
        self.name = name
        self.hand = Deck()
        self.score = 0
        self.played_cnt = 0
        self.game = None

    def __str__(self):
        return f'{self.name}: {self.hand.__str__()}'

    def draw_a_card(self, deck):  # 抽牌
        if deck.get_card_cnt() == 0:
            deck.refresh_the_deck()
            deck.shuffle()
        self.hand.cards.append(deck.deal_a_card())

    def refill(self, deck):
        if self.hand.get_card_cnt() < hand_size:
            refill_cnt = hand_size - self.hand.get_card_cnt()
            for i in range(refill_cnt):
                self.draw_a_card(deck)

    def play_a_card(self, alphabet, row, col, board):
        if alphabet in self.hand.__str__() and board.board[row][col] == ' ':
            # 檢查是否有相鄰的字母
            if not self.has_adjacent_letter(row, col, board):
                print(f'{self.name}, you can only place a card adjacent to an existing letter on the board.')
                return False

            card = self.hand.remove_a_given_card(alphabet)
            board.placeCard(card, row, col)
            self.hand.cards.remove(card)
            return True  # 返回 True 表示成功放置牌
        elif alphabet in self.hand.__str__() and board.board[row][col] != ' ':
            print(f'{self.name}, the position ({row}, {col}) is already occupied.')
        else:
            print(f'{self.name},You do not have the card {alphabet} in hand.')
        return False

    def has_adjacent_letter(self, row, col, board):
        # 檢查上下左右四個位置是否有字母
        if row > 0 and board.board[row - 1][col] != ' ':
            return True
        if row < board_size - 1 and board.board[row + 1][col] != ' ':
            return True
        if col > 0 and board.board[row][col - 1] != ' ':
            return True
        if col < board_size - 1 and board.board[row][col + 1] != ' ':
            return True
        return False

class Dictionary:
    @staticmethod
    def part_of_speach(soup):
        pos = soup.find('span', class_='pos').text
        return pos

    def check_definition(soup):
        types = soup.find('ol', class_='senses_multiple')
        senses = types.find_all('li', class_='sense')  # 多個definition
        def_order = 1
        mes = []
        for s in senses:
            definition = s.find('span', class_='def').text
            mes.append(str(def_order) + ': ' + str(definition) + '\n')
            def_order += 1
        return mes

    def simple_definition(soup):
        senses = soup.find_all('li', class_='sense')  # 單個definition
        def_order = 1
        mes = []
        for s in senses:
            definition = s.find('span', class_='def').text
            mes.append(str(def_order) + ': ' + str(definition) + '\n')
            def_order += 1
        return mes

    def isValidWord(word):
        scrape_url = 'https://www.oxfordlearnersdictionaries.com/definition/english/' + word.lower()

        headers = {"User-Agent": ""}
        web_response = requests.get(scrape_url, headers=headers)

        soup = BeautifulSoup(web_response.content, 'html.parser')

        try:  # 當此字不存在於字典裡時，會多一個div class= 'results'之項目
            result = soup.find('div', class_='results').text
            mes = str('')
            mes += "\n"
            mes = mes + f'{word} is invalid! Please try another word' + '\n'
            mes += "\n"
            createmessage(message_in=mes)
            return False
        except AttributeError:  # 此字存在於字典中
            try:
                mes = str('')
                mes += "\n"
                mes = mes + f"Word available! Here's the definition of {word}" + '\n'
                mes += "\n"
                mes += word.upper() + ' (' + str(Dictionary.part_of_speach(soup)) + ')' + '\n'
                a = Dictionary.check_definition(soup)  # 把check_definition回傳的字串裝起來
                mes += ' '.join(a) + "\n"
                createmessage(message_in=mes)
            except AttributeError:  # 若該單字只有一個意思時，Oxford字典的程式編排不同
                try:
                    mes = str('')
                    mes += f"Word available! Here's the definition of {word}" + '\n'
                    a = Dictionary.simple_definition(soup)
                    mes += ' '.join(a)
                    createmessage(message_in=mes)
                except:
                    mes = str('')
                    mes += "\n"
                    mes += f'{word} is invalid! Please try another word' + '\n'
                    mes += "\n"
                    createmessage(message_in=mes)
                    return False

class Scrabble:
    def __init__(self):
        self.deck = Deck()
        self.deck.refresh_the_deck()
        self.deck.shuffle()
        self.board = Board()
        self.players = []
        self.consecutive_passes = 0
        self.round = 1

    def add_player(self, player):
        self.players.append(player)

    def submit_word(self, player, tmp_list, cards_played, word_list):
        initial_score = player.score
        if cards_played == 0:
            # 處理沒有放置字母的情況
            print('Please play at least one card.')
            return cards_played, tmp_list, word_list, initial_score

        elif cards_played == 1:
            tmp_list = tmp_list[:cards_played]
            row, startr = self.board.find_row_start(tmp_list)
            col, startc = self.board.find_col_start(tmp_list)
            wordr = self.board.checkRowWord(row, startr).strip()
            wordc = self.board.checkColWord(col, startc).strip()
            self.consecutive_passes = 0

            if len(wordr) > 1 and len(wordc) > 1:
                if Dictionary.isValidWord(wordr) == False or Dictionary.isValidWord(wordc) == False:
                    cards_played = 0
                    word_list = []
                    return cards_played, tmp_list, word_list, initial_score
                else:
                    word_list.append(wordr)
                    word_list.append(wordc)

            elif len(wordr) == 1:
                if Dictionary.isValidWord(wordc) == False:
                    cards_played = 0
                    word_list = []
                    return cards_played, tmp_list, word_list, initial_score
                else:
                    word_list.append(wordc)

            elif len(wordc) == 1:
                if Dictionary.isValidWord(wordr) == False:
                    cards_played = 0
                    word_list = []
                    return cards_played, tmp_list, word_list, initial_score
                else:
                    word_list.append(wordr)

            # 計算每個單詞的分數
            for word in word_list:
                score = self.board.calculate_word_score(word, tmp_list)
                player.score += score
                player.played_cnt += len(word)
                print(f"{player.name}, Score for playing '{word}': {score}")
                print(f"{player.name}, Total score: {player.score}")
                self.round += 0.5

            return cards_played, tmp_list, word_list, initial_score

        else:
            self.consecutive_passes = 0
            tmp_list = tmp_list[:cards_played]
            all_same_row = True
            all_same_col = True
            for i in range(0, len(tmp_list) - 1):
                if tmp_list[i][1] != tmp_list[i + 1][1]:
                    all_same_row = False
                if tmp_list[i][2] != tmp_list[i + 1][2]:
                    all_same_col = False

            if all_same_row == False and all_same_col == False:
                print("Please make sure all of your letters are in the same row or column")
                player.hand.return_card(tmp_list)
                self.board.reset_round(tmp_list)
                cards_played = 0
                word_list = []
                return cards_played, tmp_list, word_list, initial_score

            elif all_same_row == True:
                row, startr = self.board.find_row_start(tmp_list)
                word = self.board.checkRowWord(row, startr).strip()

                if Dictionary.isValidWord(word) == False:
                    cards_played = 0
                    word_list = []
                    return cards_played, tmp_list, word_list, initial_score
                else:
                    word_list.append(word)
                    for i in range(len(tmp_list)):
                        db_check_list = []
                        db_check_list.append(tmp_list[i])
                        col, startc = self.board.find_col_start(db_check_list)
                        word = self.board.checkColWord(col, startc).strip()
                        if len(word) != 1:
                            if Dictionary.isValidWord(word) == False:
                                print("One or more of your word combinations are invalid!")
                                print()
                                cards_played = 0
                                word_list = []
                                return cards_played, tmp_list, word_list, initial_score
                            else:
                                word_list.append(word)

            elif all_same_col == True:
                col, startc = self.board.find_col_start(tmp_list)
                word = self.board.checkColWord(col, startc).strip()

                if Dictionary.isValidWord(word) == False:
                    cards_played = 0
                    word_list = []
                    return cards_played, tmp_list, word_list, initial_score
                else:
                    word_list.append(word)
                    for i in range(len(tmp_list)):
                        db_check_list = []
                        db_check_list.append(tmp_list[i])
                        row, startr = self.board.find_row_start(db_check_list)
                        word = self.board.checkRowWord(row, startr).strip()
                        if len(word) != 1:
                            if Dictionary.isValidWord(word) == False:
                                print("One or more of your word combinations are invalid!")
                                print()
                                cards_played = 0
                                word_list = []
                                return cards_played, tmp_list, word_list, initial_score
                            else:
                                word_list.append(word)

            # 計算每個單詞的分數
            for word in word_list:
                score = self.board.calculate_word_score(word, tmp_list)
                player.score += score
                player.played_cnt += len(word)
                print(f"{player.name}, Score for playing '{word}': {score}")
                print(f"{player.name}, Total score: {player.score}")
                self.round += 0.5
                # 檢查玩家是否達到 50 分或以上
                if player.score >= 50:
                    self.end_game(f"{player.name} has reached 50 points or more!")
                    break
            return cards_played, tmp_list, word_list, initial_score

    def undo_play(self, player, tmp_list, cards_played):
        if cards_played == 0:
            print('You do not have any card to remove.')
            return cards_played, tmp_list, 0
        else:
            player.hand.add_a_card(tmp_list[cards_played - 1][0])
            self.board.removeCard(tmp_list[cards_played - 1][1], tmp_list[cards_played - 1][2])
            tmp_list[cards_played - 1] = []
            cards_played -= 1
            print(self.board)
            return cards_played, tmp_list, 1

    def pass_turn(self, player, cards_played, tmp_list):
        if cards_played == 0:
            print(f'{player.name} skipped his/her turn!')
            print(self.board)
            self.consecutive_passes += 1
            self.round += 0.5
            if self.consecutive_passes == pass_conti_limit:
                self.end_game()  # 連續pass達到5次,遊戲結束
            return cards_played, tmp_list
        else:
            player.hand.return_card(tmp_list)
            self.board.reset_round(tmp_list)
            print(f'{player.name} skipped his/her turn!')
            print(self.board)
            self.consecutive_passes = 0  # 有玩家出牌，重置連續pass次數
            self.round += 0.5
            return 0, [[] for i in range(hand_size)]

    def end_game(self, message=None):
        max_score = max(player.score for player in self.players)
        winners = [player for player in self.players if player.score == max_score]

        if len(winners) == 1:
            winner_name = winners[0].name
            if message:
                print(message)
                tk.messagebox.showinfo("Game Over", message)
            else:
                print(f'Game Over! The winner is {winner_name} with a score of {max_score}!')
                tk.messagebox.showinfo("Game Over", f"The winner is {winner_name} with a score of {max_score}!")
        else:
            winner_names = [winner.name for winner in winners]
            winner_names_str = " and ".join(winner_names)
            print(f'Game Over! It\'s a tie with a score of {max_score}!')
            tk.messagebox.showinfo("Game Over", f"It's a tie between {winner_names_str} with a score of {max_score}!")
        
        # 禁用所有按鈕,只留下restart可以被玩家按
        self.gui.start_button.config(state='disabled')
        self.gui.pass_button.config(state='disabled')
        self.gui.submit_button.config(state='disabled')
        self.gui.undo_button.config(state='disabled')
        self.gui.swap_button.config(state='disabled')  # 禁用 swap_button
        self.gui.restart_button.config(state='normal')

        # 自動重置遊戲
        self.reset_game()

    def get_round(self):
        return self.round
    
    def reset_game(self):
        self.deck = Deck()
        self.deck.refresh_the_deck()
        self.deck.shuffle()
        self.board = Board()
        self.consecutive_passes = 0

        for player in self.players:
            player.hand = Deck()
            player.score = 0
            player.played_cnt = 0


class ScrabbleGUI:
    def __init__(self, master):
        self.master = master
        master.title("Scrabble Game")
        pygame.mixer.init()
        pygame.mixer.music.load("music.mp3")
        pygame.mixer.music.play(-1)

        window_width = 760
        window_height = 950
        screen_width = master.winfo_screenwidth()
        screen_height = master.winfo_screenheight()
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2) - 30

        master.geometry(f"{window_width}x{window_height}+{x}+{y}")
        master.resizable(0, 0)

        self.canvas = tk.Canvas(master)
        self.scrollbar_y = ttk.Scrollbar(master, orient=tk.VERTICAL, command=self.canvas.yview)
        self.scrollbar_x = ttk.Scrollbar(master, orient=tk.HORIZONTAL, command=self.canvas.xview)
        self.canvas.configure(yscrollcommand=self.scrollbar_y.set, xscrollcommand=self.scrollbar_x.set)

        self.main_frame = ttk.Frame(self.canvas)
        self.canvas.create_window((0, 0), window=self.main_frame, anchor='nw')

        self.game = Scrabble()
        self.game.gui = self  # 將 ScrabbleGUI 實例指定給 Scrabble 的 gui 屬性
        player1 = Player('Player 1')
        player2 = Player('Player 2')
        self.game.add_player(player1)
        self.game.add_player(player2)
        self.turn_label = None
        self.create_turn_label()
        self.player_turn = 0
        self.cards_played = 0
        self.tmp_list = [[] for _ in range(hand_size)]
        self.word_list = []
        self.selected_card = None
        self.vocabulary_list = []
        self.round = 1

        tip_frame = ttk.Frame(self.main_frame, padding=5)
        tip_frame.grid(row=3, column=0, columnspan=2, sticky='nwes')
        self.tip_label = ttk.Label(tip_frame, text="", font=('Segoe UI', 12), foreground='orange', background='white')
        self.tip_label.pack()

        self.volume_frame = ttk.LabelFrame(self.main_frame, padding=5, text='Volume')
        self.volume_frame.grid(row=3, column=1, columnspan=2, sticky='nwes')
        volume_slider = ttk.Scale(self.volume_frame, from_=0, to=1,orient='horizontal', value=1, command=self.volume, length=125)
        volume_slider.grid(row=0, column=0, sticky='N')
        volume_value = ttk.Label(self.volume_frame, text='100%', font=('Segoe UI', 12), foreground='black', background = '#d9d9d9')
        volume_value.grid(row=0, column=1, sticky='N', pady=0)
        self.vocabulary_labels = []

        style = ttk.Style()
        style.theme_use('alt')
        style.configure('TLabel', font=('Segoe UI', 12), foreground='royalblue', background='white', padding=6, anchor='center')
        style.configure('TButton', font=('Segoe UI', 12), foreground='Black')
        style.configure('TFrame', background='lightgray')

        style.map('TButton', foreground=[('active', 'royalblue'), ('disabled', 'gray')],
                background=[('active', 'powderblue'), ('disabled', 'lightgray')])

        self.create_board_labels()
        self.create_hand_labels()
        self.create_score_labels()
        self.create_control_buttons()
        self.create_vocabulary_labels()
        self.create_round_label()

        self.main_frame.bind('<Configure>', self.on_frame_configure)

        self.canvas.grid(row=0, column=0, sticky='nsew')
        self.scrollbar_y.grid(row=0, column=1, sticky='ns')
        self.scrollbar_x.grid(row=1, column=0, sticky='ew')

        master.grid_rowconfigure(0, weight=1)
        master.grid_columnconfigure(0, weight=1)

        self.volume_value = ttk.Label(self.volume_frame, text='100%', font=('Segoe UI', 12), foreground='black', background='#d9d9d9')
        self.volume_value.grid(row=0, column=1, sticky='N', pady=0)

        self.timer_label = ttk.Label(self.round_frame, font=('Segoe UI', 16), foreground='Black', background='lightgray')
        self.timer_label.grid(row=0, column=0, padx=(0, 5))
        self.timer = Timer(master, self.timer_label, sec_per_round, self.on_timeout)
        self.has_swapped = False



    def on_timeout(self):
        player = self.game.players[self.player_turn]
        createmessage(f"Time is up for {player.name}!")
        self.switch_turn()
        self.update_round_label()

    def start_timer(self):
        self.timer.reset()
        self.timer.start()

    def volume(self, value):
        volume = float(value)
        pygame.mixer.music.set_volume(volume)
        self.volume_value.config(text=f"{int(volume * 100)}%")

    def __del__(self):
        if pygame.mixer.get_init() is not None:
            pygame.mixer.music.stop()
            pygame.mixer.quit()

    def on_frame_configure(self, event):
        self.canvas.configure(scrollregion=self.canvas.bbox('all'))

    def create_round_label(self):
        # 創建最上方Frame區
        self.round_frame = ttk.Frame(self.main_frame, padding=10, relief='ridge')
        self.round_frame.grid(row=0, column=0, sticky='nsew')
        
        # 創建倒計時標籤
        self.timer_label = ttk.Label(self.round_frame, text="", font=('Segoe UI', 16), foreground='Black', background='lightgray')
        self.timer_label.grid(row=0, column=0, padx=(0, 5))

        #創建回合數Label
        self.round_label = ttk.Label(self.round_frame, text="R O U N D  1", font=('Segoe UI', 16, 'bold'), background='lightgray', foreground='Black')
        self.round_label.place(relx=0.5, rely=0.5, anchor='center')

    def update_round_label(self):
        self.round_label.config(text=f'R O U N D  {str(int(self.round//1))}')

    def create_turn_label(self):
        turn_frame = ttk.Frame(self.main_frame, padding=10, relief='ridge')
        turn_frame.grid(row=0, column=1, columnspan=2, sticky='nwes')
        self.turn_label = ttk.Label(turn_frame, text="", font=('Segoe UI',14), background='lightgray')
        self.turn_label.pack()

    def update_turn_label(self):
        current_player = self.game.players[self.player_turn]
        self.turn_label.config(text=f"{current_player.name}'s turn")

    def create_board_labels(self):
        if hasattr(self, 'board_frame'):
            self.board_frame.destroy()

        self.board_frame = ttk.Frame(self.main_frame, padding=10, relief='raised')
        self.board_frame.grid(row=1, column=0, sticky='nsew')
        self.board_labels = defaultdict(dict)
        for row in range(board_size):
            for col in range(board_size):
                cell_multiplier = self.game.board.get_cell_multiplier(row, col)
                if cell_multiplier == self.game.board.REGULAR_CELL:
                    label_bg = 'white'
                elif cell_multiplier == self.game.board.DOUBLE_WORD_CELL:
                    label_bg = 'lightgreen'
                elif cell_multiplier == self.game.board.TRIPLE_WORD_CELL:
                    label_bg = 'lightblue'
                label = ttk.Label(self.board_frame, text=self.game.board.board[row][col], relief='sunken', width=2, background=label_bg)
                label.grid(row=row, column=col, sticky='nwes')
                label.bind('<Button-1>', lambda event, r=row, c=col: self.play_card(r, c))
                self.board_labels[row][col] = label

    def create_hand_labels(self):
        if hasattr(self, 'hand_frame'):
            self.hand_frame.destroy()

        self.hand_frame = ttk.LabelFrame(self.main_frame, text='Your Hand', padding=6, relief='raised')
        self.hand_frame.grid(row=2, column=0, sticky='nsew', padx=2, pady=2)
        self.hand_labels = []
        for i in range(hand_size):
            label = ttk.Label(self.hand_frame, text='', relief='raised', width=2)
            label.grid(row=0, column=i, padx=2, pady=2)
            label.bind('<Button-1>', lambda event, index=i: self.select_card(index))
            self.hand_labels.append(label)
        
    def create_score_labels(self):
        score_frame = ttk.LabelFrame(self.main_frame, text='Scores', padding=5, relief='raised')
        score_frame.grid(row=1, column=1, sticky='nsew', padx=2, pady=2)  # 設置 sticky 屬性
        self.score_labels = []
        for i, player in enumerate(self.game.players):
            label = ttk.Label(score_frame, text=f'{player.name}: 0')
            label.grid(row=i, column=0, sticky='w')
            self.score_labels.append(label)

    def create_control_buttons(self):
        control_frame = ttk.Frame(self.main_frame, padding=5)
        control_frame.grid(row=2, column=1, sticky='nsew')  # 設置 sticky 屬性
        self.start_button = ttk.Button(control_frame, text='Start', command=self.start_game)
        self.start_button.pack(pady=2)
        self.restart_button = ttk.Button(control_frame, text='Restart', command=self.restart_game, state='disabled')
        self.restart_button.pack(pady=2)
        self.pass_button = ttk.Button(control_frame, text='Pass', command=self.pass_turn, state='disabled')
        self.pass_button.pack(pady=2)
        self.submit_button = ttk.Button(control_frame, text='Submit', command=self.submit_word, state='disabled')
        self.submit_button.pack(pady=2)
        self.undo_button = ttk.Button(control_frame, text='Undo', command=self.undo_play, state='disabled')
        self.undo_button.pack(pady=2)
        self.swap_button = ttk.Button(control_frame, text='Swap', command=self.swap_cards, state='disabled')
        self.swap_button.pack(pady=2)

    def set_layout_weights(self):
        self.master.rowconfigure(0, weight=1)
        self.master.rowconfigure(1, weight=1)
        self.master.columnconfigure(0, weight=1)
        self.master.columnconfigure(1, weight=1)

    def Name_Input_Window(self):

        names = ['Bob', 'Alice', 'Tim', 'Kyle', 'Nannie', 'Ruby', 'Aaliyah', 'Elena', 'Sean', 'Johnny', 'Jean', 'Nismo',
                 'Jason', 'LeBron', 'Jamal', 'Kaylee', 'Trump', '徐芷嫄', '憤怒金針菇', 'Jordan', '北投大火鍋',
                 '可莉玩家','卡卡羅', 'Karon', 'Mike', 'Will', 'James', '小波波', '日月大賢者', '公館劉德華']

        namewindow = tk.Toplevel()
        namewindow.title("ENTER PLAYER NAMES")

        # 使輸入玩家姓名頁面生成時可以置中
        window_width = 500
        window_height = 200
        screen_width = namewindow.winfo_screenwidth()
        screen_height = namewindow.winfo_screenheight()
        namewindow.resizable(0, 0)
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2) - 30
        namewindow.geometry(f"{window_width}x{window_height}+{x}+{y}")

        
        l2 = tk.Label(namewindow, text="Player 1: ", font=('Segoe UI', 14))
        l3 = tk.Label(namewindow, text="Player 2: ", font=('Segoe UI', 14))
        emptyl = tk.Label(namewindow)
        footnote = tk.Label(namewindow, text="Click R to use a randomly generated name", font=('Segoe UI', 12))

        l2.grid(row=1, column=0, padx=20, pady=5)
        l3.grid(row=2, column=0, padx=20, pady=5)
        footnote.grid(row=3, column=1)
        emptyl.grid(row=4, column=1, columnspan=300)

        p1id = tk.StringVar()
        p2id = tk.StringVar()
        p1 = tk.Entry(namewindow, textvariable=p1id, font=('Segoe UI', 14))
        p2 = tk.Entry(namewindow, textvariable=p2id, font=('Segoe UI', 14))
        p1.grid(row=1, column=1, columnspan=1)
        p2.grid(row=2, column=1, columnspan=1)

        def submit():
            if p1id.get().isspace() == True or len(p1id.get()) == 0 or p2id.get().isspace() == True or len(
                    p2id.get()) == 0:
                emptyl.config(text="Player name cannot be blank!", font=('Segoe UI', 14), fg='red')
            elif p1id.get() == p2id.get():
                emptyl.config(text="Player names cannot be the same!", font=('Segoe UI', 14), fg='red')
            else:
                player1 = p1id.get()
                player2 = p2id.get()
                player_count = 1
                for player in self.game.players:
                    if player_count == 1:
                        player.name = player1
                    else:
                        player.name = player2
                    player_count += 1

                for player in self.game.players:
                    player.refill(self.game.deck)

                 # 在棋盤正中間放置一個隨機字母
                center_row = board_size // 2
                center_col = board_size // 2
                random_letter = random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
                self.game.board.placeCard(Card(random_letter), center_row, center_col)

                # 一系列更新ＧUI玩家名稱的動作
                self.update_scores()
                self.update_hands()
                self.update_board()
                self.update_turn_label()
                namewindow.destroy()
                # 啟動計時器
                self.timer.start()

        def cancel():
            namewindow.destroy()
            self.restart_game()

        def random_name1():
            random_num = random.randint(0, len(names) - 1)
            p1id.set(names[random_num])

        def random_name2():
            random_num = random.randint(0, len(names) - 1)
            p2id.set(names[random_num])

        b1 = ttk.Button(namewindow, text="Submit", command=submit, style='custom.TButton')
        b2 = ttk.Button(namewindow, text="Cancel", command=cancel)
        b3 = ttk.Button(namewindow, command=random_name1, text="R", width=4)
        b4 = ttk.Button(namewindow, text="R", command=random_name2, width=4)

        b1.grid(row=5, column=1, sticky=tk.E)
        b2.grid(row=5, column=1, sticky=tk.W)
        b3.grid(row=1, column=2, sticky=tk.W)
        b4.grid(row=2, column=2, sticky=tk.W)

    def welcome_page(self):
        win = tk.Toplevel()

        rule1 = Image.open('rule1.png').resize((600,550))
        rule1tk = ImageTk.PhotoImage(rule1)
        rule2 = Image.open('rule2.png').resize((600,550))
        rule2tk = ImageTk.PhotoImage(rule2)

        self.pic_frame = ttk.Frame(win)
        self.pic_frame.grid(row=0, column=0)

        self.button_frame = ttk.Frame(win, padding=3)
        self.button_frame.grid(row=1, column=0)

        win.title("How to Play")
        window_width = 600
        window_height = 600
        screen_width = win.winfo_screenwidth()
        screen_height = win.winfo_screenheight()
        win.resizable(0, 0)
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2) - 30
        win.geometry(f"{window_width}x{window_height}+{x}+{y}")
        win.config(background='lightgray')
        win.resizable(0,0)

        def tab1():
            def tab2():
                def confirm():
                    win.destroy()
                    self.select_difficulty()

                l1.destroy()
                b1_2.config(state='normal')
                b1.config(text='Confirm', command=confirm)  # 修改這行代碼

                l2 = tk.Label(self.pic_frame, image=rule2tk)
                l2.grid(row=0, column=0)

                def back():
                    l2.destroy()
                    b2.destroy()
                    tab1()

                b2 = ttk.Button(self.button_frame, text='Back', command=back)
                b2.grid(row=0, column=0)

            l1 = tk.Label(self.pic_frame, image=rule1tk)
            l1.grid(row=0, column=0)

            b1 = ttk.Button(self.button_frame, text='Next', command=tab2)
            b1_2 = ttk.Button(self.button_frame, text='Back', state='disabled')
            b1.grid(row=0, column=1)
            b1_2.grid(row=0, column=0)
        tab1()

    def select_difficulty(self):
        difficulty_window = tk.Toplevel()
        difficulty_window.title("Select Difficulty")

        window_width = 200
        window_height = 180
        screen_width = difficulty_window.winfo_screenwidth()
        screen_height = difficulty_window.winfo_screenheight()
        difficulty_window.resizable(0, 0)
        x = (screen_width // 2) - (window_width // 2)
        y = (screen_height // 2) - (window_height // 2) - 30
        difficulty_window.geometry(f"{window_width}x{window_height}+{x}+{y}")

        difficulty_frame = ttk.Frame(difficulty_window, padding=20)
        difficulty_frame.grid(row=0, column=0)

        easy_button = ttk.Button(difficulty_frame, text="Easy", command=lambda: self.set_difficulty("easy", difficulty_window))
        easy_button.grid(row=0, column=0, padx=10, pady=10)

        medium_button = ttk.Button(difficulty_frame, text="Medium", command=lambda: self.set_difficulty("medium", difficulty_window))
        medium_button.grid(row=1, column=0, padx=10, pady=10)

        hard_button = ttk.Button(difficulty_frame, text="Hard", command=lambda: self.set_difficulty("hard", difficulty_window))
        hard_button.grid(row=2, column=0, padx=10, pady=10)

    def set_difficulty(self, difficulty, window):
        global board_size, hand_size, sec_per_round

        if difficulty == "easy":
            board_size = 11
            hand_size = 9
            sec_per_round = 80
        elif difficulty == "medium":
            board_size = 13
            hand_size = 7
            sec_per_round = 60
        elif difficulty == "hard":
            board_size = 15
            hand_size = 5
            sec_per_round = 45

        self.game.board = Board()  # 創建新的棋盤物件
        self.timer.duration = sec_per_round  # 設置新的回合時間
        self.create_board_labels()  # 重新創建棋盤標籤
        self.create_hand_labels()  # 重新創建手牌標籤
        window.destroy()  # 關閉難易度視窗
        self.Name_Input_Window()  # 進入輸入玩家姓名頁面



    def start_game(self):
        self.button_pressed = True  # 設置按鈕按下標誌位
        self.welcome_page()
        round = 1

        # 在棋盤正中間放置一個隨機字母
        center_row = board_size // 2
        center_col = board_size // 2
        random_letter = random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
        self.game.board.placeCard(Card(random_letter), center_row, center_col)

        # 更新GUI棋盤顯示
        self.update_board()

        self.update_hands()
        self.update_turn_label()
        self.start_button.config(state='disabled')
        self.restart_button.config(state='normal')
        self.pass_button.config(state='normal')
        self.submit_button.config(state='normal')
        self.undo_button.config(state='normal')
        self.swap_button.config(state='normal')
        # 啟動計時器
        self.timer.start()

    def restart_game(self):
        self.button_pressed = True  # 設置按鈕按下標誌位
        confirm = tk.messagebox.askyesno("Confirm Restart", "Are you sure you want to restart the game?")
        if confirm:
            self.game = Scrabble()
            
            player1 = Player('Player 1')
            player2 = Player('Player 2')
            self.game.add_player(player1)
            self.game.add_player(player2)
            self.player_turn = 0
            self.cards_played = 0
            self.tmp_list = [[] for _ in range(hand_size)]
            self.word_list = []
            self.selected_card = None
            self.vocabulary_list = []  # 清空vocabulary_list
            self.round = 1
            self.clear_board()
            self.update_round_label()
            self.update_scores()
            self.update_hands()
            self.update_turn_label()
            self.update_vocabulary()  # 更新vocabulary欄位
            self.start_button.config(state='normal')
            self.restart_button.config(state='disabled')
            self.pass_button.config(state='disabled')
            self.submit_button.config(state='disabled')
            self.undo_button.config(state='disabled')
            self.swap_button.config(state='disabled')
            
            # 暫停計時器
            self.timer.stop()

    def pass_turn(self):
        self.button_pressed = True  # 設置按鈕按下標誌位        
        player = self.game.players[self.player_turn]
        self.cards_played, self.tmp_list = self.game.pass_turn(player, self.cards_played, self.tmp_list)
        self.tip_label.config(text=f'{player.name} has chosen to skip!', foreground='red')
        self.update_board()
        self.update_hands()
        self.timer.reset()  # 重置計時器
        if self.game.consecutive_passes == pass_conti_limit:
            self.game.end_game()  # 連續pass達到4次，遊戲結束
            self.reset_gui()  # 重置GUI
        else:
            self.switch_turn()

    def __del__(self):
        # 停止播放音樂並退出 Pygame 混音器
        pygame.mixer.music.stop()
        pygame.mixer.quit()

    def reset_gui(self):
        self.player_turn = 0
        self.cards_played = 0
        self.round = 1
        self.tmp_list = [[] for _ in range(hand_size)]
        self.word_list = []
        self.selected_card = None
        self.vocabulary_list = []
        self.clear_board()
        self.update_scores()
        self.update_hands()
        self.update_turn_label()
        self.update_vocabulary()

    def create_vocabulary_labels(self):
        vocabulary_frame = ttk.LabelFrame(self.main_frame, text='Vocabulary', padding=5, relief='raised')
        vocabulary_frame.grid(row=1, column=2, rowspan=2, sticky='nsew', padx=2, pady=2)

        # 創建Canvas和Scrollbar
        vocabulary_canvas = tk.Canvas(vocabulary_frame)
        vocabulary_scrollbar = ttk.Scrollbar(vocabulary_frame, orient="vertical", command=vocabulary_canvas.yview)
        vocabulary_canvas.configure(yscrollcommand=vocabulary_scrollbar.set)

        # 創建一個Frame來放置單詞標籤
        self.vocabulary_inner_frame = ttk.Frame(vocabulary_canvas)
        vocabulary_canvas.create_window((0, 0), window=self.vocabulary_inner_frame, anchor='nw')

        # 將Canvas和Scrollbar放置在vocabulary_frame中
        vocabulary_canvas.pack(side="left", fill="both", expand=True)
        vocabulary_scrollbar.pack(side="right", fill="y")

        words_per_column = 15
        num_words = len(self.vocabulary_list)
        num_columns = (num_words + words_per_column - 1) // words_per_column

        self.vocabulary_labels = []  # 清空vocabulary_labels列表

        for i in range(num_columns):
            for j in range(words_per_column):
                index = i + j * num_columns
                if index < num_words:
                    word = self.vocabulary_list[index]
                    label = ttk.Label(self.vocabulary_inner_frame, text=word)
                    label.grid(row=j, column=i, padx=5, pady=2)
                    self.vocabulary_labels.append(label)  # 將新建的標籤添加到vocabulary_labels列表中

        # 更新vocabulary_inner_frame的大小
        self.vocabulary_inner_frame.update_idletasks()
        width = self.vocabulary_inner_frame.winfo_width()
        height = self.vocabulary_inner_frame.winfo_height()
        vocabulary_canvas.config(width=width, height=height)

        # 更新Canvas的滾動區域
        self.vocabulary_inner_frame.bind("<Configure>", lambda e: vocabulary_canvas.configure(
            scrollregion=vocabulary_canvas.bbox("all")))

    def submit_word(self):
        self.button_pressed = True  # 設置按鈕按下標誌位
        player = self.game.players[self.player_turn]
        self.cards_played, self.tmp_list, self.word_list, self.initial_score = self.game.submit_word(player,
                                                                                                     self.tmp_list,
                                                                                                     self.cards_played,
                                                                                                     self.word_list)
        initial_score = self.initial_score

        if self.cards_played == 0:
            # 如果沒有成功放置手牌，歸還手牌並且移除棋盤上出的牌
            for i in range(len(self.tmp_list)):
                if self.tmp_list[i]:
                    row, col = self.tmp_list[i][1], self.tmp_list[i][2]
                    self.game.board.removeCard(row, col)
                    player.hand.add_a_card(self.tmp_list[i][0])
            self.tmp_list = [[] for i in range(hand_size)]
            self.update_board()
            self.update_hands()
            self.update_timer()
        else:
            for word in self.word_list:
                if word not in self.vocabulary_list:
                    self.vocabulary_list.append(word)
            words_played = len(self.word_list)
            score_diff = player.score - initial_score
            if words_played == 1:
                self.tip_label.config(text=f'{player.name} got {score_diff} points for spelling {words_played} word!',
                                      foreground='limegreen')
            else:
                self.tip_label.config(text=f'{player.name} got {score_diff} points for spelling {words_played} words!',
                                      foreground='limegreen')

            self.update_vocabulary()
            self.update_scores()
            self.update_hands()
            self.switch_turn()
            self.vocabulary_inner_frame.update_idletasks()
            width = self.vocabulary_inner_frame.winfo_width()
            if width > self.master.winfo_width():
                self.master.geometry(f"{width + 20}x{self.master.winfo_height()}")

    def update_vocabulary(self):
        # 清空vocabulary_inner_frame中的所有標籤
        for widget in self.vocabulary_inner_frame.winfo_children():
            widget.destroy()

        words_per_column = 15
        num_words = len(self.vocabulary_list)
        num_columns = (num_words + words_per_column - 1) // words_per_column

        self.vocabulary_labels = []  # 清空vocabulary_labels列表

        for i in range(num_columns):
            for j in range(words_per_column):
                index = i + j * num_columns
                if index < num_words:
                    word = self.vocabulary_list[index]
                    label = ttk.Label(self.vocabulary_inner_frame, text=word)
                    label.grid(row=j, column=i, padx=5, pady=2)
                    self.vocabulary_labels.append(label)  # 將新建的標籤添加到vocabulary_labels列表中

        # 更新vocabulary_inner_frame的大小
        self.vocabulary_inner_frame.update_idletasks()
        width = self.vocabulary_inner_frame.winfo_width()
        height = self.vocabulary_inner_frame.winfo_height()
        self.vocabulary_inner_frame.master.config(width=width, height=height)

        # 更新Canvas的滾動區域
        self.vocabulary_inner_frame.bind("<Configure>", lambda e: self.vocabulary_inner_frame.master.configure(
            scrollregion=self.vocabulary_inner_frame.master.bbox("all")))

    def set_layout_weights(self):
        self.master.rowconfigure(0, weight=1)
        self.master.rowconfigure(1, weight=1)
        self.master.columnconfigure(0, weight=1)
        self.master.columnconfigure(1, weight=1)
        self.master.columnconfigure(2, weight=1)

    def start_timer(self):
        self.timer_running = True
        self.update_timer()  # 在這裡立即呼叫 update_timer()

    def draw_timer(self, surface):
        surface.blit(self.timer_text, self.timer_rect)

    def update_timer(self):
        player = self.game.players[self.player_turn]
        if self.button_pressed:
            self.button_pressed = False
            return

        if self.timer.timer_running:
            if self.timer.remaining_time > 0:
                minutes, seconds = divmod(self.timer.remaining_time, 60)
                time_string = f"{minutes:02d}:{seconds:02d}"
                self.timer_label.config(text=time_string)
                if self.timer.remaining_time <= 10:
                    self.timer_label.config(foreground="red")
                else:
                    self.timer_label.config(foreground="black")
                self.master.after(1000, self.update_timer)
            else:
                createmessage(f"Time is up for {player.name}!")
                self.timer.stop()
                self.switch_turn()
                self.update_round_label()

    def switch_turn(self):
        current_player = self.game.players[self.player_turn]
        current_player.refill(self.game.deck)
        self.player_turn = (self.player_turn + 1) % len(self.game.players)
        self.update_hands()
        self.cards_played = 0
        self.tmp_list = [[] for _ in range(hand_size)]
        self.word_list = []
        self.round += 0.5
        self.update_turn_label()
        self.update_round_label()
        self.selected_card = None
        self.timer.reset()  # 重置倒計時
        self.timer.start()  # 啟動計時器
        self.has_swapped = False  # 重置 has_swapped 標誌變數
        self.swap_button.config(state='normal')  # 將 swap_button 重新設置為 normal 狀態

    def clear_board(self):
        for row in range(board_size):
            for col in range(board_size):
                self.board_labels[row][col].config(text='')

    def update_board(self):
        for row in range(board_size):
            for col in range(board_size):
                self.board_labels[row][col].config(text=self.game.board.board[row][col])

    def update_hands(self):
        current_player = self.game.players[self.player_turn]
        for j, label in enumerate(self.hand_labels):
            if j < len(current_player.hand.cards):
                label.config(text=current_player.hand.cards[j].alphabet)
            else:
                label.config(text='')

    def update_scores(self):
        for i, player in enumerate(self.game.players):
            self.score_labels[i].config(text=f'{player.name}: {player.score}')

    def play_card(self, row, col):
        if self.selected_card is None:
            return
        player = self.game.players[self.player_turn]
        alphabet = self.selected_card.alphabet
        if player.play_a_card(alphabet, row, col, self.game.board):
            self.cards_played += 1
            self.tmp_list[self.cards_played - 1].append(alphabet)
            self.tmp_list[self.cards_played - 1].append(row)
            self.tmp_list[self.cards_played - 1].append(col)
            self.update_board()
            self.update_hands()
            self.selected_card = None
            for label in self.hand_labels:
                label.config(relief='raised')
        else:
            self.tip_label.config(text='Invalid move! Please try again.', foreground='red')
    def swap_cards(self):
        player = self.game.players[self.player_turn]
        if self.has_swapped:
            self.tip_label.config(text='You can only swap cards once per turn!', foreground='red')
            return

        if not hasattr(self, 'selected_swap_cards'):
            self.selected_swap_cards = []
            self.swap_button.config(text='Confirm Swap')
            self.tip_label.config(text='Select cards to swap...', foreground='blue')
        else:
            opponent = self.game.players[(self.player_turn + 1) % len(self.game.players)]
            # 隨機選擇對手手牌中等量的卡牌進行交換
            opponent_swap_cards = random.sample(opponent.hand.cards, len(self.selected_swap_cards))
            # 將玩家選擇的卡牌與對手的卡牌進行交換
            for player_card, opponent_card in zip(self.selected_swap_cards, opponent_swap_cards):
                player.hand.cards.remove(player_card)
                opponent.hand.cards.remove(opponent_card)
                player.hand.cards.append(opponent_card)
                opponent.hand.cards.append(player_card)
            self.selected_swap_cards = []
            self.swap_button.config(text='Swap')
            self.tip_label.config(text=f'{player.name} swapped cards with {opponent.name}!', foreground='blue')
            self.update_hands()
            # 取消選中卡牌的反白顯示
            for label in self.hand_labels:
                label.config(relief='raised', background='white')
            # 刪除selected_swap_cards屬性,回到正常選牌模式
            del self.selected_swap_cards
            self.selected_card = None
            self.has_swapped = True  # 設置本輪已經使用過 swap
            self.swap_button.config(state='disabled')  # 將 swap_button 設置為 disabled 狀態

    def select_card(self, index):
        player = self.game.players[self.player_turn]
        if index < len(player.hand.cards):
            if hasattr(self, 'selected_swap_cards'):
                card = player.hand.cards[index]
                if card in self.selected_swap_cards:
                    self.selected_swap_cards.remove(card)
                    self.hand_labels[index].config(relief='raised', background='white')
                else:
                    self.selected_swap_cards.append(card)
                    self.hand_labels[index].config(relief='sunken', background='powderblue')
                # 如果玩家選擇了卡牌,將Swap按鈕的文本更新為'Confirm Swap'
                if len(self.selected_swap_cards) > 0:
                    self.swap_button.config(text='Confirm Swap')
                else:
                    self.swap_button.config(text='Swap')
            else:
                self.selected_card = player.hand.cards[index]
                for label in self.hand_labels:
                    label.config(relief='raised', background='white')
                self.hand_labels[index].config(relief='sunken', background='powderblue')

    def undo_play(self):
        self.button_pressed = True  # 設置按鈕按下標誌位
        Success = 1
        player = self.game.players[self.player_turn]
        self.cards_played, self.tmp_list, Success = self.game.undo_play(player, self.tmp_list, self.cards_played)
        if Success == 1:
            self.tip_label.config(text='')
            self.update_board()
            self.update_hands()
            self.update_timer()
        else:
            self.tip_label.config(text='You have not played a card yet!', foreground='red')
            self.update_timer()

class Timer:
    def __init__(self, master, label, duration, on_timeout):
        self.master = master
        self.label = label
        self.duration = duration
        self.on_timeout = on_timeout
        self.remaining_time = duration
        self.timer_running = False
        self.start_time = 0

    def start(self):
        self.timer_running = True
        self.start_time = time.time()
        self.update_timer()

    def stop(self):
        self.timer_running = False

    def reset(self):
        self.remaining_time = self.duration
        self.update_label()

    def update_timer(self):
        if self.timer_running:
            elapsed_time = int(time.time() - self.start_time)
            self.remaining_time = self.duration - elapsed_time
            if self.remaining_time > 0:
                self.update_label()
                self.master.after(1000, self.update_timer)
            else:
                self.timer_running = False
                self.on_timeout()

    def update_label(self):
        minutes, seconds = divmod(self.remaining_time, 60)
        time_string = f"{minutes:02d}:{seconds:02d}"
        self.label.config(text=time_string)
        if self.remaining_time <= 10:
            self.label.config(foreground="red")
        else:
            self.label.config(foreground="black")


if __name__ == "__main__":
    root = tk.Tk()
    app = ScrabbleGUI(root)
    root.protocol("WM_DELETE_WINDOW", lambda: (app.__del__(), root.destroy()))  # 在關閉視窗時停止音樂播放
    root.mainloop() 
```