Implemented UI #10
|
@ -0,0 +1 @@
|
||||||
|
__pycache__/
|
40
client.py
40
client.py
|
@ -3,10 +3,14 @@
|
||||||
# Apr 2025
|
# Apr 2025
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
import socket
|
import socket
|
||||||
import select
|
import select
|
||||||
import network_utilities
|
import network_utilities
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ui import UI
|
||||||
|
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
|
|
||||||
|
@ -41,12 +45,16 @@ class Game:
|
||||||
packet_id = network_utilities.unpack_varint(self.__server)
|
packet_id = network_utilities.unpack_varint(self.__server)
|
||||||
if packet_id == 1:
|
if packet_id == 1:
|
||||||
name = network_utilities.unpack_string(self.__server)
|
name = network_utilities.unpack_string(self.__server)
|
||||||
self.__player.player_joined(name)
|
admin = bool(network_utilities.unpack_varint(self.__server))
|
||||||
|
self.__player.player_joined(name, admin)
|
||||||
elif packet_id == 2:
|
elif packet_id == 2:
|
||||||
text = network_utilities.unpack_string(self.__server)
|
text = network_utilities.unpack_string(self.__server)
|
||||||
self.__player.new_verse(text)
|
self.__player.new_verse(text)
|
||||||
elif packet_id == 3:
|
elif packet_id == 3:
|
||||||
self.__player.guess_incorrect()
|
self.__player.guess_incorrect()
|
||||||
|
elif packet_id == 7:
|
||||||
|
url = network_utilities.unpack_string(self.__server)
|
||||||
|
self.__player.guess_partial_correct(url)
|
||||||
elif packet_id == 4:
|
elif packet_id == 4:
|
||||||
self.__player.guess_correct()
|
self.__player.guess_correct()
|
||||||
elif packet_id == 5:
|
elif packet_id == 5:
|
||||||
|
@ -66,12 +74,16 @@ class Player:
|
||||||
__verse: str
|
__verse: str
|
||||||
__score: int
|
__score: int
|
||||||
__game: Game | None
|
__game: Game | None
|
||||||
|
__ui: UI
|
||||||
|
__admin: bool
|
||||||
|
|
||||||
def __init__(self, name: str):
|
def __init__(self, name: str, ui: UI):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
self.__verse = ""
|
self.__verse = ""
|
||||||
self.__score = 0
|
self.__score = 0
|
||||||
self.__game = None
|
self.__game = None
|
||||||
|
self.__ui = ui
|
||||||
|
self.__admin = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str: return self.__name
|
def name(self) -> str: return self.__name
|
||||||
|
@ -82,6 +94,9 @@ class Player:
|
||||||
@property
|
@property
|
||||||
def score(self) -> int: return self.__score
|
def score(self) -> int: return self.__score
|
||||||
|
|
||||||
|
@property
|
||||||
|
def admin(self) -> bool: return self.__admin
|
||||||
|
|
||||||
def join_game(self, host: str = 'localhost', port: int = 7788):
|
def join_game(self, host: str = 'localhost', port: int = 7788):
|
||||||
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
conn.connect((host, port))
|
conn.connect((host, port))
|
||||||
|
@ -106,29 +121,36 @@ class Player:
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
self.__game.end_game()
|
self.__game.end_game()
|
||||||
|
|
||||||
def player_joined(self, name: str):
|
def player_joined(self, name: str, admin: bool):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
if name == self.name: self.__admin = admin or self.__admin
|
||||||
|
self.__ui.player_joined(name, admin)
|
||||||
|
|
||||||
def new_verse(self, text: str):
|
def new_verse(self, text: str):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
self.__verse = text
|
||||||
|
self.__ui.new_verse(text)
|
||||||
|
|
||||||
def guess_incorrect(self):
|
def guess_incorrect(self):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
self.__ui.guess_incorrect()
|
||||||
|
|
||||||
|
def guess_partial_correct(self, url):
|
||||||
|
if self.__game is not None:
|
||||||
|
self.__ui.guess_partial_correct(url)
|
||||||
|
|
||||||
def guess_correct(self):
|
def guess_correct(self):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
self.__ui.guess_correct()
|
||||||
|
|
||||||
def verse_guessed(self, points: int, url: str, player: str):
|
def verse_guessed(self, points: int, url: str, player: str):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
self.__score += points
|
||||||
|
self.__ui.verse_guessed(points, url, player)
|
||||||
|
|
||||||
def game_over(self, players: list[str], scores: list[int]):
|
def game_over(self, players: list[str], scores: list[int]):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
pass
|
self.__ui.game_over(players, scores)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
if self.__game is not None:
|
if self.__game is not None:
|
||||||
|
|
20
library.py
20
library.py
|
@ -9,6 +9,7 @@ import socket
|
||||||
import select
|
import select
|
||||||
import network_utilities
|
import network_utilities
|
||||||
from server import Game
|
from server import Game
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
class Library:
|
class Library:
|
||||||
|
@ -18,8 +19,9 @@ class Library:
|
||||||
__host: str
|
__host: str
|
||||||
__port: int
|
__port: int
|
||||||
__socket: socket.socket
|
__socket: socket.socket
|
||||||
|
__bible_only: bool
|
||||||
|
|
||||||
def __init__(self, host: str = '', port: int = 7788):
|
def __init__(self, host: str = '', port: int = 7788, *, bible_only: bool = False):
|
||||||
with open("data/scripture-frequencies.json", encoding='utf-8') as file:
|
with open("data/scripture-frequencies.json", encoding='utf-8') as file:
|
||||||
self.__verses = json.load(file)
|
self.__verses = json.load(file)
|
||||||
self.__games = []
|
self.__games = []
|
||||||
|
@ -28,8 +30,11 @@ class Library:
|
||||||
self.__port = port
|
self.__port = port
|
||||||
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
self.__bible_only = bible_only
|
||||||
|
|
||||||
def serve_forever(self):
|
def serve_forever(self):
|
||||||
try:
|
try:
|
||||||
|
print(f"Starting server at {self.__host}:{self.__port}")
|
||||||
with self.__socket as s:
|
with self.__socket as s:
|
||||||
s.bind((self.__host, self.__port))
|
s.bind((self.__host, self.__port))
|
||||||
s.listen(1)
|
s.listen(1)
|
||||||
|
@ -40,11 +45,13 @@ class Library:
|
||||||
if not game.finished: game.update()
|
if not game.finished: game.update()
|
||||||
else: self.__games.remove(game)
|
else: self.__games.remove(game)
|
||||||
if ready_to_read:
|
if ready_to_read:
|
||||||
conn, _ = s.accept()
|
conn, addr = s.accept()
|
||||||
conn.setblocking(False)
|
conn.setblocking(False)
|
||||||
if network_utilities.unpack_varint(conn) == 1:
|
if network_utilities.unpack_varint(conn) == 1:
|
||||||
name = network_utilities.unpack_string(conn)
|
name = network_utilities.unpack_string(conn)
|
||||||
|
print(f"<< (1) join_game({name}, {addr})")
|
||||||
self.join_game(name, conn)
|
self.join_game(name, conn)
|
||||||
|
sleep(0.1)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("KeyboardInterrupt\nExiting...")
|
print("KeyboardInterrupt\nExiting...")
|
||||||
return
|
return
|
||||||
|
@ -61,6 +68,7 @@ class Library:
|
||||||
|
|
||||||
def get_verse(self, difficulty: int, game: Game):
|
def get_verse(self, difficulty: int, game: Game):
|
||||||
url = self.__select_verse(difficulty)
|
url = self.__select_verse(difficulty)
|
||||||
|
print(f"Verse Selected: {url}")
|
||||||
text = self.__get_verse_text(url)
|
text = self.__get_verse_text(url)
|
||||||
|
|
||||||
game.new_verse(url, text)
|
game.new_verse(url, text)
|
||||||
|
@ -103,9 +111,17 @@ class Library:
|
||||||
|
|
||||||
difficulty_verses = []
|
difficulty_verses = []
|
||||||
for key, value in self.__verses.items():
|
for key, value in self.__verses.items():
|
||||||
|
if self.__bible_only and not (key.startswith('/ot') or key.startswith('/nt')):
|
||||||
|
continue
|
||||||
for i, diff in enumerate(value):
|
for i, diff in enumerate(value):
|
||||||
if real_difficulty_lower <= diff <= real_difficulty_upper:
|
if real_difficulty_lower <= diff <= real_difficulty_upper:
|
||||||
difficulty_verses.append(f"{key}/{i+1}")
|
difficulty_verses.append(f"{key}/{i+1}")
|
||||||
|
|
||||||
|
if not difficulty_verses: difficulty_verses.append('/pgp/js-h/1/17')
|
||||||
|
|
||||||
return difficulty_verses
|
return difficulty_verses
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
lib = Library(bible_only=False)
|
||||||
|
lib.serve_forever()
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
def name_gen():
|
||||||
|
adjectives = [
|
||||||
|
"able", "active", "adaptable", "adventurous", "agreeable", "alert",
|
||||||
|
"amazing", "amiable", "ample", "artistic", "attractive", "balanced",
|
||||||
|
"beautiful", "blissful", "bold", "brave", "bright", "brilliant",
|
||||||
|
"bubbly", "calm", "capable", "careful", "charming", "cheerful", "clean",
|
||||||
|
"clear", "clever", "colorful", "comfortable", "compassionate",
|
||||||
|
"confident", "considerate", "cool", "cooperative", "courageous",
|
||||||
|
"creative", "cultured", "cute", "daring", "decent", "delightful",
|
||||||
|
"detailed", "determined", "dignified", "disciplined", "dynamic",
|
||||||
|
"eager", "easygoing", "elegant", "energetic", "engaging",
|
||||||
|
"enthusiastic", "excellent", "exciting", "expressive", "fair",
|
||||||
|
"faithful", "fancy", "fascinating", "flexible", "focused", "friendly",
|
||||||
|
"fun", "funny", "generous", "gentle", "genuine", "gifted", "glad",
|
||||||
|
"gleaming", "good", "graceful", "gracious", "great", "handsome",
|
||||||
|
"happy", "harmonious", "helpful", "honest", "hopeful", "humble",
|
||||||
|
"imaginative", "impressive", "independent", "innocent", "inspiring",
|
||||||
|
"intelligent", "interesting", "intuitive", "jolly", "jovial", "joyful",
|
||||||
|
"kind", "lively", "logical", "lovely", "loyal", "lucky", "mature",
|
||||||
|
"mindful", "modest",
|
||||||
|
]
|
||||||
|
animals = [
|
||||||
|
"aardvark", "albatross", "alligator", "alpaca", "ant", "anteater",
|
||||||
|
"antelope", "ape", "armadillo", "baboon", "badger", "barracuda", "bat",
|
||||||
|
"bear", "beaver", "bee", "beetle", "bison", "boar",
|
||||||
|
"bobcat", "buffalo", "butterfly", "camel", "canary", "capybara",
|
||||||
|
"caracal", "caribou", "cassowary", "cat", "caterpillar", "cattle",
|
||||||
|
"chameleon", "cheetah", "chicken", "chimpanzee", "chinchilla", "cobra",
|
||||||
|
"cockatoo", "cougar", "cow", "coyote", "crab", "crane", "crocodile",
|
||||||
|
"crow", "deer", "dingo", "dog", "dolphin", "donkey", "dove",
|
||||||
|
"dragonfly", "duck", "eagle", "echidna", "eel", "elephant", "elk",
|
||||||
|
"emu", "falcon", "ferret", "finch", "firefly", "fish", "flamingo",
|
||||||
|
"fly", "fox", "frog", "gazelle", "gecko", "giraffe", "goat", "goldfish",
|
||||||
|
"goose", "gorilla", "grasshopper", "pig", "gull", "hamster",
|
||||||
|
"hare", "hawk", "hedgehog", "hippopotamus", "horse",
|
||||||
|
"hummingbird", "hyena", "iguana", "jackal", "jaguar",
|
||||||
|
"jellyfish", "kangaroo", "kingfisher", "koala", "lemur",
|
||||||
|
"leopard", "lion", "lizard", "llama",
|
||||||
|
]
|
||||||
|
|
||||||
|
return random.choice(adjectives).capitalize() + random.choice(animals).capitalize()
|
||||||
|
|
||||||
|
def server(host: str='', port: int=7788):
|
||||||
|
from library import Library
|
||||||
|
lib = Library(host, port)
|
||||||
|
lib.serve_forever()
|
||||||
|
|
||||||
|
def client(playername: str, host: str='localhost', port: int=7788):
|
||||||
|
from ui import UI
|
||||||
|
ui = UI(playername, host, port)
|
||||||
|
ui.loop()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
client(name_gen())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
|
@ -32,8 +32,9 @@ def unpack_varint(conn: socket) -> int:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def pack_string(text: str) -> bytes:
|
def pack_string(text: str) -> bytes:
|
||||||
data = pack_varint(len(text))
|
utf = text.encode('utf-8')
|
||||||
data += text.encode('utf-8')
|
data = pack_varint(len(utf))
|
||||||
|
data += utf
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def unpack_string(conn: socket) -> str:
|
def unpack_string(conn: socket) -> str:
|
||||||
|
|
|
@ -0,0 +1,160 @@
|
||||||
|
# Kyler Olsen
|
||||||
|
# Mar 2024
|
||||||
|
import re
|
||||||
|
|
||||||
|
__LINKS = {
|
||||||
|
'/ot/gen': ("Gen.", "Genesis", ),
|
||||||
|
'/ot/ex': ("Ex.", "Exodus", ),
|
||||||
|
'/ot/lev': ("Lev.", "Leviticus", ),
|
||||||
|
'/ot/num': ("Num.", "Numbers", ),
|
||||||
|
'/ot/deut': ("Deut.", "Deuteronomy", ),
|
||||||
|
'/ot/josh': ("Josh.", "Joshua", ),
|
||||||
|
'/ot/judg': ("Judg.", "Judges", ),
|
||||||
|
'/ot/ruth': ("Ruth", ),
|
||||||
|
'/ot/1-sam': ("1 Sam.", "1 Samuel", ),
|
||||||
|
'/ot/2-sam': ("2 Sam.", "2 Samuel", ),
|
||||||
|
'/ot/1-kgs': ("1 Kgs.", "1 Kings", ),
|
||||||
|
'/ot/2-kgs': ("2 Kgs.", "2 Kings", ),
|
||||||
|
'/ot/1-chr': ("1 Chr.", "1 Chronicles", ),
|
||||||
|
'/ot/2-chr': ("2 Chr.", "2 Chronicles", ),
|
||||||
|
'/ot/ezra': ("Ezra", ),
|
||||||
|
'/ot/neh': ("Neh.", "Nehemiah", ),
|
||||||
|
'/ot/esth': ("Esth.", "Esther", ),
|
||||||
|
'/ot/job': ("Job", ),
|
||||||
|
'/ot/ps': ("Ps.", "Psalm", "Psalms", ),
|
||||||
|
'/ot/prov': ("Prov.", "Proverbs", ),
|
||||||
|
'/ot/eccl': ("Eccl.", "Ecclesiastes", ),
|
||||||
|
'/ot/song': ("Song", "Song of Solomon", ),
|
||||||
|
'/ot/isa': ("Isa.", "Isaiah", ),
|
||||||
|
'/ot/jer': ("Jer.", "Jeremiah", ),
|
||||||
|
'/ot/lam': ("Lam.", "Lamentations", ),
|
||||||
|
'/ot/ezek': ("Ezek.", "Ezekiel", ),
|
||||||
|
'/ot/dan': ("Dan.", "Daniel", ),
|
||||||
|
'/ot/hosea': ("Hosea", ),
|
||||||
|
'/ot/joel': ("Joel", ),
|
||||||
|
'/ot/amos': ("Amos", ),
|
||||||
|
'/ot/obad': ("Obad.", "Obadiah", ),
|
||||||
|
'/ot/jonah': ("Jonah", ),
|
||||||
|
'/ot/micah': ("Micah", ),
|
||||||
|
'/ot/nahum': ("Nahum", ),
|
||||||
|
'/ot/hab': ("Hab.", "Habakkuk", ),
|
||||||
|
'/ot/zeph': ("Zeph.", "Zephaniah", ),
|
||||||
|
'/ot/hag': ("Hag.", "Haggai", ),
|
||||||
|
'/ot/zech': ("Zech.", "Zechariah", ),
|
||||||
|
'/ot/mal': ("Mal.", "Malachi", ),
|
||||||
|
'/nt/matt': ("Matt.", "Matthew", ),
|
||||||
|
'/nt/mark': ("Mark", ),
|
||||||
|
'/nt/luke': ("Luke", ),
|
||||||
|
'/nt/john': ("John", ),
|
||||||
|
'/nt/acts': ("Acts", ),
|
||||||
|
'/nt/rom': ("Rom.", "Romans", ),
|
||||||
|
'/nt/1-cor': ("1 Cor.", "1 Corinthians", ),
|
||||||
|
'/nt/2-cor': ("2 Cor.", "2 Corinthians", ),
|
||||||
|
'/nt/gal': ("Gal.", "Galatians", ),
|
||||||
|
'/nt/eph': ("Eph.", "Ephesians", ),
|
||||||
|
'/nt/philip': ("Philip.", "Philippians", ),
|
||||||
|
'/nt/col': ("Col.", "Colossians", ),
|
||||||
|
'/nt/1-thes': ("1 Thes.", "1 Thessalonians", ),
|
||||||
|
'/nt/2-thes': ("2 Thes.", "2 Thessalonians", ),
|
||||||
|
'/nt/1-tim': ("1 Tim.", "1 Timothy", ),
|
||||||
|
'/nt/2-tim': ("2 Tim.", "2 Timothy", ),
|
||||||
|
'/nt/titus': ("Titus", ),
|
||||||
|
'/nt/philem': ("Philem.", "Philemon", ),
|
||||||
|
'/nt/heb': ("Heb.", "Hebrews", ),
|
||||||
|
'/nt/james': ("James", ),
|
||||||
|
'/nt/1-pet': ("1 Pet.", "1 Peter", ),
|
||||||
|
'/nt/2-pet': ("2 Pet.", "2 Peter", ),
|
||||||
|
'/nt/1-jn': ("1 Jn.", "1 John", ),
|
||||||
|
'/nt/2-jn': ("2 Jn.", "2 John", ),
|
||||||
|
'/nt/3-jn': ("3 Jn.", "3 John", ),
|
||||||
|
'/nt/jude': ("Jude", ),
|
||||||
|
'/nt/rev': ("Rev.", "Revelation", ),
|
||||||
|
'/bofm/1-ne': ("1 Ne.", "1 Nephi", ),
|
||||||
|
'/bofm/2-ne': ("2 Ne.", "2 Nephi", ),
|
||||||
|
'/bofm/jacob': ("Jacob", ),
|
||||||
|
'/bofm/enos': ("Enos", ),
|
||||||
|
'/bofm/jarom': ("Jarom", ),
|
||||||
|
'/bofm/omni': ("Omni", ),
|
||||||
|
'/bofm/w-of-m': ("WofM", "W of M", "Words of Mormon", ),
|
||||||
|
'/bofm/mosiah': ("Mosiah", ),
|
||||||
|
'/bofm/alma': ("Alma", ),
|
||||||
|
'/bofm/hel': ("Hel.", "Helaman", ),
|
||||||
|
'/bofm/3-ne': ("3 Ne.", "3 Nephi", ),
|
||||||
|
'/bofm/4-ne': ("4 Ne.", "4 Nephi", ),
|
||||||
|
'/bofm/morm': ("Morm.", "Mormon", ),
|
||||||
|
'/bofm/ether': ("Ether", ),
|
||||||
|
'/bofm/moro': ("Moro.", "Moroni", ),
|
||||||
|
'/dc-testament/dc': ("D&C", "D & C", "DandC", "D and C", "D+C", "D + C", "Doctrine and Covenants", ),
|
||||||
|
'/dc-testament/od': ("OD", "Official Declaration", ),
|
||||||
|
'/pgp/moses': ("Moses", ),
|
||||||
|
'/pgp/abr': ("Abr.", "Abraham", ),
|
||||||
|
'/pgp/js-m': ("JS—M", "Joseph Smith—Matthew", "JS-M", "JSM", "Joseph Smith-Matthew", ),
|
||||||
|
'/pgp/js-h': ("JS—H", "Joseph Smith—History", "JS-H", "JSH", "Joseph Smith-History", ),
|
||||||
|
'/pgp/a-of-f': ("A of F", "AofF", "Articles of Faith", ),
|
||||||
|
'/ot': ("OT", "Old Testament", "The Old Testament", ),
|
||||||
|
'/nt': ("NT", "New Testament", "The New Testament", ),
|
||||||
|
'/bofm': ("BofM", "Book of Mormon", "The Book of Mormon", ),
|
||||||
|
'/pgp': ("PGP", "PofGP", "Pearl of Great Price", "The Pearl of Great Price", ),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __find_book(book: str) -> tuple[str, list[str]]:
|
||||||
|
matches: dict[str, str] = {}
|
||||||
|
for key, values in __LINKS.items():
|
||||||
|
if book.lower().replace('.', '') in [i.lower().replace('.', '') for i in values]:
|
||||||
|
return (key, [])
|
||||||
|
else:
|
||||||
|
for value in values:
|
||||||
|
if value.lower().startswith(book.lower().replace('.', '')):
|
||||||
|
matches[key] = values[-1]
|
||||||
|
if len(matches) == 1:
|
||||||
|
return (list(matches.keys())[0], [])
|
||||||
|
else:
|
||||||
|
return ("", list(matches.values()))
|
||||||
|
|
||||||
|
def convert_reference(ref: str) -> tuple[str, list[str]]:
|
||||||
|
pattern = re.findall(r'^.*\w\.?\s\d', ref)
|
||||||
|
if pattern:
|
||||||
|
i = len(pattern[-1])-1
|
||||||
|
book, cv = ref[:i-1], \
|
||||||
|
ref[i:].replace(' ', '').replace('–', '-').replace(':', '/')
|
||||||
|
link, possible = __find_book(book)
|
||||||
|
if link:
|
||||||
|
return (f"{link}/{cv}", [])
|
||||||
|
else:
|
||||||
|
return ("", possible)
|
||||||
|
else:
|
||||||
|
return __find_book(ref)
|
||||||
|
|
||||||
|
def convert_url(url: str) -> str:
|
||||||
|
parts = url.strip('/').split('/')
|
||||||
|
|
||||||
|
if not parts:
|
||||||
|
raise ValueError("Empty URL")
|
||||||
|
|
||||||
|
book_key = f"/{'/'.join(parts[:2]) if len(parts) >= 2 else parts[0]}"
|
||||||
|
remainder = parts[2:] if len(parts) > 2 else []
|
||||||
|
|
||||||
|
if book_key not in __LINKS:
|
||||||
|
raise ValueError(f"Book key not found: {book_key}")
|
||||||
|
|
||||||
|
book_name = __LINKS[book_key][-1]
|
||||||
|
|
||||||
|
if not remainder:
|
||||||
|
return book_name
|
||||||
|
elif len(remainder) == 1:
|
||||||
|
return f"{book_name} {remainder[0]}"
|
||||||
|
elif len(remainder) == 2:
|
||||||
|
return f"{book_name} {remainder[0]}:{remainder[1]}"
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unexpected format in URL: {url}")
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# import json
|
||||||
|
# with open('refs.json', 'w', encoding='utf-8') as file:
|
||||||
|
# json.dump(__LINKS, file, indent=4)
|
||||||
|
with open('refs.txt', encoding='utf-8') as file:
|
||||||
|
# for i in file.readlines():
|
||||||
|
for i in file.readlines()[::50]:
|
||||||
|
ref = convert_reference(i[:-1])[0]
|
||||||
|
if ref:
|
||||||
|
print(convert_url(ref))
|
106
server.py
106
server.py
|
@ -3,7 +3,7 @@
|
||||||
# Apr 2025
|
# Apr 2025
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, ClassVar
|
||||||
import select
|
import select
|
||||||
import datetime
|
import datetime
|
||||||
import network_utilities
|
import network_utilities
|
||||||
|
@ -15,24 +15,30 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
class Game:
|
class Game:
|
||||||
|
|
||||||
|
FINAL_SCORE_LIST: ClassVar[int] = 50
|
||||||
|
|
||||||
__library: Library
|
__library: Library
|
||||||
__current_url: str
|
__current_url: str
|
||||||
|
__current_url_parts: list[str]
|
||||||
__clients: list[Player]
|
__clients: list[Player]
|
||||||
__round_points: list[int]
|
__round_points: list[int]
|
||||||
__total_scores: list[int]
|
__total_scores: list[int]
|
||||||
__active: bool
|
__active: bool
|
||||||
__finished: bool
|
__finished: bool
|
||||||
__created: datetime.datetime
|
__created: datetime.datetime
|
||||||
|
__difficulty: int
|
||||||
|
|
||||||
def __init__(self, library: Library):
|
def __init__(self, library: Library):
|
||||||
self.__library = library
|
self.__library = library
|
||||||
self.__current_url = ""
|
self.__current_url = ""
|
||||||
|
self.__current_url_parts = []
|
||||||
self.__clients = []
|
self.__clients = []
|
||||||
self.__round_points = []
|
self.__round_points = []
|
||||||
self.__total_scores = []
|
self.__total_scores = []
|
||||||
self.__active = False
|
self.__active = False
|
||||||
self.__finished = False
|
self.__finished = False
|
||||||
self.__created = datetime.datetime.now()
|
self.__created = datetime.datetime.now()
|
||||||
|
self.__difficulty = 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self) -> bool: return self.__active
|
def active(self) -> bool: return self.__active
|
||||||
|
@ -42,40 +48,75 @@ class Game:
|
||||||
|
|
||||||
def add_player(self, name: str, conn: socket):
|
def add_player(self, name: str, conn: socket):
|
||||||
if not self.__active:
|
if not self.__active:
|
||||||
self.__clients.append(Player(name, self, conn))
|
new_player = Player(name, self, conn)
|
||||||
self.__total_scores.append(0)
|
if self.__clients:
|
||||||
for player in self.__clients:
|
for player in self.__clients:
|
||||||
player.player_joined(name)
|
new_player.player_joined(player.name, False)
|
||||||
|
self.__clients.append(new_player)
|
||||||
|
self.__total_scores.append(0)
|
||||||
|
for player in self.__clients:
|
||||||
|
player.player_joined(name, False)
|
||||||
|
else:
|
||||||
|
new_player.player_joined(new_player.name, True)
|
||||||
|
self.__clients.append(new_player)
|
||||||
|
self.__total_scores.append(0)
|
||||||
|
|
||||||
def start_game(self):
|
def start_game(self):
|
||||||
self.__active = True
|
self.__active = True
|
||||||
|
|
||||||
def start_round(self, difficulty: int):
|
def start_round(self, difficulty: int):
|
||||||
if self.__active and not self.__finished:
|
if self.__active and not self.__finished:
|
||||||
|
self.__difficulty = difficulty
|
||||||
self.__round_points = [0] * len(self.__clients)
|
self.__round_points = [0] * len(self.__clients)
|
||||||
self.__library.get_verse(difficulty, self)
|
self.__library.get_verse(difficulty, self)
|
||||||
|
|
||||||
def new_verse(self, url: str, text: str):
|
def new_verse(self, url: str, text: str):
|
||||||
for player in self.__clients:
|
for player in self.__clients:
|
||||||
self.__current_url = url
|
self.__current_url = url
|
||||||
|
self.__current_url_parts = url.strip('/').split('/')
|
||||||
player.new_verse(text)
|
player.new_verse(text)
|
||||||
|
|
||||||
def guess_reference(self, url: str, player: Player):
|
def guess_reference(self, url: str, player: Player):
|
||||||
if self.__active and not self.__finished:
|
if self.__active and not self.__finished:
|
||||||
if url == self.__current_url:
|
if url == self.__current_url:
|
||||||
player.guess_correct()
|
player.guess_correct()
|
||||||
|
self.__round_points[self.__clients.index(player)] = 4 + self.__difficulty
|
||||||
for i, points in enumerate(self.__round_points):
|
for i, points in enumerate(self.__round_points):
|
||||||
self.__total_scores[i] += points
|
self.__total_scores[i] += points
|
||||||
self.__clients[i].verse_guessed(
|
self.__clients[i].verse_guessed(
|
||||||
points, self.__current_url, player.name)
|
points, self.__current_url, player.name)
|
||||||
else:
|
else:
|
||||||
player.guess_incorrect()
|
partially_correct = []
|
||||||
|
for player_url, current_url in zip(url.strip('/').split('/'), self.__current_url_parts):
|
||||||
|
if player_url == current_url:
|
||||||
|
partially_correct.append(current_url)
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
if partially_correct:
|
||||||
|
player.guess_partial_correct(f"/{'/'.join(partially_correct)}")
|
||||||
|
self.__round_points[self.__clients.index(player)] = len(partially_correct)
|
||||||
|
else: player.guess_incorrect()
|
||||||
|
|
||||||
def end_game(self):
|
def end_game(self):
|
||||||
self.__finished = True
|
self.__finished = True
|
||||||
|
items = sorted(
|
||||||
|
[(i.name, j) for i, j in zip(self.__clients, self.__total_scores)],
|
||||||
|
reverse=True,
|
||||||
|
key=lambda o: o[1]
|
||||||
|
)
|
||||||
|
players = [i for i, _ in items]
|
||||||
|
scores = [i for _, i in items]
|
||||||
for player in self.__clients:
|
for player in self.__clients:
|
||||||
player.game_over(
|
if player.name in players[:Game.FINAL_SCORE_LIST]:
|
||||||
[i.name for i in self.__clients], self.__total_scores)
|
player.game_over(
|
||||||
|
players[:Game.FINAL_SCORE_LIST],
|
||||||
|
scores[:Game.FINAL_SCORE_LIST]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
player.game_over(
|
||||||
|
players[:Game.FINAL_SCORE_LIST] + [player.name],
|
||||||
|
scores[:Game.FINAL_SCORE_LIST] + [scores[players.index(player.name)]]
|
||||||
|
)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
if not self.__active and (
|
if not self.__active and (
|
||||||
|
@ -95,34 +136,48 @@ class Player:
|
||||||
__name: str
|
__name: str
|
||||||
__game: Game
|
__game: Game
|
||||||
__client: socket
|
__client: socket
|
||||||
|
__connected: bool
|
||||||
|
|
||||||
def __init__(self, name: str, game: Game, conn: socket):
|
def __init__(self, name: str, game: Game, conn: socket):
|
||||||
self.__name = name
|
self.__name = name
|
||||||
self.__game = game
|
self.__game = game
|
||||||
self.__client = conn
|
self.__client = conn
|
||||||
|
self.__connected = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str: return self.__name
|
def name(self) -> str: return self.__name
|
||||||
|
|
||||||
def player_joined(self, name: str):
|
def player_joined(self, name: str, admin: bool):
|
||||||
|
print(f">> (1, {self.name}) player_joined({name}, {admin})")
|
||||||
data = network_utilities.pack_varint(1)
|
data = network_utilities.pack_varint(1)
|
||||||
data += network_utilities.pack_string(name)
|
data += network_utilities.pack_string(name)
|
||||||
|
data += network_utilities.pack_varint(admin)
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
|
|
||||||
def new_verse(self, text: str):
|
def new_verse(self, text: str):
|
||||||
|
print(f">> (2, {self.name}) new_verse({text})")
|
||||||
data = network_utilities.pack_varint(2)
|
data = network_utilities.pack_varint(2)
|
||||||
data += network_utilities.pack_string(text)
|
data += network_utilities.pack_string(text)
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
|
|
||||||
def guess_incorrect(self):
|
def guess_incorrect(self):
|
||||||
|
print(f">> (3, {self.name}) guess_incorrect()")
|
||||||
data = network_utilities.pack_varint(3)
|
data = network_utilities.pack_varint(3)
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
|
|
||||||
|
def guess_partial_correct(self, url):
|
||||||
|
print(f">> (7, {self.name}) guess_partial_correct({url})")
|
||||||
|
data = network_utilities.pack_varint(7)
|
||||||
|
data += network_utilities.pack_string(url)
|
||||||
|
self.__client.send(data)
|
||||||
|
|
||||||
def guess_correct(self):
|
def guess_correct(self):
|
||||||
|
print(f">> (4, {self.name}) guess_correct()")
|
||||||
data = network_utilities.pack_varint(4)
|
data = network_utilities.pack_varint(4)
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
|
|
||||||
def verse_guessed(self, points: int, url: str, player: str):
|
def verse_guessed(self, points: int, url: str, player: str):
|
||||||
|
print(f">> (5, {self.name}) verse_guessed({points}, {url})")
|
||||||
data = network_utilities.pack_varint(5)
|
data = network_utilities.pack_varint(5)
|
||||||
data += network_utilities.pack_varint(points)
|
data += network_utilities.pack_varint(points)
|
||||||
data += network_utilities.pack_string(url)
|
data += network_utilities.pack_string(url)
|
||||||
|
@ -130,24 +185,31 @@ class Player:
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
|
|
||||||
def game_over(self, players: list[str], scores: list[int]):
|
def game_over(self, players: list[str], scores: list[int]):
|
||||||
|
print(f">> (6, {self.name}) game_over({len(players)}, {len(scores)})")
|
||||||
data = network_utilities.pack_varint(6)
|
data = network_utilities.pack_varint(6)
|
||||||
data += network_utilities.pack_string_array(players)
|
data += network_utilities.pack_string_array(players)
|
||||||
data += network_utilities.pack_varint_array(scores)
|
data += network_utilities.pack_varint_array(scores)
|
||||||
self.__client.send(data)
|
self.__client.send(data)
|
||||||
self.__client.close()
|
self.__client.close()
|
||||||
|
self.__connected = False
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
ready_to_read, _, _ = select.select([self.__client], [], [], 0)
|
if self.__connected:
|
||||||
if ready_to_read:
|
ready_to_read, _, _ = select.select([self.__client], [], [], 0)
|
||||||
packet_id = network_utilities.unpack_varint(self.__client)
|
if ready_to_read:
|
||||||
if packet_id == 2:
|
packet_id = network_utilities.unpack_varint(self.__client)
|
||||||
self.__game.start_game()
|
if packet_id == 2:
|
||||||
elif packet_id == 3:
|
print(f"<< (2, {self.name}) start_game()")
|
||||||
difficulty = network_utilities.unpack_varint(self.__client)
|
self.__game.start_game()
|
||||||
self.__game.start_round(difficulty)
|
elif packet_id == 3:
|
||||||
elif packet_id == 4:
|
difficulty = network_utilities.unpack_varint(self.__client)
|
||||||
url = network_utilities.unpack_string(self.__client)
|
print(f"<< (3, {self.name}) start_round({difficulty})")
|
||||||
self.__game.guess_reference(url, self)
|
self.__game.start_round(difficulty)
|
||||||
elif packet_id == 5:
|
elif packet_id == 4:
|
||||||
self.__game.end_game()
|
url = network_utilities.unpack_string(self.__client)
|
||||||
|
print(f"<< (4, {self.name}) guess_reference({url})")
|
||||||
|
self.__game.guess_reference(url, self)
|
||||||
|
elif packet_id == 5:
|
||||||
|
print(f"<< (5, {self.name}) end_game()")
|
||||||
|
self.__game.end_game()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,157 @@
|
||||||
|
|
||||||
|
from client import Player
|
||||||
|
from reference import convert_reference, convert_url
|
||||||
|
from time import sleep
|
||||||
|
from blessed import Terminal
|
||||||
|
|
||||||
|
|
||||||
|
class UI:
|
||||||
|
|
||||||
|
__player: Player
|
||||||
|
__verse: str
|
||||||
|
__in_game: bool
|
||||||
|
__in_between_rounds: bool
|
||||||
|
__game_over: bool
|
||||||
|
__term: Terminal
|
||||||
|
__buffer: str
|
||||||
|
|
||||||
|
def __init__(self, playername: str, host: str='localhost', port: int=7788):
|
||||||
|
self.__player = Player(playername, self)
|
||||||
|
self.__player.join_game(host, port)
|
||||||
|
self.__verse = ""
|
||||||
|
self.__in_game = False
|
||||||
|
self.__in_between_rounds = False
|
||||||
|
self.__game_over = False
|
||||||
|
self.__term = Terminal()
|
||||||
|
self.__buffer = ""
|
||||||
|
|
||||||
|
def get_line(self):
|
||||||
|
with self.__term.cbreak():
|
||||||
|
val = self.__term.inkey(timeout=0)
|
||||||
|
if not val:
|
||||||
|
return None
|
||||||
|
if val.is_sequence:
|
||||||
|
if val.name == 'KEY_ENTER':
|
||||||
|
line = self.__buffer
|
||||||
|
self.__buffer = ""
|
||||||
|
print()
|
||||||
|
return line
|
||||||
|
elif val.name == 'KEY_BACKSPACE':
|
||||||
|
self.__buffer = self.__buffer[:-1]
|
||||||
|
print(f'\r{self.__term.clear_eol}{self.__buffer}', end='', flush=True)
|
||||||
|
else:
|
||||||
|
self.__buffer += val
|
||||||
|
print(val, end='', flush=True)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __reset(self):
|
||||||
|
self.__buffer = ""
|
||||||
|
print()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
while not self.__game_over:
|
||||||
|
self.__player.update()
|
||||||
|
if text := self.get_line():
|
||||||
|
if self.__in_between_rounds and self.__player.admin:
|
||||||
|
self.__next_round(text)
|
||||||
|
elif self.__in_between_rounds:
|
||||||
|
print("Waiting for the next round to start...")
|
||||||
|
elif self.__in_game: self.__guess_ref(text)
|
||||||
|
elif self.__player.admin: self.__start_game(text)
|
||||||
|
else:
|
||||||
|
print("Waiting for the game to start...")
|
||||||
|
sleep(0.1)
|
||||||
|
|
||||||
|
def __next_round(self, text: str):
|
||||||
|
if text.isdigit() and 0 <= int(text) <= 10:
|
||||||
|
print(f"Starting round with difficulty: {text}")
|
||||||
|
self.__player.new_round(int(text))
|
||||||
|
elif text.lower() == 'e':
|
||||||
|
self.__player.end_game()
|
||||||
|
else:
|
||||||
|
print("Invalid input!\nPlease enter a difficulty level between 1 and 10.")
|
||||||
|
|
||||||
|
def __guess_ref(self, text: str):
|
||||||
|
try:
|
||||||
|
url, possible = convert_reference(text)
|
||||||
|
except Exception:
|
||||||
|
print(
|
||||||
|
"An Unknown Error Occurred.\n"
|
||||||
|
"Please Check Your Reference and Try Again."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if url:
|
||||||
|
try: ref = convert_url(url)
|
||||||
|
except Exception: ref = url.upper().replace('/','.').strip('.')
|
||||||
|
print(f"Your input was interpreted as: {ref}")
|
||||||
|
self.__player.guess_reference(url)
|
||||||
|
return
|
||||||
|
elif possible:
|
||||||
|
print(
|
||||||
|
"Sorry, that reference could not be found.\n"
|
||||||
|
"Did you mean one of these:"
|
||||||
|
)
|
||||||
|
for i in possible:
|
||||||
|
print(i)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"Sorry, that reference could not be found.\n"
|
||||||
|
"Please Check Your Reference and Try Again."
|
||||||
|
)
|
||||||
|
print(self.__verse)
|
||||||
|
|
||||||
|
def __start_game(self, text: str = "1"):
|
||||||
|
print("Starting game...")
|
||||||
|
self.__in_between_rounds = True
|
||||||
|
self.__player.start_game()
|
||||||
|
self.__next_round(text)
|
||||||
|
|
||||||
|
def player_joined(self, name: str, admin: bool):
|
||||||
|
if name == self.__player.name:
|
||||||
|
if admin:
|
||||||
|
print("You are the game host.")
|
||||||
|
print("Please enter the difficulty for the first round to start the game.")
|
||||||
|
print("(Difficulty Range: 1 Easy - 10 Hard)")
|
||||||
|
print(f"* {name} Joined the Game *")
|
||||||
|
else: print(f"{name} Joined the Game")
|
||||||
|
|
||||||
|
def new_verse(self, text: str):
|
||||||
|
self.__reset()
|
||||||
|
self.__in_game = True
|
||||||
|
self.__in_between_rounds = False
|
||||||
|
self.__verse = text
|
||||||
|
print(self.__verse)
|
||||||
|
|
||||||
|
def guess_incorrect(self):
|
||||||
|
print("That guess was incorrect.")
|
||||||
|
print(self.__verse)
|
||||||
|
|
||||||
|
def guess_partial_correct(self, url):
|
||||||
|
try: ref = convert_url(url)
|
||||||
|
except Exception: ref = url.upper().replace('/','.').strip('.')
|
||||||
|
print(f"That guess was partially correct: {ref}")
|
||||||
|
print(self.__verse)
|
||||||
|
|
||||||
|
def guess_correct(self):
|
||||||
|
print("That guess was correct!")
|
||||||
|
|
||||||
|
def verse_guessed(self, points: int, url: str, player: str):
|
||||||
|
try: ref = convert_url(url)
|
||||||
|
except Exception: ref = url.upper().replace('/','.').strip('.')
|
||||||
|
print(
|
||||||
|
f"\nThe verse has been guessed by {player}.\n"
|
||||||
|
f"The reference is {ref}.\n"
|
||||||
|
f"You have been awarded {points} points for your guess."
|
||||||
|
)
|
||||||
|
self.__in_between_rounds = True
|
||||||
|
if self.__player.admin:
|
||||||
|
print("Please enter the difficulty for the next round (1-10) or 'e' to end the game.")
|
||||||
|
|
||||||
|
def game_over(self, players: list[str], scores: list[int]):
|
||||||
|
self.__game_over = True
|
||||||
|
print("\n--- THANKS FOR PLAYING! ---")
|
||||||
|
for player, score in zip(players, scores):
|
||||||
|
if player == self.__player.name:
|
||||||
|
print(f" * {player}: {score} *")
|
||||||
|
else:
|
||||||
|
print(f" {player}: {score}")
|
Loading…
Reference in New Issue