Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tttk 53 #21

Merged
merged 5 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions Server/websocket_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import logging
import json
from jsonschema import validate, ValidationError
from uuid import UUID
import uuid
from zeroconf import ServiceInfo, Zeroconf
import socket

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand All @@ -26,6 +28,14 @@ def __init__(self, admin:Player, port: int = 8765) -> None:
with open("./json_schema/client_to_server.json", "r") as f:
self._json_schema = json.load(f)

# MDNS
#https://stackoverflow.com/a/74633230
self._mdns = Zeroconf()
ip = socket.inet_aton(socket.gethostbyname(socket.gethostname())) # this is a dirty hack and also doesnt work dual stack :'(
wsInfo = ServiceInfo(type_ = '_tictactoe._tcp.local.', name = admin.display_name+'s-Server'+'._tictactoe._tcp.local.', port = self._port, addresses = [ip])
self._mdns.register_service(wsInfo)


async def handler(self, websocket):

self._connections.add(websocket)
Expand All @@ -50,7 +60,7 @@ async def handler(self, websocket):
await websocket.send("Game in progress, cannot join") # TODO jsonify
break

self._players[message_json["profile"]["uuid"]] = Player(uuid=UUID(message_json["profile"]["uuid"]), display_name=message_json["profile"]["display_name"], color=message_json["profile"]["color"])
self._players[message_json["profile"]["uuid"]] = Player(uuid=uuid.UUID(message_json["profile"]["uuid"]), display_name=message_json["profile"]["display_name"], color=message_json["profile"]["color"])

