Source

Memoreek / geezle.py

#-*- coding: utf-8 -*-

########################################################################
# Geek Memory Puzzle
# (Based on Al Sweigart's game from the book "Making Games with Python
# and Pygame")
#
# Cesar Bustios <cesarbustios@gmail.com>
#
# Google+: https://plus.google.com/u/0/103603507868093099204
#
# Released under a "Simplified BSD" license
########################################################################

import sys
import os
import random
import pygame
from pygame.locals import *


# Paths
PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))
IMAGES_DIR = os.path.join(PROJECT_DIR, 'data/images')
FONTS_DIR = os.path.join(PROJECT_DIR, 'data/fonts')
LOGOS_DIR = os.path.join(IMAGES_DIR, 'logos')
UNREVEALED_BOX_IMG_PATH = os.path.join(IMAGES_DIR, 'unrevealed_logo1.png')

# Game and board settings
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
HALF_WIDTH = WINDOW_WIDTH / 2
HALF_HEIGHT = WINDOW_HEIGHT / 2
FPS = 30
BOARD_COLS = 9
BOARD_ROWS = 6
IMAGES_NEEDED = (BOARD_COLS * BOARD_ROWS) / 2
BOX_SIZE = 40
BOX_GAP = 20
HIGHLIGHT_WIDTH = 3
X_MARGIN = int((WINDOW_WIDTH - (BOARD_COLS * (BOX_SIZE + BOX_GAP))) / 2)
Y_MARGIN = int((WINDOW_HEIGHT - (BOARD_ROWS * (BOX_SIZE + BOX_GAP))) / 2)
OPTION_X_MARGIN = 75
OPTION_Y_MARGIN = 30

# Colors
WHITE = (255, 255, 255)
BLUE = (25, 25, 255)
GOLD = (205, 180, 12)
BACKGROUND_COLOR = WHITE
HIGHLIGHT_COLOR = BLUE

pygame.init()
MAIN_SURFACE = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
CLOCK = pygame.time.Clock()
UNREVEALED_BOX_IMG = pygame.image.load(UNREVEALED_BOX_IMG_PATH)


def main():
    pygame.display.set_caption('Geezle - Memory Puzzle')
    MAIN_SURFACE.fill(BACKGROUND_COLOR)

    board = generate_board(True)
    new_game = False
    x = 0
    y = 0
    # First selected logo [row, col]
    first_choice = None

    while True:
        # New board?
        if new_game:
            board = generate_board(False)
            new_game = False

        clicked = False
        MAIN_SURFACE.fill(BACKGROUND_COLOR)
        draw_board(board)

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
            elif event.type == MOUSEMOTION:
                x, y = event.pos
            elif event.type == MOUSEBUTTONUP:
                if event.button == 1:
                    x, y = event.pos
                    clicked = True

        row, col = get_box(board, x, y)
        if row != None and col != None:
            # Mouse is over a box
            if not board['revealed'][row][col]:
                highlight_box(board, row, col)
            # Box is clicked
            if not board['revealed'][row][col] and clicked:
                reveal_box(board, row, col)
                if first_choice == None:
                    first_choice = (row, col)
                else:
                    logo1 = board['logos'][first_choice[0]][first_choice[1]]
                    logo2 = board['logos'][row][col]
                    # If your memory sucks, mark both as unrevealed
                    if logo1 != logo2:
                        pygame.time.delay(500)
                        board['revealed'][first_choice[0]][first_choice[1]] = False
                        board['revealed'][row][col] = False
                    else:
                        # TODO: Animate matched logos or something?
                        pass
                    first_choice = None

        # Check if game is over and show message
        if all_matched(board['revealed']):
            rect1, rect2 = display_game_over_messages('mk.ttf', 'pixel.ttf')
            if clicked:
                # New game?
                if rect1.collidepoint(x, y):
                    new_game = True
                # Quit!!!???
                elif rect2.collidepoint(x, y):
                    pygame.quit()
                    sys.exit()

        pygame.display.update()
        CLOCK.tick(FPS)


def animate_unrevealed(rect):
    """
    Scales the unrevealed image when clicked
    """
    img = UNREVEALED_BOX_IMG
    height = img.get_height()
    width = img.get_width()
    x = rect.x
    y = rect.y

    while height >= 0 or width >= 0:
        # Temporary surface for replacing previous unrevealed box image
        tmp = pygame.surface.Surface((40, 40))
        tmp.fill(WHITE)
        MAIN_SURFACE.blit(tmp, (rect.x, rect.y))

        # Scale and blit the image
        img = pygame.transform.scale(img, (width, height))
        MAIN_SURFACE.blit(img, (x, y))
        pygame.time.delay(25)
        pygame.display.update()
        height -= 5
        width -= 5
        x += 3.5
        y += 3.5


