game class is now separate from db load and board organization. game initializes from a dictionary with the full game state.

This commit is contained in:
2023-05-01 15:19:06 -07:00
parent ce51e5c35c
commit 2f613d0dbb
11 changed files with 448 additions and 358 deletions

View File

@@ -1,8 +1,6 @@
import wx import wx
import socket import socket
from constants import * from common import *
import json
import pprint
class ClientVCKO(wx.App): class ClientVCKO(wx.App):
@@ -14,6 +12,7 @@ class ClientVCKO(wx.App):
self.in_lobby = False self.in_lobby = False
self.in_game = False self.in_game = False
self.game_id = "" self.game_id = ""
self.game = None
self.debug_frame = DebugFrame(self) self.debug_frame = DebugFrame(self)
self.start_frame = StartFrame(self) self.start_frame = StartFrame(self)
self.lobby_frame = LobbyFrame(self) self.lobby_frame = LobbyFrame(self)
@@ -24,7 +23,10 @@ class ClientVCKO(wx.App):
return True return True
def parse_response(self, response): def parse_response(self, response):
print(f"{response}") if len(response) > 1000:
print(f"{response[:1000]}...")
else:
print(response)
first_word = response.split()[0] first_word = response.split()[0]
full_command = response.split() full_command = response.split()
match first_word: match first_word:
@@ -60,7 +62,7 @@ class ClientVCKO(wx.App):
class GameFrame(wx.Frame): class GameFrame(wx.Frame):
def __init__(self, app): def __init__(self, app):
super().__init__(parent=None, title='VCK Online', size=Constants.medium_window_size) super().__init__(parent=None, title='VCK Online', size=Constants.large_window_size)
self.app = app self.app = app
self.panel = wx.Panel(self) self.panel = wx.Panel(self)
@@ -82,28 +84,30 @@ class GameFrame(wx.Frame):
# Set the sizer for the panel # Set the sizer for the panel
self.panel.SetSizer(vbox) self.panel.SetSizer(vbox)
self.SetMinSize(Constants.small_window_size) self.SetMinSize(Constants.medium_window_size)
self.last_game_state = "" self.last_game_state = ""
self.timer_interval = 500
self.timer = wx.Timer(self) self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.get_game_status, self.timer) self.Bind(wx.EVT_TIMER, self.get_game_status, self.timer)
self.timer.Start(500) self.timer.Start(self.timer_interval)
def get_game_status(self, event=None): def get_game_status(self, event=None):
if connection_check() and self.app.in_game: if self.app.in_game and connection_check():
self.app.parse_response(send(f"game get_status {self.app.game_id}")) self.app.parse_response(send(f"game get_status {self.app.game_id}"))
new_pretty_game_state = pprint.pformat(self.app.last_game_state) if self.last_game_state == self.app.last_game_state:
if self.last_game_state == self.app.game_state: if self.timer_interval < 9500:
self.timer_interval += 500
self.timer.Start(self.timer_interval)
# If the current game state is the same as the last one, don't update the list control # If the current game state is the same as the last one, don't update the list control
return return
pretty_json_str = json.dumps(self.app.last_game_state, indent=4, sort_keys=False)
self.game_state_list.ClearAll() self.game_state_list.ClearAll()
self.game_state_list.InsertColumn(0, "Game State") self.game_state_list.InsertColumn(0, "Game State")
for idx, state in enumerate(new_pretty_game_state.split('\n')): for idx, state in enumerate(pretty_json_str.split('\n')):
self.game_state_list.InsertItem(idx, state.strip()) self.game_state_list.InsertItem(idx, state.strip())
self.game_state_list.SetColumnWidth(0, wx.LIST_AUTOSIZE) self.game_state_list.SetColumnWidth(0, wx.LIST_AUTOSIZE)
# Save the new game state # Save the new game state
self.last_game_state = self.app.game_state self.last_game_state = self.app.last_game_state
else:
self.timer.Stop()
class LobbyFrame(wx.Frame): class LobbyFrame(wx.Frame):
@@ -147,7 +151,7 @@ class LobbyFrame(wx.Frame):
event.Skip() event.Skip()
def get_lobby_status(self, event=None): def get_lobby_status(self, event=None):
if connection_check() and self.app.in_lobby: if self.app.in_lobby and connection_check():
self.app.parse_response(send(f"lobby get_status {self.app.player_id}")) self.app.parse_response(send(f"lobby get_status {self.app.player_id}"))
if self.app.lobby == self.last_lobby_state: if self.app.lobby == self.last_lobby_state:
# If the current lobby state is the same as the last one, don't update the list control # If the current lobby state is the same as the last one, don't update the list control
@@ -301,30 +305,14 @@ def connection_check():
return False return False
def _send(msg, input_socket):
message = msg.encode(Constants.text_format)
msg_length = len(message)
send_length = str(msg_length).encode(Constants.text_format)
send_length += b' ' * (Constants.header_size - len(send_length))
input_socket.send(send_length)
input_socket.send(message)
# Receive the response
response = b""
while True:
chunk = input_socket.recv(2048)
response += chunk
if len(chunk) < 2048:
break
return response.decode(Constants.text_format)
def send(message): def send(message):
client_socket = socket.socket() client_socket = socket.socket()
client_socket.connect((Constants.host, Constants.port)) client_socket.connect((Constants.host, Constants.port))
response = _send(message, client_socket) message_bytes = message.encode(Constants.encoding)
return response send_data(client_socket, message_bytes)
response = receive_data(client_socket)
client_socket.close()
return response.decode(Constants.encoding)
if __name__ == '__main__': if __name__ == '__main__':

