diff --git a/houdini/data/room.py b/houdini/data/room.py index 9b17af7..bcf31b8 100644 --- a/houdini/data/room.py +++ b/houdini/data/room.py @@ -1,242 +1,11 @@ -from random import uniform +from houdini.games.sled import SledRacingLogic +from houdini.games.four import ConnectFourLogic +from houdini.games.mancala import MancalaLogic +from houdini.games.treasure import TreasureHuntLogic from houdini.data import db, AbstractDataCollection -class ConnectFourLogic: - - def __init__(self): - self.current_player = 1 - self.board = [[0 for _ in range(6)] for _ in range(7)] - - def place_chip(self, col, row): - self.board[col][row] = self.current_player - - def is_position_win(self, col, row): - for delta_row, delta_col in [(1, 0), (0, 1), (1, 1), (1, -1)]: - streak = 1 - for delta in (1, -1): - delta_row *= delta - delta_col *= delta - next_row = row + delta_row - next_col = col + delta_col - while 0 <= next_row < 6 and 0 <= next_col < 7: - if self.board[next_col][next_row] == self.current_player: - streak += 1 - else: - break - if streak == 4: - return True - next_row += delta_row - next_col += delta_col - return False - - def is_valid_move(self, col, row): - if 0 <= row <= 5 and 0 <= col <= 6: - if row == 5 or (self.board[col][row] == 0 and self.board[col][row + 1]): - return True - return False - - def is_board_full(self): - for col in self.board: - if not col[0]: - return False - return True - - def get_string(self): - return ','.join(str(item) for row in self.board for item in row) - - -class MancalaLogic: - - def __init__(self): - self.current_player = 1 - self.board = [4, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 4, 0] - - def place_stone(self, hollow): - capture = False - hand = self.board[hollow] - self.board[hollow] = 0 - - while hand > 0: - hollow += 1 if hollow + 1 < len(self.board) else 0 - myMancala, opponentMancala = (6, 13) if self.current_player == 1 else (13, 6) - - if hollow == opponentMancala: continue - oppositeHollow = 12 - hollow - - if hand == 1 and self.board[hollow] == 0: - if (self.current_player == 1 and hollow in range(0, 6)) or (self.current_player == 2 and hollow in range(7, 13)): - self.board[myMancala] += self.board[oppositeHollow] + 1 - self.board[oppositeHollow] = 0 - capture = True - break - - self.board[hollow] += 1 - hand -= 1 - - if (self.current_player == 1 and hollow != 6) or (self.current_player == 2 and hollow != 13): - return 'c' if capture else str() - else: - self.current_player = 2 if self.current_player == 1 else 1 - return 'f' - - def is_position_win(self): - if sum(self.board[0:6]) == 0 or sum(self.board[7:-1]) == 0: - if sum(self.board[0:6]) > sum(self.board[7:-1]): - return self.current_player == 1 - return self.current_player == 2 - return False - - def is_position_tie(self): - if sum(self.board[0:6]) == 0 or sum(self.board[7:-1]) == 0: - if sum(self.board[0:6]) == sum(self.board[7:-1]): - return True - return False - - def is_valid_move(self, hollow): - if self.current_player == 1 and hollow not in range(0, 6): - return False - elif self.current_player == 2 and hollow not in range(7, 13): - return False - return True - - def get_string(self): - return ','.join(map(str, self.board)) - - -class TreasureHuntLogic: - - def __init__(self): - self.map_width = 10 - self.map_height = 10 - self.coins_hidden = 0 - self.gems_hidden = 0 - self.turns = 12 - self.gem_value = 25 - self.coin_value = 1 - self.gem_locations = [] - self.treasure_map = [] - self.coins_found = 0 - self.gems_found = 0 - self.emerald_found = 0 - self.dig_record_names = [] - self.dig_record_directions = [] - self.dig_record_numbers = [] - self.emerald = 0 - self.current_player = 1 - self.generate_map() - - def generate_map(self): - for row in range(self.map_height): - self.treasure_map.append([]) - for column in range(self.map_width): - self.treasure_map[row].append([self.generate_treasure(row, column), 0]) - - def generate_treasure(self, row, column): - treasure_type = [ - ('None', 0, 60), ('Coin', 1, 40), ('Gem', 2, 1), ('Emerald', 4, 0.5) - ] - if self.get_gem_by_piece(row, column): - return 3 - if row + 1 == self.map_height or column + 1 == self.map_width: - treasure_type = treasure_type[:2] - total = sum(weight for name, value, weight in treasure_type) - r, i = uniform(0, total), 0 - for name, value, weight in treasure_type: - if i + weight >= r: - self.coins_hidden += 1 if value == 1 else self.coins_hidden - if value > 1: - self.gems_hidden += 1 - self.gem_locations.append(str(row) + ',' + str(column)) - if self.emerald: - return 2 - if value == 4 and not self.emerald: - self.emerald = 1 - return value - i += weight - - def get_gem_by_piece(self, row, column): - for delta_row, delta_col in [(0, -1), (-1, -1), (-1, 0)]: - if row > 0 and column > 0: - treasure, digs = self.treasure_map[row + delta_row][column + delta_col] - if treasure == 2 or treasure == 4: - return row + delta_row, column + delta_col - return False - - def is_gem_uncovered(self, row, column): - for delta_row, delta_col in [(0, 1), (1, 1), (1, 0)]: - treasure, digs = self.treasure_map[row + delta_row][column + delta_col] - if digs != 2: - return False - return True - - def make_move(self, movie, direction, spade): - if direction == 'right': - row = self.treasure_map[spade] - for column, tiles in enumerate(row): - self.dig(spade, column) - elif direction == 'down': - for row, columns in enumerate(self.treasure_map): - self.dig(row, spade) - self.turns -= 1 - self.dig_record_names.append(movie) - self.dig_record_directions.append(direction) - self.dig_record_numbers.append(spade) - - def dig(self, row, column): - self.treasure_map[row][column][1] += 1 - treasure, digs = self.treasure_map[row][column] - if digs == 2: - if treasure == 1: - self.coins_found += 1 - elif treasure == 2 or treasure == 4: - if not self.is_gem_uncovered(row, column): - return - self.gems_found += 1 - elif treasure == 3: - treasure_row, treasure_col = self.get_gem_by_piece(row, column) - if not self.is_gem_uncovered(treasure_row, treasure_col): - return - self.gems_found += 1 - if treasure == 4: - self.emerald_found = 1 - - def determine_winnings(self): - total = self.coins_found * self.coin_value - total += self.gems_found * self.gem_value - total += self.emerald_found * self.gem_value * 3 - return total - - def is_valid_move(self, movie, direction, spade): - test_movie = direction + 'button' + str(spade) + '_mc' - if test_movie == movie and direction in ['down', 'right'] and 0 <= spade <= 9: - if direction == 'right': - row = self.treasure_map[spade] - for column, tiles in enumerate(row): - treasure, digs = self.treasure_map[spade][column] - if digs == 2: - return False - elif direction == 'down': - for row, columns in enumerate(self.treasure_map): - treasure, digs = self.treasure_map[row][spade] - if digs == 2: - return False - return True - return False - - def get_string(self): - treasure_map = ','.join(str(item) for row in self.treasure_map for item, digs in row) - gem_locations = ','.join(self.gem_locations) - game_array = [self.map_width, self.map_height, self.coins_hidden, self.gems_hidden, self.turns, - self.gem_value, self.coin_value, gem_locations, treasure_map] - if self.dig_record_numbers: - game_array += [self.coins_found, self.gems_found, self.emerald_found] - game_array += [','.join(self.dig_record_names), ','.join(self.dig_record_directions), - ','.join(map(str, self.dig_record_numbers))] - return '%'.join(map(str, game_array)) - - def stealth_mod_filter(stealth_mod_id): def f(p): return not p.stealth_moderator or p.id == stealth_mod_id diff --git a/houdini/games/__init__.py b/houdini/games/__init__.py new file mode 100644 index 0000000..eaa2648 --- /dev/null +++ b/houdini/games/__init__.py @@ -0,0 +1,49 @@ +from abc import ABC +from abc import abstractmethod + + +class ITable(ABC): + """ + All table game logic classes must implement this interface. + """ + + @abstractmethod + def make_move(self, *args): + """Tells logic a move has been made.""" + + @abstractmethod + def is_valid_move(self, *args): + """Returns true if the move is valid.""" + + @abstractmethod + def get_string(self): + """Returns string representation of the game.""" + + +class IWaddle(ABC): + """ + All waddle game logic classes must implement this interface. + """ + + @property + @abstractmethod + def __room_id__(self): + """External ID of waddle game room.""" + + def __init__(self, waddle): + self.penguins = list(waddle.penguins) + self.seats = waddle.seats + + async def start(self): + room_id = type(self).__room_id__ + for penguin in self.penguins: + penguin.waddle = self + await penguin.join_room(penguin.server.rooms[room_id]) + + async def remove_penguin(self, p): + self.penguins.remove(p) + p.waddle = None + + async def send_xt(self, *data): + for penguin in self.penguins: + await penguin.send_xt(*data) diff --git a/houdini/games/four.py b/houdini/games/four.py new file mode 100644 index 0000000..630675e --- /dev/null +++ b/houdini/games/four.py @@ -0,0 +1,45 @@ +from houdini.games import ITable + + +class ConnectFourLogic(ITable): + + def __init__(self): + self.current_player = 1 + self.board = [[0 for _ in range(6)] for _ in range(7)] + + def make_move(self, col, row): + self.board[col][row] = self.current_player + + def is_valid_move(self, col, row): + if 0 <= row <= 5 and 0 <= col <= 6: + if row == 5 or (self.board[col][row] == 0 and self.board[col][row + 1]): + return True + return False + + def get_string(self): + return ','.join(str(item) for row in self.board for item in row) + + def is_position_win(self, col, row): + for delta_row, delta_col in [(1, 0), (0, 1), (1, 1), (1, -1)]: + streak = 1 + for delta in (1, -1): + delta_row *= delta + delta_col *= delta + next_row = row + delta_row + next_col = col + delta_col + while 0 <= next_row < 6 and 0 <= next_col < 7: + if self.board[next_col][next_row] == self.current_player: + streak += 1 + else: + break + if streak == 4: + return True + next_row += delta_row + next_col += delta_col + return False + + def is_board_full(self): + for col in self.board: + if not col[0]: + return False + return True diff --git a/houdini/games/mancala.py b/houdini/games/mancala.py new file mode 100644 index 0000000..da51fc2 --- /dev/null +++ b/houdini/games/mancala.py @@ -0,0 +1,63 @@ +from houdini.games import ITable + + +class MancalaLogic(ITable): + + def __init__(self): + self.current_player = 1 + self.board = [ + 4, 4, 4, 4, 4, 4, 0, + 4, 4, 4, 4, 4, 4, 0 + ] + + def make_move(self, hollow): + capture = False + hand = self.board[hollow] + self.board[hollow] = 0 + + while hand > 0: + hollow = (hollow + 1) % len(self.board) + my_mancala, opponent_mancala = (6, 13) if self.current_player == 1 else (13, 6) + + if hollow == opponent_mancala: + continue + opposite_hollow = 12 - hollow + + if hand == 1 and self.board[hollow] == 0: + if (self.current_player == 1 and hollow in range(0, 6)) or (self.current_player == 2 and hollow in range(7, 13)): + self.board[my_mancala] += self.board[opposite_hollow] + 1 + self.board[opposite_hollow] = 0 + capture = True + break + + self.board[hollow] += 1 + hand -= 1 + + if (self.current_player == 1 and hollow != 6) or (self.current_player == 2 and hollow != 13): + return 'c' if capture else str() + else: + self.current_player = 2 if self.current_player == 1 else 1 + return 'f' + + def is_valid_move(self, hollow): + if self.current_player == 1 and hollow not in range(0, 6): + return False + elif self.current_player == 2 and hollow not in range(7, 13): + return False + return True + + def get_string(self): + return ','.join(map(str, self.board)) + + def is_position_win(self): + if sum(self.board[0:6]) == 0 or sum(self.board[7:-1]) == 0: + if sum(self.board[0:6]) > sum(self.board[7:-1]): + return self.current_player == 1 + return self.current_player == 2 + return False + + def is_position_tie(self): + if sum(self.board[0:6]) == 0 or sum(self.board[7:-1]) == 0: + if sum(self.board[0:6]) == sum(self.board[7:-1]): + return True + return False diff --git a/houdini/games/sled.py b/houdini/games/sled.py new file mode 100644 index 0000000..9706f62 --- /dev/null +++ b/houdini/games/sled.py @@ -0,0 +1,19 @@ +from houdini.games import IWaddle + + +class SledRacingLogic(IWaddle): + + __room_id__ = 999 + + def __init__(self, waddle): + super().__init__(waddle) + + self.payouts = [20, 10, 5, 5] + + async def remove_penguin(self, p): + await super().remove_penguin(p) + await self.send_xt('uz', self.seats, *(f'{penguin.safe_name}|{penguin.color}|' + f'{penguin.hand}|{penguin.safe_name}' for penguin in self.penguins)) + + def get_payout(self): + return self.payouts.pop(0) diff --git a/houdini/games/treasure.py b/houdini/games/treasure.py new file mode 100644 index 0000000..349878e --- /dev/null +++ b/houdini/games/treasure.py @@ -0,0 +1,129 @@ +from houdini.games import ITable + +import random + + +class TreasureHuntLogic(ITable): + + def __init__(self): + self.map_width = 10 + self.map_height = 10 + self.coins_hidden = 0 + self.gems_hidden = 0 + self.turns = 12 + self.gem_value = 25 + self.coin_value = 1 + self.gem_locations = [] + self.treasure_map = [] + self.coins_found = 0 + self.gems_found = 0 + self.emerald_found = 0 + self.dig_record_names = [] + self.dig_record_directions = [] + self.dig_record_numbers = [] + self.emerald = 0 + self.current_player = 1 + self.generate_map() + + def make_move(self, movie, direction, spade): + if direction == 'right': + row = self.treasure_map[spade] + for column, tiles in enumerate(row): + self.dig(spade, column) + elif direction == 'down': + for row, columns in enumerate(self.treasure_map): + self.dig(row, spade) + self.turns -= 1 + self.dig_record_names.append(movie) + self.dig_record_directions.append(direction) + self.dig_record_numbers.append(spade) + + def is_valid_move(self, movie, direction, spade): + test_movie = direction + 'button' + str(spade) + '_mc' + if test_movie == movie and direction in ['down', 'right'] and 0 <= spade <= 9: + if direction == 'right': + row = self.treasure_map[spade] + for column, tiles in enumerate(row): + treasure, digs = self.treasure_map[spade][column] + if digs == 2: + return False + elif direction == 'down': + for row, columns in enumerate(self.treasure_map): + treasure, digs = self.treasure_map[row][spade] + if digs == 2: + return False + return True + return False + + def get_string(self): + treasure_map = ','.join(str(item) for row in self.treasure_map for item, digs in row) + gem_locations = ','.join(self.gem_locations) + game_array = [self.map_width, self.map_height, self.coins_hidden, self.gems_hidden, self.turns, + self.gem_value, self.coin_value, gem_locations, treasure_map] + if self.dig_record_numbers: + game_array += [self.coins_found, self.gems_found, self.emerald_found] + game_array += [','.join(self.dig_record_names), ','.join(self.dig_record_directions), + ','.join(map(str, self.dig_record_numbers))] + return '%'.join(map(str, game_array)) + + def generate_map(self): + for row in range(self.map_height): + self.treasure_map.append([]) + for column in range(self.map_width): + self.treasure_map[row].append([self.generate_treasure(row, column), 0]) + + def generate_treasure(self, row, column): + treasure_type = [('None', 0), ('Coin', 1), ('Gem', 2), ('Emerald', 4)] + if self.get_gem_by_piece(row, column): + return 3 + if row + 1 == self.map_height or column + 1 == self.map_width: + treasure_type = treasure_type[:2] + name, value = random.choices(treasure_type, weights=[60, 40, 1, 0.5][:len(treasure_type)])[0] + self.coins_hidden += 1 if value == 1 else self.coins_hidden + if value > 1: + self.gems_hidden += 1 + self.gem_locations.append(str(row) + ',' + str(column)) + if self.emerald: + return 2 + if value == 4 and not self.emerald: + self.emerald = 1 + return value + + def get_gem_by_piece(self, row, column): + for delta_row, delta_col in [(0, -1), (-1, -1), (-1, 0)]: + if row > 0 and column > 0: + treasure, digs = self.treasure_map[row + delta_row][column + delta_col] + if treasure == 2 or treasure == 4: + return row + delta_row, column + delta_col + return False + + def is_gem_uncovered(self, row, column): + for delta_row, delta_col in [(0, 1), (1, 1), (1, 0)]: + treasure, digs = self.treasure_map[row + delta_row][column + delta_col] + if digs != 2: + return False + return True + + def dig(self, row, column): + self.treasure_map[row][column][1] += 1 + treasure, digs = self.treasure_map[row][column] + if digs == 2: + if treasure == 1: + self.coins_found += 1 + elif treasure == 2 or treasure == 4: + if not self.is_gem_uncovered(row, column): + return + self.gems_found += 1 + elif treasure == 3: + treasure_row, treasure_col = self.get_gem_by_piece(row, column) + if not self.is_gem_uncovered(treasure_row, treasure_col): + return + self.gems_found += 1 + if treasure == 4: + self.emerald_found = 1 + + def determine_winnings(self): + total = self.coins_found * self.coin_value + total += self.gems_found * self.gem_value + total += self.emerald_found * self.gem_value * 3 + return total diff --git a/houdini/handlers/games/four.py b/houdini/handlers/games/four.py index 0c4a277..46caed1 100644 --- a/houdini/handlers/games/four.py +++ b/houdini/handlers/games/four.py @@ -1,7 +1,7 @@ from houdini import handlers from houdini.handlers import XTPacket from houdini.handlers.games.table import table_handler -from houdini.data.room import ConnectFourLogic +from houdini.games.four import ConnectFourLogic @handlers.handler(XTPacket('gz', ext='z')) @@ -37,7 +37,7 @@ async def handle_send_move(p, col: int, row: int): if not p.table.logic.is_valid_move(col, row): return await p.table.send_xt('zm', p.table.logic.current_player - 1, col, row) - p.table.logic.place_chip(col, row) + p.table.logic.make_move(col, row) opponent = p.table.penguins[1 if p.table.logic.current_player == 1 else 0] if p.table.logic.is_position_win(col, row): await p.add_coins(10) diff --git a/houdini/handlers/games/mancala.py b/houdini/handlers/games/mancala.py index 2190dc3..ad7e251 100644 --- a/houdini/handlers/games/mancala.py +++ b/houdini/handlers/games/mancala.py @@ -1,7 +1,7 @@ from houdini import handlers from houdini.handlers import XTPacket from houdini.handlers.games.table import table_handler -from houdini.data.room import MancalaLogic +from houdini.games.mancala import MancalaLogic @handlers.handler(XTPacket('gz', ext='z')) @@ -39,7 +39,7 @@ async def handle_send_move(p, hollow: int): if not p.table.logic.is_valid_move(hollow): return - move_result = p.table.logic.place_stone(hollow) + move_result = p.table.logic.make_move(hollow) await p.table.send_xt('zm', seat_id, hollow, move_result) opponent = p.table.penguins[1 if p.table.logic.current_player == 1 else 0] diff --git a/houdini/handlers/games/treasure.py b/houdini/handlers/games/treasure.py index 4fdbb58..4f58571 100644 --- a/houdini/handlers/games/treasure.py +++ b/houdini/handlers/games/treasure.py @@ -1,7 +1,7 @@ from houdini import handlers from houdini.handlers import XTPacket from houdini.handlers.games.table import table_handler -from houdini.data.room import TreasureHuntLogic +from houdini.games.treasure import TreasureHuntLogic @handlers.handler(XTPacket('gz', ext='z'))