# 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)
# 一系列更新GUI玩家名稱的動作
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()
```