├── data ├── __init__.py ├── components │ ├── __init__.py │ ├── __pycache__ │ │ ├── enemies.cpython-37.pyc │ │ ├── items.cpython-37.pyc │ │ ├── mario.cpython-37.pyc │ │ ├── tiles.cpython-37.pyc │ │ └── __init__.cpython-37.pyc │ ├── items.py │ ├── tiles.py │ ├── enemies.py │ └── mario.py ├── resources │ ├── sounds │ │ ├── bump.ogg │ │ ├── coin.ogg │ │ ├── kick.ogg │ │ ├── pipe.ogg │ │ ├── death.wav │ │ ├── stomp.ogg │ │ ├── big_jump.ogg │ │ ├── flagpole.wav │ │ ├── powerup.ogg │ │ ├── brick_smash.ogg │ │ ├── count_down.ogg │ │ ├── main_theme.ogg │ │ ├── out_of_time.wav │ │ ├── small_jump.ogg │ │ ├── stage_clear.wav │ │ ├── powerup_appears.ogg │ │ └── main_theme_sped_up.ogg │ └── graphics │ │ ├── map.png │ │ ├── menu.png │ │ ├── digits.png │ │ ├── tile_set.png │ │ ├── background.png │ │ ├── foreground.png │ │ ├── text_image.png │ │ └── tile_set_flipped.png ├── utils.py ├── sounds.py ├── config.py ├── menu.py ├── sprites.py ├── level.py ├── basetypes.py └── main.py ├── MAC_USERS ├── Mario_Bros.command └── mac_installer.command ├── Pipfile ├── README.md ├── Mario_Bros.py ├── .gitignore └── Pipfile.lock /data/__init__.py: -------------------------------------------------------------------------------- 1 | name = "data" -------------------------------------------------------------------------------- /data/components/__init__.py: -------------------------------------------------------------------------------- 1 | name = "components" -------------------------------------------------------------------------------- /data/resources/sounds/bump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/bump.ogg -------------------------------------------------------------------------------- /data/resources/sounds/coin.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/coin.ogg -------------------------------------------------------------------------------- /data/resources/sounds/kick.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/kick.ogg -------------------------------------------------------------------------------- /data/resources/sounds/pipe.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/pipe.ogg -------------------------------------------------------------------------------- /data/resources/graphics/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/map.png -------------------------------------------------------------------------------- /data/resources/graphics/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/menu.png -------------------------------------------------------------------------------- /data/resources/sounds/death.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/death.wav -------------------------------------------------------------------------------- /data/resources/sounds/stomp.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/stomp.ogg -------------------------------------------------------------------------------- /data/resources/graphics/digits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/digits.png -------------------------------------------------------------------------------- /data/resources/sounds/big_jump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/big_jump.ogg -------------------------------------------------------------------------------- /data/resources/sounds/flagpole.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/flagpole.wav -------------------------------------------------------------------------------- /data/resources/sounds/powerup.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/powerup.ogg -------------------------------------------------------------------------------- /data/resources/graphics/tile_set.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/tile_set.png -------------------------------------------------------------------------------- /data/resources/sounds/brick_smash.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/brick_smash.ogg -------------------------------------------------------------------------------- /data/resources/sounds/count_down.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/count_down.ogg -------------------------------------------------------------------------------- /data/resources/sounds/main_theme.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/main_theme.ogg -------------------------------------------------------------------------------- /data/resources/sounds/out_of_time.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/out_of_time.wav -------------------------------------------------------------------------------- /data/resources/sounds/small_jump.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/small_jump.ogg -------------------------------------------------------------------------------- /data/resources/sounds/stage_clear.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/stage_clear.wav -------------------------------------------------------------------------------- /data/resources/graphics/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/background.png -------------------------------------------------------------------------------- /data/resources/graphics/foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/foreground.png -------------------------------------------------------------------------------- /data/resources/graphics/text_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/text_image.png -------------------------------------------------------------------------------- /data/resources/sounds/powerup_appears.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/powerup_appears.ogg -------------------------------------------------------------------------------- /MAC_USERS/Mario_Bros.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | here="`dirname \"$0\"`" 3 | echo "cd-ing to $here" 4 | cd "$here" || exit 1 5 | python3 ../Mario_Bros.py -------------------------------------------------------------------------------- /data/resources/graphics/tile_set_flipped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/graphics/tile_set_flipped.png -------------------------------------------------------------------------------- /data/resources/sounds/main_theme_sped_up.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/resources/sounds/main_theme_sped_up.ogg -------------------------------------------------------------------------------- /data/components/__pycache__/enemies.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/components/__pycache__/enemies.cpython-37.pyc -------------------------------------------------------------------------------- /data/components/__pycache__/items.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/components/__pycache__/items.cpython-37.pyc -------------------------------------------------------------------------------- /data/components/__pycache__/mario.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/components/__pycache__/mario.cpython-37.pyc -------------------------------------------------------------------------------- /data/components/__pycache__/tiles.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/components/__pycache__/tiles.cpython-37.pyc -------------------------------------------------------------------------------- /data/components/__pycache__/__init__.cpython-37.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ilianbronchart/PyMario/HEAD/data/components/__pycache__/__init__.cpython-37.pyc -------------------------------------------------------------------------------- /MAC_USERS/mac_installer.command: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 4 | brew install python 5 | brew install pip3 6 | pip3 install pipenv 7 | pipenv install -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [dev-packages] 7 | pylint = "<2.0.0" 8 | 9 | [packages] 10 | pygame = "*" 11 | pillow = "*" 12 | 13 | [requires] 14 | python_version = "3.7" 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyMario 2 | Mario Bros in python & pygame 3 |

Dependencies:

4 | 9 |

IMPORTANT:

10 |

Get 60fps by running python in low res mode

