From c1a14c29e8aa34809c467c3739827f89b6adb0bf Mon Sep 17 00:00:00 2001 From: KylerO Date: Tue, 15 Apr 2025 17:14:25 -0600 Subject: [PATCH 1/4] Started game class --- game.py | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ library.py | 10 +++------- 2 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 game.py diff --git a/game.py b/game.py new file mode 100644 index 0000000..d62a8cc --- /dev/null +++ b/game.py @@ -0,0 +1,54 @@ +# Kyler Olsen +# CS 2450 Final Project +# Apr 2025 + +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from socket import SocketIO + +class Game: + + __difficulty: int + __players: list[str] + __clients: list[SocketIO] + __scores: list[int] + __active: bool + __finished: bool + + def __init__(self): + self.__difficulty = 0 + self.__players = [] + self.__clients = [] + self.__scores = [] + self.__active = False + self.__finished = False + + @property + def active(self) -> bool: return self.__active + + @property + def finished(self) -> bool: return self.__finished + + def add_player(self, name: str): + if not self.__active: + self.__players.append(name) + self.__scores.append(0) + + def start_game(self): + self.__active = True + + def start_round(self, difficulty: int): + if self.__active and not self.__finished: + self.__difficulty = difficulty + + def new_verse(self, url: str, text: str): + pass + + def guess_reference(self, ref: str, player: str): + pass + + def end_game(self): + self.__finished = True + diff --git a/library.py b/library.py index bd8a1c2..10dcbfa 100644 --- a/library.py +++ b/library.py @@ -32,10 +32,11 @@ class Library: game_num = len(self.__games) - 1 self.__games[game_num].add_player(name) - def get_verse(self, difficulty: int): + def get_verse(self, difficulty: int, game: Game): url = self.__select_verse(difficulty) text = self.__get_verse_text(url) - print(text, end='\n\n') + + game.new_verse(url, text) def __select_verse(self, difficulty: int) -> str: @@ -76,8 +77,3 @@ class Library: return difficulty_verses - -if __name__ == '__main__': - - lib = Library() - for i in range(11): lib.get_verse(i) -- 2.39.5 From a834803378fadc0563f2e2ff03389cf200fcff79 Mon Sep 17 00:00:00 2001 From: KylerO Date: Tue, 15 Apr 2025 17:26:08 -0600 Subject: [PATCH 2/4] Started player class --- player.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 player.py diff --git a/player.py b/player.py new file mode 100644 index 0000000..0713edb --- /dev/null +++ b/player.py @@ -0,0 +1,57 @@ +# Kyler Olsen +# CS 2450 Final Project +# Apr 2025 + +from __future__ import annotations +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from socket import SocketIO + + +class Player: + + __name: str + __verse: str + __score: int + __server: SocketIO | None + + def __init__(self, name: str): + self.__name = name + self.__verse = "" + self.__score = 0 + self.__server = None + + def join_game(self): + pass + + def start_game(self): + pass + + def guess_referene(self, ref: str): + pass + + def new_round(self, difficulty: int): + pass + + def end_game(self): + pass + + def player_joined(self, name: str): + pass + + def new_verse(self, text: str): + pass + + def guess_incorrect(self): + pass + + def guess_correct(self, points: int): + pass + + def verse_guessed(self, url: str, player: str): + pass + + def game_over(self, players: list[str], scores: list[int]): + pass + -- 2.39.5 From 995be37d82504811040371b7be1cacfbb64d9611 Mon Sep 17 00:00:00 2001 From: Kyler Date: Wed, 16 Apr 2025 00:04:29 -0600 Subject: [PATCH 3/4] Started networking --- client.py | 118 +++++++++++++++++++++++++++++++++++++++ game.py | 54 ------------------ library.py | 39 ++++++++++--- network_utilities.py | 69 +++++++++++++++++++++++ player.py | 57 ------------------- server.py | 130 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 349 insertions(+), 118 deletions(-) create mode 100644 client.py delete mode 100644 game.py create mode 100644 network_utilities.py delete mode 100644 player.py create mode 100644 server.py diff --git a/client.py b/client.py new file mode 100644 index 0000000..fccce24 --- /dev/null +++ b/client.py @@ -0,0 +1,118 @@ +# Kyler Olsen +# CS 2450 Final Project +# Apr 2025 + +from __future__ import annotations +import network_utilities +import socket + + +class Game: + + __player: Player + __server: socket.socket + + def __init__(self, player: Player, server: socket.socket): + self.__player = player + self.__server = server + + def start_game(self): + data = network_utilities.pack_varint(1) + self.__server.send(data) + + def start_round(self, difficulty: int): + data = network_utilities.pack_varint(2) + data += network_utilities.pack_varint(difficulty) + self.__server.send(data) + + def guess_reference(self, url: str): + data = network_utilities.pack_varint(3) + data += network_utilities.pack_string(url) + self.__server.send(data) + + def end_game(self): + data = network_utilities.pack_varint(4) + self.__server.send(data) + + def update(self): + packet_id = network_utilities.unpack_varint(self.__server) + if packet_id == 1: + name = network_utilities.unpack_string(self.__server) + self.__player.player_joined(name) + elif packet_id == 2: + text = network_utilities.unpack_string(self.__server) + self.__player.new_verse(text) + elif packet_id == 3: + self.__player.guess_incorrect() + elif packet_id == 4: + self.__player.guess_correct() + elif packet_id == 5: + points = network_utilities.unpack_varint(self.__server) + url = network_utilities.unpack_string(self.__server) + player = network_utilities.unpack_string(self.__server) + self.__player.verse_guessed(points, url, player) + elif packet_id == 6: + players = network_utilities.unpack_string_array(self.__server) + scores = network_utilities.unpack_varint_array(self.__server) + self.__player.game_over(players, scores) + + +class Player: + + __name: str + __verse: str + __score: int + __game: Game | None + + def __init__(self, name: str): + self.__name = name + self.__verse = "" + self.__score = 0 + self.__game = None + + @property + def name(self) -> str: return self.__name + + @property + def verse(self) -> str: return self.__verse + + @property + def score(self) -> int: return self.__score + + def join_game(self, host: str = 'localhost', port: int = 7788): + pass + + def start_game(self): + if self.__game is not None: + self.__game.start_game() + + def guess_reference(self, url: str): + if self.__game is not None: + self.__game.guess_reference(url) + + def new_round(self, difficulty: int): + if self.__game is not None: + self.__game.start_round(difficulty) + + def end_game(self): + if self.__game is not None: + self.__game.end_game() + + def player_joined(self, name: str): + pass + + def new_verse(self, text: str): + pass + + def guess_incorrect(self): + pass + + def guess_correct(self): + pass + + def verse_guessed(self, points: int, url: str, player: str): + pass + + def game_over(self, players: list[str], scores: list[int]): + pass + diff --git a/game.py b/game.py deleted file mode 100644 index d62a8cc..0000000 --- a/game.py +++ /dev/null @@ -1,54 +0,0 @@ -# Kyler Olsen -# CS 2450 Final Project -# Apr 2025 - -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from socket import SocketIO - -class Game: - - __difficulty: int - __players: list[str] - __clients: list[SocketIO] - __scores: list[int] - __active: bool - __finished: bool - - def __init__(self): - self.__difficulty = 0 - self.__players = [] - self.__clients = [] - self.__scores = [] - self.__active = False - self.__finished = False - - @property - def active(self) -> bool: return self.__active - - @property - def finished(self) -> bool: return self.__finished - - def add_player(self, name: str): - if not self.__active: - self.__players.append(name) - self.__scores.append(0) - - def start_game(self): - self.__active = True - - def start_round(self, difficulty: int): - if self.__active and not self.__finished: - self.__difficulty = difficulty - - def new_verse(self, url: str, text: str): - pass - - def guess_reference(self, ref: str, player: str): - pass - - def end_game(self): - self.__finished = True - diff --git a/library.py b/library.py index 10dcbfa..c164c32 100644 --- a/library.py +++ b/library.py @@ -6,21 +6,41 @@ from __future__ import annotations from typing import TYPE_CHECKING import json import random +import socket if TYPE_CHECKING: - from game import Game + from server import Game class Library: __verses: dict __games: list[Game] + __host: str + __port: int + __socket: socket.socket - def __init__(self): + def __init__(self, host: str = '', port: int = 7788): with open("data/scripture-frequencies.json", encoding='utf-8') as file: self.__verses = json.load(file) self.__games = [] + self.__host = host + self.__port = port + self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + def serve_forever(self): + try: + with self.__socket as s: + s.bind((self.__host, self.__port)) + s.listen(1) + while True: + conn, _ = s.accept() + ... + except KeyboardInterrupt: + print("KeyboardInterrupt\nExiting...") + return + def join_game(self, name: str, game_num: int = -1): if game_num == -1: for i, game in enumerate(self.__games): @@ -48,11 +68,16 @@ class Library: lang = 'eng' volume, book_url, chapter_url, verse_url = url[1:].split('/') - if volume == 'ot': filename = f"data/{lang}.old-testament.json" - elif volume == 'nt': filename = f"data/{lang}.new-testament.json" - elif volume == 'bofm': filename = f"data/{lang}.book-of-mormon.json" - elif volume == 'dc-testament': filename = f"data/{lang}.doctrine-and-covenants.json" - elif volume == 'pgp': filename = f"data/{lang}.pearl-of-great-price.json" + if volume == 'ot': + filename = f"data/{lang}.old-testament.json" + elif volume == 'nt': + filename = f"data/{lang}.new-testament.json" + elif volume == 'bofm': + filename = f"data/{lang}.book-of-mormon.json" + elif volume == 'dc-testament': + filename = f"data/{lang}.doctrine-and-covenants.json" + elif volume == 'pgp': + filename = f"data/{lang}.pearl-of-great-price.json" with open(filename, encoding='utf-8') as file: data = json.load(file) diff --git a/network_utilities.py b/network_utilities.py new file mode 100644 index 0000000..ff95aae --- /dev/null +++ b/network_utilities.py @@ -0,0 +1,69 @@ +# Kyler Olsen +# CS 2450 Final Project +# Apr 2025 + +from __future__ import annotations +from typing import TYPE_CHECKING +import struct + +if TYPE_CHECKING: + from socket import socket + +def pack_varint(data: int) -> bytes: + ordinal = b'' + while True: + byte = data & 0x7F + data >>= 7 + ordinal += struct.pack('B', byte | (0x80 if data > 0 else 0)) + if data == 0: + break + return ordinal + +def unpack_varint(conn: socket) -> int: + data = 0 + for i in range(5): + ordinal = conn.recv(1) + if len(ordinal) == 0: + break + byte = ord(ordinal) + data |= (byte & 0x7F) << 7*i + if not byte & 0x80: + break + return data + +def pack_string(text: str) -> bytes: + data = pack_varint(len(text)) + data += text.encode('utf-8') + return data + +def unpack_string(conn: socket) -> str: + length = unpack_varint(conn) + data = conn.recv(length) + text = data.decode('utf-8') + return text + +def pack_varint_array(array: list[int]) -> bytes: + data = pack_varint(len(array)) + for i in array: + data += pack_varint(i) + return data + +def unpack_varint_array(conn: socket) -> list[int]: + length = unpack_varint(conn) + array: list[int] = [] + for _ in range(length): + array.append(unpack_varint(conn)) + return array + +def pack_string_array(array: list[str]) -> bytes: + data = pack_varint(len(array)) + for i in array: + data += pack_string(i) + return data + +def unpack_string_array(conn: socket) -> list[str]: + length = unpack_varint(conn) + array: list[str] = [] + for _ in range(length): + array.append(unpack_string(conn)) + return array diff --git a/player.py b/player.py deleted file mode 100644 index 0713edb..0000000 --- a/player.py +++ /dev/null @@ -1,57 +0,0 @@ -# Kyler Olsen -# CS 2450 Final Project -# Apr 2025 - -from __future__ import annotations -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from socket import SocketIO - - -class Player: - - __name: str - __verse: str - __score: int - __server: SocketIO | None - - def __init__(self, name: str): - self.__name = name - self.__verse = "" - self.__score = 0 - self.__server = None - - def join_game(self): - pass - - def start_game(self): - pass - - def guess_referene(self, ref: str): - pass - - def new_round(self, difficulty: int): - pass - - def end_game(self): - pass - - def player_joined(self, name: str): - pass - - def new_verse(self, text: str): - pass - - def guess_incorrect(self): - pass - - def guess_correct(self, points: int): - pass - - def verse_guessed(self, url: str, player: str): - pass - - def game_over(self, players: list[str], scores: list[int]): - pass - diff --git a/server.py b/server.py new file mode 100644 index 0000000..7709ee3 --- /dev/null +++ b/server.py @@ -0,0 +1,130 @@ +# Kyler Olsen +# CS 2450 Final Project +# Apr 2025 + +from __future__ import annotations +from typing import TYPE_CHECKING +import network_utilities + +if TYPE_CHECKING: + from socket import socket + from library import Library + + +class Game: + + __library: Library + __current_url: str + __clients: list[Player] + __round_points: list[int] + __total_scores: list[int] + __active: bool + __finished: bool + + def __init__(self, library: Library): + self.__library = library + self.__current_url = "" + self.__clients = [] + self.__round_points = [] + self.__total_scores = [] + self.__active = False + self.__finished = False + + @property + def active(self) -> bool: return self.__active + + @property + def finished(self) -> bool: return self.__finished + + def add_player(self, name: str, socket: socket): + if not self.__active: + self.__clients.append(Player(name, self, socket)) + self.__total_scores.append(0) + for player in self.__clients: + player.player_joined(name) + + def start_game(self): + self.__active = True + + def start_round(self, difficulty: int): + if self.__active and not self.__finished: + self.__round_points = [0] * len(self.__clients) + self.__library.get_verse(difficulty, self) + + def new_verse(self, url: str, text: str): + for player in self.__clients: + self.__current_url = url + player.new_verse(text) + + def guess_reference(self, url: str, player: Player): + if self.__active and not self.__finished: + if url == self.__current_url: + player.guess_correct() + for i, points in enumerate(self.__round_points): + self.__total_scores[i] += points + self.__clients[i].verse_guessed( + points, self.__current_url, player.name) + else: + player.guess_incorrect() + + def end_game(self): + self.__finished = True + + +class Player: + + __name: str + __game: Game + __client: socket + + def __init__(self, name: str, game: Game, socket: socket): + self.__name = name + self.__game = game + self.__client = socket + + @property + def name(self) -> str: return self.__name + + def player_joined(self, name: str): + data = network_utilities.pack_varint(1) + data += network_utilities.pack_string(name) + self.__client.send(data) + + def new_verse(self, text: str): + data = network_utilities.pack_varint(2) + data += network_utilities.pack_string(text) + self.__client.send(data) + + def guess_incorrect(self): + data = network_utilities.pack_varint(3) + self.__client.send(data) + + def guess_correct(self): + data = network_utilities.pack_varint(4) + self.__client.send(data) + + def verse_guessed(self, points: int, url: str, player: str): + data = network_utilities.pack_varint(5) + data += network_utilities.pack_varint(points) + data += network_utilities.pack_string(url) + data += network_utilities.pack_string(player) + self.__client.send(data) + + def game_over(self, players: list[str], scores: list[int]): + data = network_utilities.pack_varint(6) + data += network_utilities.pack_string_array(players) + data += network_utilities.pack_varint_array(scores) + self.__client.send(data) + + def update(self): + packet_id = network_utilities.unpack_varint(self.__client) + if packet_id == 1: + self.__game.start_game() + elif packet_id == 2: + difficulty = network_utilities.unpack_varint(self.__client) + self.__game.start_round(difficulty) + elif packet_id == 3: + url = network_utilities.unpack_string(self.__client) + self.__game.guess_reference(url, self) + elif packet_id == 4: + self.__game.end_game() -- 2.39.5 From ce829b62b12b55908343d7cd37f3c6b87e516e12 Mon Sep 17 00:00:00 2001 From: KylerO Date: Wed, 16 Apr 2025 11:11:22 -0600 Subject: [PATCH 4/4] Finished most of networking --- client.py | 82 +++++++++++++++++++++++++++++++++--------------------- library.py | 39 +++++++++++++++----------- server.py | 53 +++++++++++++++++++++++++---------- 3 files changed, 111 insertions(+), 63 deletions(-) diff --git a/client.py b/client.py index fccce24..cd19349 100644 --- a/client.py +++ b/client.py @@ -3,8 +3,9 @@ # Apr 2025 from __future__ import annotations -import network_utilities import socket +import select +import network_utilities class Game: @@ -17,44 +18,46 @@ class Game: self.__server = server def start_game(self): - data = network_utilities.pack_varint(1) + data = network_utilities.pack_varint(2) self.__server.send(data) def start_round(self, difficulty: int): - data = network_utilities.pack_varint(2) + data = network_utilities.pack_varint(3) data += network_utilities.pack_varint(difficulty) self.__server.send(data) def guess_reference(self, url: str): - data = network_utilities.pack_varint(3) + data = network_utilities.pack_varint(4) data += network_utilities.pack_string(url) self.__server.send(data) def end_game(self): - data = network_utilities.pack_varint(4) + data = network_utilities.pack_varint(5) self.__server.send(data) def update(self): - packet_id = network_utilities.unpack_varint(self.__server) - if packet_id == 1: - name = network_utilities.unpack_string(self.__server) - self.__player.player_joined(name) - elif packet_id == 2: - text = network_utilities.unpack_string(self.__server) - self.__player.new_verse(text) - elif packet_id == 3: - self.__player.guess_incorrect() - elif packet_id == 4: - self.__player.guess_correct() - elif packet_id == 5: - points = network_utilities.unpack_varint(self.__server) - url = network_utilities.unpack_string(self.__server) - player = network_utilities.unpack_string(self.__server) - self.__player.verse_guessed(points, url, player) - elif packet_id == 6: - players = network_utilities.unpack_string_array(self.__server) - scores = network_utilities.unpack_varint_array(self.__server) - self.__player.game_over(players, scores) + ready_to_read, _, _ = select.select([self.__server], [], [], 0) + if ready_to_read: + packet_id = network_utilities.unpack_varint(self.__server) + if packet_id == 1: + name = network_utilities.unpack_string(self.__server) + self.__player.player_joined(name) + elif packet_id == 2: + text = network_utilities.unpack_string(self.__server) + self.__player.new_verse(text) + elif packet_id == 3: + self.__player.guess_incorrect() + elif packet_id == 4: + self.__player.guess_correct() + elif packet_id == 5: + points = network_utilities.unpack_varint(self.__server) + url = network_utilities.unpack_string(self.__server) + player = network_utilities.unpack_string(self.__server) + self.__player.verse_guessed(points, url, player) + elif packet_id == 6: + players = network_utilities.unpack_string_array(self.__server) + scores = network_utilities.unpack_varint_array(self.__server) + self.__player.game_over(players, scores) class Player: @@ -80,7 +83,12 @@ class Player: def score(self) -> int: return self.__score def join_game(self, host: str = 'localhost', port: int = 7788): - pass + conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + conn.connect((host, port)) + data = network_utilities.pack_varint(1) + data += network_utilities.pack_string(self.name) + conn.send(data) + self.__game = Game(self, conn) def start_game(self): if self.__game is not None: @@ -99,20 +107,30 @@ class Player: self.__game.end_game() def player_joined(self, name: str): - pass + if self.__game is not None: + pass def new_verse(self, text: str): - pass + if self.__game is not None: + pass def guess_incorrect(self): - pass + if self.__game is not None: + pass def guess_correct(self): - pass + if self.__game is not None: + pass def verse_guessed(self, points: int, url: str, player: str): - pass + if self.__game is not None: + pass def game_over(self, players: list[str], scores: list[int]): - pass + if self.__game is not None: + pass + + def update(self): + if self.__game is not None: + self.__game.update() diff --git a/library.py b/library.py index c164c32..38daec0 100644 --- a/library.py +++ b/library.py @@ -3,13 +3,12 @@ # Apr 2025 from __future__ import annotations -from typing import TYPE_CHECKING import json import random import socket - -if TYPE_CHECKING: - from server import Game +import select +import network_utilities +from server import Game class Library: @@ -34,23 +33,31 @@ class Library: with self.__socket as s: s.bind((self.__host, self.__port)) s.listen(1) + s.setblocking(False) while True: - conn, _ = s.accept() - ... + ready_to_read, _, _ = select.select([s], [], [], 0) + for game in self.__games[:]: + if not game.finished: game.update() + else: self.__games.remove(game) + if ready_to_read: + conn, _ = s.accept() + conn.setblocking(False) + if network_utilities.unpack_varint(conn) == 1: + name = network_utilities.unpack_string(conn) + self.join_game(name, conn) except KeyboardInterrupt: print("KeyboardInterrupt\nExiting...") return - def join_game(self, name: str, game_num: int = -1): - if game_num == -1: - for i, game in enumerate(self.__games): - if not game.active: - game_num = i - break - else: - self.__games.append(Game()) - game_num = len(self.__games) - 1 - self.__games[game_num].add_player(name) + def join_game(self, name: str, conn: socket.socket): + for i, game in enumerate(self.__games): + if not game.active: + game_num = i + break + else: + self.__games.append(Game(self)) + game_num = len(self.__games) - 1 + self.__games[game_num].add_player(name, conn) def get_verse(self, difficulty: int, game: Game): url = self.__select_verse(difficulty) diff --git a/server.py b/server.py index 7709ee3..1223251 100644 --- a/server.py +++ b/server.py @@ -4,6 +4,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +import select +import datetime import network_utilities if TYPE_CHECKING: @@ -20,6 +22,7 @@ class Game: __total_scores: list[int] __active: bool __finished: bool + __created: datetime.datetime def __init__(self, library: Library): self.__library = library @@ -29,6 +32,7 @@ class Game: self.__total_scores = [] self.__active = False self.__finished = False + self.__created = datetime.datetime.now() @property def active(self) -> bool: return self.__active @@ -36,9 +40,9 @@ class Game: @property def finished(self) -> bool: return self.__finished - def add_player(self, name: str, socket: socket): + def add_player(self, name: str, conn: socket): if not self.__active: - self.__clients.append(Player(name, self, socket)) + self.__clients.append(Player(name, self, conn)) self.__total_scores.append(0) for player in self.__clients: player.player_joined(name) @@ -69,6 +73,21 @@ class Game: def end_game(self): self.__finished = True + for player in self.__clients: + player.game_over( + [i.name for i in self.__clients], self.__total_scores) + + def update(self): + if not self.__active and ( + datetime.datetime.now() - self.__created >= + datetime.timedelta(minutes=10) + ) or ( + datetime.datetime.now() - self.__created >= + datetime.timedelta(1) + ): self.__finished = True + if not self.__finished: + for player in self.__clients: + player.update() class Player: @@ -77,10 +96,10 @@ class Player: __game: Game __client: socket - def __init__(self, name: str, game: Game, socket: socket): + def __init__(self, name: str, game: Game, conn: socket): self.__name = name self.__game = game - self.__client = socket + self.__client = conn @property def name(self) -> str: return self.__name @@ -115,16 +134,20 @@ class Player: data += network_utilities.pack_string_array(players) data += network_utilities.pack_varint_array(scores) self.__client.send(data) + self.__client.close() def update(self): - packet_id = network_utilities.unpack_varint(self.__client) - if packet_id == 1: - self.__game.start_game() - elif packet_id == 2: - difficulty = network_utilities.unpack_varint(self.__client) - self.__game.start_round(difficulty) - elif packet_id == 3: - url = network_utilities.unpack_string(self.__client) - self.__game.guess_reference(url, self) - elif packet_id == 4: - self.__game.end_game() + ready_to_read, _, _ = select.select([self.__client], [], [], 0) + if ready_to_read: + packet_id = network_utilities.unpack_varint(self.__client) + if packet_id == 2: + self.__game.start_game() + elif packet_id == 3: + difficulty = network_utilities.unpack_varint(self.__client) + self.__game.start_round(difficulty) + elif packet_id == 4: + url = network_utilities.unpack_string(self.__client) + self.__game.guess_reference(url, self) + elif packet_id == 5: + self.__game.end_game() + -- 2.39.5