Coder Social home page Coder Social logo

code-jam-10's Introduction

The Pickled Peps

Team Members

  • A5rocks
  • at
  • BigbadSmurfface
  • GiGaGon
  • Inventor4Life

Markdown by GiGaGon based on the Google Slides

Getting Started

To run the game, clone the repo with git clone https://github.com/A5rocks/code-jam-10

CD into the directory and run python -m venv venv to create the venv

Run ./venv/Scripts/activate or source venv/bin/activate to activate the venv

Run pip install -r requirements.txt to install the requirements

Run python main.py to launch the game

Basic controls are wasd/arrow keys to move, mouse to interact with puzzles

Build Idea and Scope

Original Scope

Have to escape a room by solving puzzles related to images and cyphers

Pixel art aesthetic, isomorphic perspective

Cyphers give you the steps that are used in constructing final “key” images from the ones fixed and gathered thought the map

image.png.url

Concept game loop diagram by GiGaGon

Final Scope

As the deadline approached:

  • Top down perspective was used as the complexity is lower than isometric
  • No cyphers were successfully implemented
  • Image puzzles never got their own locations
  • No proper menus, lacking world interactivity
  • No image combinations
  • No end screen, game crashes when all puzzles are finished

Currently implemented ideas:

  • Navigable map with partial interaction system
  • Flipping, inversion, and sliding puzzles
  • Semi-extensible framework
  • (half-hearted) Transition between map navigation and puzzle solving

Theme Connections

Image manipulation:

  • The implemented puzzles all have to do with manipulating an image
  • The actual manipulation of the images happens only through numpy, with loading from PIL

Secret Codes:

  • Originally, the images themselves were part of the “secret code”
  • The cypher puzzles would have been a more literal interpretation of the “secret code” theme
  • The image combination steps would have tied the systems together

Technologies Used

Image loading:

  • PIL

Image processing:

  • Numpy

Image display and input handling:

  • Pygame

Code formatting:

  • Black
  • ISort
  • Flake8

Code quality:

  • MyPy

The Navigable Map

The Visible Map

The visible map is split into two layers, the floor surface and the deco surface

The floor surface will always be displayed under the player character

The deco surface will either be below or above the player character, implementation details later

The visible map gets position updates from the player, and uses that to slice different sections of the surfaces, giving the illusion of movement

Map.Navigation.mp4

Game was recorded smaller than normal to better fit in the presentation

The Floor Surface

Created by Inventor4Life and tweaked by GiGaGon

The floor surface is the always visible background layer, drawn under the player character

The floor tiles are originally 16x16, squished to 16x12 for the perspective

Due to alpha issues between Numpy arrays and pygame explained later, the shadows need to be on this image

Assets found by BigbadSmurfface, created by ShatteredReality and uploaded here on itch.io under the Creative Common Zero v1.0 Universal license

floor_surface

The Deco Surface

Created by Inventor4Life and tweaked by GiGaGon

The floor surface will either be below or above the player depending on the z order from the collision map. The lets the player walk behind objects

Since pygame doesn't support RGBA arrays, the deco layer uses a (255, 0, 255) chroma key for transparency

Assets found by BigbadSmurfface, created by ShatteredReality and uploaded here on itch.io under the Creative Common Zero v1.0 Universal license

deco_surface

The GameMap Class

Developed by GiGaGon

The GameMap class manages the different surfaces

It is instantiated in main, and takes in the two surface image paths along with the relevant needed info for rendering correctly

The scaling factor is used when creating the surface to scale up the pixel art to a visible size

game_map = GameMap(
    directory / "GameMap/floor_surface.png",
    directory / "GameMap/deco_surface.png",
    tile_pixel_size,
    fitting_tile_amount,
    scaling_factor,
    starting_offset,
    )

The update method takes in a movement vector py tuple[int, int] that is included in the EventTypes.MAP_POSITION_UPDATE event, and moves the map accordingly