469
common.py
View File

@@ -1,9 +1,9 @@
import json import json
from json import JSONEncoder from json import JSONEncoder, JSONDecoder
import mysql.connector import mysql.connector
import random import random
from typing import List, Dict from typing import List, Dict
from constants import *
import shortuuid import shortuuid
import uuid import uuid
@@ -46,6 +46,26 @@ class Player:
self.soldier_count = 0 self.soldier_count = 0
self.worker_count = 0 self.worker_count = 0
@classmethod
def from_dict(cls, data):
player_id = data['player_id']
name = data['name']
player = cls(player_id, name)
player.owned_starters = [Starter.from_dict(s) for s in data['owned_starters']]
player.owned_citizens = [Citizen.from_dict(c) for c in data['owned_citizens']]
player.owned_domains = [Domain.from_dict(d) for d in data['owned_domains']]
player.owned_dukes = [Duke.from_dict(d) for d in data['owned_dukes']]
player.owned_monsters = [Monster.from_dict(m) for m in data['owned_monsters']]
player.gold_score = data['gold_score']
player.strength_score = data['strength_score']
player.magic_score = data['magic_score']
player.is_first = data['is_first']
player.shadow_count = data['shadow_count']
player.holy_count = data['holy_count']
player.soldier_count = data['soldier_count']
player.worker_count = data['worker_count']
return player
def calc_roles(self): def calc_roles(self):
for citizen in self.owned_citizens: for citizen in self.owned_citizens:
self.shadow_count = self.shadow_count + citizen.shadow_count self.shadow_count = self.shadow_count + citizen.shadow_count
@@ -100,6 +120,15 @@ class Starter(Card):
"expansion": self.expansion "expansion": self.expansion
} }
@classmethod
def from_dict(cls, data):
return cls(data["starter_id"], data["name"], data["roll_match1"], data["roll_match2"],
data["gold_payout_on_turn"], data["gold_payout_off_turn"], data["strength_payout_on_turn"],
data["strength_payout_off_turn"], data["magic_payout_on_turn"], data["magic_payout_off_turn"],
data["has_special_payout_on_turn"], data["has_special_payout_off_turn"],
data["special_payout_on_turn"],
data["special_payout_off_turn"], data["expansion"])
class Citizen(Card): class Citizen(Card):
def __init__(self, citizen_id, name, gold_cost, roll_match1, roll_match2, shadow_count, holy_count, soldier_count, def __init__(self, citizen_id, name, gold_cost, roll_match1, roll_match2, shadow_count, holy_count, soldier_count,
@@ -154,6 +183,30 @@ class Citizen(Card):
"special_citizen": self.special_citizen, "special_citizen": self.special_citizen,
"expansion": self.expansion} "expansion": self.expansion}
@classmethod
def from_dict(cls, dict_):
return cls(citizen_id=dict_["citizen_id"],
name=dict_["name"],
gold_cost=dict_["gold_cost"],
roll_match1=dict_["roll_match1"],
roll_match2=dict_["roll_match2"],
shadow_count=dict_["shadow_count"],
holy_count=dict_["holy_count"],
soldier_count=dict_["soldier_count"],
worker_count=dict_["worker_count"],
gold_payout_on_turn=dict_["gold_payout_on_turn"],
gold_payout_off_turn=dict_["gold_payout_off_turn"],
strength_payout_on_turn=dict_["strength_payout_on_turn"],
strength_payout_off_turn=dict_["strength_payout_off_turn"],
magic_payout_on_turn=dict_["magic_payout_on_turn"],
magic_payout_off_turn=dict_["magic_payout_off_turn"],
has_special_payout_on_turn=dict_["has_special_payout_on_turn"],
has_special_payout_off_turn=dict_["has_special_payout_off_turn"],
special_payout_on_turn=dict_["special_payout_on_turn"],
special_payout_off_turn=dict_["special_payout_off_turn"],
special_citizen=dict_["special_citizen"],
expansion=dict_["expansion"])
class Domain(Card): class Domain(Card):
def __init__(self, domain_id, name, gold_cost, shadow_count, holy_count, soldier_count, worker_count, vp_reward, def __init__(self, domain_id, name, gold_cost, shadow_count, holy_count, soldier_count, worker_count, vp_reward,
@@ -193,6 +246,25 @@ class Domain(Card):
"expansion": self.expansion "expansion": self.expansion
} }
@classmethod
def from_dict(cls, dict_):
return cls(
domain_id=dict_['domain_id'],
name=dict_['name'],
gold_cost=dict_['gold_cost'],
shadow_count=dict_['shadow_count'],
holy_count=dict_['holy_count'],
soldier_count=dict_['soldier_count'],
worker_count=dict_['worker_count'],
vp_reward=dict_['vp_reward'],
has_activation_effect=dict_['has_activation_effect'],
has_passive_effect=dict_['has_passive_effect'],
passive_effect=dict_['passive_effect'],
activation_effect=dict_['activation_effect'],
text=dict_['text'],
expansion=dict_['expansion']
)
class Monster(Card): class Monster(Card):
def __init__(self, monster_id, name, area, monster_type, order, strength_cost, magic_cost, vp_reward, gold_reward, def __init__(self, monster_id, name, area, monster_type, order, strength_cost, magic_cost, vp_reward, gold_reward,
@@ -239,6 +311,28 @@ class Monster(Card):
} }
return {**card_dict, **monster_dict} return {**card_dict, **monster_dict}
@classmethod
def from_dict(cls, d):
return cls(
d['monster_id'],
d['name'],
d['area'],
d['monster_type'],
d['order'],
d['strength_cost'],
d['magic_cost'],
d['vp_reward'],
d['gold_reward'],
d['strength_reward'],
d['magic_reward'],
d['has_special_reward'],
d['special_reward'],
d['has_special_cost'],
d['special_cost'],
d['is_extra'],
d['expansion'],
)
def add_strength_cost(self, added_strength): def add_strength_cost(self, added_strength):
self.strength_cost = self.strength_cost + added_strength self.strength_cost = self.strength_cost + added_strength
@@ -290,304 +384,41 @@ class Duke(Card):
"expansion": self.expansion "expansion": self.expansion
} }
@classmethod
def from_dict(cls, data):
duke_id = data["duke_id"]
name = data["name"]
gold_mult = data["gold_multiplier"]
strength_mult = data["strength_multiplier"]
magic_mult = data["magic_multiplier"]
shadow_mult = data["shadow_multiplier"]
holy_mult = data["holy_multiplier"]
soldier_mult = data["soldier_multiplier"]
worker_mult = data["worker_multiplier"]
monster_mult = data["monster_multiplier"]
citizen_mult = data["citizen_multiplier"]
domain_mult = data["domain_multiplier"]
boss_mult = data["boss_multiplier"]
minion_mult = data["minion_multiplier"]
beast_mult = data["beast_multiplier"]
titan_mult = data["titan_multiplier"]
expansion = data["expansion"]
return cls(duke_id, name, gold_mult, strength_mult, magic_mult, shadow_mult, holy_mult, soldier_mult,
worker_mult, monster_mult, citizen_mult, domain_mult, boss_mult, minion_mult, beast_mult,
titan_mult, expansion)
class Game: class Game:
def __init__(self, game_id, player_list_from_lobby, preset="shuffled", number_of_dukes=2): def __init__(self, game_state):
self.game_id = game_id self.game_id = game_state['game_id']
self.player_count = len(player_list_from_lobby) self.player_list = game_state['player_list']
self.preset = preset self.monster_grid = game_state['monster_grid']
self.number_of_dukes = number_of_dukes self.citizen_grid = game_state['citizen_grid']
self.player_list = [] self.domain_grid = game_state['domain_grid']
self.citizen_grid: List[List[Citizen]] = [[] for _ in range(10)] self.die_one = game_state['die_one']
self.domain_grid: List[List[Domain]] = [[] for _ in range(5)] self.die_two = game_state['die_two']
self.monster_grid: List[List[Monster]] = [[] for _ in range(5)] self.die_sum = game_state['die_sum']
self.duke_stack = [] self.exhausted_count = game_state['exhausted_count']
self.domain_stack = []
self.citizen_stack = []
self.monster_stack = []
self.starter_stack = []
self.graveyard = []
self.die_one = 0
self.die_two = 0
self.die_sum = 0
self.exhausted_count = 0
my_connect = mysql.connector.connect(user='vckonline', password='vckonline', host='localhost',
database='vckonline')
my_cursor = my_connect.cursor(dictionary=True)
# load game data
my_cursor.execute("SELECT * FROM dukes")
my_result = my_cursor.fetchall()
for row in my_result:
my_duke = Duke(row['id_dukes'], row['name'], row['gold_mult'], row['strength_mult'], row['magic_mult'],
row['shadow_mult'], row['holy_mult'], row['soldier_mult'], row['worker_mult'],
row['monster_mult'], row['citizen_mult'], row['domain_mult'], row['boss_mult'],
row['minion_mult'], row['beast_mult'], row['titan_mult'], row['expansion'])
self.duke_stack.append(my_duke)
random.shuffle(self.duke_stack)
my_cursor.execute("SELECT * FROM domains")
my_result = my_cursor.fetchall()
for row in my_result:
my_domain = Domain(row['id_domains'], row['name'], row['gold_cost'], row['shadow_count'], row['holy_count'],
row['soldier_count'], row['worker_count'], row['vp_reward'],
row['has_activation_effect'], row['has_passive_effect'], row['passive_effect'],
row['activation_effect'], row['text'], row['expansion'])
self.domain_stack.append(my_domain)
random.shuffle(self.domain_stack)
my_cursor.execute("SELECT * FROM citizens")
my_result = my_cursor.fetchall()
for row in my_result:
for i in range(6):
my_citizen = Citizen(row['id_citizens'], row['name'], row['gold_cost'], row['roll_match1'],
row['roll_match2'], row['shadow_count'], row['holy_count'], row['soldier_count'],
row['worker_count'], row['gold_payout_on_turn'], row['gold_payout_off_turn'],
row['strength_payout_on_turn'], row['strength_payout_off_turn'],
row['magic_payout_on_turn'], row['magic_payout_off_turn'],
row['has_special_payout_on_turn'], row['has_special_payout_off_turn'],
row['special_payout_on_turn'], row['special_payout_off_turn'],
row['special_citizen'],
row['expansion'])
self.citizen_stack.append(my_citizen)
my_cursor.execute("SELECT * FROM starters")
my_result = my_cursor.fetchall()
for row in my_result:
my_starter = Starter(row['id_starters'], row['name'], row['roll_match1'], row['roll_match2'],
row['gold_payout_on_turn'], row['gold_payout_off_turn'],
row['strength_payout_on_turn'], row['strength_payout_off_turn'],
row['magic_payout_on_turn'], row['magic_payout_off_turn'],
row['has_special_payout_on_turn'], row['has_special_payout_off_turn'],
row['special_payout_on_turn'], row['special_payout_off_turn'], row['expansion'])
self.starter_stack.append(my_starter)
my_cursor.execute("SELECT * FROM monsters")
my_result = my_cursor.fetchall()
for row in my_result:
my_monster = Monster(row['id_monsters'], row['name'], row['area'], row['monster_type'],
row['monster_order'], row['strength_cost'], row['magic_cost'], row['vp_reward'],
row['gold_reward'], row['strength_reward'], row['magic_reward'],
row['has_special_reward'], row['special_reward'], row['has_special_cost'],
row['special_cost'], row['is_extra'], row['expansion'])
self.monster_stack.append(my_monster)
my_connect.close()
# end load game data
# remove extra cards
if self.player_count != 5:
extra_monsters = []
remaining_monsters = []
for monster in self.monster_stack:
if monster.is_extra == 1:
extra_monsters.append(monster)
else:
remaining_monsters.append(monster)
self.monster_stack = remaining_monsters
self.graveyard.extend(extra_monsters)
match self.preset:
case "base1":
base1_monsters = []
other_expansion_monsters = []
for monster in self.monster_stack:
if monster.expansion == "base1":
base1_monsters.append(monster)
else:
other_expansion_monsters.append(monster)
self.monster_stack = base1_monsters
self.graveyard.extend(other_expansion_monsters)
base1_citizens = []
other_expansion_citizens = []
for citizen in self.citizen_stack:
if citizen.expansion == "base1":
base1_citizens.append(citizen)
else:
other_expansion_citizens.append(citizen)
self.citizen_stack = base1_citizens
self.graveyard.extend(other_expansion_citizens)
case "base2":
base1_monsters = []
base2_monsters = []
other_expansion_monsters = []
for monster in self.monster_stack:
if monster.expansion == "base1":
base1_monsters.append(monster)
elif monster.expansion == "base2":
base2_monsters.append(monster)
else:
other_expansion_monsters.append(monster)
# add 2 random monster areas from base1 to fill out base2 monsters
grouped_monsters = {}
for base1_monster in base1_monsters:
area = base1_monster.area
if area in grouped_monsters:
grouped_monsters[area].append(base1_monster)
else:
grouped_monsters[area] = [base1_monster]
areas = list(grouped_monsters.keys())
chosen_areas = random.sample(areas, 2)
not_chosen_monsters = [monster for area, monsters in grouped_monsters.items() if
area not in chosen_areas for monster in monsters]
self.graveyard.extend(not_chosen_monsters)
for i, area in enumerate(chosen_areas):
monsters = grouped_monsters[area]
base2_monsters.extend(monsters)
self.monster_stack = base2_monsters
self.graveyard.extend(other_expansion_monsters)
base2_citizens = []
other_expansion_citizens = []
for citizen in self.citizen_stack:
if citizen.expansion == "base2":
base2_citizens.append(citizen)
else:
other_expansion_citizens.append(citizen)
# add peasant and knight from base1
for citizen in other_expansion_citizens:
if citizen.name == "Peasant" and citizen.expansion == "base1":
base2_citizens.append(citizen)
elif citizen.name == "Knight" and citizen.expansion == "base1":
base2_citizens.append(citizen)
self.citizen_stack = base2_citizens
# put the rest of the cards in the graveyard
grouped_citizens = {}
for citizen in other_expansion_citizens:
expansion = citizen.expansion
if expansion in grouped_citizens:
grouped_citizens[expansion].append(citizen)
else:
grouped_citizens[expansion] = [citizen]
if "base1" in grouped_citizens:
base1_citizens = grouped_citizens["base1"]
base1_citizens = [citizen for citizen in base1_citizens if
citizen.name not in ("Peasant", "Knight")]
grouped_citizens["base1"] = base1_citizens
other_expansion_citizens = []
for expansion in grouped_citizens.values():
other_expansion_citizens.extend(expansion)
self.graveyard.extend(other_expansion_citizens)
case "shadowvale":
shadowvale_monsters = []
other_expansion_monsters = []
for monster in self.monster_stack:
if monster.expansion == "shadowvale":
shadowvale_monsters.append(monster)
else:
other_expansion_monsters.append(monster)
self.monster_stack = shadowvale_monsters
self.graveyard.extend(other_expansion_monsters)
shadowvale_citizens = []
other_expansion_citizens = []
for citizen in self.citizen_stack:
if citizen.expansion == "shadowvale":
shadowvale_citizens.append(citizen)
else:
other_expansion_citizens.append(citizen)
self.citizen_stack = shadowvale_citizens
self.graveyard.extend(other_expansion_citizens)
case "flamesandfrost":
flamesandfrost_monsters = []
other_expansion_monsters = []
for monster in self.monster_stack:
if monster.expansion == "flamesandfrost":
flamesandfrost_monsters.append(monster)
else:
other_expansion_monsters.append(monster)
self.monster_stack = flamesandfrost_monsters
self.graveyard.extend(other_expansion_monsters)
flamesandfrost_citizens = []
other_expansion_citizens = []
for citizen in self.citizen_stack:
if citizen.expansion == "flamesandfrost":
flamesandfrost_citizens.append(citizen)
else:
other_expansion_citizens.append(citizen)
self.citizen_stack = flamesandfrost_citizens
self.graveyard.extend(other_expansion_citizens)
case _:
if self.player_count != 5:
for stack in self.monster_grid:
# Remove monsters with isExtra = True from each stack
stack[:] = [monster for monster in stack if not monster.is_extra]
# end remove extra cards
# create players and determine order
for player in player_list_from_lobby:
my_player = Player(player.player_id, player.name)
self.player_list.append(my_player)
random.shuffle(self.player_list)
self.player_list[0].is_first = True
# give players starters and dukes
for player in self.player_list:
player.owned_starters.append(self.starter_stack[0])
player.owned_starters.append(self.starter_stack[1])
for i in range(number_of_dukes):
player.owned_dukes.append(self.duke_stack.pop())
# deal monsters onto the board
grouped_monsters = {}
for monster in self.monster_stack:
area = monster.area
if area in grouped_monsters:
grouped_monsters[area].append(monster)
else:
grouped_monsters[area] = [monster]
# Reverse the order of each group by monster_order
for area, monsters in grouped_monsters.items():
monsters.sort(key=lambda item: item.order, reverse=True)
areas = list(grouped_monsters.keys())
chosen_areas = random.sample(areas, 5)
for i, area in enumerate(chosen_areas):
monsters = grouped_monsters[area]
self.monster_grid[i].extend(monsters)
for i, stack in enumerate(self.monster_grid):
for monster in stack:
monster.toggle_visibility(True)
# Make the last monster in the stack accessible
stack[-1].toggle_accessibility(True)
self.monster_stack = []
# deal citizens onto the board
# Create a dictionary to store citizen lists with roll numbers as keys
citizens_by_roll = {roll: [] for roll in [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]}
# Group citizens by roll number
for citizen in self.citizen_stack:
citizen.toggle_visibility()
citizens_by_roll[citizen.roll_match1].append(citizen)
for roll in citizens_by_roll:
# Map 11 roll to index 9
index = roll - 1 if roll < 11 else 9
citizens = citizens_by_roll[roll]
self.citizen_grid[index].extend(list(citizens))
# Make the first citizen in each list accessible
self.citizen_grid[index][-1].toggle_accessibility(True)
self.citizen_stack = []
# Deal the domains into the stacks
for i in range(5):
stack = self.domain_grid[i]
for j in range(3):
if j == 2: # top domain is visible and accessible
domain = self.domain_stack.pop()
domain.toggle_visibility(True)
domain.toggle_accessibility(True)
stack.append(domain)
else: # other domains are not visible or accessible
domain = self.domain_stack.pop()
stack.append(domain)
self.get_game_state()
def get_game_state(self):
for i, monster_list in enumerate(self.monster_grid):
print(
f"Monster Stack {i + 1}: {[f'{monster.name} ({monster.monster_id})' + ('E' if monster.is_extra else '') + ('V' if monster.is_visible else '') + ('A' if monster.is_accessible else '') for monster in monster_list]}")
for i, citizen_list in enumerate(self.citizen_grid):
print(
f"Citizen Stack {i + 1}: {[f'{citizen.name} ({citizen.citizen_id})' + ('V' if citizen.is_visible else '') + ('A' if citizen.is_accessible else '') for citizen in citizen_list]}")
for i, domain_list in enumerate(self.domain_grid):
print(
f"Domain Stack {i + 1}: {[f'{domain.name} ({domain.domain_id})' + ('V' if domain.is_visible else '') + ('A' if domain.is_accessible else '') for domain in domain_list]}")
for i, player in enumerate(self.player_list):
print(
f"Player {i + 1}: {[f'{player.player_id}' + (' *' if player.is_first else '') + f' G{player.gold_score} S{player.strength_score} M{player.magic_score}']}")
print(f"monster stack size {len(self.monster_stack)}")
print(f"citizen stack size {len(self.citizen_stack)}")
print(f"domain stack size {len(self.domain_stack)}")
print(f"graveyard stack size {len(self.graveyard)}")
def roll_phase(self): def roll_phase(self):
self.die_one = random.randint(1, 6) self.die_one = random.randint(1, 6)
@@ -617,7 +448,7 @@ class Game:
self.roll_phase() self.roll_phase()
def end_check(self): def end_check(self):
if self.exhausted_count <= (self.player_count * 2): if self.exhausted_count <= (len(self.player_list) * 2):
return False return False
@@ -654,19 +485,10 @@ class GameObjectEncoder(JSONEncoder):
elif isinstance(obj, Game): elif isinstance(obj, Game):
return { return {
"game_id": obj.game_id, "game_id": obj.game_id,
"player_count": obj.player_count,
"preset": obj.preset,
"number_of_dukes": obj.number_of_dukes,
"player_list": obj.player_list, "player_list": obj.player_list,
"monster_grid": obj.monster_grid, "monster_grid": obj.monster_grid,
"citizen_grid": obj.citizen_grid, "citizen_grid": obj.citizen_grid,
"domain_grid": obj.domain_grid, "domain_grid": obj.domain_grid,
"duke_stack": obj.duke_stack,
"domain_stack": obj.domain_stack,
"citizen_stack": obj.citizen_stack,
"monster_stack": obj.monster_stack,
"starter_stack": obj.starter_stack,
"graveyard": obj.graveyard,
"die_one": obj.die_one, "die_one": obj.die_one,
"die_two": obj.die_two, "die_two": obj.die_two,
"die_sum": obj.die_sum, "die_sum": obj.die_sum,
@@ -675,3 +497,34 @@ class GameObjectEncoder(JSONEncoder):
else: else:
return super().default(obj) return super().default(obj)
def send_data(conn, data):
header = f"{len(data):<{Constants.header_size}}"
conn.send(header.encode(Constants.encoding))
offset = 0
while offset < len(data):
chunk = data[offset:offset + Constants.buffer_size]
conn.send(chunk)
offset += Constants.buffer_size
def receive_data(conn):
# Read the header to determine the message length
header = b""
while len(header) < Constants.header_size:
chunk = conn.recv(Constants.header_size - len(header))
if not chunk:
raise ConnectionError("Connection closed by server")
header += chunk
msg_length = int(header.decode(Constants.encoding).strip())
# Read the message in chunks until the entire message is received
data = b""
while len(data) < msg_length:
chunk_size = min(Constants.buffer_size, msg_length - len(data))
chunk = conn.recv(chunk_size)
if not chunk:
raise ConnectionError("Connection closed by server")
data += chunk
return data

View File

@@ -2,8 +2,12 @@ class Constants:
green = (106, 171, 115) green = (106, 171, 115)
red = (219, 92, 92) red = (219, 92, 92)
host = "lukesau.com" host = "lukesau.com"
# host = "127.0.1.1"
port = 8328 port = 8328
header_size = 1024
text_format = "utf-8" text_format = "utf-8"
small_window_size = (300, 150) small_window_size = (300, 150)
medium_window_size = (300, 500) medium_window_size = (300, 500)
large_window_size = (1440, 900)
header_size = 512
buffer_size = 4096
encoding = "utf-8"

1
json.txt Normal file

File diff suppressed because one or more lines are too long

202
server.py
View File

@@ -2,8 +2,8 @@ import socket
import time import time
import threading import threading
from common import * from common import *
from constants import *
import json import json
import mariadb
class ServerVCKO: class ServerVCKO:
@@ -38,7 +38,7 @@ class ServerVCKO:
match first_word: match first_word:
case "connection_check": case "connection_check":
connected = False connected = False
conn.send("received".encode(Constants.text_format)) send_data(conn, "received".encode(Constants.encoding))
case "lobby": case "lobby":
connected = False connected = False
if full_command[1] == "join" and len(full_command) > 2: if full_command[1] == "join" and len(full_command) > 2:
@@ -47,7 +47,7 @@ class ServerVCKO:
joining_player = LobbyMember(joining_player_name, joining_player_id) joining_player = LobbyMember(joining_player_name, joining_player_id)
self.lobby.append(joining_player) self.lobby.append(joining_player)
message = f"lobby joined {joining_player_id}" message = f"lobby joined {joining_player_id}"
conn.send(message.encode(Constants.text_format)) send_data(conn, message.encode(Constants.encoding))
elif full_command[1] == "leave" and len(full_command) > 2: elif full_command[1] == "leave" and len(full_command) > 2:
temp_lobby = [] temp_lobby = []
for player in self.lobby: for player in self.lobby:
@@ -59,18 +59,18 @@ class ServerVCKO:
found = False found = False
for player in self.lobby: for player in self.lobby:
if full_command[2] == player.player_id: if full_command[2] == player.player_id:
player.last_active_time = time.time() # update last active time player.last_active_time = time.time() # update last active time
self.send_lobby_state(conn) self.send_lobby_state(conn)
found = True found = True
for player in self.gamers: for player in self.gamers:
if full_command[2] == player.player_id: if full_command[2] == player.player_id:
message = f"game joined {player.game_id}" message = f"game joined {player.game_id}"
conn.send(message.encode(Constants.text_format)) send_data(conn, message.encode(Constants.encoding))
found = True found = True
# this only runs if we somehow receive an invalid player id # this only runs if we somehow receive an invalid player id
if not found: if not found:
message = f"Unable to find any player with player id: {full_command[2]}" message = f"Unable to find any player with player id: {full_command[2]}"
conn.send(message.encode(Constants.text_format)) send_data(conn, message.encode(Constants.encoding))
elif full_command[1] == "ready" and len(full_command) > 2: elif full_command[1] == "ready" and len(full_command) > 2:
ready_check = 0 ready_check = 0
for player in self.lobby: for player in self.lobby:
@@ -92,11 +92,11 @@ class ServerVCKO:
for player in players_to_remove: for player in players_to_remove:
self.lobby.remove(player) self.lobby.remove(player)
# START GAME # START GAME
new_game = Game(new_game_id, self.gamers, "base1", 2) new_game = Game(load_game_data(new_game_id, "base1", self.gamers))
self.game_dict[new_game.game_id] = new_game self.game_dict[new_game.game_id] = new_game
print(f"size of game dict: {len(self.game_dict)}") print(f"size of game dict: {len(self.game_dict)}")
message = f"game joined {new_game_id}" message = f"game joined {new_game_id}"
conn.send(message.encode(Constants.text_format)) send_data(conn, message.encode(Constants.encoding))
else: else:
self.send_lobby_state(conn) self.send_lobby_state(conn)
elif full_command[1] == "unready" and len(full_command) > 2: elif full_command[1] == "unready" and len(full_command) > 2:
@@ -105,7 +105,7 @@ class ServerVCKO:
player.is_ready = False player.is_ready = False
self.send_lobby_state(conn) self.send_lobby_state(conn)
else: else:
conn.send("invalid message".encode(Constants.text_format)) send_data(conn, "invalid message".encode(Constants.encoding))
case "game": case "game":
connected = False connected = False
if full_command[1] == "get_status" and len(full_command) == 3: if full_command[1] == "get_status" and len(full_command) == 3:
@@ -117,12 +117,12 @@ class ServerVCKO:
game = self.game_dict.get(game_id) game = self.game_dict.get(game_id)
if not game: if not game:
message = "game state error: game not found" message = "game state error: game not found"
conn.send(message.encode(Constants.text_format)) send_data(conn, message.encode(Constants.encoding))
else: else:
self.send_game_state(conn, full_command[2]) self.send_game_state(conn, full_command[2])
case _: case _:
connected = False connected = False
conn.send("invalid message".encode(Constants.text_format)) send_data(conn, "invalid message".encode(Constants.encoding))
print(f"[{addr}] {msg}") print(f"[{addr}] {msg}")
conn.close() conn.close()
@@ -145,7 +145,7 @@ class ServerVCKO:
} }
lobby_data.append(player_dict) lobby_data.append(player_dict)
response = f"lobby state {json.dumps(lobby_data)}" response = f"lobby state {json.dumps(lobby_data)}"
conn.send(response.encode(Constants.text_format)) send_data(conn, response.encode(Constants.encoding))
def send_game_state(self, conn, game_id): def send_game_state(self, conn, game_id):
game = self.game_dict.get(game_id) game = self.game_dict.get(game_id)
@@ -154,7 +154,7 @@ class ServerVCKO:
else: else:
game_json = json.dumps(game, cls=GameObjectEncoder, indent=2) game_json = json.dumps(game, cls=GameObjectEncoder, indent=2)
response = f"game state {game_json}" response = f"game state {game_json}"
conn.send(response.encode(Constants.text_format)) send_data(conn, response.encode(Constants.encoding))
class LobbyMember: class LobbyMember:
@@ -171,6 +171,182 @@ class GameMember:
self.game_id = game_id self.game_id = game_id
def load_game_data(game_id, preset, player_list_from_lobby):
monster_query = ""
monster_stack = []
citizen_query = ""
citizen_stack = []
domain_query = "select_random_domains"
domain_stack = []
duke_query = "select_random_dukes"
duke_stack = []
starter_query = "SELECT * FROM starters"
starter_stack = []
player_list = []
citizen_grid: List[List[Citizen]] = [[] for _ in range(10)]
domain_grid: List[List[Domain]] = [[] for _ in range(5)]
monster_grid: List[List[Monster]] = [[] for _ in range(5)]
graveyard = []
die_one = 0
die_two = 0
die_sum = 0
exhausted_count = 0
match preset:
case "base1":
monster_query = "select_base1_monsters"
citizen_query = "select_base1_citizens"
case "base2":
monster_query = "select_base2_monsters"
citizen_query = "select_base2_citizens"
try:
my_connect = mariadb.connect(user='vckonline', password='vckonline', host='127.0.0.1',
database='vckonline')
my_cursor = my_connect.cursor(dictionary=True)
my_cursor.callproc(monster_query)
results = my_cursor.fetchall()
for row in results:
my_monster = Monster(row['id_monsters'], row['name'], row['area'], row['monster_type'],
row['monster_order'], row['strength_cost'], row['magic_cost'], row['vp_reward'],
row['gold_reward'], row['strength_reward'], row['magic_reward'],
row['has_special_reward'], row['special_reward'], row['has_special_cost'],
row['special_cost'], row['is_extra'], row['expansion'])
monster_stack.append(my_monster)
my_cursor.callproc(citizen_query)
citizen_count = 5
if len(player_list_from_lobby) == 5:
citizen_count = 6
results = my_cursor.fetchall()
for row in results:
for i in range(citizen_count):
my_citizen = Citizen(row['id_citizens'], row['name'], row['gold_cost'], row['roll_match1'],
row['roll_match2'], row['shadow_count'], row['holy_count'], row['soldier_count'],
row['worker_count'], row['gold_payout_on_turn'], row['gold_payout_off_turn'],
row['strength_payout_on_turn'], row['strength_payout_off_turn'],
row['magic_payout_on_turn'], row['magic_payout_off_turn'],
row['has_special_payout_on_turn'], row['has_special_payout_off_turn'],
row['special_payout_on_turn'], row['special_payout_off_turn'],
row['special_citizen'],
row['expansion'])
citizen_stack.append(my_citizen)
my_cursor.callproc(domain_query)
results = my_cursor.fetchall()
for row in results:
my_domain = Domain(row['id_domains'], row['name'], row['gold_cost'], row['shadow_count'], row['holy_count'],
row['soldier_count'], row['worker_count'], row['vp_reward'],
row['has_activation_effect'], row['has_passive_effect'], row['passive_effect'],
row['activation_effect'], row['text'], row['expansion'])
domain_stack.append(my_domain)
my_cursor.callproc(duke_query)
results = my_cursor.fetchall()
for row in results:
my_duke = Duke(row['id_dukes'], row['name'], row['gold_mult'], row['strength_mult'], row['magic_mult'],
row['shadow_mult'], row['holy_mult'], row['soldier_mult'], row['worker_mult'],
row['monster_mult'], row['citizen_mult'], row['domain_mult'], row['boss_mult'],
row['minion_mult'], row['beast_mult'], row['titan_mult'], row['expansion'])
duke_stack.append(my_duke)
my_cursor.execute(starter_query)
my_result = my_cursor.fetchall()
for row in my_result:
my_starter = Starter(row['id_starters'], row['name'], row['roll_match1'], row['roll_match2'],
row['gold_payout_on_turn'], row['gold_payout_off_turn'],
row['strength_payout_on_turn'], row['strength_payout_off_turn'],
row['magic_payout_on_turn'], row['magic_payout_off_turn'],
row['has_special_payout_on_turn'], row['has_special_payout_off_turn'],
row['special_payout_on_turn'], row['special_payout_off_turn'], row['expansion'])
starter_stack.append(my_starter)
except Exception as e:
print(f"Error: {e}")
finally:
my_cursor.close()
my_connect.close()
print(f"size of monster stack: {len(monster_stack)}")
print(f"size of citizen stack: {len(citizen_stack)}")
print(f"size of domain stack: {len(domain_stack)}")
print(f"size of duke stack: {len(duke_stack)}")
print(f"size of starter stack: {len(starter_stack)}")
# create players and determine order
for player in player_list_from_lobby:
my_player = Player(player.player_id, player.name)
player_list.append(my_player)
random.shuffle(player_list)
player_list[0].is_first = True
# give players starters and dukes
for player in player_list:
player.owned_starters.append(starter_stack[0])
player.owned_starters.append(starter_stack[1])
for i in range(2):
player.owned_dukes.append(duke_stack.pop())
# deal monsters onto the board
grouped_monsters = {}
for monster in monster_stack:
area = monster.area
if area in grouped_monsters:
grouped_monsters[area].append(monster)
else:
grouped_monsters[area] = [monster]
# Reverse the order of each group by monster_order
for area, monsters in grouped_monsters.items():
monsters.sort(key=lambda item: item.order, reverse=True)
areas = list(grouped_monsters.keys())
chosen_areas = random.sample(areas, 5)
for i, area in enumerate(chosen_areas):
monsters = grouped_monsters[area]
monster_grid[i].extend(monsters)
for i, stack in enumerate(monster_grid):
for monster in stack:
monster.toggle_visibility(True)
# Make the last monster in the stack accessible
stack[-1].toggle_accessibility(True)
monster_stack = []
# deal citizens onto the board
# Create a dictionary to store citizen lists with roll numbers as keys
citizens_by_roll = {roll: [] for roll in [1, 2, 3, 4, 5, 6, 7, 8, 9, 11]}
# Group citizens by roll number
for citizen in citizen_stack:
citizen.toggle_visibility()
citizens_by_roll[citizen.roll_match1].append(citizen)
for roll in citizens_by_roll:
# Map 11 roll to index 9
index = roll - 1 if roll < 11 else 9
citizens = citizens_by_roll[roll]
citizen_grid[index].extend(list(citizens))
# Make the first citizen in each list accessible
citizen_grid[index][-1].toggle_accessibility(True)
citizen_stack = []
# Deal the domains into the stacks
for i in range(5):
stack = domain_grid[i]
for j in range(3):
if j == 2: # top domain is visible and accessible
domain = domain_stack.pop()
domain.toggle_visibility(True)
domain.toggle_accessibility(True)
stack.append(domain)
else: # other domains are not visible or accessible
domain = domain_stack.pop()
stack.append(domain)
# Create a dictionary to store all the stacks
game_state = {'game_id': game_id,
'player_list': player_list,
'monster_grid': monster_grid,
'citizen_grid': citizen_grid,
'domain_grid': domain_grid,
'die_one': die_one,
'die_two': die_two,
'die_sum': die_sum,
'exhausted_count': exhausted_count}
# Return the dictionary
return game_state
if __name__ == '__main__': if __name__ == '__main__':
print("server starting") print("server starting")
server = ServerVCKO() server = ServerVCKO()

