<repo_name>HussainAther/drmario<file_sep>/src/colors.py
black = (0, 0, 0)
blue = (43, 85, 117)
brightred = (255,0,0)
brightgreen = (0,255,0)
green = (0, 200, 0)
darkblue = (76, 48, 124)
darkgray = (15, 15, 15)
red = (182, 70, 58)
white = (255, 255, 255)
yellow = (255, 248, 68)
<file_sep>/src/dublock.py
class dublock(object):
    """
    A dublock is a block made of 2 square parts, blocka and blockb.
    This is the megavitamin of Dr. Mario that the player controls.
    """
    def __init__(self, blocks):
        self._blocks = blocks
        self._images = None 

    def getorient(self):
        """
        Get the orientation (from the list o) of each block in the dublock (megavitamin).
        This is used for sprite purposes.
        """
        a, b = self.blocks # Get the two individuals blocks of the 
                                   # dublock.
        if a.y < b.y:
            o = ["bottom", "top"]
        elif a.y > b.y:
            o = ["top", "bottom"]
        elif a.x < b.x:
            o = ["left", "right"] 
        elif a.x > b.x:
            o =  ["right", "left"]
        return o

    def setblocks(self, blocka, blockb):
        self._blocks = (blocka, blockb)

    @property
    def blocks(self):
        return self._blocks

    def ishorizontal(self):
        return self.blocks[0].y == self.blocks[1].y
 
<file_sep>/README.md
# Dr. Mario
![Drop those vitamins](img/drmarioscreenshot.png)

This is a Python-based Dr. Mario game. You don't even have to go through any pre-medical requirements to play.

## Instructions

1. Install `pygame` and download the repository (using the green "Clone or download" button).
2. Unzip `music.zip`.
3. Run `python src/drmario.py` in the directory to play.
<file_sep>/src/utils.py
class Pos(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def add(self, other):
        if isinstance(other, self):
            return Pos(self.x + other.x, self.y + other.y)
        elif isinstance(other, (tuple, list)):
            return Pos(self.x + other[0], self.y + other[1])
        else:
            raise TypeError("Unknown object in Pos add")

    radd = add
    iadd = add

    def eq(self, other):
        if isinstance(other, self):
            return self.x == other.x and self.y == other.y
        elif isinstance(other, (tuple, list)):
            return self.x == other[0] and self.y == other[1]
        else:
            raise TypeError("Unknown object in Pos eq")

    def ne(self, other):
        """
        When the two aren't equal.
        """
        return not self.eq(other)
<file_sep>/src/ex.py
class InvalidOperation(Exception):
    pass

class InvalidParameter(Exception):
    pass

class OutOfBoard(Exception):
    pass

class BottomReached(Exception):
    pass

class PositionOccupied(Exception):
    pass
<file_sep>/src/block.py
import ex
import os
import pygame
import utils

"""
Coding the blocks is straightforward with a position and color
as well as status for cleared and falling for each one. Here we define
properties of the blocks themselves that can be used later for creating
vitamins.
"""

width = 20
height = 14

class Color(object):
    """
    Give the object a color depending on how it spawns
    and how it touches another block.
    """
    clear = 0
    blue = 1
    red = 2
    yellow = 3

class block(object):
    """
    Give properties to the block itself. It needs to fall
    at a certain speed and keep track of its color.
    """
    def __init__(self, x, y, color=None):
        """
        Initialize a cleared empty block.
        """
        self._x = x
        self._y = y
        self._color = Color.clear
        self._falling = False
        if color:
            self.setcolor(color)
    
   def loadimage(self, name):
        """
        Load the sprite from the img folder.
        """
        fullname = os.path.join("img", "megavitamin", name)
        try:
            image = pygame.image.load(fullname)
        except pygame.error as message:
            print("Cannot load image:", name)
            raise SystemExit(message)
        image = image.convert()
        return image

    def setcolor(self, color):
        """
        Set the color of the block to another color.
        """
        if self._color != Color.clear:
            raise ex.InvalidOperation("Trying to set color on a colored block.")
        if color != Color.blue and color != Color.red and color != Color.yellow:
            raise ex.InvalidParameter("Invalid color value: {}".format(color))
        self._color = color

    def setimage(self, color, orient):
        """
        For a color and orientation, set and load the image.
        """
        return loadimage(str(orient) + str(color) + str.bmp))

    def clear(self):
        """
        Clear the color.
        """
        self._color = Color.clear

    def setfalling(self, falling):
        """
        Set a piece as falling.
        """
        self._falling = falling

    def __repr__(self):
        """
        Get the information about a block.
        """
        return "Block ({}, {}) color: {} falling: {}".format(self.x, self.y, self.color, self._falling)

    def isclear(self):
        """
        Is the block clear?
        """
        return self.color == Color.clear
 
    def isfalling(self):
        """
        Is the block falling?
        """
        return self._falling

    @property
    def x(self):
        """
        Return x-coordinate of the block.
        """
        return self._x

    @property
    def y(self):
        """
        Return y-coordinate of the block.
        """
        return self._y

    @property
    def xpixels(self):
        """
        Get the number of pixels on the screen in the
        x-direction.
        """
        return self.x*width
 
    @property
    def ypixels(self):
        """
        In the y-direction 
        """
        return self.y*height

    @property
    def pos(self):
        """
        Get the current block position.
        """
        return utils.Pos(self.x, self.y) 

    @property
    def color(self):
        """
        Get the block color.
        """
        return self._color