for game_event in EventHandler.get():
    if game_event.type == EventTypes.MAP_POSITION_UPDATE:
        screen.fill((0, 0, 0))
        game_map.update(game_event.data)

Movement and the Player Class

Developed by GiGaGon

Movement is done with either the arrow keys or WASD

The key alternatives, as well as the sprites are hard coded in the player.py file

class MovementDirections(Enum):
    UP = (0, -1)
    DOWN = (0, 1)
    LEFT = (-1, 0)
    RIGHT = (1, 0)
    NULL = (0, 0)

KEYPRESS_ALTERNATIVES: dict[int, MovementDirections] = {
    **dict.fromkeys([pygame.K_w, pygame.K_UP], MovementDirections.UP),
    **dict.fromkeys([pygame.K_s, pygame.K_DOWN], MovementDirections.DOWN),
    **dict.fromkeys([pygame.K_a, pygame.K_LEFT], MovementDirections.LEFT),
    **dict.fromkeys([pygame.K_d, pygame.K_RIGHT], MovementDirections.RIGHT),
}

PLAYER_SPRITES: dict[MovementDirections, numpy.typing.NDArray] = {
    MovementDirections.UP: np.array(PIL.Image.open("Player/player_up.png")),
    MovementDirections.DOWN: np.array(PIL.Image.open("Player/player_down.png")),
    MovementDirections.LEFT: np.array(PIL.Image.open("Player/player_left.png")),
    MovementDirections.RIGHT: np.array(PIL.Image.open("Player/player_right.png")),
}

The player “moves” by updating an internal position, and being drawn to the center of the screen in the correct orientation

if game_event.type == EventTypes.PLAYER_SPRITE_UPDATE:
    screen.blit(player.image, tuple(middle_tile_pixel_location))

If the player actually moved and didn't just rotate or stand still, this info is passed to the GameMap through an EventTypes.MAP_POSITION_UPDATE event

EventHandler.add(EventTypes.MAP_POSITION_UPDATE, movement_direction.value)

The player class is instantiated with just the scaling factor and starting position, since the image paths were hardcoded

The final product is free from collision/visible map desyncs, but there is still the unintended behavior of the starting position changing if the window size changes

The middle tile offset is used later when blitting the player sprite

