PynPong/pypong/networking/gui.py

158 lines
5.2 KiB
Python

"""
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('PyPong 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 (<COMMAND>:<RESPONSE>)
# 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='PyPong 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()