<file_sep>/src/game.py
# Import the functionality of other files.
import board
import ex

# Import installed modules.
import pygame
import sys

# Import the Board class from the board.py file, the keyboard functions
# from pygame, the colors from colors.py, and the Pos (position) function from 
# utils.py.
from board import Board
from pygame.constants import KEYUP, KEYDOWN, K_DOWN, K_LEFT, K_RIGHT, K_SPACE, K_UP, K_q, QUIT 
from colors import black, blue, brightred, brightgreen, darkblue, darkgray, green, red, white, yellow
from utils import Pos

fps = 60 # frames per second
windowwidth = 400 # width of the overall window 
windowheight = 400 # height of the window

# where the Dr. Mario game box will be
boardoffsetx = (windowwidth - 140) / 2
boardoffsety = (windowheight - 400) / 2
boardborder = 1 # border thickness

blockfallinterval = 300 # time before the block falls automatically 
speedfallmultiplier = 100.0

def gameintro(self):
    """
    Display the menu until the user starts the game.
    """
    intro = True
    pygame.mixer.music.load("music/birabuto.wav")
    pygame.mixer.music.play(0)
    while intro:
        win = pygame.display.set_mode((400, 400))
        win.fill((0,0,0))
        titlefont = pygame.font.SysFont("Times New Roman", 36, bold = True) 
        instructionfont = pygame.font.SysFont("Times New Roman", 24) 
        startfont = pygame.font.SysFont("Times New Roman", 24) 
        title = titlefont.render("Dr. Mario", 36, (255, 255, 255))
        instruction1 = instructionfont.render("Use arrow keys to move", 1, (255, 255, 255))
        instruction2 = instructionfont.render("and space to rotate.", 1, (255, 255, 255))
        starttext1 = startfont.render("Press space to start", 1, (255, 255, 255))
        starttext2 = startfont.render("and q to quit.", 1, (255, 255, 255))
        self._display.blit(title, (20, 100))
        self._display.blit(instruction1, (20, 150))
        self._display.blit(instruction2, (20, 170))
        self._display.blit(starttext1, (20, 300))
        self._display.blit(starttext2, (20, 320))
        drmariomenu = pygame.image.load("img/drmariomenu.bmp") 
        self._display.blit(drmariomenu, (20, 50)) 
        nursemenu = pygame.image.load("img/nurse.bmp") 
        self._display.blit(nursemenu, (50, 40)) 
        pygame.display.update()
        for event in pygame.event.get():
            if event.type == KEYDOWN and event.key == K_SPACE:
                intro = False

