Skip to content

Commit

Permalink
Merge pull request #148 from arnauddupuis/arnauddupuis/issue145
Browse files Browse the repository at this point in the history
Arnauddupuis/issue145

Create a Color class.
Add all tests.
Update other objects to use the new Color object.
Add a Console class (singleton to blessed.Terminal).

Fix issue #145
  • Loading branch information
arnauddupuis committed Oct 30, 2020
2 parents a65c87a + 1453b5d commit 47a5449
Show file tree
Hide file tree
Showing 13 changed files with 771 additions and 273 deletions.
40 changes: 18 additions & 22 deletions pgl-editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ def color_picker():
def custom_color_picker():
# Custom color chooser.
global game
r = g = b = 128
step = 1
base_char = ""
print("What type of color block do you want:\n1 - Rectangle\n2 - Square")
Expand All @@ -124,37 +123,37 @@ def custom_color_picker():
base_char = " "
else:
base_char = " "
spr = gfx_core.Sprixel(base_char, game.terminal.on_color_rgb(r, g, b))
spr = gfx_core.Sprixel(base_char, gfx_core.Color(128, 128, 128))
key = None
while key != engine.key.ENTER:
game.clear_screen()
print(game.terminal.center("Build your color\n"))
spr.bg_color = game.terminal.on_color_rgb(r, g, b)
print(
f"{base.Text.red('Red')}: {r} {base.Text.green_bright('Green')}: {g} "
f"{base.Text.blue_bright('Blue')}: {b}\n\nYour color: {spr}\n"
f"{base.Text.red('Red')}: {spr.bg_color.r} "
f"{base.Text.green_bright('Green')}: {spr.bg_color.g} "
f"{base.Text.blue_bright('Blue')}: {spr.bg_color.b}\n\nYour color: {spr}\n"
)
print("4/1 - Increase/decrease the red value")
print("5/2 - Increase/decrease the green value")
print("6/3 - Increase/decrease the blue value")
print(f"+/- - Increase/decrease the increment step (current: {step})")
if key == "1" and r >= step:
r -= step
elif key == "4" and r + step <= 255:
r += step
if key == "2" and g >= step:
g -= step
elif key == "5" and g + step <= 255:
g += step
if key == "3" and b >= step:
b -= step
elif key == "6" and b + step <= 255:
b += step
key = game.get_key()
if key == "1" and spr.bg_color.r >= step:
spr.bg_color.r -= step
elif key == "4" and spr.bg_color.r + step <= 255:
spr.bg_color.r += step
if key == "2" and spr.bg_color.g >= step:
spr.bg_color.g -= step
elif key == "5" and spr.bg_color.g + step <= 255:
spr.bg_color.g += step
if key == "3" and spr.bg_color.b >= step:
spr.bg_color.b -= step
elif key == "6" and spr.bg_color.b + step <= 255:
spr.bg_color.b += step
if key == "-":
step -= 1
elif key == "+":
step += 1
key = game.get_key()
return spr


Expand Down Expand Up @@ -981,10 +980,7 @@ def first_use():
):
fct = getattr(gfx_core.Sprixel, f_name)
game.add_menu_entry(
"graphics_utils",
str(i),
f'"{fct()}"',
fct(),
"graphics_utils", str(i), f'"{fct()}"', fct(),
)
i += 1

