| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- from os.path import join
- import pygame
- from jogai import _globals, settings
- from jogai.translations import gettext as _
- class Text:
- def __init__(
- self,
- message: str = '',
- size: int = 20,
- color: tuple = (0, 0, 0),
- position: tuple = (0, 0),
- font: str = '',
- bold: bool = False,
- italic: bool = False,
- shadow: tuple = (),
- ):
- # TODO: Implement shadow
- if not isinstance(size, int):
- raise TypeError(_('The font size must be an integer'))
- if size < 1:
- raise ValueError(_('The font size must be a positive integer'))
- if not isinstance(font, str):
- raise TypeError(_('The font must be a string'))
- if not isinstance(bold, bool):
- raise TypeError(_('The bold argument must be a boolean'))
- if not isinstance(italic, bool):
- raise TypeError(_('The italic argument must be a boolean'))
- if not pygame.font.get_init():
- pygame.font.init()
- self._message = message
- self.color = color
- try:
- if not font:
- raise FileNotFoundError
- self.font = pygame.font.Font(join(settings.FONTS_DIR, font), size)
- except FileNotFoundError:
- self.font = pygame.font.Font(None, size)
- self.font.set_bold(bold)
- self.font.set_italic(italic)
- self.render(position, message)
- @property
- def x(self):
- return self.rect.x
- @property
- def y(self):
- return self.rect.y
- def render(self, position: tuple = (), message: str = ''):
- if not isinstance(position, tuple) and not isinstance(position, list):
- raise TypeError(_('The position must be a tuple or list (x, y).'))
- if position and len(position) != 2:
- raise ValueError(_('The position has two coordinates (x, y).'))
- if not isinstance(message, str):
- raise TypeError(_('The message must be a string.'))
- if not position:
- position = (self.rect.x, self.rect.y)
- if message:
- self._message = message
- self.image = self.font.render(self._message, True, self.color)
- self.rect = self.image.get_rect()
- self.rect.x, self.rect.y = position
- def set_message(self, message: str, key: str = ''):
- """
- Set a new message for the text.
- Parameters:
- message: The new message
- Examples:
- >>> t = Text()
- >>> t.set_message('Test')
- """
- if not key or _get_keys()[utils.eval_key(key)]:
- self.render(message=message)
- def set_position(self, position: tuple | list, key: str = ''):
- """
- Set the text position as tuple.
- Parameters:
- position: A tuple with abscissa and ordinate coordinates
- Examples:
- >>> t = Text('Test')
- >>> t.set_position((200, 200))
- """
- if not key or _get_keys()[utils.eval_key(key)]:
- self.render(position=position)
- class Chronometer(Text):
- def __init__(
- self, initial: int = 0, in_milliseconds: bool = False, **kwargs
- ):
- if not isinstance(initial, int):
- raise TypeError(_('The initial value must be an integer.'))
- if initial < 0:
- raise ValueError(_('The countdown cannot be negative.'))
- if not isinstance(in_milliseconds, bool):
- raise TypeError(_('in_milliseconds argument must be a boolean.'))
- super().__init__(**kwargs)
- self._initial = initial if in_milliseconds else initial * 1000
- self._time = 0
- self._timestamp = 0
- self._onpause = True
- self._message = self.format_clock()
- if 'position' in kwargs:
- self.render(position=kwargs['position'])
- else:
- self.render()
- def __int__(self):
- return self.get_milliseconds()
- def __repr__(self):
- return f'Chronometer <{self.format_clock()}>'
- def __str__(self):
- return self._message
- @property
- def time(self):
- return self.get_milliseconds()
- @property
- def onpause(self) -> bool:
- return self._onpause
- def format_clock(
- self,
- include_millis: bool = False,
- delimiter: str = ':',
- delimiter_millis: str = '.',
- ) -> str:
- """
- Create a string with the chronometer time information, formatted as a digital clock.
- Parameters:
- include_millis: True if milliseconds must be present. False, otherwise.
- delimiter: The string delimiter between hours, minutes and seconds.
- delimiter_millis: The string delimiter between seconds and milliseconds.
- Returns:
- A string with the formatted time information.
- """
- if not isinstance(include_millis, bool):
- raise TypeError(_('include_millis must be a boolean.'))
- if not isinstance(delimiter, str):
- raise TypeError(_('The delimiter must be a string.'))
- if not isinstance(delimiter_millis, str):
- raise TypeError(
- _('The delimiter for milliseconds must be a string.')
- )
- formatted_time = tuple(
- map(lambda x: str(x).zfill(2), self.get_clock())
- )
- formatted_clock = ':'.join(formatted_time[:-1])
- if include_millis:
- formatted_clock += f'.{formatted_time[-1]}'
- return formatted_clock
- def get_clock(self) -> tuple:
- """
- Get the current chronometer time information.
- Returns:
- A tuple with the time information (hours, minutes, seconds, milliseconds)
- """
- millis = self.get_milliseconds()
- if millis > 0:
- quotient = millis // 1000
- millis = millis % 1000
- seconds = quotient % 60
- quotient = quotient // 60
- minutes = quotient % 60
- hours = quotient // 60
- return hours, minutes, seconds, millis
- return 0, 0, 0, 0
- def get_milliseconds(self) -> int:
- """
- Get the current chronometer time information in milliseconds.
- Returns:
- An integer with the current time in milliseconds.
- """
- if self._onpause:
- current = (
- self._initial - self._time if self._initial else self._time
- )
- else:
- if self._initial:
- current = self._initial - (
- self._time + pygame.time.get_ticks() - self._timestamp
- )
- else:
- current = (
- self._time + pygame.time.get_ticks() - self._timestamp
- )
- current = max(0, current)
- return current
- def get_seconds(self) -> int:
- """
- Get the current chronometer time information in seconds.
- Returns:
- An integer with the current time in seconds.
- """
- return self.get_milliseconds() // 1000
- def start(self):
- """
- Start the chronometer count.
- """
- self._time = 0
- self._timestamp = pygame.time.get_ticks()
- self._onpause = False
- def pause(self):
- """
- Pause the chronometer count.
- """
- if not self._onpause:
- self._onpause = True
- self._time = self._time + pygame.time.get_ticks() - self._timestamp
- def reset(self):
- """
- Reset the chronometer count.
- """
- self._time = 0
- def resume(self):
- """
- Resume the chronometer count.
- """
- if self._onpause:
- self._onpause = False
- self._timestamp = pygame.time.get_ticks()
- def update(self):
- """
- Update the text message with the current time information.
- """
- self._message = self.format_clock()
- self.render()
- class LevelBar(pygame.Surface):
- def __init__(
- self,
- parts: int,
- initial_level: int | None = None,
- position: tuple | list = settings.LEVELBARS_DEFAULT_POSITION,
- border_color: tuple | list = settings.LEVELBARS_DEFAULT_BORDER_COLOR,
- fill_color: tuple | list = settings.LEVELBARS_DEFAULT_FILL_COLOR,
- ):
- if not isinstance(parts, int):
- raise TypeError(_('The amount of parts must be an integer.'))
- if not (0 < parts <= settings.LEVELBARS_MAX_PARTS):
- raise TypeError(
- _(
- f'The amount of parts must be between 1 and {settings.LEVELBARS_MAX_PARTS}.'
- )
- )
- super().__init__(
- (
- settings.LEVELBARS_DEFAULT_WIDTH,
- settings.LEVELBARS_DEFAULT_HEIGHT,
- )
- )
- self._parts = parts
- if initial_level is None:
- initial_level = parts
- self.set(initial_level)
- self.border_color = border_color
- self.fill_color = fill_color
- self.x, self.y = position
- self._width = settings.LEVELBARS_DEFAULT_WIDTH
- self._height = settings.LEVELBARS_DEFAULT_HEIGHT
- self._thick = settings.LEVELBARS_DEFAULT_THICK
- def __int__(self):
- return int(self._level)
- def __repr(self):
- return f'LevelBar <{self._level}/{self._parts}>'
- def __str__(self):
- return f'{self._level}/{self._parts}'
- def decrease(self, decrease: int):
- """
- Decrease the level for this bar.
- Parameters:
- decrease: amount to decrease
- Examples:
- >>> lifes = LevelBar(5, 3)
- >>> lifes.decrease(1)
- """
- if not isinstance(decrease, int):
- raise TypeError(_('The decrease level must be an integer.'))
- if self._level - decrease < 0:
- raise ValueError(_('The decrease cannot be less than zero.'))
- self.set(self._level - decrease)
- def increase(self, increase: int):
- """
- Increase the level for this bar.
- Parameters:
- increase: amount to increase
- Examples:
- >>> lifes = LevelBar(5, 2)
- >>> lifes.increase(1)
- """
- if not isinstance(increase, int):
- raise TypeError(_('The increase level must be an integer.'))
- if self._level + increase > self._parts:
- raise ValueError(
- _('The increase cannot exceed the maximum level.')
- )
- self.set(self._level + increase)
- def set(self, level):
- """
- Set the new level for this bar.
- Parameters:
- level: the new level
- Examples:
- >>> lifes = LevelBar(5)
- >>> lifes.set(3)
- """
- if not isinstance(level, int):
- raise TypeError(_('The level must be an integer.'))
- if not (0 <= level <= self._parts):
- raise ValueError(
- _(
- 'The level range must be between zero and the number of parts.'
- )
- )
- self._level = level
- def draw(self, position: tuple | list | None = None):
- if position is None:
- position = (self.x, self.y)
- else:
- self.x, self.y = position
- division = int(self._width / self._parts)
- # left border
- pygame.draw.line(
- _globals._window,
- self.border_color,
- position,
- (position[0], position[1] + self._height),
- self._thick,
- )
- # right border
- pygame.draw.line(
- _globals._window,
- self.border_color,
- (position[0] + division * self._parts, position[1]),
- (position[0] + division * self._parts, position[1] + self._height),
- self._thick,
- )
- for part in range(self._parts):
- # top border
- pygame.draw.line(
- _globals._window,
- self.border_color,
- (position[0] + division * part, position[1]),
- (position[0] + division * (part + 1), position[1]),
- self._thick,
- )
- # inside
- if part < self._level:
- pygame.draw.line(
- _globals._window,
- self.fill_color,
- (
- position[0] + division * part + self._thick / 2,
- position[1] + int(self._height / 2),
- ),
- (
- position[0] + division * (part + 1) - self._thick / 2,
- position[1] + int(self._height / 2),
- ),
- self._height,
- )
- # bottom border
- pygame.draw.line(
- _globals._window,
- self.border_color,
- (position[0] + division * part, position[1] + self._height),
- (
- position[0] + division * (part + 1),
- position[1] + self._height,
- ),
- self._thick,
- )
- self.rect = self.get_rect()
- class Scoreboard(Text):
- def __init__(self, prefix: str, score: int = 0, **kwargs):
- if not isinstance(prefix, str):
- raise TypeError(_('The prefix text must be a string.'))
- super().__init__(**kwargs)
- self._prefix = prefix
- self.set_score(score)
- if 'position' in kwargs:
- self.render(position=kwargs['position'])
- else:
- self.render()
- def __int__(self):
- return int(self._score)
- def __repr__(self):
- return f'Scoreboard <{self._message}>'
- def __str__(self):
- return self._message
- @property
- def score(self):
- return self._score
- @property
- def message(self):
- return self._message
- def set_score(self, score: int):
- """
- Set the character score.
- Parameters:
- score: the new character score
- Examples:
- >>> scoreboard = Scoreboard('My character: ')
- >>> scoreboard.set_score(100)
- """
- if not isinstance(score, int):
- raise TypeError(_('Score must be an integer.'))
- self._score = score
- self._message = f'{self._prefix}{self._score}'
- self.render()
- def add_score(self, score: int):
- """
- Increment the character score.
- Parameters:
- score: the character score to be added
- Examples:
- >>> scoreboard = Scoreboard('My character: ')
- >>> scoreboard.add_score(5)
- """
- if not isinstance(score, int):
- raise TypeError(_('Score must be an integer.'))
- self._score += score
|