def text_objects(text, font):
    """
    Create a rectangular object around which to put text.
    """
    textSurface = font.render(text, True, black)
    return textSurface, textSurface.get_rect()

class Game(object):
    """
    This is the class for the game itself. It's the main class that controls the 
    other parts of the game. It's in charge of the basic, fundamental functions and 
    processes that the game needs.
    """
    def __init__(self):
        self._board = Board()
        pygame.init()
        pygame.mixer.init()
        self.fpsClock = pygame.time.Clock()
        self._display = pygame.display.set_mode((windowwidth, windowheight), 0, 32)
        gameintro(self)
        pygame.mixer.music.load("music/fever.wav")
        pygame.mixer.music.play(-1)
        self.blockfalltimer = blockfallinterval
        self.speed = False
        scorefont = pygame.font.Font(None, 36)
        scoretext = scorefont.render(str(self._board.score), 14, white)
        self._display.blit(scoretext, (10, 200))
        pygame.display.update()

    def run(self):
        self.board.spawndublock()

        while True:
            for event in pygame.event.get():
                self.processevent(event)

            self.update(self.fpsclock.get_time())

            self._display.fill(darkgray)

            pygame.draw.rect(self._display, darkblue,
                             (boardoffsetx-boardborder, boardoffsety-boardborder,
                              140+boardborder*2,
                              400+boardborder*2))
            boarddisplay = self.board.render()
            self._display.blit(boarddisplay, (boardoffsetx, boardoffsety))
            scorefont = pygame.font.Font(None, 36)
            scoretext = scorefont.render(str(self._board.score), 14, white)
            self._display.blit(scoretext, (10, 200))
            drmariomenu = pygame.image.load("img/drmariomenu.bmp") 
            self._display.blit(drmariomenu, (20, 50)) 
            pygame.display.update()
            self.fpsclock.tick(fps)

    def update(self, delta):
        if self.blockfalltimer <= 0:
            try:
                self.board.movedublock("down")
            except (ex.BottomReached, ex.PositionOccupied):
                self.handlecollision()
            self.blockfalltimer = blockfallinterval
        else:
            if self.speed:
                delta *= speedfallmultiplier
            self.blockfalltimer -= delta


    def handlecollision(self):
        self.board.handlecollision()

    def processevent(self, event):
        if event.type == QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == KEYDOWN:
            if event.key == K_LEFT or event.key == K_RIGHT:
                direction = "left" if event.key == K_LEFT else "right"
                self.movedublock(direction)
            elif event.key == K_DOWN:
                self.speed = True
            elif event.key == K_SPACE:
                self.board.rotatedublock()
            elif event.key == K_q:
               pygame.quit()
               sys.exit()
        elif event.type == KEYUP:
            if event.key == K_DOWN:
                self.speed = False

    def movedublock(self, direction):
        try:
            self.board.movedublock(direction)
        except (ex.OutOfBoard, ex.PositionOccupied):
            # If we can't move to a certain position,
            # simply ignore those. The dublock will not move at all.
            pass

    @property
    def board(self):
        """
        Define the board property.
        """
        return self._board

    @property
    def display(self):
        """
        Define the display property for the actual pygame display.
        """
        return self._display

    @property
    def fpsclock(self):
        """
        This is a neat clock based on the fps.
        """
        return self.fpsClock
<file_sep>/src/board.py
import block
import dublock
import ex
import random

from block import block, Color
from colors import black, blue, darkblue, darkgray, red, white, yellow
from dublock import dublock
from pygame import Surface
from utils import Pos

"""
Create the board. We want to use the blocks to fill up the board
with a starting setup and allow blocks to fall as they do.
"""

width = 10
height = 20
spawnpos = Pos(x=3, y=0)

