def get_move(self, bag, board, dic): self.wild_letters = [] self.full_bonus = False self.is_passing = False # Change all wild tiles to letter S if '@' in self.letters: for i in range(self.letters.count('@')): self.letters[self.letters.index('@')] = 'S' self.wild_letters.append('S') words = [] word_set = set() # All the possible valid permutations of the letters for n in range(2, len(self.letters) + 1): word_set = word_set.union(self._permute(n, dic)) # Check if the word can be played validly horizontally or vertically for word in word_set: for spot in board.board.keys(): word_d = Word(spot, 'd', word, board, dic) word_r = Word(spot, 'r', word, board, dic) if word_d.validate(): words.append(word_d) if word_r.validate(): words.append(word_r) if len(words) == 0: self.is_passing = True self._pass_letters(bag) else: self.word = words[0] # Add wild tile S to word wild tile list so that it doesn't get points for i in range(len(self.wild_letters)): if 'S' in self.word.word: self.word.wild_letters.append( self.word.range[self.word.word.index('S')]) for word in words: # Add wild tile S to word wild tile list so that it doesn't get points for i in range(len(self.wild_letters)): if 'S' in word.word: word.wild_letters.append( word.range[word.word.index('S')]) # Pick the word with more points if word.calculate_total_points( ) > self.word.calculate_total_points(): self.word = word # Put back the wild letter character in the letters array for letter in self.wild_letters: self.letters[self.letters.index('S')] = '@' return self.word
class GamePage(Frame): def __init__(self, parent, options, dic='./dics/sowpods.txt'): Frame.__init__(self, parent, bg='azure') self.grid(row=0, column=0, sticky=S + N + E + W) self.dict = Dict(dic) self.bag = Bag() self.board = Board() self.set_variables() # There isn't a play_num key in the options, it is a game joined on lan if options.get('play_num', False): self.joined_lan = False self.resolve_options(options) else: self.joined_lan = True self.thread = threading.Thread(target=lh.join_lan_game, args=(options, self.queue)) self.thread.start() self.resolve_options(options) self.run() def run(self): if self.server_not_found: self.destroy() else: self.draw_main_frame() self.draw_info_frame() self.initialize_game() def set_variables(self): self.word = None self.thread = None self.winner = None self.cur_player = None self.start_tile = None self.time_up = False self.game_over = False self.chal_failed = False self.is_challenged = False # Necessary for lan game self.letters_passed = False # Necessary for lan game self.server_not_found = False # Necessary for lan game self.first_turn = True self.game_online = True self.may_proceed = True self.turns = 0 # For games against computer self.seconds = 0 self.own_mark = 0 # Necessary for lan game self.op_score = 0 # For games against computer self.pass_num = 0 self.cur_play_mark = 0 self.gui_board = {} self.used_spots = {} self.placed_tiles = {} self.rack = [] self.losers = [] self.raw_word = [] self.wild_tiles = [] self.prev_words = [] # Necessary for challenge mode self.spots_buffer = [] # Necessary for challenge mode self.empty_rack_tiles = [] self.wild_tiles_clone = [] # Necessary for challenge mode self.prev_spots_buffer = [] # Necessary for challenge mode self.queue = queue.Queue() self.bag_info = StringVar() self.time_info = StringVar() self.status_info = StringVar() self.words_info = StringVar() def resolve_options(self, options): if self.joined_lan: if not options.get('ip', None): showinfo( 'Searching...', 'Will try to find a hosted game. This might take a while depending on the computer.\n\nClick OK to start.' ) # If a server is not found, a single value False comes # from the queue causing an exception. try: self.options, self.own_mark = self.queue.get() except: self.options = options self.server_not_found = True else: self.options = options self.chal_mode = self.options.get('chal_mode', False) self.comp_mode = self.options.get('comp_mode', False) self.norm_mode = self.options.get('normal_mode', False) self.lan_mode = self.options.get('lan_mode', False) self.time_limit = self.options.get('time_limit', 0) self.point_limit = self.options.get('point_limit', 0) self.players = self.options.get('names', []) self.play_num = self.options.get('play_num', 0) self.loading = self.options.get('loading', False) self.minutes = self.time_limit def draw_main_frame(self): out_f = Frame(self, padx=30, bg='azure') out_f.pack(side=LEFT) l = Label(out_f, textvariable=self.status_info) l.config(bg='azure', fg='#FF4500', font=('times', 25, 'italic')) l.pack(side=TOP, pady=15) if self.lan_mode and not self.joined_lan: self.status_info.set('... Waiting for player(s) ...') board_f = Frame(out_f) board_f.pack(side=TOP) row_num = 0 row_name = 15 while row_num < 15: col_num = 0 col_name = 'a' while col_num < 15: tile = BoardTile(row_num, col_num, board_f) tile.bind('<1>', self.place_tile) tile.name = col_name + str(row_name) self.determine_tile_background(tile) self.gui_board[tile.name] = tile col_num += 1 col_name = chr(ord(col_name) + 1) row_num += 1 row_name -= 1 rack = Frame(out_f, pady=15, bg='azure') rack.pack(side=TOP) for i in range(7): tile = RackTile(rack) tile.bind('<1>', self.place_tile) tile['bg'] = '#BE975B' # In the normal mode, conceal the letters so others can't see if self.norm_mode: tile['fg'] = '#BE975B' self.rack.append(tile) button_f = Frame(out_f, bg='azure') button_f.pack(side=TOP) self.sub = Button(button_f, text='Submit') self.sub.config(command=self.process_word) self.sub.pack(side=LEFT, padx=5) self.pas = Button(button_f, text='Pass') self.pas.config(command=self.pass_letters) self.pas.pack(side=LEFT, padx=5) if self.chal_mode: self.chal = Button(button_f, text='Challenge') self.chal.config(command=self.challenge) self.chal.pack(side=LEFT, padx=5) if self.norm_mode: Button(button_f, text='Reveal', command=self.reveal_tile).pack() def determine_tile_background(self, tile): if tile.name in 'a1 a8 a15 h15 o15 h1 o8 o1'.split(): tile['bg'] = '#ff3300' elif tile.name in 'h8 b2 c3 d4 e5 b14 c13 d12 e11 n2 m3 l4 k5 n14 m13 l12 k11'.split( ): tile['bg'] = '#ff99cc' elif tile.name in 'b6 b10 n6 n10 f2 f6 f10 f14 j2 j6 j10 j14'.split(): tile['bg'] = '#3366ff' elif tile.name in 'a4 a12 c7 c9 d1 d8 d15 g3 g7 g9 g13 h4 h12 o4 o12 m7 m9 l1 l8 l15 i3 i7 i9 i13'.split( ): tile['bg'] = '#b3c6ff' else: tile['bg'] = '#ffd6cc' def reveal_tile(self): for tile in self.rack: tile['fg'] = 'black' def draw_info_frame(self): info_frame = Frame(self, bg='azure') info_frame.pack(side=LEFT, fill=BOTH) self.sav = Button(info_frame, text='Save Game') self.sav.config(command=self.save_game) self.sav.pack(side=TOP, pady=50) cont_f = Frame(info_frame, bg='azure') cont_f.pack(side=TOP, pady=40, fill=X) options = { 'font': ('times', 16, 'italic'), 'bg': 'azure', 'fg': '#004d00' } if self.time_limit: l = Label(cont_f, textvariable=self.time_info) l.config(font=('times', 16, 'italic'), bg='#004d00', fg='azure') l.pack(anchor=NW) Label(cont_f, textvariable=self.bag_info, **options).pack(pady=10) play_f = LabelFrame(cont_f, pady=5, padx=5, bg='azure') play_f.pack(anchor=NW) # For displaying players' names and points self.pl1_info = StringVar() Label(play_f, textvariable=self.pl1_info, **options).pack(anchor=NW) self.pl2_info = StringVar() Label(play_f, textvariable=self.pl2_info, **options).pack(anchor=NW) if self.play_num >= 3: self.pl3_info = StringVar() Label(play_f, textvariable=self.pl3_info, **options).pack(anchor=NW) if self.play_num == 4: self.pl4_info = StringVar() Label(play_f, textvariable=self.pl4_info, **options).pack(anchor=NW) Label(cont_f, text='Words:', **options).pack(anchor=NW, pady=10) # For displaying the words made and their point values m = Message(cont_f, textvariable=self.words_info) m.config(font=('times', 15, 'italic'), anchor=NW, bg='azure', fg='#004d00') m.pack(anchor=NW, fill=X) def set_word_info(self, words): message = '' for word in words: message = message + ('{} {}\n'.format(word, words[word])) if self.cur_player.full_bonus: message = message + ('\nBonus 60\n') self.cur_player.full_bonus = False self.words_info.set(message[:-1]) # Undo tile placement and word formation def undo_placement(self): for t1, t2 in zip(self.empty_rack_tiles, self.placed_tiles.values()): t1.letter.set(t2.letter.get()) t2.letter.set('') t1['bg'] = '#BE975B' self.determine_tile_background(t2) self.placed_tiles = {} self.may_proceed = False def initialize_game(self): self.check_game_over() # No need to create new players if a saved game is loaded or joined a game on lan if self.lan_mode and not self.joined_lan: self.thread = threading.Thread(target=lh.create_lan_game, args=(self.options, self.queue, self.bag)) self.thread.start() self.initialize_players() elif self.joined_lan: self.players, self.bag = self.queue.get() self.init_turn() elif self.loading: # If a saved game is being loaded, it is good to wait # for one second and load the board so that variables are set self.master.master.after(1000, self.load_board) else: self.initialize_players() def countdown(self): if self.seconds == 0: self.seconds = 59 self.minutes -= 1 else: self.seconds -= 1 if self.seconds >= 0 and self.minutes >= 0: if self.seconds > 9: seconds = str(self.seconds) else: seconds = '0' + str(self.seconds) self.time_info.set('{}:{} Left'.format(self.minutes, seconds)) self.master.master.after(1000, self.countdown) else: self.time_up = True self.end_game() def initialize_players(self): if self.lan_mode and len(self.players) < self.play_num: self.master.master.after(1000, self.initialize_players) else: # Play num in joined mode is 0 for i in range(self.play_num): pl = Player(self.players[i]) pl.draw_letters(self.bag) self.players.append(pl) # If the game is against a computer, no need for the second # iteration because its class is different if self.comp_mode: self.opponent = AIOpponent() self.opponent.draw_letters(self.bag) self.players.append(self.opponent) break # Deletes the name strings del self.players[:self.play_num] if self.lan_mode: self.queue.put(self.players) self.init_turn() # Set the words of the loaded game on the board def load_board(self): spots = [] letters = [] for spot, letter in self.board.board.items(): if re.fullmatch('[A-Z@]', letter): spots.append(spot) letters.append(letter) for spot, letter in self.gui_board.items(): if spot in spots: self.gui_board[spot].letter.set(letters[spots.index(spot)]) self.gui_board[spot].active = False self.gui_board[spot]['bg'] = '#BE975B' self.used_spots[spot] = self.gui_board[spot] for spot in spots: del self.gui_board[spot] self.init_turn() def init_turn(self): self.turns += 1 if self.lan_mode and not self.first_turn: if self.own_mark == self.cur_play_mark: self.disable_board() if self.letters_passed: self.queue.put((self.own_mark, self.bag, self.game_online)) elif not self.chal_failed: self.queue.put( (self.own_mark, self.word, self.w_range, self.placed_tiles, self.prev_spots_buffer, self.players, self.bag, self.board, self.game_online)) else: # Failed challenge self.queue.put( (self.own_mark, False, None, None, self.game_online)) # Wait till the queue is emptied by the other thread # So that the same item is not captured by this thread while not self.queue.empty(): continue elif not self.is_challenged: self.enable_board() elif self.joined_lan and self.first_turn: self.disable_board() self.placed_tiles = {} self.empty_rack_tiles = [] self.spots_buffer = [] self.start_tile = None if not self.comp_mode and not self.loading and not self.first_turn: self.cur_play_mark = (self.cur_play_mark + 1) % self.play_num self.cur_player = self.players[self.cur_play_mark] self.update_info() self.decorate_rack() if self.time_limit and self.first_turn: self.countdown() self.loading = False self.first_turn = False self.chal_failed = False if self.lan_mode and self.own_mark != self.cur_play_mark: self.process_word() if self.comp_mode and self.turns % 2 == 0: self.wait_comp() def update_info(self): if self.comp_mode: self.status_info.set('... Player\'s Turn ...') self.bag_info.set('{} Tiles in Bag'.format(len(self.bag.bag))) self.pl1_info.set('{}: {}'.format(self.players[0].name, self.players[0].score)) self.pl2_info.set('Computer: {}'.format(self.players[1].score)) else: if self.lan_mode and self.own_mark == self.cur_play_mark: name = 'Your' else: name = self.cur_player.name + '\'s' self.status_info.set('... {} Turn ...'.format(name)) self.pl1_info.set('{}: {}'.format(self.players[0].name, self.players[0].score)) self.pl2_info.set('{}: {}'.format(self.players[1].name, self.players[1].score)) if self.play_num >= 3: self.pl3_info.set('{}: {}'.format(self.players[2].name, self.players[2].score)) if self.play_num == 4: self.pl4_info.set('{}: {}'.format(self.players[3].name, self.players[3].score)) self.bag_info.set('{} Tiles in Bag'.format(len(self.bag.bag))) def decorate_rack(self): # The challenged player is the previous player. if self.is_challenged: player = self.players[self.own_mark] else: player = self.cur_player # If it is a game on lan, no need to show other players tiles on the rack if not self.lan_mode or self.lan_mode and self.own_mark == self.cur_play_mark: for letter, tile in zip(player.letters, self.rack): if letter == '@': tile.letter.set(' ') else: tile.letter.set(letter) tile['bg'] = '#BE975B' # Hide the letters on the board if it is a normal game. # So that players can't see each others letters if self.norm_mode: tile['fg'] = '#BE975B' # If there are no letters left, leave the spots of the used tiles blank if len(self.bag.bag) == 0: # Slice the rack array up to the length of player's letters array for tile in self.rack[len(player.letters):]: tile.letter.set('') tile['bg'] = '#cccccc' # If a player joins a game on lan, display his own letters on the 1st turn. elif self.joined_lan and self.first_turn: for letter, tile in zip(self.players[self.own_mark].letters, self.rack): if letter == '@': tile.letter.set(' ') else: tile.letter.set(letter) tile['bg'] = '#BE975B' # Disable board and buttons when it is not a player's turn # so that they don't mess things up def disable_board(self): self.sub.config(state=DISABLED) self.pas.config(state=DISABLED) self.sav.config(state=DISABLED) if self.lan_mode and self.chal_mode: self.chal.config(state=DISABLED) for spot in self.gui_board.values(): spot.active = False # Enable board and buttons when it is player's turn def enable_board(self): self.sub.config(state=NORMAL) self.pas.config(state=NORMAL) self.sav.config(state=NORMAL) if self.lan_mode and self.chal_mode: self.chal.config(state=NORMAL) for spot in self.gui_board.values(): spot.active = True def wait_comp(self): self.disable_board() self.pl1_info.set('Player: {}'.format(self.cur_player.score)) self.bag_info.set('{} Tiles in Bag'.format(len(self.bag.bag))) self.status_info.set('... Computer\'s Turn ...') args = (self.queue, self.opponent, self.bag, self.board, self.dict) t = threading.Thread(target=self.get_comp_move, args=args) t.start() self.process_comp_word() def get_comp_move(self, queue, opponent, bag, board, dic): word = opponent.get_move(bag, board, dic) queue.put(word) def process_comp_word(self): if self.queue.empty(): self.master.master.after(1000, self.process_comp_word) else: word = self.queue.get() if self.opponent.is_passing: self.pass_num += 1 else: self.pass_num = 0 for spot, letter in zip(word.range, word.word): if self.gui_board.get(spot, False): self.gui_board[spot].letter.set(letter) self.gui_board[spot]['bg'] = '#BE975B' self.gui_board[spot].active = False self.used_spots[spot] = self.gui_board[spot] del self.gui_board[spot] self.opponent.update_rack(self.bag) self.opponent.update_score() self.set_word_info(word.words) self.decorate_rack() self.board.place(word.word, word.range) self.enable_board() self.init_turn() def place_tile(self, event): start_t_name = type(self.start_tile).__name__ end_tile = event.widget end_t_name = type(end_tile).__name__ end_t_letter = end_tile.letter if start_t_name == 'RackTile' and self.start_tile.letter.get() != '': if end_t_name == 'BoardTile' and end_tile.active: if end_t_letter.get() == '': end_t_letter.set(self.start_tile.letter.get()) end_tile['bg'] = self.start_tile['bg'] self.placed_tiles[end_tile.name] = end_tile self.spots_buffer.append(end_tile.name) self.empty_rack_tiles.append(self.start_tile) self.start_tile['bg'] = '#cccccc' self.start_tile.letter.set('') self.start_tile = None else: temp = end_t_letter.get() end_t_letter.set(self.start_tile.letter.get()) self.start_tile.letter.set(temp) self.start_tile = None elif end_t_name == 'RackTile': temp = end_t_letter.get() end_t_letter.set(self.start_tile.letter.get()) if end_tile in self.empty_rack_tiles: self.empty_rack_tiles.append(self.start_tile) del self.empty_rack_tiles[self.empty_rack_tiles.index( end_tile)] end_tile['bg'] = '#BE975B' self.start_tile['bg'] = '#cccccc' self.start_tile.letter.set(temp) self.start_tile = None else: self.start_tile = None elif start_t_name == 'BoardTile' and self.start_tile.letter.get( ) != '' and self.start_tile.active: if end_t_name == 'RackTile' and end_t_letter.get() == '': del self.placed_tiles[self.start_tile.name] del self.empty_rack_tiles[self.empty_rack_tiles.index( end_tile)] self.spots_buffer.remove(self.start_tile.name) end_t_letter.set(self.start_tile.letter.get()) end_tile['bg'] = '#BE975B' self.determine_tile_background(self.start_tile) self.start_tile.letter.set('') self.start_tile = None elif end_t_name == 'BoardTile' and end_tile.active: if end_t_letter.get() == '': end_t_letter.set(self.start_tile.letter.get()) end_tile['bg'] = self.start_tile['bg'] self.update_buffer_letters(end_tile) self.determine_tile_background(self.start_tile) del self.placed_tiles[self.start_tile.name] self.placed_tiles[end_tile.name] = end_tile self.start_tile.letter.set('') self.start_tile = None elif end_t_letter.get() == self.start_tile.letter.get(): self.start_tile = None else: temp = end_t_letter.get() end_t_letter.set(self.start_tile.letter.get()) self.start_tile.letter.set(temp) self.update_buffer_letters(end_tile) self.placed_tiles[self.start_tile.name] = self.start_tile self.placed_tiles[end_tile.name] = end_tile self.start_tile = None else: self.start_tile = end_tile def update_buffer_letters(self, tile): for spot in self.spots_buffer: if spot == self.start_tile.name: self.spots_buffer.remove(spot) self.spots_buffer.append(tile.name) def get_lan_move(self): if self.queue.empty(): self.may_proceed = False self.master.master.after(1000, self.process_word) else: pack = self.queue.get() # Other player passed letters if len(pack) == 3: self.bag = pack[1] self.pass_num += 1 self.init_turn() # There was a challenge elif type(pack[1]) == type(True): # In this case, pack[1] is the flag for being challenged successfully or not if pack[1]: self.is_challenged = True self.challenge(pack) self.master.master.after(1000, self.process_word) else: self.challenge() else: self.may_proceed = True self.is_challenged = False # pack[0] is a number significant for create_server and handle_lan_game # pack[-1] is the game_online flag self.word, self.w_range, received_tiles, self.prev_spots_buffer, self.players, self.bag, self.board = pack[ 1:-1] # It is a new word for the receiver self.word.new = True # Should populate placed_tiles because there were no clicks on this side. for spot, letter in received_tiles.items(): self.placed_tiles[spot] = self.gui_board[spot] self.placed_tiles[spot].letter.set(letter) self.placed_tiles[spot].active = False def determine_direction(self): # If there is only one letter in the list, find its direction if len(self.w_range) == 1: # Get the spots on the right and left side by changing letter value of the spot r = chr(ord(self.w_range[0][0]) + 1) + self.w_range[0][1:] l = chr(ord(self.w_range[0][0]) - 1) + self.w_range[0][1:] # Check the spots on the left and right side. # If they are occupied, the direction is r. If not, d. # Also check if they go over the board boundary. if self.board.board.get(r, False) and re.fullmatch( '[A-Z@]', self.board.board[r]): self.direction = 'r' elif self.board.board.get(l, False) and re.fullmatch( '[A-Z@]', self.board.board[l]): self.direction = 'r' else: self.direction = 'd' else: # use letter parts of the first and last spots for checking check1 = self.w_range[0][0] check2 = self.w_range[-1][0] # If letters are the same, direction is down if check1 == check2: # Need to sort number parts of the spots as digits for accuracy digits = sorted([int(x[1:]) for x in self.w_range]) self.w_range = [check1 + str(x) for x in digits] self.w_range.reverse() self.direction = 'd' else: self.direction = 'r' def set_raw_word(self): for spot in self.w_range: self.raw_word.append(self.placed_tiles[spot].letter.get()) self.set_aob_list(spot) # offset is necessary because array is mutable and it is dynamically # changed as the loop continues offset = 0 length = len(self.w_range) for spot, index, letter in self.aob_list: if index < 0: index = 0 elif index > length: index = length - 1 self.raw_word.insert(index + offset, letter) self.w_range.insert(index + offset, spot) offset += 1 self.raw_word = ''.join(self.raw_word) if ' ' in self.raw_word: self.change_wild_tile() def get_norm_move(self): self.raw_word = [] self.may_proceed = True # Array for letters already on board and are included in the word made self.aob_list = [] self.w_range = sorted(self.placed_tiles) self.determine_direction() self.set_raw_word() # Just the letters are necessary for word object aob_list = [x[2] for x in self.aob_list] self.word = Word(self.w_range[0], self.direction, self.raw_word, self.board, self.dict, aob_list, self.chal_mode) # Check if all the spots are on the same row or column if not self.valid_sorted_letters(): self.may_proceed = False def process_word(self): if self.lan_mode and self.own_mark != self.cur_play_mark: self.get_lan_move() elif self.placed_tiles: self.get_norm_move() if self.may_proceed and type(self.word) != type( None) and self.word.new and self.word.validate(): self.cur_player.word = self.word self.pass_num = 0 self.wild_tiles = [] self.prev_words = [] # Deactivate and disclude used spots for spot in self.w_range: if spot in self.placed_tiles: self.placed_tiles[spot].active = False self.used_spots[spot] = self.gui_board[spot] del self.gui_board[spot] # On lan games, the game object of the current player updates rack and score of its own. if not self.lan_mode or self.own_mark == self.cur_play_mark: self.cur_player.update_rack(self.bag) self.cur_player.update_score() self.prev_spots_buffer = self.spots_buffer.copy() self.decorate_rack() self.board.place(self.word.word, self.w_range) self.set_word_info(self.word.words) self.prev_words.append(self.word.word) self.prev_words.extend([x[0] for x in self.word.extra_words]) # Get the background correct for the lan game words # as there are no clicks on this side if self.lan_mode and self.own_mark != self.cur_play_mark: for tile in self.placed_tiles.values(): tile['bg'] = '#BE975B' # For lan game turn_packs, no tkinter objects, hence no tiles, for pickling. # Spots and letters are enough if self.lan_mode: self.placed_tiles = { spot: tile.letter.get() for spot, tile in self.placed_tiles.items() } self.init_turn() else: if self.wild_tiles: for tile in self.wild_tiles: tile.letter.set(' ') self.wild_tiles = [] def set_aob_list(self, spot): # Checks are to see if the spot is already on the list # If aft or bef is not in the gui board, it was previusly placed on the board # Otherwise, flag is false. flag = True if self.direction == 'd': # Modify the number part of the spots bef = spot[0] + str(int(spot[1:]) + 1) aft = spot[0] + str(int(spot[1:]) - 1) check = [x[0] for x in self.aob_list if x[0] == aft or x[0] == bef] while flag and not check: # range(1, 16) because there are 15 rows if aft not in self.gui_board and int(aft[1:]) in range(1, 16): self.aob_list.append((aft, self.w_range.index(spot) + 1, self.used_spots[aft].letter.get())) aft = aft[0] + str(int(aft[1:]) - 1) elif bef not in self.gui_board and int(bef[1:]) in range( 1, 16): self.aob_list.insert(0, (bef, self.w_range.index(spot) - 1, self.used_spots[bef].letter.get())) bef = bef[0] + str(int(bef[1:]) + 1) else: flag = False else: # Modify the letter part of the spots bef = chr(ord(spot[0]) - 1) + spot[1:] aft = chr(ord(spot[0]) + 1) + spot[1:] check = [x[0] for x in self.aob_list if x[0] == aft or x[0] == bef] while flag and not check: # range(97, 112) because these are ascii codes fo the lower case letters a-o if aft not in self.gui_board and ord(aft[0]) in range(97, 112): self.aob_list.append((aft, self.w_range.index(spot) + 1, self.used_spots[aft].letter.get())) aft = chr(ord(aft[0]) + 1) + aft[1:] elif bef not in self.gui_board and ord(bef[0]) in range( 97, 112): self.aob_list.insert(0, (bef, self.w_range.index(spot) - 1, self.used_spots[bef].letter.get())) bef = chr(ord(bef[0]) - 1) + bef[1:] else: flag = False def valid_sorted_letters(self): if self.direction == 'd': # Check1 is the number part of the spot check1 = int(self.w_range[0][1:]) # Check2 is the letter part of the spot check2 = self.w_range[0][0] # Skip first spot because it is already used in checks for spot in self.w_range[1:]: # Numbers should be consequent if int(spot[1:]) != check1 - 1: return False # Letters should be the same if spot[0] != check2: return False check1 -= 1 else: # Check1 is the ascii value for the letter part of the spot check1 = ord(self.w_range[0][0]) # Check2 is the number part of the spot check2 = self.w_range[0][1:] # Skip first spot because it is already used in checks for spot in self.w_range[1:]: # letters should be consequent if ord(spot[0]) != check1 + 1: return False # Numbers should be the same if spot[1:] != check2: return False check1 += 1 return True def change_wild_tile(self): # askstring returns None if the action is cancelled letter = askstring('Set Wild Tile', 'Enter letter(s):') or '0' letter = re.sub('[^A-Z]', '', letter.upper()) if letter: if re.fullmatch('[A-Z]+', letter): self.raw_word = re.sub(' ', letter[0], self.raw_word, 1) if ' ' in self.raw_word: self.raw_word = re.sub(' ', letter[1], self.raw_word, 1) for spot in self.w_range: try: if self.placed_tiles[spot].letter.get() == ' ': self.wild_tiles.append(self.placed_tiles[spot]) if self.chal_mode: self.wild_tiles_clone = self.wild_tiles.copy() self.board.wild_letters_on_board.append( self.placed_tiles[spot].name) except KeyError: continue for i, tile in enumerate(self.wild_tiles): tile.letter.set(letter[i]) self.cur_player.wild_letters.append(letter[i]) else: self.undo_placement() def pass_letters(self): letters = askstring('Pass Letters', 'Enter letters to pass:'******'': self.empty_rack_tiles[0].letter.set(tile.letter.get()) self.empty_rack_tiles[0]['bg'] = '#BE975B' del self.empty_rack_tiles[0] tile.letter.set('') self.determine_tile_background(tile) passed_letters = list(re.sub('[^A-Z ]', '', letters.upper())) if passed_letters: # If a player wants to change a wild letter if ' ' in passed_letters and '@' in self.cur_player.letters: count1 = self.cur_player.letters.count('@') count2 = passed_letters.count(' ') for i in range(count2): passed_letters.remove(' ') passed_letters.append('@') if i == count1 - 1: break # Remove the remaining spaces while ' ' in passed_letters: passed_letters.remove(' ') self.cur_player.passed_letters = passed_letters self.bag.put_back(passed_letters) self.cur_player.update_rack(self.bag) self.decorate_rack() self.pass_num += 1 if self.comp_mode: self.wait_comp() else: self.init_turn() def challenge(self, pack=None): for word in self.prev_words: if not self.dict.valid_word(word): self.players[self.cur_play_mark - 1].update_score( self.word.points) # Substract full bonus if applicable if len(self.players[self.cur_play_mark - 1].new_letters) == 7: self.players[self.cur_play_mark - 1].update_score(60) # Remove and return all the new letters for letter in self.players[self.cur_play_mark - 1].new_letters: self.players[self.cur_play_mark - 1].letters.remove(letter) self.bag.put_back([letter]) # Remove the letters of the challenged word and put them back on rack for spot in self.prev_spots_buffer: if spot in self.used_spots: for tile in self.wild_tiles_clone: if spot == tile.name: self.board.wild_letters_on_board.remove(spot) self.used_spots[spot].letter.set('') self.players[self.cur_play_mark - 1].letters.append('@') # If the letter value is set to a letter if self.used_spots[spot].letter.get(): self.players[ self.cur_play_mark - 1].letters.append( self.used_spots[spot].letter.get()) self.used_spots[spot].letter.set('') self.board.board[spot] = ' ' self.used_spots[spot].active = True self.determine_tile_background(self.used_spots[spot]) self.gui_board[spot] = self.used_spots[spot] del self.used_spots[spot] self.prev_words = [] self.prev_spots_buffer = [] if self.lan_mode: if self.is_challenged: # If the player is challenged on the lan self.players = pack[2] self.bag = pack[3] self.decorate_rack() else: # If the challenger is the player self.queue.put( (self.own_mark, True, self.players, self.bag)) self.update_info() return True # If challenge fails if self.lan_mode: self.chal_failed = True self.init_turn() return False def check_game_over(self): if len(self.bag.bag) == 0: for pl in self.players: if len(pl.letters) == 0: self.end_game() return self.master.master.after(1000, self.check_game_over) elif self.pass_num == 3 * self.play_num: self.end_game() elif self.point_limit: for pl in self.players: if type(pl) != type('') and pl.score >= self.point_limit: self.end_game() return self.master.master.after(1000, self.check_game_over) else: self.master.master.after(1000, self.check_game_over) def end_game(self): if not self.game_over: self.game_over = True if self.chal_mode and len(self.bag.bag) == 0 and [ pl for pl in self.players if len(pl.letters) == 0 ]: text = 'Will you challenge any of \'{}\'?'.format(', '.join( self.prev_words)) challenged = askyesno('Challenge', text) and self.challenge() else: challenged = False if not challenged: self.determine_winner() if self.time_up: rea = 'Time Is Up' else: rea = 'Game Is Over' mes = '{} has won with {} points!'.format( self.winner[0].name, self.winner[1]) self.game_online = False self.show_end_game_popup(rea, mes) else: self.game_over = False self.check_game_over() def show_end_game_popup(self, reason, message): pop = Toplevel(self) pop.title(reason) # Show popup inside the main window self.master.master.update() x = self.master.master.winfo_rootx() + 200 pop.geometry('+{}+{}'.format(x, 300)) pop.protocol('WM_DELETE_WINDOW', lambda: self.quit_game(pop)) Label(pop, text=message, font=('times', 30, 'italic')).pack(side=TOP, padx=50, pady=30) info_f = Frame(pop) info_f.pack(side=TOP) for player, subt in self.losers: if subt > 0: text = '{} {} points for {} left on rack...'.format( player.name, -subt, ', '.join(player.letters)) Label(info_f, text=text).pack(side=TOP) button_f = Frame(pop) button_f.pack(side=TOP, pady=20) Button(button_f, text='Quit', command=lambda: self.quit_game(pop)).pack(side=LEFT, padx=15) Button(button_f, text='Restart', command=self.restart_game).pack(side=LEFT) pop.grab_set() pop.focus_set() pop.wait_window() def determine_winner(self): # Substract the remaining letter values # and add them to the winner if s/he has no tiles left bonus_getter = None bonus = 0 for player in self.players: if len(player.letters) == 0: bonus_getter = player else: subt = 0 try: for letter in player.letters: subt += self.word.letter_points[letter] player.update_score(self.word.letter_points[letter]) except AttributeError: pass bonus += subt self.losers.append((player, subt)) if bonus_getter: bonus_getter.score += bonus scores = [player.score for player in self.players] points = max(scores) winner = self.players[scores.index(points)] self.winner = (winner, points) # Remove the winner from losers if s/he has no tiles left if len(self.winner[0].letters) == 0: del self.losers[scores.index(points)] def quit_game(self, win): self.game_online = False win.destroy() self.destroy() self.master.master.quit() def restart_game(self): self.game_online = False self.master.master.geometry('704x420') self.master.master.minsize(704, 420) self.destroy() def save_game(self): if not os.path.exists('./saves'): os.mkdir('./saves') filename = asksaveasfilename(initialdir='saves', defaultextension='.pickle') if filename: data = {} data['play_num'] = self.play_num data['players'] = self.players data['pass_num'] = self.pass_num data['cur_play_mark'] = self.cur_play_mark data['chal_mode'] = self.chal_mode data['comp_mode'] = self.comp_mode data['norm_mode'] = self.norm_mode or self.lan_mode data['point_limit'] = self.point_limit data['time_limit'] = self.time_limit data['bag'] = self.bag data['board'] = self.board data['op_score'] = self.op_score data['seconds'] = self.seconds data['minutes'] = self.minutes data['turns'] = self.turns file = open(filename, 'wb') pickle.dump(data, file) # Necessary for preventing lag in lan games def destroy(self): if self.server_not_found: showwarning('Game Not Found', 'There are no hosted games.') # Prevent infinite loop self.server_not_found = False self.restart_game() if self.lan_mode: # End the socket self.queue.put([self.own_mark, False]) self.thread.join() super().destroy()