| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- # jogai - Python Game Library
- #
- # This library is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Library General Public
- # License as published by the Free Software Foundation; either
- # version 2 of the License, or (at your option) any later version.
- #
- # This library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Library General Public License for more details.
- #
- # You should have received a copy of the GNU Library General Public
- # License along with this library; if not, write to the Free
- # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- #
- # Ramón Palleiro Rodríguez
- # Zero! Factorial Studio
- # info@zero.gal
- import builtins
- import os
- import time
- from collections import OrderedDict
- from typing import Any, Tuple, Type
- import pygame
- import jogai
- import jogai._globals as _globals
- from jogai import character, groups, logger, scene, texts, utils, widgets
- from jogai.settings import settings
- from jogai.translations import gettext as _
- from jogai.utils import _get_events, _get_keys
- _PREDEFINED_ATTRIBUTES_ALLOWED = (
- _('caption'),
- _('characters'),
- _('gravity'),
- _('marks'),
- _('scene'),
- _('texts'),
- )
- # --------------- #
- # GLOBALS #
- # --------------- #
- running = True
- # ------------------ #
- # FUNCTIONS #
- # ------------------ #
- def _calculate_initial_collisions():
- for loaded_character in _globals.vg_characters:
- loaded_character.current_collisions = (
- loaded_character.calculate_collisions(
- (loaded_character.x, loaded_character.y)
- )
- )
- def _calculate_initial_touch():
- for loaded_character in _globals.vg_characters:
- loaded_character.current_touch = loaded_character.calculate_touch(
- (loaded_character.x, loaded_character.y)
- )
- def _load_data(data: dict):
- """
- Loads data from the schema.
- Parameters:
- data: input schema
- """
- if not isinstance(data, dict):
- raise TypeError(_('the attributes in the input schema must be a dict'))
- for name, value in data.items():
- # utils._convert_function_arguments_to_string()
- if name in _PREDEFINED_ATTRIBUTES_ALLOWED:
- if isinstance(value, str):
- value = f'"{value}"'
- eval(_('set_{}({})').format(name, value))
- else:
- add_attribute(name, value)
- def simulate_key_press(key: int | str, down: bool = True):
- """
- Posts a key press simulation to pygame event module.
- Parameters:
- key: key to simulate
- down: keystroke direction. True for KEYDOWN, False for KEYUP
- Examples:
- >>> simulate_key_press('a') # doctest: +SKIP
- """
- key = utils.eval_key(key)
- if isinstance(down, bool) and not down:
- event_type = pygame.KEYUP
- else:
- event_type = pygame.KEYDOWN
- key_event = pygame.event.Event(
- event_type,
- key=key,
- )
- pygame.event.post(key_event)
- def key_pressed(key: int | str) -> bool:
- """
- Checks if a key has been pressed.
- Parameters:
- key: the key to be checked
- Returns:
- True if the key is pressed, False otherwise.
- Examples:
- >>> key_pressed('a')
- False
- """
- key = utils.eval_key(key)
- return _get_keys()[key]
- def add_attribute(name: str, value: Any):
- """
- Adds a new attribute to the videogame.
- Parameters:
- name: a name for this attribute
- value: a value for this attribute
- Examples:
- >>> add_attribute('test': 'foo') # doctest: +SKIP
- """
- _globals.vg_attributes.update({name: value})
- def add_character(new_character: 'character.Character'):
- """
- Adds a new character to the videogame.
- Parameters:
- new_character: the character to add
- Examples:
- >>> p = character.Character('protagonist.png')
- >>> add_character(p) # doctest: +SKIP
- """
- if not isinstance(new_character, character.Character):
- raise TypeError(_('only Character instances can be added'))
- _globals.vg_characters.append(new_character)
- def add_chronometer(new_chronometer: 'texts.Chronometer'):
- """
- Adds a new chronometer to the videogame.
- Parameters:
- new_chronometer: the chronometer to be added
- Examples:
- >>> chrono = texts.Chronometer(30)
- >>> add_chronometer(chrono) # doctest: +SKIP
- """
- if not isinstance(new_chronometer, texts.Chronometer):
- raise TypeError(_('only Chronometer instances can be added'))
- _globals.vg_chronometers.append(new_chronometer)
- def add_group(new_group: 'group.SquareGroup'):
- """
- Adds a new group to the videogame.
- Parameters:
- new_group: the character to be added
- Examples:
- >>> p = character.Character('protagonist.png')
- >>> rectangle = group.RectangleGroup(p, 4, 2)
- >>> add_group(rectangle) # doctest: +SKIP
- """
- if not isinstance(new_group, groups.Group):
- raise TypeError(_('only Group instances can be added'))
- _globals.vg_groups.append(new_group)
- for vg_character in new_group.sprites():
- if vg_character in _globals.vg_characters:
- _globals.vg_characters.remove(vg_character)
- def add_levelbar(new_levelbar: 'widgets.LevelBar'):
- """
- Adds a new level bar to the videogame.
- Parameters:
- new_levelbar: the level bar to be added
- Examples:
- >>> lb = widgets.LevelBar(3)
- >>> add_levelbar(lb) # doctest: +SKIP
- """
- if not isinstance(new_levelbar, widgets.LevelBar):
- raise TypeError(_('only LevelBar instances can be added'))
- _globals.vg_levelbars.append(new_levelbar)
- def add_scoreboard(new_scoreboard: 'texts.Scoreboard'):
- """
- Adds a new scoreboard to the videogame.
- Parameters:
- new_scoreboard: the scoreboard to be added
- Examples:
- >>> sc = texts.Scoreboard('Test')
- >>> add_scoreboard(sc) # doctest: +SKIP
- """
- if not isinstance(new_scoreboard, texts.Scoreboard):
- raise TypeError(_('only Scoreboard instances can be added'))
- _globals.vg_scoreboards.append(new_scoreboard)
- def add_text(new_text: 'texts.Text'):
- """
- Adds a new text to the videogame.
- Parameters:
- new_text: the text to be added
- Examples:
- >>> text = texts.Text('Test')
- >>> add_text(text) # doctest: +SKIP
- """
- if not isinstance(new_text, texts.Text):
- raise TypeError(_('only Text instances can be added'))
- _globals.vg_texts.append(new_text)
- def init():
- """
- Starts the pygame engine if it is not started.
- Examples:
- >>> init()
- """
- if not pygame.get_init():
- pygame.init()
- _globals._window = pygame.display.set_mode(
- (settings.WINDOW_WIDTH, settings.WINDOW_HEIGHT)
- )
- pygame.key.set_repeat(settings.KEY_DELAY, settings.KEY_INTERVAL)
- def is_running() -> bool:
- """
- Checks if the game is running. Useful in the main loop.
- Returns:
- True if the game is running. False otherwise.
- Examples:
- >>> state = is_running()
- """
- return running
- def listen_events():
- """
- Listens for events coming from the keyboard and window clicks.
- Examples:
- >>> simulate_key_press('ESCAPE')
- >>> listen_events()
- """
- _globals._events = _get_events()
- _globals._keys = _get_keys()
- for event in _globals._events:
- match event.type:
- case pygame.KEYDOWN:
- if event.key == pygame.K_ESCAPE:
- quit()
- case pygame.QUIT:
- quit()
- def get_color(x: int, y: int) -> Tuple[int, int, int]:
- """
- Gets the RGB components for a point in the game window.
- Parameters:
- x: Position on the abscissa axis
- y: Position on the ordinate axis
- Returns:
- A tuple of integers with the RGB components
- Examples:
- >>> color = get_color(100, 200)
- """
- if not type(x) == int or not type(y) == int:
- raise TypeError(
- _('the coordinates of the point must be a non-negative integers')
- )
- if not (0 <= x <= settings.WINDOW_WIDTH) or not (
- 0 <= y <= settings.WINDOW_HEIGHT
- ):
- raise ValueError(
- _('the coordinates of the point must be a non-negative integers')
- )
- if _globals._window:
- return tuple(_globals._window.get_at((x, y))[:-1])
- return None
- def paint(
- sprite: Type['scene.Scene']
- | Type['character.Character']
- | Type['texts.Text'],
- ):
- """
- Paints multiple sprite types on the main window.
- Parameters:
- sprite: the element to be painted
- """
- # TODO: check sprite type and show error if no image or no rect
- if sprite.image and sprite.rect:
- _globals._window.blit(sprite.image, sprite.rect)
- def paint_character(sprite: Type['character.Character']):
- """
- Paints a character on the main window.
- Parameters:
- sprite: the element to be painted
- """
- if sprite.visible:
- paint(sprite)
- def paint_group(group: Type['groups.Group']):
- """
- Paints a group of sprites on the main window.
- Parameters:
- group: the group of sprites to be painted.
- """
- for sprite_item in group.characters:
- sprite_item._update_rect_position()
- paint(sprite_item)
- def paint_scene(sprite: Type['scene.Scene']):
- """
- Paints a scene on the main window.
- Parameters:
- sprite: the element to be painted
- """
- # TODO: check sprite type
- if hasattr(sprite, 'background'):
- _globals._window.fill(sprite.background)
- paint(sprite)
- def paint_text(lines: Type['texts.Text'] | list):
- """
- Paints a text on the main window.
- Parameters:
- text: the element to be painted
- """
- if isinstance(lines, jogai.texts.Text):
- lines = [lines]
- for line in lines:
- if line.visible:
- paint(line)
- def set_caption(caption: str):
- """
- Sets the caption for the videogame.
- Parameters:
- caption: caption to be setted
- Examples:
- >>> set_caption('My videogame')
- """
- if not isinstance(caption, str):
- raise TypeError(_('the caption for the videogame must be a string'))
- _globals.vg_title = caption
- pygame.display.set_caption(caption)
- def set_characters(data_characters: dict):
- (
- _globals.vg_characters,
- _globals.vg_groups,
- ) = character._load_characters_and_groups(data_characters)
- def set_gravity(gravity: int):
- """
- Sets the gravity in the videogame.
- Parameters:
- gravity: Gravity force to be setted.
- Examples:
- >>> set_gravity(5)
- """
- if not type(gravity) == int:
- raise TypeError(_('the gravity must be an integer'))
- _globals._gravity = gravity
- def set_marks(
- x_gap: int = settings.MARKS_DEFAULT_X_GAP,
- y_gap: int = settings.MARKS_DEFAULT_Y_GAP,
- x_color: tuple = settings.MARKS_DEFAULT_X_COLOR,
- y_color: tuple = settings.MARKS_DEFAULT_Y_COLOR,
- width: int = settings.MARKS_DEFAULT_WIDTH,
- debug: bool = False,
- ):
- """
- Sets distance mark lines and position information along both axis.
- Parameters:
- x_gap: The gap distance on abscissa axis.
- y_gap: The gap distance on ordinate axis.
- x_color: The line color on abscissa axis.
- y_color: The line color on ordinate axis.
- width: The line width.
- debug: If True, extra detailed information is shown.
- """
- _globals.marks = utils.Marks(x_gap, y_gap, x_color, y_color, width, debug)
- def set_scene(data_scene):
- _globals.vg_scene = scene._load_scene(data_scene)
- def set_texts(data_texts):
- _globals.vg_texts = texts._load_texts(data_texts)
- def prepare(environment_name: str):
- """
- Decorator function to prepare the game environments before entering the main loop.
- Examples:
- >>> init()
- >>> prepare()
- """
- def decorator(environment):
- _globals.environments.update({environment_name: environment})
- return decorator
- def load_environment(
- new_environment: str,
- items_to_transfer: list | dict = [],
- to_export: bool = False,
- ):
- if isinstance(items_to_transfer, dict):
- items_to_transfer = [
- items_to_transfer,
- ]
- if not to_export:
- if not new_environment in _globals.environments.keys():
- raise KeyError(_('the environment name does not exist'))
- if not _globals._window:
- init()
- _globals.vg_chronometers = []
- _globals.vg_levelbars = []
- _globals.vg_scoreboards = []
- vg_data = utils._load_yaml_from_filename(f'{new_environment}.yaml')
- _load_data(vg_data)
- character_names = [c.name for c in _globals.vg_characters]
- for item in items_to_transfer:
- match item:
- case character.Character():
- if item.name in character_names:
- character_names.pop(character_names.index(item.name))
- add_character(item)
- case groups.Group():
- add_group(item)
- case texts.Chronometer():
- add_chronometer(item)
- case texts.Scoreboard():
- add_scoreboard(item)
- case texts.Text():
- add_text(item)
- case widgets.LevelBar():
- add_levelbar(item)
- case dict():
- for name, value in item.items():
- add_attribute(name, value)
- if _globals.vg_characters:
- _calculate_initial_collisions()
- _globals.character_focus = _globals.vg_characters[0]
- builtins.__dict__.update({_('current_scene'): _globals.vg_scene})
- for vg_text in _globals.vg_texts:
- # Check if character names exist in the predefined builtins
- # if not vg_character.name in _globals.predefined_builtins:
- # if not vg_text.name in builtins.__dict__.keys():
- builtins.__dict__[vg_text.name] = vg_text
- for vg_character in _globals.vg_characters:
- # Check if character names exist in the predefined builtins
- # if not vg_character.name in _globals.predefined_builtins:
- # if not vg_character.name in builtins.__dict__.keys():
- builtins.__dict__[vg_character.name] = vg_character
- for vg_group in _globals.vg_groups:
- # Check if group names exist in the predefined builtins
- # if not vg_group.name in _globals.predefined_builtins:
- # if not vg_group.name in builtins.__dict__.keys():
- builtins.__dict__[vg_group.name] = vg_group
- for name, value in _globals.vg_attributes.items():
- builtins.__dict__[name] = value
- _globals.current_environment = new_environment
- def stop():
- """
- Finishes the infinite videogame loop.
- """
- global running
- running = False
- def update(to_quit: bool = False):
- """
- Updates the display surface and listen events.
- """
- if _globals.vg_scene:
- if _globals.vg_characters:
- _globals.vg_scene._set_abscissa_position(
- _globals.character_focus.x
- )
- paint_scene(_globals.vg_scene)
- if _globals.vg_characters:
- for pos, vg_character in enumerate(_globals.vg_characters):
- tracking = (
- True if vg_character == _globals.character_focus else False
- )
- vg_character._update_rect_position(tracking=tracking)
- paint_character(vg_character)
- vg_character.update()
- if _globals.vg_groups:
- for vg_group in _globals.vg_groups:
- paint_group(vg_group)
- vg_group.update()
- if _globals.vg_levelbars:
- for levelbar_item in _globals.vg_levelbars:
- levelbar_item.draw()
- if _globals.vg_scoreboards:
- for scoreboard_item in _globals.vg_scoreboards:
- scoreboard_item.draw()
- if _globals.vg_chronometers:
- for chronometers_item in _globals.vg_chronometers:
- chronometers_item.draw()
- chronometers_item.update()
- if _globals.vg_texts:
- for texts_item in _globals.vg_texts:
- if texts_item.visible:
- texts_item._update_rect_position()
- texts_item.draw()
- if _globals.visible_inventory:
- _globals.visible_inventory.draw()
- if _globals.marks:
- _globals.marks.draw()
- pygame.display.flip()
- _globals._clock.tick(settings.FPS)
- listen_events()
- if not to_quit:
- _globals.environments[_globals.current_environment]()
- def quit(wait: int = settings.DEFAULT_TIME_TO_QUIT):
- """
- Exits the game.
- Parameters:
- wait: Seconds to wait before exiting
- Examples:
- >>> quit(1)
- """
- update(to_quit=True)
- if type(wait) == int and wait > 0:
- time.sleep(wait)
- pygame.quit()
- stop()
|