# send new lobby status
websockets.broadcast(self._connections, json.dumps({
Expand Down Expand Up @@ -91,6 +101,7 @@ async def handler(self, websocket):
self._game = Game(player1 = list(self._players.values())[0], player2 = list(self._players.values())[1], rule_base = rulebase)

self._inprogress = True
self._mdns.unregister_all_services()

websockets.broadcast(self._connections, json.dumps({
"message_type": "game/start",
Expand Down Expand Up @@ -204,5 +215,5 @@ def run(self):
asyncio.run(self.start_server())

if __name__ == "__main__":
lobby = Lobby(port = 8765, admin = Player(uuid=UUID("c4f0eccd-a6a4-4662-999c-17669bc23d5e"), display_name="admin", color=0xffffff, ready=True))
lobby = Lobby(port = 8765, admin = Player(uuid=uuid.UUID("c4f0eccd-a6a4-4662-999c-17669bc23d5e"), display_name="admin", color=0xffffff, ready=True))
lobby.run()
42 changes: 42 additions & 0 deletions UI/autodiscovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from zeroconf import ServiceBrowser, ServiceListener, Zeroconf
import logging
import time

logger = logging.getLogger(__name__)

def discover_games(timeout: int = 5) -> dict:
"""
Returns a dictionary of games that are available to play.
The key is the name of the game and the value is the IP address of the server as string.

param: timeout. The time in seconds to wait for responses. Default is 5 seconds.

return: dict. A dictionary of games on the network that are available to play. Key is the name, Value is the IP as string.
"""

class MyListener(ServiceListener):
def __init__(self):
self.results = {}

def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
info = zc.get_service_info(type_, name)
if info:
logger.info(f"Service {name} found, service address: {info.parsed_addresses()[0]}") # TODO only gets first address, so dual stack is not possible
self.results.update({name.removesuffix('._tictactoe._tcp.local.'): info.parsed_addresses()[0]})
else:
logger.error(f"Service {name} found, no info")


zeroconf = Zeroconf()
listener = MyListener()
ServiceBrowser(zeroconf, "_tictactoe._tcp.local.", listener)

time.sleep(timeout)

zeroconf.close()
return listener.results

if __name__ == "__main__":
while True:
print(discover_games())
time.sleep(5)
70 changes: 58 additions & 12 deletions UI/multi.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#import tkinter as tk
from .lib import tttk_tk as tk
from uuid import UUID
from threading import Thread
from queue import Queue
from sys import exit

from .base_frame import base_frame
from .field_frame import Field
Expand All @@ -9,7 +11,8 @@
from .field_frame import player_type
from Server.main import server_thread
from AI.ai_context import AIContext
from AI.ai_strategy import AIStrategy, WeakAIStrategy, AdvancedAIStrategy
from AI.ai_strategy import WeakAIStrategy, AdvancedAIStrategy
from .autodiscovery import discover_games

class Join(base_frame):
def __init__(self, master, *args, opponent=player_type.unknown, **kwargs):
Expand Down Expand Up @@ -81,41 +84,84 @@ def _menu(self):
self.master.out_queue.put({'message_type': 'server/terminate', 'args': {}})
self.master.show_menu()


def reload(tkinter_obj: tk.Widget, queue: Queue):
print('hiu')
for i in range(10):
servers = discover_games(i+1)
queue.put(servers)
tkinter_obj.event_generate('<<lobby/reload>>')
print(servers)
tkinter_obj.event_generate('<<thread/exit>>')
print('dead')
exit()

class Lobby_Overview(tk.Container):
def __init__(self, master):
super().__init__(master)
self._create_widgets()
self._display_widgets()
self.queue = Queue()
self.thread = False
self.servers = {}
self.bind('<<lobby/reload>>', lambda *args: self._finish_reload())
self.bind('<<thread/exit>>', lambda *args: self._thread_reset())
self._start_reload()

def _create_widgets(self):
self.frame = tk.Frame(self)
self.innerframe = self.frame.widget
self.lblHeading = tk.Label(self.innerframe, text="Join public lobbies", font=self.master.master.title_font)

self.btnManual = tk.Button(self.innerframe, text="Join by address", command=lambda *args: self.manually())
self.btnReload = tk.Button(self.innerframe, text="\u21BB", command=lambda *args: self._start_reload(), border=0)
self.wrapper = tk.Container(self.innerframe)
self.btnManual = tk.Button(self.innerframe, text="Join by address", command=lambda *args: self._manually())
self.etrAddress = tk.Entry(self.innerframe)
self.btnConnect = tk.Button(self.innerframe, text="Connect", command=lambda *args: self._connect())
self.btnConnect = tk.Button(self.innerframe, text="Connect", command=lambda ip=self.etrAddress.get(), *args: self._connect(ip))

def _display_widgets(self):
self.frame.pack(expand=True, fill=tk.BOTH)
self.innerframe.columnconfigure([0, 2, 4], weight=1)
self.innerframe.columnconfigure([0, 2, 4, 5], weight=1)
self.innerframe.columnconfigure([1, 3], weight=5)
self.innerframe.rowconfigure([0, 4], weight=2)
self.innerframe.rowconfigure([1, 3], weight=1)
self.innerframe.rowconfigure([2], weight=40)
self.lblHeading.grid(sticky=tk.E+tk.W+tk.N+tk.S, column=1, row=0, columnspan=3)
self.btnManual.grid(sticky=tk.E+tk.W+tk.N+tk.S, column=1, row=4, columnspan=3)
self.btnReload.grid(sticky=tk.E+tk.W+tk.N+tk.S, column=4, row=0)
self.btnManual.grid(sticky=tk.E+tk.W+tk.N+tk.S, column=1, row=4, columnspan=4)

def manually(self):
def _manually(self):
self.btnManual.grid_forget()
self.etrAddress.grid(column=1, row=10, sticky=tk.E+tk.W+tk.N+tk.S)
self.btnConnect.grid(column=3, row=10, sticky=tk.E+tk.W+tk.N+tk.S)
self.btnConnect.grid(column=3, row=10, columnspan=2, sticky=tk.E+tk.W+tk.N+tk.S)

def _connect(self):
def _connect(self, ip):
root = self.master.master
root.network_client = client_thread(root, in_queue=root.out_queue, out_queue=root.in_queue, player=root.player, ip=self.etrAddress.get())
root.network_client = client_thread(root, in_queue=root.out_queue, out_queue=root.in_queue, player=root.player, ip=ip)
root.show(Join)

def _start_reload(self):
if(self.thread):
return
Thread(target=reload, args=(self, self.queue), daemon=True).start()
self.thread = True

def _finish_reload(self):
servers = self.queue.get()
if(servers == self.servers):
return
self.servers = servers
self.wrapper.destroy()
self.wrapper = tk.Container(self.innerframe)
self.wrapper.columnconfigure([0], weight=1)
self.wrapper.columnconfigure([1], weight=0)
for i, (server, ip) in enumerate(self.servers.items()):
tk.Label(self.wrapper, text=server).grid(column=0, row=i, sticky=tk.W+tk.N+tk.S)
tk.Button(self.wrapper, text="Join", command=lambda ip=ip, *args: self._connect(ip)).grid(column=1, row=i, sticky=tk.E+tk.W+tk.N+tk.S)
self.wrapper.grid(column=1, row=2, columnspan=4, sticky=tk.E+tk.W+tk.N+tk.S)

def _thread_reset(self):
self.thread = False

class Multiplayer(base_frame):
def __new__(cls, master, *args, **kwargs):
if(master.player == None):
Expand All @@ -126,7 +172,7 @@ def __init__(self, master, *args):
super().__init__(master)
self._create_widgets()
self._display_widgets()
self.address_toogle = False


def _create_widgets(self):
self.lblTitle = tk.Label(self, text='Multiplayer', font=self.master.title_font)
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
websockets==12.0
jsonschema==4.21.1
emoji==2.10.1
zeroconf
Loading