class Board(object):
    """
    Define the class for the boards to include the block functions for
    spawning and falling.
    """
    def __init__(self):
        """
        Initialize the display and board for the game.
        """
        self._display = Surface((140, 300))
        self._board = []
        for h in range(0, height):
            self._board.append([])
            for w in range(0, width-3):
                self._board[h].append(block(w, h))
        self.score = 0
        self._dublock = None
    
    def spawndublock(self):
        """
        Spawn the blocks from the spawn position.
        """
        blocks = (
                self.block(spawnpos.x, spawnpos.y),
                self.block(spawnpos.x + 1, spawnpos.y),
        )

        for block in blocks:
            if not block.isclear(): # Block must be clear when they spawn.
                                    # If not, then you lose the game because you've
                                    # reached the top. 
                raise ex.PositionOccupied("Block is not clear at spawn point.")
        colors = (random.randint(1, 3), # Choose the block colors.
                  random.randint(1, 3))
        for i in (0, 1): # Set the block colors.
            blocks[i].setcolor(colors[i])
        self._dublock = dublock(blocks)

    def movedublock(self, direction):
        """
        Move the block in a direction.
        """
        a, b = self.dublock.blocks # Get the two individuals blocks of the 
                                   # dublock.
        directdict = {"down" : (0, 1), # Get the direction and how to move the block.
                      "left" : (-1, 0),
                      "right" : (1, 0)}
        (x, y) = directdict[direction]
        try: # Check if we've reached the bottom of the board.
            newa = self.block(a.x+x, a.y+y)
            newb = self.block(b.x+x, b.y+y)
        except (ex.OutOfBoard, ex.BottomReached) as e:
            raise e
        if (newa != b and not newa.isclear()) or (newb != a and not newb.isclear()):
            raise ex.PositionOccupied("Collision occured")
        acolor = a.color
        bcolor = b.color
        a.clear()
        b.clear()
        newa.setcolor(acolor) 
        newb.setcolor(bcolor)
        self.dublock.setblocks(newa, newb)
        self.dublock.getsprite()

    def rotatedublock(self):
        """
        Rotate the dublock. 
        """
        transform = (
            # vertical to horizontal
            ((Pos(1, 0), Pos(0, 1)),
             (Pos(0, 0), Pos(-1, 1))),
            # horizontal to vertical
            ((Pos(0, 0), Pos(-1, -1)),
             (Pos(0, 1), Pos(-1, 0)))
        )
        section = int(self.dublock.ishorizontal())
        origin = self.dublock.blocks
        for offset in transform[section]:
            try:
                i = not section
                j = not i
                newblocks = (
                    self.block(origin[i].x + offset[i].x, origin[i].y + offset[i].y),
                    self.block(origin[j].x + offset[j].x, origin[j].y + offset[j].y),
                ) 
