""" Starting launcher of the game. This is where you host new games or join other ones. """ import tkinter as tk import concurrent.futures import pypong.networking.client as client global running # Don't allow any more than this number of threads when looking for games # Can cripple a network at high numbers MAX_SCAN_REQUESTS = 64 def main(): """ Runs the game launcher. """ # Executor used for scanning local network for servers executor = concurrent.futures.ThreadPoolExecutor(max_workers=MAX_SCAN_REQUESTS) main_window = tk.Tk() main_window.title('PynPong Launcher') main_window.resizable(width=False, height=False) main_window.geometry('500x300') def quit_launcher(): """ Quits the main menu. """ global running running = False # Stop executor from creating new scanning tasks executor.shutdown(wait=False, cancel_futures=True) def check_game(game_ip): """ Checks to see if there is a game bein ghosted at a given IP :param game_ip: IP address to check for a host :return: Response from the server (or timeout if nothing found) """ result = client.check_ip(game_ip) return result def get_games(network): """ Refreshes the game list. :param network: The network to scan for games on """ listbox.delete(0, tk.END) listbox.insert(0, ' Building IP range...') main_window.update() possible_ips = client.get_ips_from_network(network.get().split(' ')[0]) listbox.delete(0, tk.END) listbox.insert(0, 'this might take some time...') listbox.insert(0, 'Refreshing server listing,') main_window.update() futures = [] # Use the executor to create threads to search for games for ip in possible_ips: future = executor.submit(check_game, ip) futures.append(future) game_found = False for future in futures: result = future.result() if 'PYPONGRST;SVRINFO' in result: # Check to make sure response has the right header and type raw_response = result.split(';') # Split response by block (denoted by semicolons) response = [] # Split up each block by chunks (:) # e.g. the block 'NAME:Nicholas Dyer' -> ['NAME', 'Nicholas Dyer'] for block in raw_response: chunks = block.split(':') for chunk in chunks: response.append(chunk) # Make sure the response has the NAME command if 'NAME' in response: # Session name is the next index after the command session_name = response[response.index('NAME') + 1] if not game_found: # If this is the first game found, clear out the list listbox.delete(0, tk.END) game_found = True listbox.insert(0, session_name) main_window.update() # Update screen while finding games (laggy, but works) if not game_found: listbox.delete(0, tk.END) listbox.insert(0, 'No games found!') def get_addresses(): """ Get the current machine's local interfaces and their corresponding addresses in CIDR notation :return: Address in CIDR notation along with the interface Example: ['172.20.126.4/24 [eth0]', 192.168.2.43/16 [wlan0]'] """ interface_info = client.get_interface_info() shortened_info = [] for interface in interface_info: shortened_info.append('{0} [{1}]'.format(interface.network, interface.interface_name)) return shortened_info # Create the top title label title_label = tk.Label(text='PynPong Launcher', fg='white', bg='#000040', padx=1, pady=20) title_label.pack(side=tk.TOP, fill=tk.BOTH) # Create the list of games global listbox listbox = tk.Listbox(main_window) listbox.pack(side=tk.LEFT, expand=True, fill=tk.BOTH) # Add scrollbar scrollbar = tk.Scrollbar(main_window) scrollbar.pack(side=tk.LEFT, fill=tk.Y) # Link scrollbar to list of games listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command=listbox.yview) # Create buttons button_frame = tk.Frame(main_window) tk.Button(button_frame, text='Host a Game', height=3, width=30).pack() tk.Button(button_frame, text='Join Selected Game', height=3, width=30).pack() tk.Button(button_frame, text='Refresh Game List', height=2, width=20, command=lambda: get_games(selected_network)).pack() networks = get_addresses() selected_network = tk.StringVar() selected_network.set(networks[0]) interface_menu = tk.OptionMenu(button_frame, selected_network, *networks) interface_menu.pack() button_frame.pack(side=tk.RIGHT) # Set it so that if the X is pressed the application quits main_window.protocol('WM_DELETE_WINDOW', quit_launcher) global running running = True while running: main_window.update() if __name__ == '__main__': main()