316 lines
10 KiB
Python
316 lines
10 KiB
Python
import tkinter as tk
|
|
import color
|
|
import main as quadraLudi
|
|
import importlib
|
|
import sys
|
|
from socket import socket, AF_INET, SOCK_STREAM
|
|
from datetime import datetime
|
|
|
|
# Server
|
|
SERVER_ADDRESS = 'eveldee.ddns.net'
|
|
SERVER_PORT = 1409
|
|
|
|
# Tags
|
|
TEXT_TIME = 'Time'
|
|
TEXT_PSEUDO = 'Pseudo'
|
|
TEXT_SEPARATION = 'Separation'
|
|
TEXT_MESSAGE = 'Message'
|
|
TEXT_RANK = 'Rank'
|
|
TEXT_SHIFT_SEPARATOR = 'Shift_Separation'
|
|
TEXT_SCORE = 'Score'
|
|
|
|
class Lobby:
|
|
def __init__(self):
|
|
# Win
|
|
win = tk.Tk()
|
|
win.title('QuadraLudi - Lobby')
|
|
win.geometry('800x640')
|
|
win['bg'] = color.LEVEL_1
|
|
# win.resizable(False, False)
|
|
self.win = win
|
|
|
|
# Frame
|
|
self.frame = None
|
|
|
|
# Variables
|
|
self.pseudo = tk.StringVar(win, "")
|
|
self.message = tk.StringVar(win, "")
|
|
self.scoreLen = 0
|
|
self.chatLen = 0
|
|
|
|
# Logo
|
|
if sys.platform.startswith('win'):
|
|
win.iconbitmap('res/logo/icon.ico')
|
|
else:
|
|
logo = tk.PhotoImage('res/logo/icon.png')
|
|
win.tk.call('wm', 'iconphoto', win._w, logo)
|
|
|
|
# Network
|
|
self.networkManager = NetworkManager()
|
|
self.isUpdating = True
|
|
|
|
# First
|
|
self.nameDisplay()
|
|
|
|
def resetFrame(self):
|
|
if self.frame != None:
|
|
self.frame.destroy()
|
|
|
|
frame = tk.Frame(self.win, bg = color.LEVEL_1)
|
|
frame.pack(fill = tk.BOTH, expand = True, padx = 5, pady = 5)
|
|
self.frame = frame
|
|
|
|
def nameDisplay(self):
|
|
self.resetFrame()
|
|
|
|
# Place
|
|
pseudoFrame = tk.LabelFrame(self.frame, text = 'Pseudo', bg = color.LEVEL_1, fg = color.PURPLE)
|
|
pseudoFrame.place(relx = 0.5, rely = 0.5, width = 200, height = 50, anchor = 'center')
|
|
|
|
entry = tk.Entry(pseudoFrame, textvariable = self.pseudo, bg = color.LEVEL_2, fg = color.GHOST)
|
|
entry.bind('<Return>', lambda e: self.nameConfirm())
|
|
entry.pack(side = tk.LEFT, expand = True)
|
|
entry.focus()
|
|
tk.Button(
|
|
pseudoFrame, text = 'Valider', bg = color.PURPLE, fg = color.LEVEL_1,
|
|
command = lambda: self.nameConfirm()
|
|
).pack(side = tk.LEFT, expand = True)
|
|
|
|
def lobbyDisplay(self):
|
|
self.resetFrame()
|
|
|
|
# Configure layout
|
|
mainFrame = self.frame
|
|
mainFrame.columnconfigure(0, weight = 4)
|
|
mainFrame.columnconfigure(1, weight = 1)
|
|
mainFrame.rowconfigure(0, weight = 1)
|
|
|
|
# Score
|
|
scoreFrame = tk.LabelFrame(mainFrame, text = 'Score', bg = color.LEVEL_1, fg = color.PURPLE, font = (None, 12))
|
|
scoreFrame.grid(column = 0, row = 0, sticky = 'NSEW', padx = 5)
|
|
scoreFrameDisplay = tk.Text(scoreFrame, bg = color.LEVEL_1, bd = 0, wrap = tk.WORD, font = (None, 11), spacing1 = 7)
|
|
scoreFrameDisplay.tag_config(TEXT_RANK, foreground = color.PURPLE)
|
|
scoreFrameDisplay.tag_config(TEXT_SHIFT_SEPARATOR, foreground = color.BLUE)
|
|
scoreFrameDisplay.tag_config(TEXT_PSEUDO, foreground = color.GREEN)
|
|
scoreFrameDisplay.tag_config(TEXT_SEPARATION, foreground = color.YELLOW)
|
|
scoreFrameDisplay.tag_config(TEXT_SCORE, foreground = color.PINK)
|
|
scoreFrameScroll = tk.Scrollbar(scoreFrame, command = scoreFrameDisplay.yview)
|
|
scoreFrameDisplay.config(yscrollcommand = scoreFrameScroll.set)
|
|
scoreFrameScroll.pack(side = tk.RIGHT, fill = tk.Y)
|
|
scoreFrameDisplay.pack(side = tk.LEFT, fill = tk.BOTH)
|
|
self.scoreFrameDisplay = scoreFrameDisplay
|
|
|
|
# Chat Frame
|
|
chatFrame = tk.LabelFrame(mainFrame, text = 'Chat', bg = color.LEVEL_1, fg = color.PURPLE, font = (None, 12))
|
|
chatFrame.grid(column = 1, row = 0, sticky = 'NSEW', padx = 5)
|
|
|
|
# Chat input
|
|
bottomFrame = tk.Frame(chatFrame, bg = color.LEVEL_5, name = 'bottomFrame')
|
|
bottomFrame.pack(side = tk.BOTTOM, fill = tk.X, expand = True)
|
|
entry = tk.Entry(bottomFrame, textvariable = self.message, bg = color.LEVEL_1, fg = color.GHOST, font = (None, 11))
|
|
entry.bind('<Return>', lambda e: self.sendMessage())
|
|
entry.pack(side = tk.LEFT, fill = tk.BOTH, expand = True, padx = 5, pady = 5)
|
|
entry.focus()
|
|
self.chatEntry = entry
|
|
tk.Button(
|
|
bottomFrame, text = 'Envoyer', bg = color.PURPLE, fg = color.LEVEL_1,
|
|
command = lambda: self.sendMessage()
|
|
).pack(side = tk.LEFT, padx = 5, pady = 5)
|
|
|
|
# Chat display
|
|
chatTextFrame = tk.Frame(chatFrame, bg = color.LEVEL_1)
|
|
chatTextFrame.pack(side = tk.TOP, fill = tk.BOTH, pady = 2)
|
|
chatTextDisplay = tk.Text(chatTextFrame, bg = color.LEVEL_1, bd = 0, state = tk.DISABLED, wrap = tk.WORD, font = (None, 11), spacing1 = 7)
|
|
chatTextDisplayScroll = tk.Scrollbar(chatTextFrame, command = chatTextDisplay.yview)
|
|
chatTextDisplay.config(yscrollcommand = chatTextDisplayScroll.set)
|
|
|
|
chatTextDisplayScroll.pack(side = tk.RIGHT, fill = tk.Y)
|
|
chatTextDisplay.pack(side = tk.LEFT, fill = tk.BOTH, padx = 3)
|
|
self.chatTextDisplay = chatTextDisplay
|
|
|
|
chatTextDisplay.tag_config(TEXT_TIME, foreground = color.PURPLE)
|
|
chatTextDisplay.tag_config(TEXT_PSEUDO, foreground = color.GREEN)
|
|
chatTextDisplay.tag_config(TEXT_SEPARATION, foreground = color.YELLOW)
|
|
chatTextDisplay.tag_config(TEXT_MESSAGE, foreground = color.PINK)
|
|
|
|
# Play
|
|
tk.Button(
|
|
mainFrame, text = 'Jouer', bg = color.PURPLE, fg = color.LEVEL_1,
|
|
command = lambda: self.play()
|
|
).grid(column = 0, row = 1, columnspan = 2, sticky = 'NSEW', padx = 5, pady = 5)
|
|
|
|
self.update()
|
|
|
|
def update(self):
|
|
if self.isUpdating:
|
|
messages = self.networkManager.requestMessages()
|
|
scores = self.networkManager.requestScores()
|
|
|
|
# Score
|
|
if len(scores) > self.scoreLen:
|
|
|
|
self.scoreLen = len(scores)
|
|
|
|
rank = 1
|
|
for score in scores:
|
|
pseudo, score = score.split(';')
|
|
self.addScoreEntry(rank, pseudo, score)
|
|
rank += 1
|
|
|
|
# Chat
|
|
if len(messages) > self.chatLen:
|
|
for messsage in messages[self.chatLen:]:
|
|
split = messsage.split(';')
|
|
time, pseudo, content = split[0], split[1], ';'.join(split[2:])
|
|
self.addChatEntry(time, pseudo, content)
|
|
|
|
self.chatLen = len(messages)
|
|
|
|
self.win.after(1000, lambda: self.update())
|
|
|
|
def nameConfirm(self):
|
|
pseudo = self.pseudo.get().replace('@', ' ').replace(';', ' ')
|
|
|
|
if pseudo == '『 』':
|
|
return
|
|
elif pseudo == ' ':
|
|
self.pseudo.set('『 』')
|
|
elif pseudo == '' or pseudo.isspace():
|
|
return
|
|
|
|
self.lobbyDisplay()
|
|
|
|
def sendMessage(self):
|
|
# Check empty
|
|
message = self.message.get()
|
|
pseudo = self.pseudo.get()
|
|
if message == '':
|
|
return
|
|
|
|
# Send
|
|
message = message.replace('@', ' ')
|
|
self.networkManager.sendMessage(pseudo, message)
|
|
|
|
# Clear
|
|
now = datetime.now().time()
|
|
time = f"{now.hour}:{str(now.minute).zfill(2)}"
|
|
self.addChatEntry(time, pseudo, message)
|
|
self.chatLen += 1
|
|
self.message.set('')
|
|
|
|
def sendScore(self, score):
|
|
self.networkManager.sendScore(self.pseudo.get(), score)
|
|
|
|
def play(self):
|
|
# Reload
|
|
importlib.reload(quadraLudi)
|
|
|
|
# Minimize
|
|
self.win.iconify()
|
|
|
|
# Display
|
|
self.isUpdating = False
|
|
quadraLudi.main(self.win, lambda score: self.playEnd(score))
|
|
|
|
def playEnd(self, score):
|
|
# Display window, focus
|
|
self.win.deiconify()
|
|
self.win.focus_force()
|
|
self.chatEntry.focus()
|
|
self.isUpdating = True
|
|
|
|
# Send score
|
|
self.sendScore(score)
|
|
|
|
def addScoreEntry(self, rank, pseudo, score):
|
|
self.scoreFrameDisplay['state'] = tk.NORMAL
|
|
|
|
self.scoreFrameDisplay.insert(tk.END, f'{rank}', TEXT_RANK)
|
|
self.scoreFrameDisplay.insert(tk.END, f'> ', TEXT_SHIFT_SEPARATOR)
|
|
self.scoreFrameDisplay.insert(tk.END, f'{pseudo}', TEXT_PSEUDO)
|
|
self.scoreFrameDisplay.insert(tk.END, f': ', TEXT_SEPARATION)
|
|
self.scoreFrameDisplay.insert(tk.END, f'{score}\n', TEXT_SCORE)
|
|
|
|
self.scoreFrameDisplay['state'] = tk.DISABLED
|
|
|
|
def addChatEntry(self, time, pseudo, message):
|
|
self.chatTextDisplay['state'] = tk.NORMAL
|
|
|
|
self.chatTextDisplay.insert(tk.END, f'[{time}] ', TEXT_TIME)
|
|
self.chatTextDisplay.insert(tk.END, f'({pseudo})', TEXT_PSEUDO)
|
|
self.chatTextDisplay.insert(tk.END, ': ', TEXT_SEPARATION)
|
|
self.chatTextDisplay.insert(tk.END, f'{message}\n', TEXT_MESSAGE)
|
|
|
|
self.chatTextDisplay.see(tk.END)
|
|
self.chatTextDisplay['state'] = tk.DISABLED
|
|
|
|
def show(self):
|
|
self.win.mainloop()
|
|
|
|
class NetworkManager:
|
|
def __init__(self):
|
|
self.address = (SERVER_ADDRESS, SERVER_PORT)
|
|
|
|
def _createSocket(self):
|
|
# Create TCP socket
|
|
sock = socket(AF_INET, SOCK_STREAM)
|
|
sock.connect(self.address)
|
|
|
|
return sock
|
|
|
|
def _sendPacket(self, command, content):
|
|
packet = f"{command}\n{content}".encode('utf-8')
|
|
length = len(packet).to_bytes(4, 'little')
|
|
|
|
sock = self._createSocket()
|
|
sock.sendall(length)
|
|
sock.sendall(packet)
|
|
sock.close()
|
|
|
|
def _receivePacket(self, command):
|
|
sock = self._createSocket()
|
|
|
|
# Send command
|
|
packet = f"{command}\n".encode('utf-8')
|
|
length = len(packet).to_bytes(4, 'little')
|
|
|
|
sock.sendall(length)
|
|
sock.sendall(packet)
|
|
|
|
# Receive
|
|
# Length
|
|
length = sock.recv(4)
|
|
length = int.from_bytes(length, 'little')
|
|
# Data
|
|
response = sock.recv(length)
|
|
response = response.decode('utf-8')
|
|
|
|
sock.close()
|
|
|
|
split = response.split('\n')
|
|
return (split[0], '\n'.join(split[1:]))
|
|
|
|
def sendMessage(self, pseudo, message):
|
|
now = datetime.now().time()
|
|
time = f"{now.hour}:{str(now.minute).zfill(2)}"
|
|
|
|
self._sendPacket('ChatAdd', f"{time};{pseudo};{message}")
|
|
|
|
def sendScore(self, pseudo, score):
|
|
self._sendPacket('ScoreAdd', f"{pseudo};{score}")
|
|
|
|
def requestMessages(self):
|
|
_, response = self._receivePacket('ChatRequest')
|
|
return response.split('@')
|
|
|
|
def requestScores(self):
|
|
_, response = self._receivePacket('ScoreRequest')
|
|
return response.split('@')
|
|
|
|
def main():
|
|
lobby = Lobby()
|
|
lobby.show()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|