View File

@@ -0,0 +1,8 @@
DELIMITER //
CREATE PROCEDURE select_base1_citizens()
BEGIN
SELECT * FROM citizens WHERE expansion = "base1";
END //
DELIMITER ;

View File

@@ -0,0 +1,8 @@
DELIMITER //
CREATE PROCEDURE select_base1_monsters()
BEGIN
SELECT * FROM monsters WHERE expansion = "base1";
END //
DELIMITER ;

View File

@@ -0,0 +1,10 @@
DELIMITER //
CREATE PROCEDURE select_base2_citizens()
BEGIN
SELECT * FROM citizens WHERE expansion = "base2"
UNION
SELECT * FROM citizens WHERE expansion = "base1" AND name IN ('Peasant', 'Knight');
END //
DELIMITER ;

View File

@@ -0,0 +1,26 @@
DELIMITER //
CREATE PROCEDURE select_base2_monsters()
BEGIN
DECLARE chosen_area1 VARCHAR(255);
DECLARE chosen_area2 VARCHAR(255);
SET chosen_area1 = (
SELECT area FROM monsters WHERE expansion = 'base1' GROUP BY area ORDER BY RAND() LIMIT 1
);
SET chosen_area2 = (
SELECT area FROM monsters WHERE expansion = 'base1' AND area <> chosen_area1 ORDER BY RAND() LIMIT 1
);
SELECT id_monsters, name, area, monster_type, monster_order,
strength_cost, magic_cost, vp_reward, gold_reward, strength_reward, magic_reward,
has_special_reward, special_reward, has_special_cost, special_cost, is_extra, expansion
FROM monsters
WHERE expansion = 'base2'
UNION
SELECT id_monsters, name, area, monster_type, monster_order,
strength_cost, magic_cost, vp_reward, gold_reward, strength_reward, magic_reward,
has_special_reward, special_reward, has_special_cost, special_cost, is_extra, expansion
FROM monsters
WHERE expansion = 'base1' AND area IN (chosen_area1, chosen_area2);
END //
DELIMITER ;

View File

@@ -0,0 +1,8 @@
DELIMITER //
CREATE PROCEDURE select_random_domains()
BEGIN
SELECT * FROM domains ORDER BY RAND() LIMIT 15;
END //
DELIMITER ;

View File

@@ -0,0 +1,8 @@
DELIMITER //
CREATE PROCEDURE select_random_dukes()
BEGIN
SELECT * FROM dukes ORDER BY RAND();
END //
DELIMITER ;