Here is a functional "card-drafting engine" which runs through pygame. It simulates fliping cards face up on the table, and the ability for two players to draft through the cards.There are two questions I have, and am very open to advice.
import pygame
import random
import pandas as pd
"""Layout"""
DISPLAY_HEI = 700
DISPLAY_WID = 1400
CARD_WIDTH = 100
CARD_HEIGHT = 150
TOP_Y = 100
MID_Y = DISPLAY_HEI / 2 - CARD_HEIGHT / 2
BOT_Y = DISPLAY_HEI - CARD_HEIGHT - TOP_Y
CENTER_X = DISPLAY_WID / 2
CENTER_PH_X = CENTER_X - CARD_WIDTH / 2
LEFT_PH_1_X = CENTER_PH_X - (CARD_WIDTH * 3)
LEFT_PH_2_X = CENTER_PH_X - (CARD_WIDTH * 1.5)
RIGHT_PH_1_X = CENTER_PH_X + (CARD_WIDTH * 3)
RIGHT_PH_2_X = CENTER_PH_X + (CARD_WIDTH * 1.5)
PH_BOARDER = 10
DRAFTZONE_W = CARD_WIDTH * 1.5
DRAFTZONE_H = DISPLAY_HEI
P1_ZONE_X = 0
P1_ZONE_Y = 0
P2_ZONE_X = DISPLAY_WID - DRAFTZONE_W
P2_ZONE_Y = 0
FPS = 60
W = (166, 166, 166)
R = (255, 10, 10)
B = (64, 64, 64)
G = (45, 171, 7)
U = (7, 45, 171)
M = (222, 222, 7)
A = (140, 109, 45)
L = (125, 45, 125)
DARK_BRN = (71, 53, 34)
LIGHT_BRN = (181, 121, 58)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREY = (50, 50, 50)
Here we create the card list from which we are drafting.
xlsx_file = 'cube2019-12-19.xlsx'
WHITE_DF = pd.read_excel(xlsx_file, 'White')
RED_DF = pd.read_excel(xlsx_file, 'Red')
BLACK_DF = pd.read_excel(xlsx_file, 'Black')
GREEN_DF = pd.read_excel(xlsx_file, 'Green')
BLUE_DF = pd.read_excel(xlsx_file, 'Blue')
MULTI_DF = pd.read_excel(xlsx_file, 'Multi')
ART_DF = pd.read_excel(xlsx_file, 'Artifact')
LAND_DF = pd.read_excel(xlsx_file, 'Land')
ALL_DF = [WHITE_DF, RED_DF, BLACK_DF, GREEN_DF, BLUE_DF, MULTI_DF, ART_DF, LAND_DF]
And we begin OOP... For DRY, should I not have multiple classes all with 'render' and 'is_over' functions? Is there a neater way to write this?
"""Placeholders will show up as background figures and will have the cards 'snap into place' when they are dragged onto them."""
class Placeholder(object):
def __init__(self, x, y, col, width=CARD_WIDTH + PH_BOARDER, height=CARD_HEIGHT + PH_BOARDER):
self.x = x
self.y = y
self.col = col
self.width = width
self.height = height
def render(self, screen):
pygame.draw.rect(screen, self.col, (self.x, self.y, self.width, self.height))
def is_over(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
P1 = Placeholder(LEFT_PH_1_X, TOP_Y, BLACK)
P2 = Placeholder(LEFT_PH_2_X, TOP_Y, BLACK)
P3 = Placeholder(CENTER_PH_X, TOP_Y, BLACK)
P4 = Placeholder(RIGHT_PH_1_X, TOP_Y, BLACK)
P5 = Placeholder(RIGHT_PH_2_X, TOP_Y, BLACK)
P6 = Placeholder(LEFT_PH_1_X, MID_Y, BLACK)
P7 = Placeholder(LEFT_PH_2_X, MID_Y, BLACK)
P8 = Placeholder(CENTER_PH_X, MID_Y, BLACK)
P9 = Placeholder(RIGHT_PH_1_X, MID_Y, BLACK)
P10 = Placeholder(RIGHT_PH_2_X, MID_Y, BLACK)
P11 = Placeholder(LEFT_PH_1_X, BOT_Y, BLACK)
P12 = Placeholder(LEFT_PH_2_X, BOT_Y, BLACK)
P13 = Placeholder(CENTER_PH_X, BOT_Y, BLACK)
P14 = Placeholder(RIGHT_PH_1_X, BOT_Y, BLACK)
P15 = Placeholder(RIGHT_PH_2_X, BOT_Y, BLACK)
ALL_PH = [P1, P2, P3, P4, P5,
P6, P7, P8, P9, P10,
P11, P12, P13, P14, P15]
class Card(object):
def __init__(self, name, cmc, tpe, col, x=pos[0], y=pos[1], width=CARD_WIDTH, height=CARD_HEIGHT):
self.name = name
self.cmc = cmc
self.tpe = tpe
self.x = x
self.y = y
self.width = width
self.height = height
if col == 'W':
self.col = W
if col == 'R':
self.col = R
if col == 'B':
self.col = B
if col == 'U':
self.col = U
if col == 'G':
self.col = G
if col == 'MULTI':
self.col = M
if col == 'A':
self.col = A
if col == 'L':
self.col = L
def render(self, screen):
pygame.draw.rect(screen, self.col, (self.x, self.y, self.width, self.height))
font = pygame.font.SysFont('ariel', 18)
text = font.render(self.name, 1, WHITE)
screen.blit(text,
(self.x + (self.width / 2 - text.get_width() / 2),
self.y + (self.height / 2 - text.get_height() / 2)))
def is_over(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
"""Technical name of the deck of cards that are being drafted is 'cube'."""
class Cube(object):
def __init__(self):
self.cards = []
self.build()
self.shuffle()
def build(self):
for df in ALL_DF:
for ind, row in df.iterrows():
self.cards.append(
Card(name=row['Name'], cmc=row['CMC'],
tpe=row['Type'], col=row['Color'])
)
def shuffle(self):
random.shuffle(self.cards)
def top_card(self):
return self.cards.pop()
class DraftZone(object):
def __init__(self, x, y, col=LIGHT_BRN, width=DRAFTZONE_W, height=DRAFTZONE_H):
self.col = col
self.x = x
self.y = y
self.width = width
self.height = height
def render(self, screen):
pygame.draw.rect(screen, self.col, (self.x, self.y, self.width, self.height))
font = pygame.font.SysFont('ariel', 18)
text = font.render('DraftZone', 1, WHITE)
screen.blit(text,
(self.x + (self.width / 2 - text.get_width() / 2),
self.y + (self.height / 2 - text.get_height() / 2)))
def is_over(self, pos):
if pos[0] > self.x and pos[0] < self.x + self.width:
if pos[1] > self.y and pos[1] < self.y + self.height:
return True
return False
Here's where I got confused. I wanted to obviously have a single class Player, but ran into trouble. I would create player1 = Player... and player2 = Player... BUT whenever either player 'drafted' a card, both players' rosters would be updated (because I assume, the class "Player" had a single 'roster list' which was being appended by the 'draft' function. I don't know how to do this, and would love some suggestions.
class Player1(object):
def __init__(self, d_zone_x, d_zone_y, name):
self.d_zone_x = d_zone_x
self.d_zone_y = d_zone_y
self.draft_zone = DraftZone(self.d_zone_x, self.d_zone_y)
self.name = name
roster = []
def draft(self, card):
pick_num = len(self.roster) + 1
self.roster.append((pick_num, card.name))
class Player2(object):
def __init__(self, d_zone_x, d_zone_y, name):
self.d_zone_x = d_zone_x
self.d_zone_y = d_zone_y
self.draft_zone = DraftZone(self.d_zone_x, self.d_zone_y)
self.name = name
roster = []
def draft(self, card):
pick_num = len(self.roster) + 1
self.roster.append((pick_num, card.name))
PLAYER1 = Player1(P1_ZONE_X, P1_ZONE_Y, 'Player 1')
PLAYER2 = Player2(P2_ZONE_X, P2_ZONE_Y, 'Player 2')
PLAYERS = [PLAYER1, PLAYER2]
def print_rosters():
for player in PLAYERS:
print(f'\n{player.name}:')
for each in player.roster:
print(each)
And the main, of course.
def main():
pygame.init()
cube = Cube()
screen = pygame.display.set_mode((DISPLAY_WID, DISPLAY_HEI))
running = True
render_list = []
mouse_pressed = False # pressed down THIS frame
mouse_down = False # mouse held down
mouse_released = False # released THIS frame
right_clicked = False # to delete
target = None # target of drag / drop or delete
while running:
screen.fill(DARK_BRN)
PLAYER1.draft_zone.render(screen)
PLAYER2.draft_zone.render(screen)
for ph in ALL_PH:
ph.render(screen)
pos = pygame.mouse.get_pos()
for event in pygame.event.get():
# print(event)
if event.type == pygame.QUIT:
print_rosters() # Print rosters on exit.
pygame.quit()
quit()
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
mouse_pressed = True
mouse_down = True
elif event.button == 3:
right_clicked = True
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
mouse_released = True
mouse_down = False
"""Handling 'drag and drop'."""
if mouse_pressed:
for item in render_list:
if item.is_over(pos):
target = item
if target is None: # create new card if you're not dragging one
target = cube.top_card()
render_list.append(target)
if mouse_down and target is not None:
target.x = pos[0] - CARD_WIDTH / 2 # grabs the center of the card
target.y = pos[1] - CARD_HEIGHT / 2
if mouse_released:
for ph in ALL_PH: # snap-to-placeholders
if ph.is_over(pos):
target.x = ph.x + PH_BOARDER / 2
target.y = ph.y + PH_BOARDER / 2
for player in PLAYERS: # drafting cards
if player.draft_zone.is_over(pos):
player.draft(target)
render_list.remove(target)
target = None
if right_clicked: # right-click to delete
for item in render_list:
if item.is_over(pos):
render_list.remove(item)
for item in render_list:
item.render(screen)
mouse_pressed = False
mouse_released = False
right_clicked = False
# mid_clicked = False
pygame.display.flip()
if __name__ == '__main__':
main()