tile_pixel_size = np.array((16, 12))
scaling_factor = 4
fitting_tile_amount = np.ceil(
    np.array(screen_size) / (tile_pixel_size * scaling_factor)
).astype(int)
middle_tile_pixel_location = np.array(
    (fitting_tile_amount // 2) * tile_pixel_size * scaling_factor
)
# offset to get the player in the middle of the tiles
middle_tile_pixel_location += tile_pixel_size // 2
starting_offset = np.array((2, 10))
...
magic_player_offset = (fitting_tile_amount) // 2 + (0, 1)
player = Player(scaling_factor, starting_offset + magic_player_offset)

Player sprites created by Inventor4Life

player_sprites

The Collision Map

Map by Inventor4Life and implementation by GiGaGon

Collision is handled through a colored image that is m by n pixels wide, where m and n are the amount of tiles in the displayed map

The collision map is loaded in the player.py file

self._collision_map = np.array(
    PIL.Image.open("Player/collision_map.png")
).swapaxes(0, 1)

Due to some implementation misunderstandings, the image must be a transparent PNG and not a 24-bit PNG

Implementation:

 - Red       (255, 0, 0) = Solid
 - Blue      (0, 255, 0) = Walkable, Z Layer 0
 - Dark Blue (0, 127, 0) = Walkable, Z Layer 1
 - Green     (0, 0, 255) = Solid and Interactable

In-game collision map:

collision_map

The Puzzles

The Puzzle Class

Developed by Inventor4Life

The base class for the rest of the puzzles

Interaction is done via the mouse

Contains methods that normally don't need overwritten and are provided for convenience

The only method that needs to be implemented in subclasses is loop

Subclasses are instantiated with an image directory, the number of tiles per side, and the target display size

The puzzle image paths and tile numbers are stored in a list in main

current_puzzle = 0
puzzles = [
    (FlippingPuzzle, directory / "sample_images/Monalisa.png", 4),
    (SlidingPuzzle, directory / "sample_images/Monalisa.png", 4),
    (LightsOut, directory / "sample_images/Monalisa.png", 4),
    # NOTE: the sample image is not used (it could be... w/ filters?)
    (Connector, directory / "sample_images/Monalisa.png", 8),
]

When it is time to switch to the next puzzle (ie, on puzzle completion) a helper function is used to return the new puzzle instance

def switch_puzzle(puzzle_index, puzzle_list: list):
    """Changes the active puzzle"""
    my_image = PIL.Image.open(puzzle_list[puzzle_index][1])
    my_pieces = puzzle_list[puzzle_index][2]
    my_puzzle = puzzle_list[puzzle_index][0](my_image, my_pieces, (380, 500))
    return my_puzzle

Flipping Puzzle

Developed by BigbadSmurfface

An image is separated into tiles, some of which are flipped horizontally

When a tile is pressed, it flips horizontally

The goal is to return the image to a completely unflipped state

Flipping.Puzzle.mp4

Invert Puzzle/Lights Out

Developed by Inventor4Life

An image is separated into tiles, with some having their colors inverted

When a tile is pressed, the tile plus the 4 adjacent tiles invert in color

The goal is to return the image to a completely un-inverted state

Invert.Puzzle.mp4

Sliding Puzzle/15 Puzzle

Developed by Inventor4Life

An image is separated into randomly shuffled tiles, with one removed

When a tile is pressed, the empty space moves there if orthogonally adjacent

The goal is to return the image to an unshuffled state

Sliding.Puzzle.mp4

Sped up for your convenience, the original is 3 mins long and can be found here

Connector Puzzle/Flow Puzzle

Developed by A5rocks

Generate blue, red, and green tiles in a grid of white tiles

When a tile is pressed, cycle through white -> red -> green -> blue

Right mouse button cycles the opposite direction

The goal is to connect the starting tiles through a path of the same color

Currently may not always generate a solvable puzzle

Connector.Puzzle.mp4

The helpers.py File

Developed by GiGaGon

make_2d_surface_from_array - since pygame's default makesurface has some issues, this function attempts to solve them. It takes in a numpy array, flips it if needed (for PIL created arrays), does the upscaling, and can apply a color key

EventTypes - Simple event type enum for all the event types the program uses

class EventTypes(Enum):
    PLAYER_SPRITE_UPDATE = auto()
    PLAYER_MOVEMENT_UPDATE = auto()
    PUZZLE_SPRITE_UPDATE = auto()
    PUZZLE_SOLVED = auto()
    MAP_POSITION_UPDATE = auto()
    INTERACTION_EVENT = auto()
    EXIT_INTERACTION = auto()

Event - a type to mimic pygame's events, store both the enum type and optional data

EventHandler.add(EventTypes.MAP_POSITION_UPDATE, movement_direction.value)

EventHandler - a handler to mimic pygame's events, stores a list of Events, clears on read

for game_event in EventHandler.get():
    if game_event.type == EventTypes.MAP_POSITION_UPDATE:
        screen.fill((0, 0, 0))
        game_map.update(game_event.data)

code-jam-10's People

Contributors

a5rocks avatar inventor4life avatar jasonydog9 avatar megagigagon avatar

code-jam-10's Issues

Find sound effects for map

singular footstep
door opening
some form of alert sound ("like a secretary bell) when new items are picked up
one action sound (like sliding, scraping, or clicking when a puzzle piece is interacted with)

Design gameplay

what will the player need to do to move from room to room? in what order should it be done?

Ciphers and decoders should be located in separate rooms
Ciphers with clues to key locations, such as in "RED BOOK" should have multiple matching locations (i.e. multiple bookshelves with red books in them) to slow the player down and make it more rewarding when the key is found.

upgrade map

Give more information for the example map in discord (or create a new map)
preferably including room names, sizes, and a visual reference.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.