def highlight_box(board, row, col):
    """
    Highlights a box when mouse is over
    """
    rect = board['rects'][row][col]
    r = pygame.rect.Rect(rect.left-5, rect.top-5, BOX_SIZE+10, BOX_SIZE+10)
    pygame.draw.rect(MAIN_SURFACE, HIGHLIGHT_COLOR, r, HIGHLIGHT_WIDTH)


def get_box(board, x, y):
    """
    Return [row, col] position of box at pixel position (x, y) if found.
    """
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            rect = board['rects'][row][col]
            if rect.collidepoint(x, y):
                return row, col
    return None, None


def display_game_over_messages(win_style, option_style):
    """
    Message and options for logo masters. Returns options rects
    """
    # Fonts
    win_font = pygame.font.Font(os.path.join(FONTS_DIR, win_style), 96)
    option_font = pygame.font.Font(os.path.join(FONTS_DIR, option_style), 20)

    # Surfaces
    win_surface = win_font.render('Logo master!', True, GOLD)
    option1_surface = option_font.render('New Game', True, GOLD)
    option2_surface = option_font.render('Quit', True, GOLD)

    # Rects
    win_rect = win_surface.get_rect()
    option1_rect = option1_surface.get_rect()
    option2_rect = option2_surface.get_rect()

    # Positions
    win_rect.center = (HALF_WIDTH, HALF_HEIGHT)
    option1_rect.center = (HALF_WIDTH - OPTION_X_MARGIN, WINDOW_HEIGHT - OPTION_Y_MARGIN)
    option2_rect.center = (HALF_WIDTH + OPTION_X_MARGIN, WINDOW_HEIGHT - OPTION_Y_MARGIN)

    MAIN_SURFACE.blit(win_surface, win_rect)
    MAIN_SURFACE.blit(option1_surface, option1_rect)
    MAIN_SURFACE.blit(option2_surface, option2_rect)

    return option1_rect, option2_rect


def reveal_box(board, row, col):
    """
    Revealed a box when it is clicked
    """
    rect = board['rects'][row][col]

    # Is it already revealed?
    if board['revealed'][row][col] == True:
        return

    animate_unrevealed(rect)
    # Mark box as revealed
    board['revealed'][row][col] = True
    MAIN_SURFACE.blit(board['logos'][row][col], (rect.x, rect.y))
    pygame.display.update()


def all_matched(revealed_data):
    """
    Returns true if there are no signs of memory damage in your brain
    """
    for row in range(BOARD_ROWS):
        if False in revealed_data[row]:
            return False # Not yet buddy
    # No signs of Alzheimer
    return True


def get_logos_images():
    """
    Returns a shuffled list of logos images
    """
    logos = []
    for img in os.listdir(LOGOS_DIR):
        try:
            # Go Archie!
            logos.append(pygame.image.load(os.path.join(LOGOS_DIR, img)).convert_alpha())
        except pygame.error, e:
            continue
    random.shuffle(logos)
    return logos


def generate_board(reveal=False):
    """
    Generate all the necessary data: the image logos, the rect that
    represents each image on the surface and the revealed data.
    Returns a dictionary containing all the information.
    """
    logos = [list() for r in range(BOARD_ROWS)]
    rects = [list() for r in range(BOARD_ROWS)]
    revealed = []

    # Get the logos images
    images = get_logos_images()[:IMAGES_NEEDED]
    assert len(images) >= IMAGES_NEEDED, 'Not enough logos'
    images *= 2
    random.shuffle(images)

    x = X_MARGIN
    y = Y_MARGIN
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            rect = images[0].get_rect()
            rect.x = x
            rect.y = y
            rects[row].append(rect)
            logos[row].append(images[0])
            x += (BOX_SIZE + BOX_GAP)
            del images[0]
        x = X_MARGIN
        y += (BOX_SIZE + BOX_GAP)

    for i in range(BOARD_ROWS):
        revealed.append([reveal] * BOARD_COLS)

    # Everyday I'm shuffling
    random.shuffle(images)

    return {'logos': logos, 'rects': rects, 'revealed': revealed}


def draw_board(board):
    """
    Draw the board according to the current revealed state
    """
    for row in range(BOARD_ROWS):
        for col in range(BOARD_COLS):
            rect = board['rects'][row][col]
            x = rect.x
            y = rect.y
            if not board['revealed'][row][col]:
                MAIN_SURFACE.blit(UNREVEALED_BOX_IMG, (x, y))
            else:
                MAIN_SURFACE.blit(board['logos'][row][col], (x, y))


if __name__ == '__main__':
    main()