Expand Down
66 changes: 60 additions & 6 deletions pygamelib/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
.. autosummary::
:toctree: .
Console
Math
PglException
PglInvalidLevelException
Expand All @@ -26,13 +27,40 @@
Text
"""
from pygamelib import constants
from pygamelib.functions import pgl_isinstance
import math
from colorama import Fore, Back, Style, init
from blessed import Terminal

# Initialize terminal colors for colorama.
init()


class Console:
__instance = None

@classmethod
def instance(cls):
"""Returns the instance of the Terminal object
The pygamelib extensively use the Terminal object from the blessed module.
However we find ourselves in need of a Terminal instance a lot, so to help with
memory and execution time we just encapsulate the Terminal object in a singleton
so any object can use it without instanciating it many times (and messing up
with the contexts).
:return: Instance of blessed.Terminal object
Example::
term = Console.instance()
"""
if cls.__instance is None:
cls.__instance = Terminal()
return cls.__instance


class Text:
"""
An object to manipulate and display text in multiple contexts.
Expand Down Expand Up @@ -61,13 +89,29 @@ class Text:
:type style: str
"""

def __init__(self, text="", fg_color="", bg_color="", style=""):
def __init__(self, text="", fg_color=None, bg_color=None, style=""):
self.text = text
"""The text attribute. It needs to be a str."""
self.fg_color = fg_color
"""The fg_color attribute sets the foreground color. It needs to be a str."""
self.bg_color = bg_color
"""The bg_color attribute sets the background color. It needs to be a str."""
self.fg_color = None
"""The fg_color attribute sets the foreground color. It needs to be a
:class:`~pyagemlib.gfx.core.Color`."""
if fg_color is None or pgl_isinstance(fg_color, "pygamelib.gfx.core.Color"):
self.fg_color = fg_color
else:
raise PglInvalidTypeException(
"Text(text, bg_color, fg_color, style): fg_color needs to be a "
"pygamelib.gfx.core.Color object."
)
self.bg_color = None
"""The bg_color attribute sets the background color. It needs to be a
:class:`~pyagemlib.gfx.core.Color`."""
if bg_color is None or pgl_isinstance(bg_color, "pygamelib.gfx.core.Color"):
self.bg_color = bg_color
else:
raise PglInvalidTypeException(
"Text(text, bg_color, fg_color, style): bg_color needs to be a "
"pygamelib.gfx.core.Color object."
)
self.style = style
"""The style attribute sets the style of the text. It needs to be a str."""
self.parent = None
Expand All @@ -77,7 +121,17 @@ def __init__(self, text="", fg_color="", bg_color="", style=""):
self._item = None

def __repr__(self):
return "".join([self.bg_color, self.fg_color, self.style, self.text, "\x1b[0m"])
t = Console.instance()
bgc = fgc = ""
if self.bg_color is not None and pgl_isinstance(
self.bg_color, "pygamelib.gfx.core.Color"
):
bgc = t.on_color_rgb(self.bg_color.r, self.bg_color.g, self.bg_color.b)
if self.fg_color is not None and pgl_isinstance(
self.fg_color, "pygamelib.gfx.core.Color"
):
fgc = t.color_rgb(self.fg_color.r, self.fg_color.g, self.fg_color.b)
return "".join([bgc, fgc, self.style, self.text, "\x1b[0m"])

def __str__(self): # pragma: no cover
return self.__repr__()
Expand Down
48 changes: 25 additions & 23 deletions pygamelib/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1251,8 +1251,8 @@ def __init__(
self._configuration = None
self._configuration_internals = None
self.object_library = []
self.terminal = Terminal()
self.screen = Screen(self.terminal)
self.terminal = base.Console.instance()
self.screen = Screen()
self.mode = mode
self.user_update = user_update
self.input_lag = input_lag
Expand Down Expand Up @@ -2986,35 +2986,37 @@ class Screen(object):
At the moment it relies heavily on the blessed module, but it wraps a lot of its
methods and provide easy calls to actions.
:param terminal: A Terminal reference.
:type terminal: :class:`~blessed.Terminal`
.. WARNING:: Starting with version 1.3.0 the terminal parameter has been removed.
The Screen object now takes advantage of base.Console.instance() to get a
reference to a blessed.Terminal object.
Example::
screen = Screen(terminal=Terminal())
screen = Screen()
screen.display_at('This is centered', int(screen.height/2), int(screen.width/2))
"""

def __init__(self, terminal=None):
def __init__(self):
super().__init__()
# get clear sequence for the terminal
if terminal is None:
raise base.PglException(
"terminal_is_missing",
"Screen must be constructed with a terminal object.",
)
elif "terminal.Terminal" in str(type(terminal)):
self.terminal = terminal
else:
raise base.PglException(
"terminal_not_blessed",
"Screen: terminal must be from the blessed module\n"
"Please install blessed if it is not already installed:\n"
" pip3 install blessed --user"
"And instantiate Screen with terminal=blessed.Terminal()"
"or let the Game object do it and use mygame.screen to access the "
"screen (assuming that mygame is your Game() instance).",
)
self.terminal = base.Console.instance()
# if terminal is None:
# raise base.PglException(
# "terminal_is_missing",
# "Screen must be constructed with a terminal object.",
# )
# elif "terminal.Terminal" in str(type(terminal)):
# self.terminal = terminal
# else:
# raise base.PglException(
# "terminal_not_blessed",
# "Screen: terminal must be from the blessed module\n"
# "Please install blessed if it is not already installed:\n"
# " pip3 install blessed --user"
# "And instantiate Screen with terminal=blessed.Terminal()"
# "or let the Game object do it and use mygame.screen to access the "
# "screen (assuming that mygame is your Game() instance).",
# )

def clear(self):
"""
Expand Down
35 changes: 35 additions & 0 deletions pygamelib/functions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
This file holds utility functions that belongs to nowhere else.
"""

import inspect


def pgl_isinstance(obj, obj_type):
"""Check if an object is instance of some type.
This function is similar to Python isinstance() function except that it only look at
the MRO to find a matching type name. Therefor it does not require importing a whole
module just to check the type of a variable.
.. Important:: You need to give the full type name string not just a part of it. For
example, pgl_isinstance(board_items.Player(), 'Movable') will return False.
pgl_isinstance(board_items.Player(), 'pygamelib.board_items.Movable') will
return True.
:param obj: The object to check.
:type obj: object
:param obj_type: The type to check **as a string**
:type obj_type: str
:rtype: bool
Example::
if pgl_isinstance(item, 'pygamelib.gfx.core.Color'):
print('This is a color!')
"""
# Adapted from:
# https://stackoverflow.com/questions/16964467/isinstance-without-importing-candidates
return obj_type in [
x.__module__ + "." + x.__name__ for x in inspect.getmro(type(obj))
]

0 comments on commit 47a5449

Please sign in to comment.