11 | 18 | -------------------------------------------------------------------------------- /data/utils.py: -------------------------------------------------------------------------------- 1 | from . import config as c 2 | from .basetypes import Vector2 3 | import math 4 | 5 | def clamp(x, a, b): 6 | """Clamps value x between a and b""" 7 | return max(a, min(b, x)) 8 | 9 | def accelerate(obj, accel_x, accel_y, limit_x = None): 10 | """Accelerate until limit is reached""" 11 | obj.vel += Vector2(accel_x, accel_y) * c.delta_time 12 | if limit_x != None: 13 | if obj.vel.x > 0: 14 | obj.vel.x = clamp(obj.vel.x, 0, limit_x) 15 | elif obj.vel.x < 0: 16 | obj.vel.x = clamp(obj.vel.x, -limit_x, 0) 17 | 18 | def get_flipped_sprite(sprite): 19 | """Returns coordinates of a flipped sprite""" 20 | #429 is the width of the atlas 21 | return (429 - sprite[0] - sprite[2], sprite[1], sprite[2], sprite[3]) -------------------------------------------------------------------------------- /Mario_Bros.py: -------------------------------------------------------------------------------- 1 | from data import main 2 | from data import menu 3 | from data import config as c 4 | import pygame as pg 5 | import os 6 | import sys 7 | 8 | class App(): 9 | def __init__(self): 10 | self.menu = None 11 | self.main = None 12 | 13 | def run(self): 14 | self.menu = menu.Menu() 15 | self.menu.menu_loop() 16 | if self.menu.quit_state == 'play': #Check whether to continue to game or quit app 17 | self.main = main.Main() 18 | self.main.main_loop() 19 | if self.main.quit_state == 'menu': 20 | #If you think this is a cheat 21 | #to avoid destroying instances, 22 | #you are right, I'm just too 23 | #lazy to do that. 24 | os.execl(sys.executable, sys.executable, *sys.argv) #Restart game 25 | 26 | if __name__ == '__main__': 27 | pg.init() #Initialize pygame module 28 | c.screen = pg.display.set_mode((c.SCREEN_SIZE.x, c.SCREEN_SIZE.y)) 29 | pg.display.set_caption(c.CAPTION) 30 | c.clock = pg.time.Clock() 31 | 32 | app = App() 33 | app.run() 34 | 35 | pg.quit() -------------------------------------------------------------------------------- /data/sounds.py: -------------------------------------------------------------------------------- 1 | import pygame as pg 2 | from os import path 3 | 4 | pg.init() 5 | #initialize mixer 6 | pg.mixer.pre_init(44100, 16, 2, 4096) 7 | 8 | sounds_folder = path.join(path.dirname(__file__), 'resources', 'sounds') 9 | 10 | #Load all sounds 11 | small_jump = pg.mixer.Sound(path.join(sounds_folder, 'small_jump.ogg')) 12 | big_jump = pg.mixer.Sound(path.join(sounds_folder, 'big_jump.ogg')) 13 | bump = pg.mixer.Sound(path.join(sounds_folder, 'bump.ogg')) 14 | powerup_appears = pg.mixer.Sound(path.join(sounds_folder, 'powerup_appears.ogg')) 15 | powerup = pg.mixer.Sound(path.join(sounds_folder, 'powerup.ogg')) 16 | coin = pg.mixer.Sound(path.join(sounds_folder, 'coin.ogg')) 17 | stomp = pg.mixer.Sound(path.join(sounds_folder, 'stomp.ogg')) 18 | brick_smash = pg.mixer.Sound(path.join(sounds_folder, 'brick_smash.ogg')) 19 | kick = pg.mixer.Sound(path.join(sounds_folder, 'kick.ogg')) 20 | flagpole_sound = pg.mixer.Sound(path.join(sounds_folder, 'flagpole.wav')) 21 | count_down = pg.mixer.Sound(path.join(sounds_folder, 'count_down.ogg')) 22 | pipe = pg.mixer.Sound(path.join(sounds_folder, 'pipe.ogg')) 23 | 24 | #Get path to music files 25 | main_theme = path.join(sounds_folder, 'main_theme.ogg') 26 | stage_clear = path.join(sounds_folder, 'stage_clear.wav') 27 | death = path.join(sounds_folder, 'death.wav') 28 | out_of_time = path.join(sounds_folder, 'out_of_time.wav') 29 | main_theme_sped_up = path.join(sounds_folder, 'main_theme_sped_up.ogg') 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ -------------------------------------------------------------------------------- /data/config.py: -------------------------------------------------------------------------------- 1 | from .basetypes import Vector2 2 | import pygame as pg 3 | 4 | #Variables shared among different basetypes 5 | screen = None 6 | clock = None 7 | camera = None 8 | keys = None 9 | delta_time = None 10 | mario = None 11 | final_count_down = False 12 | 13 | #Colors for level loading 14 | BLACK = (0, 0, 0, 255) 15 | RED = (255, 0, 0, 255) 16 | GRAY = (100, 100, 100, 255) 17 | YELLOW = (255, 255, 0, 255) 18 | GREEN = (100, 255, 100, 255) 19 | BROWN = (124, 66, 0, 255) 20 | PURPLE = (124, 0, 255, 255) 21 | 22 | #Background color of level 23 | BACKGROUND_COLOR = (107, 140, 255) 24 | 25 | #Window settings 26 | SCREEN_SIZE = Vector2(744, 672) 27 | CAPTION = 'Mario Bros' 28 | 29 | #Start positions 30 | MARIO_START_POSITION = Vector2(138, 552) 31 | FOREGROUND_POS = Vector2(9840, 505) 32 | 33 | TILE_SIZE = 48 34 | 35 | #Physics values 36 | ACCELERATION = 0 37 | MARIO_ACCELERATION = 0.0005 38 | MAX_VEL = 0.35 39 | GRAVITY = 0.002 40 | MAX_JUMP_HEIGHT = 140 41 | FRICTION = 1 42 | DECEL_FRICTION = 0.95 43 | BRAKE_FRICTION = 0.85 44 | 45 | #Velocities for different events 46 | BOUNCE_VEL = 0.1 47 | JUMP_VELOCITY = -0.5 48 | MUSHROOM_START_VEL_X = 0.2 49 | ENEMY_START_VEL_X = -0.1 50 | STOMP_VEL = -0.4 51 | DEATH_VEL_Y = -0.8 52 | GOOMBA_KNOCKED_VEL_Y = -0.8 53 | 54 | #End of level settings 55 | MAXIMUM_CAMERA_SCROLL = 9300 56 | LEVEL_END_X = 9840 57 | 58 | #Distance from left side of the screen, when camera starts following 59 | CAMERA_FOLLOW_X = 300 60 | 61 | #Sets timer value so animations start instantly instead of counting up first 62 | INITIAL_TIMER_VALUE = 1000 63 | 64 | #Score values 65 | collected_coins = 0 66 | total_score = 0 67 | COIN_SCORE = 200 68 | MUSHROOM_SCORE = 1000 69 | GOOMBA_SCORE = 100 70 | TIME_SCORE = 1000 71 | 72 | #Events when certain songs end 73 | WIN_SONG_END = pg.USEREVENT + 1 74 | DEATH_SONG_END = pg.USEREVENT + 2 75 | OUT_OF_TIME_END = pg.USEREVENT + 3 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /data/menu.py: -------------------------------------------------------------------------------- 1 | from .basetypes import Vector2 2 | from . import config as c 3 | from . import sprites 4 | import pygame as pg 5 | 6 | class Menu(): 7 | def __init__(self): 8 | self.selected = 0 9 | self.quit_state = None 10 | 11 | self.pressed_up = False 12 | self.pressed_down = False 13 | 14 | self.selector_pos = Vector2(239, 404) 15 | 16 | def draw(self): 17 | c.screen.fill((0, 0, 0)) 18 | c.screen.blit(sprites.menu, (0, 0)) 19 | c.screen.blit(sprites.tile_set, (self.selector_pos.x, self.selector_pos.y), sprites.SELECTOR) 20 | 21 | def input_actions(self): 22 | if c.keys[pg.K_w] and not self.pressed_down and not self.pressed_up: 23 | self.selected += 1 24 | self.pressed_up = True 25 | if c.keys[pg.K_s] and not self.pressed_up and not self.pressed_down: 26 | self.selected -= 1 27 | self.pressed_down = True 28 | 29 | if not c.keys[pg.K_w]: 30 | self.pressed_up = False 31 | if not c.keys[pg.K_s]: 32 | self.pressed_down = False 33 | 34 | def check_for_quit(self): 35 | for event in pg.event.get(): 36 | if event.type == pg.QUIT: 37 | self.quit_state = 'exit' 38 | return False 39 | 40 | if c.keys[pg.K_ESCAPE]: 41 | self.quit_state = 'exit' 42 | return False 43 | 44 | if c.keys[pg.K_RETURN] and self.selected % 2 == 0: 45 | self.quit_state = 'play' 46 | return False 47 | 48 | return True 49 | 50 | def menu_loop(self): 51 | while True: 52 | c.keys = pg.key.get_pressed() 53 | c.clock.tick() 54 | 55 | self.input_actions() 56 | if self.selected % 2 == 0: 57 | self.selector_pos = Vector2(239, 404) 58 | else: 59 | self.selector_pos = Vector2(239, 448) 60 | self.draw() 61 | 62 | if not self.check_for_quit(): 63 | break 64 | 65 | pg.display.flip() 66 | 67 | -------------------------------------------------------------------------------- /data/sprites.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | import pygame as pg 3 | from PIL import Image 4 | 5 | graphics_folder = path.join(path.dirname(__file__), 'resources', 'graphics') 6 | 7 | #Load sprites needed for the game 8 | tile_set = pg.image.load(path.join(graphics_folder, 'tile_set.png')) 9 | tile_set_flipped = pg.image.load(path.join(graphics_folder, 'tile_set_flipped.png')) 10 | text_image = pg.image.load(path.join(graphics_folder, 'text_image.png')) 11 | background = pg.image.load(path.join(graphics_folder, 'background.png')) 12 | foreground = pg.image.load(path.join(graphics_folder, 'foreground.png')) 13 | menu = pg.image.load(path.join(graphics_folder, 'menu.png')) 14 | digits = pg.image.load(path.join(graphics_folder, 'digits.png')) 15 | level_1 = Image.open(path.join(graphics_folder, 'map.png')) 16 | 17 | #Sprite rectangles to retrieve section of atlas 18 | EMPTY_SPRITE = (240, 48, 48, 48) 19 | 20 | SMALL_MARIO_RUN = [ 21 | (0, 168, 48, 48), 22 | (48, 168, 48, 48), 23 | (96, 168, 48, 48) 24 | ] 25 | SMALL_MARIO_IDLE = (294, 168, 36, 48) 26 | SMALL_MARIO_JUMP = (192, 168, 48, 48) 27 | SMALL_MARIO_BRAKE = (150, 168, 40, 48) 28 | DEAD_MARIO = (243, 168, 42, 42) 29 | SMALL_MARIO_POLE = [ 30 | (342 ,168, 39, 48), 31 | (380, 168, 39, 48) 32 | ] 33 | 34 | BRICK = (0, 0, 48, 48) 35 | BRICK_FRAGMENT = [ 36 | (252, 12, 24, 24), 37 | (300, 12, 24, 24) 38 | ] 39 | 40 | Q_BLOCK_OPEN = (48, 0, 48, 48) 41 | Q_BLOCK_CLOSED = [ 42 | (96, 0, 48, 48), 43 | (144, 0, 48, 48), 44 | (192, 0, 48, 48) 45 | ] 46 | 47 | GOOMBA_RUN = [ 48 | (0, 48, 48, 48), 49 | (48, 48, 48, 48) 50 | ] 51 | GOOMBA_SQUISHED = (96, 48, 48, 48) 52 | GOOMBA_KNOCKED = (144, 48, 48, 48) 53 | 54 | SUPER_MUSHROOM = (192, 48, 48, 48) 55 | 56 | COIN = [ 57 | (144, 126, 48, 42), 58 | (192, 126, 48, 42), 59 | (240, 126, 48, 42), 60 | (288, 126, 48, 42) 61 | ] 62 | 63 | MIDDLE_MARIO_IDLE = (48, 327, 48, 72) 64 | BIG_MARIO_IDLE = (289, 216, 48, 96) 65 | BIG_MARIO_RUN = [ 66 | (0, 216, 48, 96), 67 | (48, 216, 48, 96), 68 | (96, 216, 48, 96) 69 | ] 70 | BIG_MARIO_JUMP = (192, 216, 48, 96) 71 | BIG_MARIO_BRAKE = (144, 216, 48, 96) 72 | MARIO_CROUCH = (240, 246, 48, 66) 73 | MARIO_SWIM = (0, 311, 48, 88) 74 | 75 | GROW_SPRITES = [ 76 | SMALL_MARIO_IDLE, 77 | MIDDLE_MARIO_IDLE, 78 | BIG_MARIO_IDLE 79 | ] 80 | 81 | SHRINK_SPRITES = [ 82 | MARIO_SWIM, 83 | EMPTY_SPRITE, 84 | SMALL_MARIO_IDLE 85 | ] 86 | 87 | TURTLE = [ 88 | (0, 96, 48, 72), 89 | (48, 96, 48, 72) 90 | ] 91 | TURTLE_SHELL = (96, 126, 48, 42) 92 | 93 | FLAG = (336, 0, 48, 48) 94 | 95 | WIN_SPRITES_BIG = [ 96 | (339, 216, 48, 96), 97 | (387, 216, 48, 96) 98 | ] 99 | 100 | WIN_SPRITES_SMALL = [ 101 | (339, 168, 42, 48), 102 | (390, 168, 42, 48) 103 | ] 104 | 105 | SELECTOR = (394, 12, 24, 24) 106 | 107 | 108 | -------------------------------------------------------------------------------- /data/level.py: -------------------------------------------------------------------------------- 1 | from .sprites import level_1 2 | from .basetypes import Vector2, Rectangle 3 | from . import config as c 4 | from .components.tiles import Question, Brick, Collider_Rect, Flagpole 5 | from .components.items import * 6 | from .components.enemies import * 7 | 8 | #Colliders that don't possess velocity 9 | static_colliders = [] 10 | 11 | #Colliders that possess velocity 12 | dynamic_colliders = [] 13 | 14 | coins = [] 15 | super_mushrooms = [] 16 | enemies = [] 17 | 18 | #Fragments go here when a brick tile gets broken 19 | brick_fragments = [] 20 | 21 | #Start and End tile for grouping large rows of tiles into one collider 22 | start_tile = None 23 | end_tile = None 24 | 25 | #Read pixel data from level map and instantiate objects corresponding to pixel colors 26 | for y in range(0, level_1.size[1]): 27 | for x in range(0, level_1.size[0]): 28 | 29 | color = level_1.getpixel((x, y)) 30 | pos = Vector2(x * c.TILE_SIZE, y * c.TILE_SIZE + 24) 31 | 32 | #Black = Static ground collider, which are grouped together for optimizations 33 | if color == c.BLACK: 34 | if start_tile == None: 35 | start_tile = pos 36 | if end_tile == None: 37 | if x + 1 > level_1.size[0]: 38 | end_tile = pos 39 | if level_1.getpixel((x + 1, y)) != c.BLACK: 40 | end_tile = pos 41 | if end_tile != None and start_tile != None: 42 | w = end_tile.x - start_tile.x + c.TILE_SIZE 43 | h = c.TILE_SIZE 44 | rect = Rectangle(start_tile, w, h) 45 | static_colliders.append(Collider_Rect(rect)) 46 | end_tile = None 47 | start_tile = None 48 | 49 | #Red = Pipe collider 50 | elif color == c.RED: 51 | h = c.SCREEN_SIZE.y - pos.y 52 | w = 2 * c.TILE_SIZE 53 | rect = Rectangle(pos, w, h) 54 | static_colliders.append(Collider_Rect(rect)) 55 | 56 | #Yellow = Question tile with coin as item 57 | elif color == c.YELLOW: 58 | coin_rect = Rectangle(Vector2(pos.x, pos.y), 48, 42) 59 | contents = Coin(coin_rect) 60 | coins.append(contents) 61 | rect = Rectangle(pos, c.TILE_SIZE, c.TILE_SIZE) 62 | dynamic_colliders.append(Question(rect, contents)) 63 | 64 | #Gray = Brick tile 65 | elif color == c.GRAY: 66 | rect = Rectangle(pos, c.TILE_SIZE, c.TILE_SIZE) 67 | dynamic_colliders.append(Brick(rect)) 68 | 69 | #Green = Question tile with mushroom as item 70 | elif color == c.GREEN: 71 | mushroom_rect = Rectangle(Vector2(pos.x, pos.y), c.TILE_SIZE, c.TILE_SIZE) 72 | contents = Super_Mushroom(mushroom_rect, Vector2(c.MUSHROOM_START_VEL_X, 0)) 73 | super_mushrooms.append(contents) 74 | rect = Rectangle(pos, c.TILE_SIZE, c.TILE_SIZE) 75 | dynamic_colliders.append(Question(rect, contents)) 76 | 77 | #Brown = Goomba 78 | elif color == c.BROWN: 79 | rect = Rectangle(pos, c.TILE_SIZE, c.TILE_SIZE) 80 | enemies.append(Goomba(rect, Vector2())) 81 | 82 | elif color == c.PURPLE: 83 | rect = Rectangle(Vector2(pos.x, pos.y - 24), 48, 72) 84 | enemies.append(Turtle(rect, Vector2())) 85 | 86 | #Instantiate flagpole 87 | rect = Rectangle(Vector2(9504, 96), 48, 456) 88 | flag_pos = Vector2(9480, 120) 89 | c.flagpole = Flagpole(rect, flag_pos) 90 | 91 | -------------------------------------------------------------------------------- /data/components/items.py: -------------------------------------------------------------------------------- 1 | from ..basetypes import Game_Object, Vector2, Entity 2 | from .. import config as c 3 | from .. import sprites 4 | from .. import sounds 5 | from .. import level 6 | from ..utils import accelerate 7 | 8 | class Coin(Game_Object): 9 | """Coin item class""" 10 | def __init__(self, rect): 11 | super(Coin, self).__init__(rect) 12 | self.animation = self.Animation(self.pos.y) 13 | self.deployed = False 14 | self.collected = False 15 | 16 | def update(self): 17 | self.animation.anim() 18 | self.pos.y = self.animation.new_y 19 | if self.animation.bounce_iteration > 23: 20 | self.collected = True 21 | 22 | self.check_for_destroy() 23 | 24 | def check_for_destroy(self): 25 | """Checks if instance can be destroyed""" 26 | if self.collected: 27 | level.coins.remove(self) 28 | 29 | def draw(self): 30 | view_pos = c.camera.to_view_space(self.pos) 31 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), self.animation.current_sprite) 32 | 33 | class Animation(): 34 | """Contains specific animation variables and functions for this class""" 35 | def __init__(self, start_height): 36 | self.current_sprite = sprites.COIN[0] 37 | 38 | self.start_height = start_height 39 | self.new_y = start_height 40 | self.anim_timer = c.INITIAL_TIMER_VALUE 41 | self.anim_frame = 0 42 | self.bounce_iteration = 0 43 | 44 | def anim(self): 45 | """Spinning animation""" 46 | self.current_sprite = sprites.COIN[self.anim_frame % 4] 47 | self.anim_timer += c.delta_time 48 | if self.anim_timer > 3 * c.delta_time: 49 | self.anim_frame += 1 50 | self.anim_timer = 0 51 | self.bounce_iteration += 0.6 52 | 53 | self.new_y = self.start_height - self.anim_function(self.bounce_iteration) 54 | 55 | def anim_function(self, bounce_iteration): 56 | """Returns new y based on quadratic function to create bounce""" 57 | return -(bounce_iteration - 12) ** 2 + 144 58 | 59 | class Super_Mushroom(Entity): 60 | """Super mushroom class""" 61 | def __init__(self, rect, vel): 62 | super(Super_Mushroom, self).__init__(vel, rect) 63 | 64 | self.deployed = False 65 | self.collected = False 66 | 67 | self.animation = self.Animation(self.pos.y) 68 | 69 | def draw(self): 70 | view_pos = c.camera.to_view_space(self.pos) 71 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), sprites.SUPER_MUSHROOM) 72 | 73 | def update(self): 74 | if self.animation.has_animated: 75 | accelerate(self, 0, c.GRAVITY) 76 | self.move() 77 | else: 78 | self.animation.deploy_anim() 79 | self.pos.y = self.animation.new_y 80 | 81 | self.check_for_destroy() 82 | 83 | def check_for_destroy(self): 84 | """Checks if instance can be destroyed""" 85 | if self.collected: 86 | sounds.powerup.play() 87 | c.total_score += c.MUSHROOM_SCORE 88 | level.super_mushrooms.remove(self) 89 | 90 | def move(self): 91 | """Separates x and y movement""" 92 | if self.vel.x != 0: 93 | self.move_single_axis(self.vel.x, 0) 94 | if self.vel.y != 0: 95 | self.move_single_axis(0, self.vel.y) 96 | 97 | def move_single_axis(self, dx, dy): 98 | """Checks to see whether x or y movement caused collisions""" 99 | self.pos.x += dx * c.delta_time 100 | self.pos.y += dy * c.delta_time 101 | other_collider = self.rect.check_collisions(level.static_colliders + level.dynamic_colliders) 102 | 103 | if other_collider is None: 104 | return 105 | if dx > 0: 106 | self.pos.x = other_collider.pos.x - self.rect.w 107 | self.vel.x = -self.vel.x 108 | elif dx < 0: 109 | self.pos.x = other_collider.pos.x + other_collider.rect.w 110 | self.vel.x = -self.vel.x 111 | elif dy > 0: 112 | self.pos.y = other_collider.pos.y - self.rect.h 113 | self.vel.y = 0 114 | 115 | class Animation(): 116 | """Contains specific animation variables and functions for this class""" 117 | def __init__(self, start_height): 118 | self.new_y = start_height 119 | self.anim_iteration = 0 120 | self.has_animated = False 121 | 122 | def deploy_anim(self): 123 | """Animation when deploying super mushroom""" 124 | if self.anim_iteration == 48: 125 | self.has_animated = True 126 | if not self.has_animated: 127 | self.new_y -= 1 128 | self.anim_iteration += 1 129 | -------------------------------------------------------------------------------- /data/basetypes.py: -------------------------------------------------------------------------------- 1 | from . import config as c 2 | from . import sprites 3 | import pygame as pg 4 | import math 5 | 6 | class Game_Object(): 7 | def __init__(self, rect): 8 | self.rect = rect 9 | 10 | def __getattr__(self, name): 11 | """Makes lines shorter by not having to type rect.pos when retrieving position""" 12 | if name == 'pos': 13 | return self.rect.pos 14 | return object.__getattribute__(self, name) 15 | 16 | def __setattr__(self, name, value): 17 | """Makes lines shorter by not having to type rect.pos when setting position""" 18 | if name == 'pos': 19 | self.rect.pos = value 20 | else: 21 | object.__setattr__(self, name, value) 22 | 23 | class Vector2(): 24 | """Vector class for 2D positions and velocities""" 25 | def __init__(self, x = 0, y = 0): 26 | self.x = x 27 | self.y = y 28 | 29 | def __mul__(self, other): 30 | """Overload multiplication""" 31 | return Vector2(self.x * other, self.y * other) 32 | 33 | def __add__(self, other): 34 | """Overload Addition""" 35 | return Vector2(self.x + other.x, self.y + other.y) 36 | 37 | class Rectangle(): 38 | """Rectangle class for collider rectangles""" 39 | def __init__(self, pos = Vector2(), w = 0, h = 0): 40 | self.pos = pos 41 | self.w = w 42 | self.h = h 43 | 44 | def overlaps(self, other): 45 | """Check if two rectangles overlap""" 46 | return not(other.pos.x + other.w <= self.pos.x or 47 | other.pos.x >= self.pos.x + self.w or 48 | other.pos.y + other.h <= self.pos.y or 49 | other.pos.y >= self.pos.y + self.h) 50 | 51 | def check_collisions(self, collider_list): 52 | """Check collisions between two rectangles, if in a rangle of 100px, returns a single collider""" 53 | for collider in collider_list: 54 | if abs(self.pos.x - collider.pos.x) < 100 or collider.rect.w >= 100: #Wider colliders are checked anyway 55 | if self.overlaps(collider.rect): 56 | return collider 57 | 58 | def check_entity_collisions(self, entity_list): 59 | """Check collisions but return a list of all colliding entities""" 60 | others = [] 61 | for entity in entity_list: 62 | if entity.rect is not self and abs(self.pos.x - entity.pos.x) < 100: 63 | if self.overlaps(entity.rect): 64 | others.append(entity) 65 | return others 66 | 67 | class Entity(Game_Object): 68 | """Entity class for Gameobjects that possess velocity""" 69 | def __init__(self, vel, rect): 70 | super(Entity, self).__init__(rect) 71 | self.vel = vel 72 | 73 | class Camera(Rectangle): 74 | def __init__(self, pos, w, h): 75 | super(Camera, self).__init__(pos, w, h) 76 | 77 | def contains(self, other): 78 | """Checks if camera horizontally contains a rectangle""" 79 | return ((other.pos.x > self.pos.x and other.pos.x < self.pos.x + c.SCREEN_SIZE.x) or 80 | (other.pos.x + other.w > self.pos.x and other.pos.x + other.w < self.pos.x + c.SCREEN_SIZE.x)) 81 | 82 | def to_view_space(self, pos): 83 | """Returns position relative to camera""" 84 | return Vector2(pos.x - self.pos.x, pos.y) 85 | 86 | def update(self): 87 | """Update position of camera based on mario velocity and position""" 88 | if self.pos.x < c.MAXIMUM_CAMERA_SCROLL: 89 | if c.mario.pos.x > c.camera.pos.x + c.CAMERA_FOLLOW_X and c.mario.vel.x > 0: 90 | c.camera.pos.x += c.mario.vel.x * c.delta_time 91 | 92 | class State_Machine(): 93 | """Manages states""" 94 | def __init__(self, initial_state, owner_object): 95 | self.state = initial_state 96 | self.owner_object = owner_object 97 | 98 | def on_event(self, event): 99 | """Updates current state and runs on_exit and on_enter""" 100 | new_state = self.state.on_event(event) 101 | if new_state is not self.state: 102 | self.state.on_exit(self.owner_object) 103 | self.state = new_state 104 | self.state.on_enter(self.owner_object) 105 | 106 | def update(self): 107 | self.state.update(self.owner_object) 108 | 109 | def get_state(self): 110 | return self.state.__class__.__name__ 111 | 112 | class State(): 113 | """State Class""" 114 | def on_event(self, event): 115 | """Handles events delegated to this state""" 116 | pass 117 | 118 | def on_enter(self, owner_object): 119 | """Performs actions when entering state""" 120 | pass 121 | 122 | def update(self, owner_object): 123 | """Performs actions specific to state when active""" 124 | pass 125 | 126 | def on_exit(self, owner_object): 127 | """Performs actions when exiting state""" 128 | pass 129 | 130 | class Digit_System(): 131 | """Class for displaying and handling on-screen digits like score""" 132 | def __init__(self, start_pos, number_of_digits, start_value = 0): 133 | self.total_value = start_value 134 | self.start_pos = start_pos 135 | self.number_of_digits = number_of_digits #Total amount of digits the digit system handles 136 | self.digit_array = [] 137 | self.update_value(start_value) 138 | 139 | def update_value(self, new_value): 140 | """Updates the total value and digit array of the digit system""" 141 | self.total_value = new_value 142 | if new_value > 0: 143 | remaining_digits = self.number_of_digits - self.get_number_of_digits(new_value) 144 | self.digit_array = [0] * remaining_digits 145 | for x in str(self.total_value): 146 | self.digit_array.append(int(x)) 147 | else: 148 | self.digit_array = [0] * self.number_of_digits 149 | 150 | def draw(self): 151 | """Draw the digit system""" 152 | for i, x in enumerate(self.digit_array): 153 | #Digit width = 24 154 | c.screen.blit(sprites.digits, (self.start_pos.x + 24 * i, self.start_pos.y), (24 * x, 0, 24, 21)) 155 | 156 | def get_number_of_digits(self, value): 157 | """Gets the number of digits in an integer""" 158 | if value == 0: 159 | return 0 160 | elif value == 1: 161 | return 1 162 | else: 163 | return math.ceil(math.log10(value)) 164 | 165 | 166 | -------------------------------------------------------------------------------- /data/main.py: -------------------------------------------------------------------------------- 1 | from . import config as c 2 | from . import level 3 | from . import sprites 4 | from . import sounds 5 | from .basetypes import Camera, Vector2, Rectangle, Digit_System 6 | import pygame as pg 7 | from .components import mario 8 | 9 | class Main(): 10 | """Contains main loop and handles the game""" 11 | def __init__(self): 12 | c.camera = Camera(Vector2(), c.SCREEN_SIZE.x, c.SCREEN_SIZE.y) 13 | c.mario = mario.Mario(Rectangle(c.MARIO_START_POSITION, 36, 48)) 14 | 15 | pg.mixer.music.load(sounds.main_theme) 16 | pg.mixer.music.play() 17 | 18 | self.quit_state = None 19 | self.out_of_time = False 20 | 21 | self.score_system = Digit_System(Vector2(66, 49), 6) #Displays total score on screen 22 | self.coin_score = Digit_System(Vector2(306, 49), 2) #Displays collected coins on screen 23 | self.time = Digit_System(Vector2(610, 49), 3, 300) #Displays time on screen 24 | self.timer = 0 #timer for counting down the in-game time 25 | 26 | def draw(self): 27 | """Draw all GameObjects and sprites that are currently on screen""" 28 | c.screen.fill(c.BACKGROUND_COLOR) 29 | self.draw_background() 30 | 31 | for item in (level.coins + level.super_mushrooms): 32 | if item.deployed: 33 | item.draw() 34 | 35 | for tile in level.dynamic_colliders: 36 | if c.camera.contains(tile.rect): 37 | view_pos = c.camera.to_view_space(tile.pos) 38 | tile.draw(view_pos) 39 | 40 | for enemy in level.enemies: 41 | if enemy.is_active: 42 | enemy.draw() 43 | 44 | for fragment in level.brick_fragments: 45 | fragment.draw() 46 | 47 | c.flagpole.draw_flag() 48 | 49 | c.mario.draw() 50 | 51 | self.draw_foreground() 52 | self.draw_digit_systems() 53 | 54 | def draw_background(self): 55 | """Extract rectangle from background image based on camera position""" 56 | c.screen.blit(sprites.background, 57 | (0, 0), 58 | (c.camera.pos.x, c.camera.pos.y, c.SCREEN_SIZE.x, c.SCREEN_SIZE.y)) 59 | 60 | def draw_foreground(self): 61 | """Draw the foreground at the end of the level to make mario disappear behind the castle""" 62 | view_pos = c.camera.to_view_space(c.FOREGROUND_POS) 63 | if view_pos.x < c.camera.pos.x + c.SCREEN_SIZE.x: 64 | c.screen.blit(sprites.foreground, (view_pos.x, view_pos.y)) 65 | c.screen.blit(sprites.text_image, (0,0)) 66 | 67 | def draw_digit_systems(self): 68 | """Draw all digit systems on screen""" 69 | self.score_system.draw() 70 | self.coin_score.draw() 71 | self.time.draw() 72 | 73 | def handle_digit_systems(self): 74 | """Updates all on-screen digit systems""" 75 | if not c.mario.current_mario_state == 'Dead_Mario': 76 | self.handle_time() 77 | self.score_system.update_value(c.total_score) 78 | self.coin_score.update_value(c.collected_coins) 79 | 80 | def handle_time(self): 81 | """Handles events delegated to the on-screen timer""" 82 | 83 | #Count down the timer 84 | self.timer += c.delta_time 85 | if not c.final_count_down and self.timer > 14 * c.delta_time: 86 | self.time.update_value(self.time.total_value - 1) 87 | self.timer = 0 88 | 89 | #If timer is lower than 100, play out of time music 90 | if not c.mario.current_mario_state == 'Win_State': 91 | if not c.final_count_down and self.time.total_value < 100 and not self.out_of_time: 92 | pg.mixer.music.stop() 93 | pg.mixer.music.set_endevent(c.OUT_OF_TIME_END) 94 | pg.mixer.music.load(sounds.out_of_time) 95 | pg.mixer.music.play() 96 | self.out_of_time = True 97 | 98 | #If the timer runs out and mario has not won, kill mario 99 | if not c.final_count_down and self.time.total_value == 0: 100 | c.mario.mario_states.on_event('dead') 101 | 102 | #If mario has won and time is still > 0, count down and add score 103 | if c.final_count_down and self.time.total_value > 0: 104 | self.time.update_value(self.time.total_value - 1) 105 | c.total_score += c.TIME_SCORE 106 | sounds.count_down.play() 107 | sounds.count_down.set_volume(0.15) 108 | if self.time.total_value == 0: 109 | sounds.count_down.stop() 110 | sounds.coin.play() 111 | 112 | def update_level(self): 113 | """Update all Gameobjects in the level""" 114 | c.mario.update() 115 | c.mario.physics_update() 116 | c.camera.update() 117 | for tile in level.dynamic_colliders: 118 | tile.update() 119 | 120 | for item in (level.coins + level.super_mushrooms): 121 | if item.deployed: 122 | item.update() 123 | 124 | if not c.mario.freeze_movement: 125 | for enemy in level.enemies: 126 | if enemy.pos.x < c.camera.pos.x + c.SCREEN_SIZE.x: 127 | enemy.is_active = True 128 | enemy.update() 129 | 130 | for fragment in level.brick_fragments: 131 | fragment.update() 132 | 133 | c.flagpole.update() 134 | 135 | def check_for_quit(self): 136 | """event manager for quitting the app or going back to menu""" 137 | for event in pg.event.get(): 138 | if event.type == pg.QUIT: 139 | return False 140 | 141 | if event.type == c.WIN_SONG_END and self.time.total_value == 0: 142 | self.quit_state = 'menu' 143 | return False 144 | 145 | if event.type == c.DEATH_SONG_END: 146 | self.quit_state = 'menu' 147 | return False 148 | 149 | if event.type == c.OUT_OF_TIME_END: 150 | pg.mixer.music.stop() 151 | pg.mixer.music.load(sounds.main_theme_sped_up) 152 | pg.mixer.music.play() 153 | 154 | if c.mario.to_menu: 155 | self.quit_state = 'menu' 156 | return False 157 | 158 | if c.keys[pg.K_ESCAPE]: 159 | return False 160 | return True 161 | 162 | def main_loop(self): 163 | """Main game loop, updates and draws the level every frame""" 164 | while True: 165 | c.delta_time = c.clock.tick(60) 166 | c.keys = pg.key.get_pressed() 167 | 168 | self.update_level() 169 | self.handle_digit_systems() 170 | self.draw() 171 | 172 | if not self.check_for_quit(): 173 | break 174 | 175 | pg.display.update() -------------------------------------------------------------------------------- /data/components/tiles.py: -------------------------------------------------------------------------------- 1 | from .. import config as c 2 | from ..basetypes import Vector2, Game_Object, State_Machine, State, Entity, Rectangle 3 | from .. import sprites 4 | from .. import sounds 5 | from ..utils import accelerate 6 | from .. import level 7 | import pygame as pg 8 | 9 | 10 | class Collider_Rect(Game_Object): 11 | """Class for static colliders""" 12 | def __init__(self, rect): 13 | super(Collider_Rect, self).__init__(rect) 14 | 15 | class Question(Game_Object): 16 | """Question Block""" 17 | def __init__(self, rect, contents): 18 | super(Question, self).__init__(rect) 19 | self.contents = contents 20 | 21 | self.animation = self.Animation(self.pos.y) 22 | self.state_machine = State_Machine(self.Closed_State(), self) 23 | 24 | def update(self): 25 | self.state_machine.update() 26 | 27 | def draw(self, pos): 28 | c.screen.blit(sprites.tile_set, (pos.x, pos.y), self.animation.current_sprite) 29 | 30 | class Animation(): 31 | """Contains specific animation variables and functions for this class""" 32 | def __init__(self, start_height): 33 | self.current_sprite = sprites.Q_BLOCK_CLOSED[0] 34 | 35 | self.outer_timer = c.INITIAL_TIMER_VALUE 36 | self.inner_timer = c.INITIAL_TIMER_VALUE 37 | self.closed_frame = 0 38 | self.closed_frames = [0, 1, 2, 1, 0] 39 | 40 | self.start_height = start_height 41 | self.bounce_iteration = 0 42 | self.new_y = start_height 43 | 44 | def closed_anim(self): 45 | """Animation when not opened""" 46 | self.current_sprite = sprites.Q_BLOCK_CLOSED[self.closed_frames[self.closed_frame]] 47 | self.outer_timer += c.delta_time 48 | if self.outer_timer > 20 * c.delta_time: 49 | self.inner_timer += c.delta_time 50 | if self.inner_timer > 6 * c.delta_time: 51 | self.closed_frame += 1 52 | self.inner_timer = 0 53 | 54 | if self.closed_frame == 5: 55 | self.outer_timer = 0 56 | self.closed_frame = 0 57 | 58 | def bounce_anim(self): 59 | """Animation when bouncing""" 60 | self.new_y = self.start_height - self.bounce_anim_function(self.bounce_iteration) 61 | self.bounce_iteration += 4 62 | 63 | def bounce_anim_function(self, bounce_iteration): 64 | """Returns new y position based on mathematical function""" 65 | return -abs(bounce_iteration - 24) + 24 66 | 67 | class Closed_State(State): 68 | """State when not opened yet""" 69 | def on_event(self, event): 70 | if event == 'bounce': 71 | return Question.Bounce_State() 72 | return self 73 | 74 | def update(self, owner_object): 75 | owner_object.animation.closed_anim() 76 | 77 | class Bounce_State(State): 78 | """State when opening""" 79 | def on_event(self, event): 80 | if event == 'open': 81 | return Question.Open_State() 82 | return self 83 | 84 | def on_enter(self, owner_object): 85 | owner_object.animation.current_sprite = sprites.Q_BLOCK_OPEN 86 | if owner_object.contents.__class__.__name__ == 'Coin': 87 | owner_object.contents.deployed = True 88 | c.total_score += c.COIN_SCORE 89 | c.collected_coins += 1 90 | sounds.coin.play() 91 | else: 92 | sounds.powerup_appears.play() 93 | 94 | def update(self, owner_object): 95 | owner_object.animation.bounce_anim() 96 | owner_object.pos.y = owner_object.animation.new_y 97 | if owner_object.animation.bounce_iteration > 48: 98 | owner_object.state_machine.on_event('open') 99 | 100 | class Open_State(State): 101 | """State when opened""" 102 | def on_event(self, event): 103 | return self 104 | 105 | def on_enter(self, owner_object): 106 | owner_object.contents.deployed = True 107 | 108 | class Brick(Game_Object): 109 | """Brick class""" 110 | def __init__(self, rect): 111 | super(Brick, self).__init__(rect) 112 | 113 | self.animation = self.Animation(self.pos.y) 114 | self.state_machine = State_Machine(self.Idle_State(), self) 115 | 116 | self.remove = False 117 | 118 | def update(self): 119 | self.state_machine.update() 120 | 121 | def draw(self, pos): 122 | c.screen.blit(sprites.tile_set, (pos.x, pos.y), sprites.BRICK) 123 | 124 | def instantiate_fragments(self): 125 | """Instantiate fragments when broken""" 126 | level.brick_fragments.append(Brick_Fragment(Vector2(self.pos.x, self.pos.y), Vector2(-0.1, -0.5), Rectangle())) 127 | level.brick_fragments.append(Brick_Fragment(Vector2(self.pos.x + 24, self.pos.y), Vector2(0.1, -0.5), Rectangle())) 128 | level.brick_fragments.append(Brick_Fragment(Vector2(self.pos.x + 24, self.pos.y + 24), Vector2(0.1, -0.4), Rectangle())) 129 | level.brick_fragments.append(Brick_Fragment(Vector2(self.pos.x, self.pos.y + 24), Vector2(-0.1, -0.4), Rectangle())) 130 | 131 | class Animation(): 132 | """Contains specific animation variables and functions for this class""" 133 | def __init__(self, start_height): 134 | 135 | self.bounce_iteration = 0 136 | self.new_y = start_height 137 | self.start_height = start_height 138 | 139 | def bounce_anim(self): 140 | self.new_y = self.start_height - self.bounce_anim_function(self.bounce_iteration) 141 | self.bounce_iteration += 4 142 | 143 | def bounce_anim_function(self, bounce_iteration): 144 | return -abs(bounce_iteration - 24) + 24 145 | 146 | class Idle_State(State): 147 | """State when not interacting with anything""" 148 | def on_event(self, event): 149 | if event == 'bounce': 150 | return Brick.Bounce_State() 151 | elif event == 'break': 152 | return Brick.Break_State() 153 | return self 154 | 155 | class Bounce_State(State): 156 | """State when small mario hits brick from under""" 157 | def on_event(self, event): 158 | if event == 'idle': 159 | return Brick.Idle_State() 160 | return self 161 | 162 | def update(self, owner_object): 163 | owner_object.animation.bounce_anim() 164 | owner_object.pos.y = owner_object.animation.new_y 165 | if owner_object.animation.bounce_iteration > 48: 166 | owner_object.animation.bounce_iteration = 0 167 | owner_object.state_machine.on_event('idle') 168 | 169 | class Break_State(State): 170 | """State when big mario hits brick from under""" 171 | def __init__(self): 172 | self.wait_for_frame = 0 173 | 174 | def on_enter(self, owner_object): 175 | owner_object.instantiate_fragments() 176 | sounds.brick_smash.play() 177 | 178 | def update(self, owner_object): 179 | if self.wait_for_frame > 0: 180 | level.dynamic_colliders.remove(owner_object) 181 | self.wait_for_frame += 1 182 | 183 | class Brick_Fragment(Entity): 184 | """Handles individual brick fragments and their animations""" 185 | def __init__(self, pos, vel, rect): 186 | super(Brick_Fragment, self).__init__(vel, rect) 187 | self.rect.pos = pos 188 | self.animation = self.Animation() 189 | 190 | def update(self): 191 | accelerate(self, 0, c.GRAVITY) 192 | self.pos += self.vel * c.delta_time 193 | self.animation.anim() 194 | self.check_for_destroy() 195 | 196 | def check_for_destroy(self): 197 | """Checks if instance can be destroyed""" 198 | if self.pos.y > c.SCREEN_SIZE.y: 199 | level.brick_fragments.remove(self) 200 | 201 | def draw(self): 202 | view_pos = c.camera.to_view_space(self.pos) 203 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), self.animation.current_sprite) 204 | 205 | class Animation(): 206 | """Contains specific animation variables and functions for this class""" 207 | def __init__(self): 208 | self.current_sprite = None 209 | self.anim_frame = 0 210 | self.anim_timer = c.INITIAL_TIMER_VALUE 211 | 212 | def anim(self): 213 | self.current_sprite = sprites.BRICK_FRAGMENT[self.anim_frame % 2] 214 | self.anim_timer += c.delta_time 215 | if self.anim_timer > 8 * c.delta_time: 216 | self.anim_frame += 1 217 | self.anim_timer = 0 218 | 219 | class Flagpole(Game_Object): 220 | """Handles flagpole at the end of level and triggers win events""" 221 | def __init__(self, rect, flag_pos): 222 | super(Flagpole, self).__init__(rect) 223 | self.flag_pos = flag_pos 224 | 225 | def update(self): 226 | if self.rect.check_collisions([c.mario]) is not None: 227 | c.mario.mario_states.on_event('win') 228 | 229 | if c.mario.current_mario_state == 'Win_State': 230 | if not self.flag_pos.y >= self.pos.y + self.rect.h - 60: 231 | self.flag_pos.y += 4 232 | 233 | def draw_flag(self): 234 | view_pos = c.camera.to_view_space(self.flag_pos) 235 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), sprites.FLAG) 236 | -------------------------------------------------------------------------------- /data/components/enemies.py: -------------------------------------------------------------------------------- 1 | from .. import config as c 2 | from ..basetypes import Vector2, Entity, State, State_Machine, Rectangle 3 | from .. import sprites 4 | from .. import sounds 5 | from ..utils import accelerate 6 | from .. import level 7 | 8 | class Goomba(Entity): 9 | """Goomba class""" 10 | def __init__(self, rect, vel): 11 | super(Goomba, self).__init__(vel, rect) 12 | self.animation = self.Animation() 13 | self.state_machine = State_Machine(self.Run_State(), self) 14 | self.vel.x = c.ENEMY_START_VEL_X 15 | 16 | self.is_active = False 17 | self.can_kill = True 18 | 19 | def draw(self): 20 | view_pos = c.camera.to_view_space(self.pos) 21 | if c.camera.contains(self.rect): 22 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), self.animation.current_sprite) 23 | 24 | def update(self): 25 | self.state_machine.update() 26 | if self.is_active: 27 | if all(self.state_machine.get_state() != state for state in ['Squish_State', 'Dead_State']): 28 | accelerate(self, 0, c.GRAVITY) 29 | self.move() 30 | self.check_for_destroy() 31 | 32 | def check_for_destroy(self): 33 | """Checks if instance can be destroyed""" 34 | if self.pos.y > c.SCREEN_SIZE.y: 35 | level.enemies.remove(self) 36 | 37 | def move(self): 38 | """Splits up x and y movement""" 39 | if self.vel.x != 0: 40 | self.move_single_axis(self.vel.x, 0) 41 | if self.vel.y != 0: 42 | self.move_single_axis(0, self.vel.y) 43 | 44 | def move_single_axis(self, dx, dy): 45 | """Updates position""" 46 | self.pos.x += dx * c.delta_time 47 | self.pos.y += dy * c.delta_time 48 | if self.state_machine.get_state() != 'Knocked_State': 49 | self.check_collisions(dx, dy) 50 | 51 | def check_collisions(self, dx, dy): 52 | """Checks whether x or y movement caused collisions""" 53 | other_collider = self.rect.check_collisions(level.static_colliders + level.dynamic_colliders) 54 | other_enemy = self.rect.check_collisions([enemy for enemy in level.enemies if enemy is not self and enemy.is_active]) 55 | 56 | if other_collider is None and other_enemy is None: 57 | return 58 | if other_collider is not None: 59 | if dx > 0: 60 | self.pos.x = other_collider.pos.x - self.rect.w 61 | self.vel.x = -self.vel.x 62 | elif dx < 0: 63 | self.pos.x = other_collider.pos.x + other_collider.rect.w 64 | self.vel.x = -self.vel.x 65 | elif dy > 0: 66 | self.pos.y = other_collider.pos.y - self.rect.h 67 | self.vel.y = 0 68 | if hasattr(other_collider, 'state_machine') and any(other_collider.state_machine.get_state() == state for state in ['Bounce_State', 'Break_State']): 69 | self.state_machine.on_event('knocked') 70 | if other_enemy is not None: 71 | self.pos.x -= dx * c.delta_time 72 | self.vel.x = -self.vel.x 73 | 74 | 75 | class Animation(): 76 | """Contains specific animation variables and functions for this class""" 77 | def __init__(self): 78 | self.current_sprite = sprites.GOOMBA_RUN[0] 79 | 80 | self.anim_timer = c.INITIAL_TIMER_VALUE 81 | self.anim_frame = 0 82 | 83 | self.squish_delay_over = False 84 | 85 | def run_anim(self): 86 | """Animation when running""" 87 | self.current_sprite = sprites.GOOMBA_RUN[self.anim_frame % 2] 88 | self.anim_timer += c.delta_time 89 | if self.anim_timer > 14 * c.delta_time: 90 | self.anim_frame += 1 91 | self.anim_timer = 0 92 | 93 | def squish_delay(self): 94 | """Make goomba remain for a certain amount of time after being squished""" 95 | self.anim_timer += c.delta_time 96 | if self.anim_timer > 20 * c.delta_time: 97 | self.squish_delay_over = True 98 | 99 | def reset_anim_vars(self): 100 | """Reset animation variables""" 101 | self.anim_timer = 0 102 | self.anim_frame = 0 103 | 104 | class Run_State(State): 105 | """State when running around""" 106 | def on_event(self, event): 107 | if event == 'knocked': 108 | return Goomba.Knocked_State() 109 | elif event == 'squish': 110 | return Goomba.Squish_State() 111 | return self 112 | 113 | def update(self, owner_object): 114 | owner_object.animation.run_anim() 115 | 116 | def on_exit(self, owner_object): 117 | owner_object.animation.reset_anim_vars() 118 | 119 | class Knocked_State(State): 120 | """State when knocked by brick block or turtle shell""" 121 | def on_event(self, event): 122 | if event == 'dead': 123 | return Goomba.Dead_State() 124 | return self 125 | 126 | def on_enter(self, owner_object): 127 | owner_object.vel.y = c.GOOMBA_KNOCKED_VEL_Y 128 | owner_object.animation.current_sprite = sprites.GOOMBA_KNOCKED 129 | c.total_score += c.GOOMBA_SCORE 130 | sounds.kick.play() 131 | 132 | class Squish_State(State): 133 | """State when getting squished""" 134 | def on_event(self, event): 135 | if event == 'dead': 136 | return Goomba.Dead_State() 137 | return self 138 | 139 | def on_enter(self, owner_object): 140 | owner_object.animation.current_sprite = sprites.GOOMBA_SQUISHED 141 | owner_object.rect = Rectangle(owner_object.pos, 0, 0) 142 | sounds.stomp.play() 143 | c.total_score += c.GOOMBA_SCORE 144 | 145 | def update(self, owner_object): 146 | owner_object.animation.squish_delay() 147 | if owner_object.animation.squish_delay_over: 148 | owner_object.state_machine.on_event('dead') 149 | 150 | class Dead_State(State): 151 | """State when dead, destroys instance of goomba""" 152 | def on_enter(self, owner_object): 153 | level.enemies.remove(owner_object) 154 | 155 | class Turtle(Entity): 156 | """Turtle Class""" 157 | def __init__(self, rect, vel): 158 | super(Turtle, self).__init__(vel, rect) 159 | self.animation = self.Animation() 160 | self.state_machine = State_Machine(self.Run_State(), self) 161 | self.vel.x = c.ENEMY_START_VEL_X 162 | self.is_active = False 163 | 164 | self.can_kill = True 165 | 166 | def update(self): 167 | if self.is_active: 168 | accelerate(self, 0, c.GRAVITY) 169 | self.move() 170 | self.state_machine.update() 171 | self.check_for_destroy() 172 | 173 | def draw(self): 174 | view_pos = c.camera.to_view_space(self.pos) 175 | if c.camera.contains(self.rect): 176 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), self.animation.current_sprite) 177 | 178 | def check_for_destroy(self): 179 | """Checks if instance can be destroyed""" 180 | if self.pos.y > c.SCREEN_SIZE.y: 181 | level.enemies.remove(self) 182 | 183 | def move(self): 184 | if self.vel.x != 0: 185 | self.move_single_axis(self.vel.x, 0) 186 | if self.vel.y != 0: 187 | self.move_single_axis(0, self.vel.y) 188 | 189 | def move_single_axis(self, dx, dy): 190 | """Move position based on velocity""" 191 | self.pos.x += dx * c.delta_time 192 | self.pos.y += dy * c.delta_time 193 | if self.state_machine.get_state() != 'Knocked_State': 194 | self.check_collisions(dx, dy) 195 | 196 | def check_collisions(self, dx, dy): 197 | """Checks if x or y movement caused collisions and performs according actions""" 198 | other_collider = self.rect.check_collisions(level.static_colliders + level.dynamic_colliders) 199 | other_enemy = self.rect.check_collisions([enemy for enemy in level.enemies if enemy is not self]) 200 | 201 | if other_collider is None and other_enemy is None: 202 | return 203 | if other_collider is not None: 204 | if dx > 0: 205 | self.pos.x = other_collider.pos.x - self.rect.w 206 | self.vel.x = -self.vel.x 207 | elif dx < 0: 208 | self.pos.x = other_collider.pos.x + other_collider.rect.w 209 | self.vel.x = -self.vel.x 210 | elif dy > 0: 211 | self.pos.y = other_collider.pos.y - self.rect.h 212 | self.vel.y = 0 213 | 214 | if other_enemy is not None: 215 | if self.state_machine.get_state() != 'Move_Shell': 216 | self.pos.x -= dx * c.delta_time 217 | self.vel.x = -self.vel.x 218 | else: 219 | other_enemy.state_machine.on_event('knocked') 220 | other_enemy.is_active = True 221 | 222 | class Animation(): 223 | """Contains specific animation variables and functions for this class""" 224 | def __init__(self): 225 | self.current_sprite = sprites.TURTLE[0] 226 | 227 | self.anim_timer = 0 228 | self.anim_frame = 0 229 | 230 | def run_anim(self): 231 | self.current_sprite = sprites.TURTLE[self.anim_frame % 2] 232 | self.anim_timer += c.delta_time 233 | if self.anim_timer > 13 * c.delta_time: 234 | self.anim_frame += 1 235 | self.anim_timer = 0 236 | 237 | class Run_State(State): 238 | """State when running around""" 239 | def on_event(self, event): 240 | if event == 'squish': 241 | return Turtle.Shell_State() 242 | return self 243 | 244 | def update(self, owner_object): 245 | owner_object.animation.run_anim() 246 | 247 | class Shell_State(State): 248 | """State when turtle is in its shell""" 249 | def on_event(self, event): 250 | if event == 'move shell': 251 | return Turtle.Move_Shell() 252 | return self 253 | 254 | def on_enter(self, owner_object): 255 | owner_object.rect.h = 42 256 | owner_object.pos.y += 30 257 | owner_object.animation.current_sprite = sprites.TURTLE_SHELL 258 | owner_object.vel.x = 0 259 | owner_object.can_kill = False 260 | sounds.stomp.play() 261 | 262 | class Move_Shell(State): 263 | """State when turtle is in its shell and moving""" 264 | def __init__(self): 265 | self.can_kill_timer = 0 266 | 267 | def on_event(self, event): 268 | return self 269 | 270 | def on_enter(self, owner_object): 271 | sounds.kick.play() 272 | 273 | def update(self, owner_object): 274 | self.can_kill_timer += c.delta_time 275 | if self.can_kill_timer > 10 * c.delta_time: 276 | owner_object.can_kill = True 277 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "9f0909ab96da699558eea604b35d25109d548e9ceebfdef2ba62fc8e428e36c4" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "pillow": { 20 | "hashes": [ 21 | "sha256:00def5b638994f888d1058e4d17c86dec8e1113c3741a0a8a659039aec59a83a", 22 | "sha256:026449b64e559226cdb8e6d8c931b5965d8fc90ec18ebbb0baa04c5b36503c72", 23 | "sha256:03dbb224ee196ef30ed2156d41b579143e1efeb422974719a5392fc035e4f574", 24 | "sha256:03eb0e04f929c102ae24bc436bf1c0c60a4e63b07ebd388e84d8b219df3e6acd", 25 | "sha256:1be66b9a89e367e7d20d6cae419794997921fe105090fafd86ef39e20a3baab2", 26 | "sha256:1e977a3ed998a599bda5021fb2c2889060617627d3ae228297a529a082a3cd5c", 27 | "sha256:22cf3406d135cfcc13ec6228ade774c8461e125c940e80455f500638429be273", 28 | "sha256:24adccf1e834f82718c7fc8e3ec1093738da95144b8b1e44c99d5fc7d3e9c554", 29 | "sha256:2a3e362c97a5e6a259ee9cd66553292a1f8928a5bdfa3622fdb1501570834612", 30 | "sha256:3832e26ecbc9d8a500821e3a1d3765bda99d04ae29ffbb2efba49f5f788dc934", 31 | "sha256:4fd1f0c2dc02aaec729d91c92cd85a2df0289d88e9f68d1e8faba750bb9c4786", 32 | "sha256:4fda62030f2c515b6e2e673c57caa55cb04026a81968f3128aae10fc28e5cc27", 33 | "sha256:5044d75a68b49ce36a813c82d8201384207112d5d81643937fc758c05302f05b", 34 | "sha256:522184556921512ec484cb93bd84e0bab915d0ac5a372d49571c241a7f73db62", 35 | "sha256:5914cff11f3e920626da48e564be6818831713a3087586302444b9c70e8552d9", 36 | "sha256:6661a7908d68c4a133e03dac8178287aa20a99f841ea90beeb98a233ae3fd710", 37 | "sha256:79258a8df3e309a54c7ef2ef4a59bb8e28f7e4a8992a3ad17c24b1889ced44f3", 38 | "sha256:7d74c20b8f1c3e99d3f781d3b8ff5abfefdd7363d61e23bdeba9992ff32cc4b4", 39 | "sha256:81918afeafc16ba5d9d0d4e9445905f21aac969a4ebb6f2bff4b9886da100f4b", 40 | "sha256:8194d913ca1f459377c8a4ed8f9b7ad750068b8e0e3f3f9c6963fcc87a84515f", 41 | "sha256:84d5d31200b11b3c76fab853b89ac898bf2d05c8b3da07c1fcc23feb06359d6e", 42 | "sha256:989981db57abffb52026b114c9a1f114c7142860a6d30a352d28f8cbf186500b", 43 | "sha256:a3d7511d3fad1618a82299aab71a5fceee5c015653a77ffea75ced9ef917e71a", 44 | "sha256:b3ef168d4d6fd4fa6685aef7c91400f59f7ab1c0da734541f7031699741fb23f", 45 | "sha256:c1c5792b6e74bbf2af0f8e892272c2a6c48efa895903211f11b8342e03129fea", 46 | "sha256:c5dcb5a56aebb8a8f2585042b2f5c496d7624f0bcfe248f0cc33ceb2fd8d39e7", 47 | "sha256:e2bed4a04e2ca1050bb5f00865cf2f83c0b92fd62454d9244f690fcd842e27a4", 48 | "sha256:e87a527c06319428007e8c30511e1f0ce035cb7f14bb4793b003ed532c3b9333", 49 | "sha256:f63e420180cbe22ff6e32558b612e75f50616fc111c5e095a4631946c782e109", 50 | "sha256:f8b3d413c5a8f84b12cd4c5df1d8e211777c9852c6be3ee9c094b626644d3eab" 51 | ], 52 | "index": "pypi", 53 | "version": "==5.2.0" 54 | }, 55 | "pygame": { 56 | "hashes": [ 57 | "sha256:06dc92ccfea33b85f209db3d49f99a2a30c88fe9fb80fa2564cee443ece787b5", 58 | "sha256:0919a2ec5fcb0d00518c2a5fa99858ccf22d7fbcc0e12818b317062d11386984", 59 | "sha256:0a8c92e700e0042faefa998fa064616f330201890d6ea1c993eb3ff30ab53e99", 60 | "sha256:220a1048ebb3d11a4d48cc4219ec8f65ca62fcafd255239478677625e8ead2e9", 61 | "sha256:315861d2b8428f7b4d56d2c98d6c1acc18f08c77af4b129211bc036774f64be2", 62 | "sha256:3469e87867832fe5226396626a8a6a9dac9b2e21a7819dd8cd96cf0e08bbcd41", 63 | "sha256:54c19960180626165512d596235d75dc022d38844467cec769a8d8153fd66645", 64 | "sha256:5ba598736ab9716f53dc943a659a9578f62acfe00c0c9c5490f3aca61d078f75", 65 | "sha256:60ddc4f361babb30ff2d554132b1f3296490f3149d6c1c77682213563f59937a", 66 | "sha256:6a49ab8616a9de534f1bf62c98beabf0e0bb0b6ff8917576bba22820bba3fdad", 67 | "sha256:6d4966eeba652df2fd9a757b3fc5b29b578b47b58f991ad714471661ea2141cb", 68 | "sha256:700d1781c999af25d11bfd1f3e158ebb660f72ebccb2040ecafe5069d0b2c0b6", 69 | "sha256:73f4c28e894e76797b8ccaf6eb1205b433efdb803c70f489ebc3db6ac9c097e6", 70 | "sha256:786eca2bea11abd924f3f67eb2483bcb22acff08f28dbdbf67130abe54b23797", 71 | "sha256:7bcf586a1c51a735361ca03561979eea3180de45e6165bcdfa12878b752544af", 72 | "sha256:82a1e93d82c1babceeb278c55012a9f5140e77665d372a6d97ec67786856d254", 73 | "sha256:9e03589bc80a21ae951fca7659a767b7cac668289937e3756c0ab3d753cf6d24", 74 | "sha256:aa8926a4e34fb0943abe1a8bb04a0ad82265341bf20064c0862db0a521100dfc", 75 | "sha256:aa90689b889c417d2ac571ef2bbb5f7e735ae30c7553c60fae7508404f46c101", 76 | "sha256:c9f8cdefee267a2e690bf17d61a8f5670b620f25a981f24781b034363a8eedc9", 77 | "sha256:d9177afb2f46103bfc28a51fbc49ce18987a857e5c934db47b4a7030cb30fbd0", 78 | "sha256:deb0551d4bbfb8131e2463a7fe1943bfcec5beb11acdf9c4bfa27fa5a9758d62", 79 | "sha256:e7edfe57a5972aa9130ce9a186020a0f097e7a8e4c25e292109bdae1432b77f9", 80 | "sha256:f0ad32efb9e26160645d62ba6cf3e5a5828dc4e82e8f41f9badfe7b685b07295" 81 | ], 82 | "index": "pypi", 83 | "version": "==1.9.4" 84 | } 85 | }, 86 | "develop": { 87 | "astroid": { 88 | "hashes": [ 89 | "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a", 90 | "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a" 91 | ], 92 | "version": "==1.6.5" 93 | }, 94 | "backports.functools-lru-cache": { 95 | "hashes": [ 96 | "sha256:9d98697f088eb1b0fa451391f91afb5e3ebde16bbdb272819fd091151fda4f1a", 97 | "sha256:f0b0e4eba956de51238e17573b7087e852dfe9854afd2e9c873f73fc0ca0a6dd" 98 | ], 99 | "markers": "python_version == '2.7'", 100 | "version": "==1.5" 101 | }, 102 | "configparser": { 103 | "hashes": [ 104 | "sha256:5308b47021bc2340965c371f0f058cc6971a04502638d4244225c49d80db273a" 105 | ], 106 | "markers": "python_version == '2.7'", 107 | "version": "==3.5.0" 108 | }, 109 | "enum34": { 110 | "hashes": [ 111 | "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850", 112 | "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a", 113 | "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79", 114 | "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1" 115 | ], 116 | "markers": "python_version < '3.4'", 117 | "version": "==1.1.6" 118 | }, 119 | "futures": { 120 | "hashes": [ 121 | "sha256:9ec02aa7d674acb8618afb127e27fde7fc68994c0437ad759fa094a574adb265", 122 | "sha256:ec0a6cb848cc212002b9828c3e34c675e0c9ff6741dc445cab6fdd4e1085d1f1" 123 | ], 124 | "version": "==3.2.0" 125 | }, 126 | "isort": { 127 | "hashes": [ 128 | "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af", 129 | "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8", 130 | "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497" 131 | ], 132 | "version": "==4.3.4" 133 | }, 134 | "lazy-object-proxy": { 135 | "hashes": [ 136 | "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33", 137 | "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39", 138 | "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019", 139 | "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088", 140 | "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b", 141 | "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e", 142 | "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6", 143 | "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b", 144 | "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5", 145 | "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff", 146 | "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd", 147 | "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7", 148 | "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff", 149 | "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d", 150 | "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2", 151 | "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35", 152 | "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4", 153 | "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514", 154 | "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252", 155 | "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109", 156 | "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f", 157 | "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c", 158 | "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92", 159 | "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577", 160 | "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d", 161 | "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d", 162 | "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f", 163 | "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a", 164 | "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b" 165 | ], 166 | "version": "==1.3.1" 167 | }, 168 | "mccabe": { 169 | "hashes": [ 170 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 171 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 172 | ], 173 | "version": "==0.6.1" 174 | }, 175 | "pylint": { 176 | "hashes": [ 177 | "sha256:09bc539f85706f2cca720a7ddf28f5c6cf8185708d6cb5bbf7a90a32c3b3b0aa", 178 | "sha256:b8471105f12c73a1b9eee2bb2474080370e062a7290addd215eb34bc4dfe9fd8" 179 | ], 180 | "index": "pypi", 181 | "version": "==1.9.3" 182 | }, 183 | "singledispatch": { 184 | "hashes": [ 185 | "sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c", 186 | "sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8" 187 | ], 188 | "markers": "python_version < '3.4'", 189 | "version": "==3.4.0.3" 190 | }, 191 | "six": { 192 | "hashes": [ 193 | "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", 194 | "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" 195 | ], 196 | "version": "==1.11.0" 197 | }, 198 | "wrapt": { 199 | "hashes": [ 200 | "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6" 201 | ], 202 | "version": "==1.10.11" 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /data/components/mario.py: -------------------------------------------------------------------------------- 1 | from ..basetypes import Game_Object, Vector2, Entity, Rectangle, State_Machine, State 2 | from .. import config as c 3 | from .. import sprites 4 | from .. import sounds 5 | from ..utils import accelerate, clamp, get_flipped_sprite 6 | from .. import level 7 | import pygame as pg 8 | import random 9 | 10 | class Mario(Entity): 11 | """Mario Class""" 12 | def __init__(self, rect, vel = Vector2()): 13 | super(Mario, self).__init__(vel, rect) 14 | self.animation = self.Animation() 15 | self.action_states = State_Machine(self.Idle_State(), self) 16 | self.mario_states = State_Machine(self.Small_Mario(), self) 17 | 18 | self.pressed_left = False 19 | self.pressed_right = False 20 | self.spacebar = False 21 | self.crouch = False 22 | self.freeze_movement = False 23 | self.freeze_input = False 24 | 25 | self.flip_sprites = False 26 | self.to_menu = False 27 | 28 | self.start_height = 0 29 | 30 | def __getattr__(self, name): 31 | """Shorter variable calls""" 32 | if name == 'current_action_state': 33 | return self.action_states.get_state() 34 | elif name == 'pos': 35 | return self.rect.pos 36 | elif name == 'current_mario_state': 37 | return self.mario_states.get_state() 38 | return object.__getattribute__(self, name) 39 | 40 | def draw(self): 41 | """Extract sprite from atlas""" 42 | if c.camera.contains(self.rect): 43 | view_pos = c.camera.to_view_space(self.pos) 44 | if self.flip_sprites: 45 | flipped_sprite = get_flipped_sprite(self.animation.current_sprite) 46 | c.screen.blit(sprites.tile_set_flipped, (view_pos.x, view_pos.y), flipped_sprite) 47 | else: 48 | c.screen.blit(sprites.tile_set, (view_pos.x, view_pos.y), self.animation.current_sprite) 49 | 50 | def update(self): 51 | """Get input and perform actions""" 52 | if not self.freeze_input: 53 | if c.keys[pg.K_a] and not c.keys[pg.K_d]: 54 | self.pressed_left = True 55 | c.ACCELERATION = -c.MARIO_ACCELERATION 56 | elif c.keys[pg.K_d] and not c.keys[pg.K_a]: 57 | self.pressed_right = True 58 | c.ACCELERATION = c.MARIO_ACCELERATION 59 | else: 60 | c.ACCELERATION = 0 61 | 62 | if not c.keys[pg.K_a]: 63 | self.pressed_left = False 64 | if not c.keys[pg.K_d]: 65 | self.pressed_right = False 66 | 67 | if c.keys[pg.K_SPACE] and not self.spacebar: 68 | self.spacebar = True 69 | self.action_states.on_event('jump') 70 | 71 | if not c.keys[pg.K_SPACE]: 72 | self.spacebar = False 73 | 74 | if c.keys[pg.K_s]: 75 | self.crouch = True 76 | else: 77 | self.crouch = False 78 | 79 | def physics_update(self): 80 | """Perform actions based on input""" 81 | if self.current_mario_state != 'Invincible_Mario': 82 | self.mario_states.update() 83 | 84 | if not self.freeze_movement: 85 | self.state_events() 86 | self.action_states.update() 87 | self.movement() 88 | 89 | #Make sure that mario can't jump when running off a ledge 90 | if self.pos.y > self.start_height: 91 | self.action_states.on_event('no jump') 92 | 93 | self.check_flip_sprites() 94 | 95 | if self.current_mario_state == 'Invincible_Mario': 96 | self.mario_states.update() 97 | 98 | self.rect.h = self.animation.current_sprite[3] 99 | 100 | if self.pos.y > c.SCREEN_SIZE.y: 101 | self.mario_states.on_event('dead') 102 | 103 | def movement(self): 104 | """Aggregates movement related statements""" 105 | accelerate(self, c.ACCELERATION, c.GRAVITY, c.MAX_VEL) 106 | self.vel.x *= c.FRICTION 107 | self.move() 108 | 109 | def check_flip_sprites(self): 110 | """Check whether to flip sprites""" 111 | if self.vel.x < 0: 112 | self.flip_sprites = True 113 | elif self.vel.x > 0: 114 | self.flip_sprites = False 115 | 116 | def state_events(self): 117 | """Change current state based on events and perform actions based on current state""" 118 | if any(self.current_action_state == state for state in ['Move_State', 'Decel_State', 'Brake_State', 'Idle_State']): 119 | self.start_height = self.pos.y 120 | 121 | if self.vel.y == 0: 122 | if self.pressed_left or self.pressed_right: 123 | self.action_states.on_event('move') 124 | 125 | if ((self.vel.x < 0 and not self.pressed_left) or 126 | (self.vel.x > 0 and not self.pressed_right)): 127 | self.action_states.on_event('decel') 128 | 129 | if ((self.vel.x < 0 and self.pressed_right) or 130 | (self.vel.x > 0 and self.pressed_left)): 131 | self.action_states.on_event('brake') 132 | 133 | if abs(self.vel.x) < 0.02 and self.current_action_state != 'Move_State': 134 | self.vel.x = 0 135 | self.action_states.on_event('idle') 136 | 137 | if all(self.current_action_state != state for state in ['Decel_State', 'Brake_State', 'Crouch_State']): 138 | c.FRICTION = 1 139 | 140 | if any(self.current_action_state == state for state in ['Jump_State', 'No_Jump_State']): 141 | if self.animation.mario_size == 'Small_Mario': 142 | self.animation.current_sprite = sprites.SMALL_MARIO_JUMP 143 | else: 144 | self.animation.current_sprite = sprites.BIG_MARIO_JUMP 145 | 146 | if self.current_mario_state == 'Big_Mario': 147 | if self.crouch: 148 | self.action_states.on_event('crouch') 149 | 150 | def move(self): 151 | """Separates x and y movement""" 152 | if self.vel.x != 0: 153 | self.move_single_axis(self.vel.x, 0) 154 | if self.vel.y != 0: 155 | self.move_single_axis(0, self.vel.y) 156 | 157 | def move_single_axis(self, dx, dy): 158 | """Move based on velocity and check for collisions based on new position""" 159 | self.pos.x += dx * c.delta_time 160 | self.pos.y += dy * c.delta_time 161 | 162 | self.collider_collisions(dx, dy) 163 | if self.current_mario_state != 'Invincible_Mario': 164 | self.check_entity_collisions() 165 | 166 | self.check_backtrack() 167 | 168 | def check_backtrack(self): 169 | """Stop mario from backtracking in the level""" 170 | if self.pos.x < c.camera.pos.x: 171 | self.pos.x = clamp(self.pos.x, c.camera.pos.x, c.SCREEN_SIZE.x) 172 | self.vel.x = 0 173 | if all(self.current_action_state != state for state in ["Jump_State", "No_Jump_State"]): 174 | self.action_states.on_event('idle') 175 | 176 | def collider_collisions(self, dx, dy): 177 | """Check for collisions with tiles""" 178 | other_collider = self.rect.check_collisions(level.static_colliders + level.dynamic_colliders) 179 | 180 | if other_collider is None: 181 | return 182 | if dx > 0: 183 | if self.current_action_state == 'Move_State': 184 | self.action_states.on_event('idle') 185 | self.pos.x = other_collider.pos.x - self.rect.w 186 | self.vel.x = 0 187 | elif dx < 0: 188 | if self.current_action_state == 'Move_State': 189 | self.action_states.on_event('idle') 190 | self.pos.x = other_collider.pos.x + other_collider.rect.w 191 | self.vel.x = 0 192 | elif dy > 0: 193 | if self.current_action_state == 'No_Jump_State': 194 | self.action_states.on_event('idle') 195 | self.pos.y = other_collider.pos.y - self.rect.h 196 | self.vel.y = 0 197 | elif dy < 0: 198 | self.interact_with_tile(other_collider) 199 | self.action_states.on_event('no jump') 200 | self.pos.y = other_collider.pos.y + other_collider.rect.h 201 | self.vel.y = c.BOUNCE_VEL 202 | 203 | def check_entity_collisions(self): 204 | """Check for collisions with entities""" 205 | entities = self.rect.check_entity_collisions(level.super_mushrooms + level.enemies) 206 | 207 | for entity in entities: 208 | if entity.__class__.__name__ == 'Super_Mushroom' and entity.deployed: 209 | self.mario_states.on_event('grow') 210 | entity.collected = True 211 | 212 | if hasattr(entity, 'state_machine') and entity.state_machine.get_state() != 'Knocked_State': 213 | if entity.state_machine.get_state() == 'Shell_State': 214 | if self.pos.x + self.rect.w < entity.pos.x + entity.rect.w / 2: 215 | entity.vel.x = 0.5 216 | elif self.pos.x + self.rect.w > entity.pos.x + entity.rect.w / 2: 217 | entity.vel.x = -0.5 218 | elif self.vel.x < 0: 219 | entity.vel.x = -0.5 220 | elif self.vel.x > 0: 221 | entity.vel.x = 0.5 222 | else: 223 | entity.vel.x = random.choice([-0.5, 0.5]) 224 | entity.state_machine.on_event('move shell') 225 | 226 | elif self.pos.y + self.rect.h - self.vel.y * c.delta_time < entity.pos.y: 227 | if entity.state_machine.get_state() == 'Run_State': 228 | self.vel.y = c.STOMP_VEL 229 | self.pos.y = entity.pos.y - self.rect.h 230 | entity.state_machine.on_event('squish') 231 | return 232 | else: 233 | if entity.state_machine.get_state() != 'Shell_State' and entity.can_kill: 234 | self.mario_states.on_event('shrink') 235 | 236 | def interact_with_tile(self, tile): 237 | """Interact with tile based on current mario state""" 238 | if self.current_mario_state == 'Small_Mario': 239 | tile.state_machine.on_event('bounce') 240 | if tile.__class__.__name__ == 'Brick': 241 | sounds.bump.play() 242 | elif self.current_mario_state == 'Big_Mario': 243 | tile.state_machine.on_event('break') 244 | if tile.__class__.__name__ == 'Question': 245 | tile.state_machine.on_event('bounce') 246 | 247 | class Animation(): 248 | """Contains specific animation variables and functions for this class""" 249 | def __init__(self): 250 | self.current_sprite = sprites.SMALL_MARIO_IDLE 251 | 252 | self.mario_size = 'Small_Mario' 253 | self.anim_frame = 0 254 | self.anim_timer = c.INITIAL_TIMER_VALUE 255 | self.invincible_timer = 0 256 | 257 | self.start_height = None 258 | self.new_y = self.start_height 259 | 260 | self.grow_frames = [0, 1, 0, 1, 2, 0, 1, 2] 261 | self.shrink_frames = [0, 1, 0, 1, 2, 1, 2, 1] 262 | self.run_frames = [0, 1, 2, 1] 263 | self.start_sprite_height = 0 264 | 265 | def reset_anim_vars(self): 266 | """Reset animation variables""" 267 | self.anim_frame = 0 268 | self.anim_timer = c.INITIAL_TIMER_VALUE 269 | 270 | def grow_anim(self): 271 | """Animation when growing""" 272 | self.current_sprite = sprites.GROW_SPRITES[self.grow_frames[self.anim_frame]] 273 | self.anim_timer += c.delta_time 274 | if self.anim_timer > 6 * c.delta_time: 275 | self.anim_frame += 1 276 | self.anim_timer = 0 277 | self.new_y = self.start_height - (self.current_sprite[3] - 48) 278 | 279 | def run_anim(self): 280 | """Animation when running""" 281 | if self.mario_size == 'Small_Mario': 282 | self.current_sprite = sprites.SMALL_MARIO_RUN[self.run_frames[self.anim_frame % 4]] 283 | else: 284 | self.current_sprite = sprites.BIG_MARIO_RUN[self.run_frames[self.anim_frame % 4]] 285 | self.anim_timer += c.delta_time 286 | if self.anim_timer > 6 * c.delta_time: 287 | self.anim_frame += 1 288 | self.anim_timer = 0 289 | 290 | def shrink_anim(self): 291 | """Animation when shrinking""" 292 | self.current_sprite = sprites.SHRINK_SPRITES[self.shrink_frames[self.anim_frame]] 293 | self.anim_timer += c.delta_time 294 | if self.anim_timer > 6 * c.delta_time: 295 | self.anim_frame += 1 296 | self.anim_timer = 0 297 | self.new_y = self.start_height + (self.start_sprite_height - self.current_sprite[3]) 298 | 299 | def win_anim_on_flag(self): 300 | """Animation when sliding down flag pole""" 301 | if self.mario_size == 'Small_Mario': 302 | self.current_sprite = sprites.WIN_SPRITES_SMALL[self.anim_frame % 2] 303 | else: 304 | self.current_sprite = sprites.WIN_SPRITES_BIG[self.anim_frame % 2] 305 | self.anim_timer += c.delta_time 306 | if self.anim_timer > 8 * c.delta_time: 307 | self.anim_frame += 1 308 | self.anim_timer = 0 309 | 310 | class Idle_State(State): 311 | """State when on the ground and not moving""" 312 | def on_enter(self, owner_object): 313 | if owner_object.animation.mario_size == 'Small_Mario': 314 | owner_object.animation.current_sprite = sprites.SMALL_MARIO_IDLE 315 | else: 316 | owner_object.animation.current_sprite = sprites.BIG_MARIO_IDLE 317 | 318 | def on_event(self, event): 319 | if event == 'jump': 320 | return Mario.Jump_State() 321 | elif event == 'move': 322 | return Mario.Move_State() 323 | elif event == 'decel': 324 | return Mario.Decel_State() 325 | elif event == 'brake': 326 | return Mario.Brake_State() 327 | elif event == 'crouch': 328 | return Mario.Crouch_State() 329 | return self 330 | 331 | class Jump_State(State): 332 | """State when jumping when spacebar input affects velocity""" 333 | def on_event(self, event): 334 | if event == 'no jump': 335 | return Mario.No_Jump_State() 336 | return self 337 | 338 | def on_enter(self, owner_object): 339 | if owner_object.current_mario_state == 'Small_Mario': 340 | sounds.small_jump.play() 341 | else: 342 | sounds.big_jump.play() 343 | 344 | def update(self, owner_object): 345 | owner_object.vel.y = c.JUMP_VELOCITY 346 | if (not owner_object.spacebar or 347 | owner_object.pos.y < owner_object.start_height - c.MAX_JUMP_HEIGHT): 348 | owner_object.action_states.on_event('no jump') 349 | 350 | class No_Jump_State(State): 351 | """State when in mid air but spacebar input does not affect velocity""" 352 | def on_event(self, event): 353 | if event == 'idle': 354 | return Mario.Idle_State() 355 | elif event == 'decel': 356 | return Mario.Decel_State() 357 | elif event == 'brake': 358 | return Mario.Brake_State() 359 | elif event == 'move': 360 | return Mario.Move_State() 361 | return self 362 | 363 | class Move_State(State): 364 | """State when moving on the ground and not breaking or decelerating""" 365 | def on_event(self, event): 366 | if event == 'decel': 367 | return Mario.Decel_State() 368 | elif event == 'brake': 369 | return Mario.Brake_State() 370 | elif event == 'no jump': 371 | return Mario.No_Jump_State() 372 | elif event == 'jump': 373 | return Mario.Jump_State() 374 | elif event == 'crouch': 375 | return Mario.Crouch_State() 376 | elif event == 'idle': 377 | return Mario.Idle_State() 378 | return self 379 | 380 | def update(self, owner_object): 381 | if owner_object.pressed_left: 382 | c.ACCELERATION = -c.MARIO_ACCELERATION 383 | elif owner_object.pressed_right: 384 | c.ACCELERATION = c.MARIO_ACCELERATION 385 | owner_object.animation.run_anim() 386 | 387 | class Brake_State(State): 388 | """State when input is opposite velocity""" 389 | def on_event(self, event): 390 | if event == 'move': 391 | return Mario.Move_State() 392 | elif event == 'decel': 393 | return Mario.Decel_State() 394 | elif event == 'no jump': 395 | return Mario.No_Jump_State() 396 | elif event == 'jump': 397 | return Mario.Jump_State() 398 | elif event == 'crouch': 399 | return Mario.Crouch_State() 400 | elif event == 'idle': 401 | return Mario.Idle_State() 402 | return self 403 | 404 | def on_enter(self, owner_object): 405 | c.ACCELERATION = 0 406 | c.FRICTION = c.BRAKE_FRICTION 407 | if owner_object.animation.mario_size == 'Small_Mario': 408 | owner_object.animation.current_sprite = sprites.SMALL_MARIO_BRAKE 409 | else: 410 | owner_object.animation.current_sprite = sprites.BIG_MARIO_BRAKE 411 | 412 | class Decel_State(State): 413 | """State when moving when there is no longer any input""" 414 | def on_event(self, event): 415 | if event == 'idle': 416 | return Mario.Idle_State() 417 | elif event == 'brake': 418 | return Mario.Brake_State() 419 | elif event == 'move': 420 | return Mario.Move_State() 421 | elif event == 'no jump': 422 | return Mario.No_Jump_State() 423 | elif event == 'jump': 424 | return Mario.Jump_State() 425 | elif event == 'crouch': 426 | return Mario.Crouch_State() 427 | return self 428 | 429 | def on_enter(self, owner_object): 430 | c.ACCELERATION = 0 431 | c.FRICTION = c.DECEL_FRICTION 432 | 433 | def update(self, owner_object): 434 | owner_object.animation.run_anim() 435 | 436 | class Invincible_Mario(State): 437 | """State after shrinking when mario is invincible""" 438 | def __init__(self): 439 | self.invincible_timer = 0 440 | self.blink_timer = 0 441 | 442 | def on_event(self, event): 443 | if event == 'small mario': 444 | return Mario.Small_Mario() 445 | return self 446 | 447 | def update(self, owner_object): 448 | self.invincible_timer += c.delta_time 449 | if self.invincible_timer > 40 * c.delta_time: 450 | owner_object.mario_states.on_event('small mario') 451 | 452 | self.blink_timer += c.delta_time 453 | if self.blink_timer > 7 * c.delta_time: 454 | owner_object.animation.current_sprite = sprites.EMPTY_SPRITE 455 | if self.blink_timer > 14 * c.delta_time: 456 | self.blink_timer = 0 457 | 458 | def on_exit(self, owner_object): 459 | owner_object.animation.reset_anim_vars() 460 | 461 | class Small_Mario(State): 462 | """State when mario is small""" 463 | def on_event(self, event): 464 | if event == 'grow': 465 | return Mario.Grow_Mario() 466 | elif event == 'shrink': 467 | return Mario.Dead_Mario() 468 | elif event == 'win': 469 | return Mario.Win_State() 470 | elif event == 'dead': 471 | return Mario.Dead_Mario() 472 | return self 473 | 474 | class Grow_Mario(State): 475 | """State when mario is growing""" 476 | def on_event(self, event): 477 | if event == 'big mario': 478 | return Mario.Big_Mario() 479 | if event == 'shrink': 480 | return Mario.Shrink_Mario() 481 | return self 482 | 483 | def on_enter(self, owner_object): 484 | owner_object.animation.start_height = owner_object.pos.y 485 | owner_object.animation.reset_anim_vars() 486 | owner_object.freeze_movement = True 487 | 488 | def update(self, owner_object): 489 | owner_object.animation.grow_anim() 490 | owner_object.pos.y = owner_object.animation.new_y 491 | if owner_object.animation.anim_frame > 7: 492 | owner_object.mario_states.on_event('big mario') 493 | 494 | def on_exit(self, owner_object): 495 | owner_object.rect.h = 96 496 | owner_object.animation.mario_size = 'Big_Mario' 497 | owner_object.animation.reset_anim_vars() 498 | owner_object.freeze_movement = False 499 | 500 | class Big_Mario(State): 501 | """State when mario is big""" 502 | def on_event(self, event): 503 | if event == 'shrink': 504 | return Mario.Shrink_Mario() 505 | elif event == 'dead': 506 | return Mario.Dead_Mario() 507 | elif event == 'win': 508 | return Mario.Win_State() 509 | return self 510 | 511 | class Shrink_Mario(State): 512 | """State when mario is shrinking""" 513 | def on_event(self, event): 514 | if event == 'invincible': 515 | return Mario.Invincible_Mario() 516 | if event == 'grow mario': 517 | return Mario.Grow_Mario() 518 | return self 519 | 520 | def on_enter(self, owner_object): 521 | owner_object.animation.reset_anim_vars() 522 | owner_object.animation.start_height = owner_object.pos.y 523 | owner_object.animation.start_sprite_height = owner_object.animation.current_sprite[3] 524 | owner_object.freeze_movement = True 525 | sounds.pipe.play() 526 | 527 | def update(self, owner_object): 528 | owner_object.animation.shrink_anim() 529 | owner_object.pos.y = owner_object.animation.new_y 530 | if owner_object.animation.anim_frame > 7: 531 | owner_object.mario_states.on_event('invincible') 532 | 533 | def on_exit(self, owner_object): 534 | owner_object.rect.h = 48 535 | owner_object.animation.mario_size = 'Small_Mario' 536 | owner_object.animation.reset_anim_vars() 537 | owner_object.freeze_movement = False 538 | 539 | class Crouch_State(State): 540 | """State when mario is crouching""" 541 | def on_event(self, event): 542 | if event == 'brake': 543 | return Mario.Brake_State() 544 | elif event == 'jump': 545 | return Mario.Jump_State() 546 | elif event == 'decel': 547 | return Mario.Decel_State() 548 | elif event == 'move': 549 | return Mario.Move_State() 550 | elif event == 'idle': 551 | return Mario.Idle_State() 552 | return self 553 | 554 | def on_enter(self, owner_object): 555 | c.FRICTION = c.BRAKE_FRICTION 556 | c.ACCELERATION = 0 557 | owner_object.animation.current_sprite = sprites.MARIO_CROUCH 558 | owner_object.pos.y += 30 559 | owner_object.rect.h = owner_object.animation.current_sprite[3] 560 | 561 | def update(self, owner_object): 562 | c.ACCELERATION = 0 563 | if owner_object.vel.x == 0: 564 | if owner_object.pressed_left: 565 | owner_object.flip_sprites = True 566 | if owner_object.pressed_right: 567 | owner_object.flip_sprites = False 568 | 569 | def on_exit(self, owner_object): 570 | owner_object.pos.y -= 31 571 | owner_object.start_height = owner_object.pos.y 572 | 573 | class Dead_Mario(State): 574 | """State when mario is dead""" 575 | def __init__(self): 576 | self.death_timer = 0 577 | 578 | def on_event(self, event): 579 | return self 580 | 581 | def on_enter(self, owner_object): 582 | owner_object.animation.current_sprite = sprites.DEAD_MARIO 583 | owner_object.vel.y = c.DEATH_VEL_Y 584 | owner_object.vel.x = 0 585 | owner_object.freeze_movement = True 586 | owner_object.freeze_input = True 587 | pg.mixer.music.stop() 588 | pg.mixer.music.set_endevent(c.DEATH_SONG_END) 589 | pg.mixer.music.load(sounds.death) 590 | pg.mixer.music.play() 591 | 592 | def update(self, owner_object): 593 | self.death_timer += c.delta_time 594 | if self.death_timer > 20 * c.delta_time: 595 | accelerate(owner_object, 0, c.GRAVITY) 596 | owner_object.pos += owner_object.vel * c.delta_time 597 | 598 | class Win_State(State): 599 | """State when mario wins, runs and manages events related to the final win animation""" 600 | def __init__(self): 601 | self.animation_step = 0 602 | self.timer = 0 603 | 604 | def on_event(self, event): 605 | return self 606 | 607 | def on_enter(self, owner_object): 608 | owner_object.animation.reset_anim_vars() 609 | owner_object.animation.start_height = owner_object.pos.y 610 | owner_object.animation.new_y = owner_object.pos.y 611 | owner_object.pos.x = c.flagpole.pos.x - 16 612 | owner_object.freeze_movement = True 613 | owner_object.freeze_input = True 614 | owner_object.vel = Vector2() 615 | pg.mixer.music.stop() 616 | sounds.flagpole_sound.play() 617 | 618 | def update(self, owner_object): 619 | 620 | if self.animation_step == 0: 621 | owner_object.animation.win_anim_on_flag() 622 | owner_object.pos.y += 4 623 | if owner_object.pos.y > c.flagpole.pos.y + c.flagpole.rect.h - 100: 624 | self.animation_step = 1 625 | 626 | elif self.animation_step == 1: 627 | owner_object.pos.x = c.flagpole.pos.x + 24 628 | owner_object.flip_sprites = True 629 | self.timer += c.delta_time 630 | if self.timer > 20 * c.delta_time: 631 | owner_object.flip_sprites = False 632 | owner_object.freeze_movement = False 633 | owner_object.pos.x = c.flagpole.pos.x + c.flagpole.rect.w 634 | self.animation_step = 2 635 | pg.mixer.music.set_endevent(c.WIN_SONG_END) 636 | pg.mixer.music.load(sounds.stage_clear) 637 | pg.mixer.music.play() 638 | 639 | elif self.animation_step == 2: 640 | c.ACCELERATION = c.MARIO_ACCELERATION 641 | owner_object.pressed_right = True 642 | if owner_object.pos.x > c.LEVEL_END_X: 643 | owner_object.freeze_movement = True 644 | c.final_count_down = True 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | --------------------------------------------------------------------------------