Move all minigame logic to its own module

This commit is contained in:
Ben 2020-02-14 21:27:09 +00:00
parent 2c911aa856
commit 9bd7bada62
9 changed files with 314 additions and 240 deletions

View File

@ -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 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 stealth_mod_filter(stealth_mod_id):
def f(p): def f(p):
return not p.stealth_moderator or p.id == stealth_mod_id return not p.stealth_moderator or p.id == stealth_mod_id

49
houdini/games/__init__.py Normal file
View File

@ -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)

45
houdini/games/four.py Normal file
View File

@ -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

63
houdini/games/mancala.py Normal file
View File

@ -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

19
houdini/games/sled.py Normal file
View File

@ -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)

129
houdini/games/treasure.py Normal file
View File

@ -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

View File

@ -1,7 +1,7 @@
from houdini import handlers from houdini import handlers
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.handlers.games.table import table_handler 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')) @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): if not p.table.logic.is_valid_move(col, row):
return return
await p.table.send_xt('zm', p.table.logic.current_player - 1, col, row) 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] opponent = p.table.penguins[1 if p.table.logic.current_player == 1 else 0]
if p.table.logic.is_position_win(col, row): if p.table.logic.is_position_win(col, row):
await p.add_coins(10) await p.add_coins(10)

View File

@ -1,7 +1,7 @@
from houdini import handlers from houdini import handlers
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.handlers.games.table import table_handler 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')) @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): if not p.table.logic.is_valid_move(hollow):
return 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) 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] opponent = p.table.penguins[1 if p.table.logic.current_player == 1 else 0]

View File

@ -1,7 +1,7 @@
from houdini import handlers from houdini import handlers
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.handlers.games.table import table_handler 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')) @handlers.handler(XTPacket('gz', ext='z'))