├── 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 |
5 | - Python - 3.7
6 | - PyGame - 1.9.3
7 | - PIL - 1.1.7
8 |
9 | IMPORTANT:
10 | Get 60fps by running python in low res mode
11 |
12 | - Run the program
13 | - Right click program in the dock
14 | - Go to options and click "show in finder"
15 | - Right click the python application and click "Get info"
16 | - Check "Open in low resolution"
17 |
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 |
--------------------------------------------------------------------------------