#                # Don't overwrite blocks.
#                for b in newblocks:
#                    if not (b.isclear() and b.pos != origin[0].pos and b.pos != origin[1].pos):
#                        raise ex.InvalidOperation("New block is occupied.")

                if self.dublock.ishorizontal():
                    # standard colors
                    colors = (origin[0].color, origin[1].color)
                else:
                    # swap colors
                    colors = (origin[1].color, origin[0].color)

                for k in range(0, 2):
                    origin[k].clear()
                    newblocks[k].setcolor(colors[k])
                self.dublock.setblocks(*newblocks)
            except (ex.OutOfBoard, ex.InvalidOperation) as e:
                continue
            else:
                break

    def checkmatch(self, blocks):
        """
        If there's a match, make them disppear.
        """
        match = False
        for block in blocks:
            if block.isclear():
                continue # If the block is cleared, we need to see what 
                         # it matched to.
            # Check the horizontal and vertical directions to get the matches.
            horizontal = set(self.getmatchesindirection(block, 1, 0) + self.getmatchesindirection(block, -1, 0))
            vertical = set(self.getmatchesindirection(block, 0, 1) + self.getmatchesindirection(block, 0, 1))
            for matches in (horizontal, vertical):
                # For the matches, check if they're 3 or more blocks.
                # If they are, they disappear.
                if len(matches) >= 3:
                    match = True
                    for nextblock in matches:
                        self.score += 10
                        nextblock.clear()
                    block.clear()
        return match

    def getmatchesindirection(self, block, xdir, ydir):
        """
        Get the matches in the x and y directions. 
        """
        matches = []
        x = block.x + xdir
        y = block.y + ydir
        while True:
            try:
                nextblock = self.block(x, y)
            except:
                break
            if nextblock.isclear():
                break
            matches.append(nextblock)
            x += xdir
            y += ydir
        return matches 

    def checkblocksinair(self):
        """
        If one part of a block is cleared, the second part should drop.
        This function runs on all the blocks that have been changed. It
        checks each block one by one.
        """
        changed = True
        while changed:
            changed = False
            for x in range(0, width-3):
                for y in range(0, height):
                    block = self.block(x, y)
                    blockchanged = self.checkblockinair(block)
                    if blockchanged:
                        changed = True

    def checkblockinair(self, block):
        """
        Check a single block in the air.
        """
        if block.isclear() or block.isfalling():
            return False
        try:
            bottomblock = self.block(block.x, block.y+1)
        except ex.BottomReached as e:
            return False
        rightblock = None
        leftblock = None
        try:
            rightblock = self.block(block.x+1, block.y+1)
        except ex.OutOfBoard as e:
            pass
        try:
            leftblock = self.block(block.x-1, block.y-1)
        except ex.OutOfBoard as e:
            pass    
        if (bottomblock.isclear() or bottomblock.isfalling()) and \
             (not rightblock or rightblock.isclear()) and \
             (not leftblock or leftblock.isclear()):
            block.setfalling(True)
            return True
        return False

    def getfallingblocks(self):
        """
        Get the blocks that are falling by checking their
        .isfalling() status.
        """
        return [block for rows in reversed(self._board)
                for block in rows
                if block.isfalling()]

    def handlefallingblocks(self):
        """
        Check the falling blocks.
        """
        blocks = self.getfallingblocks()
        if not blocks:
            return []
        blocksatbottom = []
        for block in blocks:
            try:
                bottomblock = self.block(block.x, block.y+1)
            except ex.BottomReached:
                blocksatbottom.append(block)
                block.setfalling(False)
                continue

            if bottomblock.isclear():
                bottomblock.setcolor(block.color)
                bottomblock.setfalling(True)
                block.clear()
                block.setfalling(False)
            else:
                blocksatbottom.append(block)
                block.setfalling(False)
        return blocksatbottom

    def handlecollision(self):
        """
        Check the collisions for matches.
        """
        self.checkmatch(self._dublock.blocks)
        while True:
            self.checkblocksinair()
            blocks = []
            while self.getfallingblocks():
                blocks.extend(self.handlefallingblocks())
            if not self.checkmatch(blocks):
                break
        self.spawndublock()

    def render(self):
        self._display.fill(black)
        for h in range(0, height):
            for w in range(0, width-3):
                self.renderblock(self._display, self.block(w, h))
        return self._display

    def renderblock(self, display, block):
        if block.color == Color.clear:
            return
        elif block.color == Color.red:
            color = red
        elif block.color == Color.blue:
            color = blue
        elif block.color == Color.yellow:
            color = yellow
        else:
            raise ex.InvalidParameter("Block has invalid color: {}".format(block.color))

        display.fill(color,
                     (block.xpixels,
                      block.ypixels,
                      14,
                      20))

    def block(self, x, y):
        if x < 0 or y < 0:
            raise ex.OutOfBoard("Trying to get block at negative position")

        if y <= len(self._board)-1:
            if x <= len(self._board[y])-1:
                return self._board[y][x]
            else:
                raise ex.OutOfBoard("Position ({}, {}) not on board".format(x, y))
        else:
            raise ex.BottomReached("Bottom reached by block")

    @property
    def dublock(self):
        return self._dublock

    @property
    def display(self):
        return self._display 
