Mini-project description - Memory#

program template

# implementation of card game - Memory
# The emojis do not work on Codeskulptor. Use simplequi for emojis

try:
    import simplegui
except ModuleNotFoundError:
    import simplequi as simplegui
import random

# if you increase the pair count, then also pay attention to the Emoji count
CARD_PAIR_COUNT = 8
CARD_COUNT = CARD_PAIR_COUNT * 2
CARD_WIDTH = 90
CARD_HEIGHT = 100
FONT_SIZE = CARD_HEIGHT-25
FONT_POS_OFFSET_Y = -15
FONT_POS_OFFSET_X = 5
EMOJIS = ['🎄', '✨', '🔥', '🎁', '🐻', '🍔', '⚽', '🌇', '😊', '🥺', '✔️']
MODE_EMOJIS = 1
MODE_NUMBERS = 2

cards = []
exposed = []
state = 0
last_exposed_card_ids = []
turns = 0
mode = MODE_EMOJIS


# helper function to initialize globals
def new_game():
    global cards, exposed, state, last_exposed_card_ids, turns

    if mode == MODE_NUMBERS:
        cards = list(range(CARD_PAIR_COUNT)) * 2
    else:
        cards = EMOJIS[0:CARD_PAIR_COUNT] * 2
    # alternatively
    # cards = list(range(CARD_PAIR_COUNT)) + list(range(CARD_PAIR_COUNT))
    random.shuffle(cards)

    exposed = [False] * CARD_COUNT
    # alternatively
    # exposed = [False for _ in range(CARD_COUNT)]

    state = 0
    last_exposed_card_ids = []
    turns = 0


def card_id(pos_x):
    """ Returns the card_id which the x position belongs to """
    for i in range(CARD_COUNT):
        if pos_x < CARD_WIDTH * (i+1):
            return i


# define event handlers
def mouseclick(pos):
    global exposed, last_exposed_card_ids, turns
    # only check for x to determine the click position
    clicked_card = card_id(pos[0])

    # add game state logic here
    # 0: start of the game
    # 1: single exposed unpaired card
    # 2: end of a turn
    global state
    if state == 0:
        exposed[clicked_card] = True
        last_exposed_card_ids.append(clicked_card)
        state = 1
    elif state == 1:
        if not exposed[clicked_card]:
            exposed[clicked_card] = True
            last_exposed_card_ids.append(clicked_card)
            state = 2
    else:
        # change state only if you click on an unexposed card
        if not exposed[clicked_card]:
            # only flip the last exposed cards over if they are not the same
            if cards[last_exposed_card_ids[0]] != \
               cards[last_exposed_card_ids[1]]:
                exposed[last_exposed_card_ids[0]] = False
                exposed[last_exposed_card_ids[1]] = False

            exposed[clicked_card] = True
            last_exposed_card_ids = [clicked_card]
            turns += 1
            label.set_text(f'Turns = {turns}')
            state = 1


def change_mode():
    global mode
    mode = not mode
    new_game()


# cards are logically 50x100 pixels in size
def draw(canvas: simplegui.Canvas):
    for i, card in enumerate(cards):
        # `enumerate()` is alternative to for card_index in range(len(cards))

        # card borders of one pixel. For example for CARD_WIDTH = 50
        # and CARD_HEIGHT = 100
        # (0, 0), (49, 0), (49, 99), (0, 99)
        nw = (0+i*CARD_WIDTH), 0  # northwest anchor point
        ne = nw[0] + CARD_WIDTH - 1, 0  # northeast
        se = ne[0], CARD_HEIGHT - 1     # southeast
        sw = nw[0], se[1]              # southwest

        # draw face up or down
        if exposed[i]:
            text_pos = sw[0] + FONT_POS_OFFSET_X, sw[1] + FONT_POS_OFFSET_Y
            canvas.draw_text(
                str(card), text_pos, FONT_SIZE, 'yellow', 'monospace')
            fill_color = None
        else:
            fill_color = 'green'

        canvas.draw_polygon([nw, ne, se, sw], 1, 'gray', fill_color)


# create frame and add a button and labels
frame = simplegui.create_frame("Memory", CARD_WIDTH*CARD_COUNT, CARD_HEIGHT)
frame.add_button("Reset", new_game)
label = frame.add_label("Turns = 0")
change_mode_button = frame.add_button('Change mode', change_mode)

# register event handlers
frame.set_mouseclick_handler(mouseclick)
frame.set_draw_handler(draw)

# get things rolling
new_game()
frame.start()