def layout_subviews(self): for i in range(len(self.labels)): is_in_view = self.get_is_in_view(i) if is_in_view: y = 1 + self.frame.y + i - self.min_row self.labels[i].frame = Rect( Point(self.frame.x + 1, y), Size(self.frame.width - self.value_column_width - 2, 1)) self.values[i].frame = Rect( Point( self.frame.x + 1 + self.frame.width - self.value_column_width - 2, y), Size(self.value_column_width, 1)) self.labels[i].is_hidden = not is_in_view self.values[i].is_hidden = not is_in_view
def __init__(self, *args, **kwargs): views = [ LabelView( LOGO[1:].rstrip(), layout_options=LayoutOptions.row_top(0.5)), LabelView( "Try resizing the window!", layout_options=LayoutOptions.centered('intrinsic', 'intrinsic')), ButtonView( text="Play", callback=self.play, color_bg='#000000', color_fg='#00ff00', layout_options=LayoutOptions.row_bottom(4).with_updates( left=0.2, width=0.2, right=None)), ButtonView( text="Settings", callback=self.show_settings, layout_options=LayoutOptions.row_bottom(4).with_updates( left=0.4, width=0.2, right=None)), ButtonView( text="[color=red]Quit", callback=lambda: self.director.pop_scene(), # [color=red] messes up auto size calculations size=Size(4, 1), layout_options=LayoutOptions.row_bottom(4).with_updates( left=0.6, width=0.2, right=None)), ] super().__init__(views, *args, **kwargs)
def intrinsic_size(self): height = 0 width = 0 for line in self.text.splitlines(): height += 1 width = max(width, len(line)) return Size(width, height)
def draw(self, ctx): ctx.bkcolor(self.color_bg) ctx.clear_area(self.bounds) frac_width = floor(self.bounds.width * self.fraction) ctx.bkcolor(self.color_fg) ctx.clear_area( self.bounds.with_size(Size(frac_width, self.bounds.height)))
def terminal_update(self, is_active=False): terminal.bkcolor('#000000') self.view.frame = self.view.frame.with_size( Size(blt_state.width, blt_state.height)) self.view.perform_layout() self.view.perform_draw(self.ctx) terminal.bkcolor('#000000')
def __init__(self, game, **kwargs): super().__init__(fill=True, **kwargs) self.game = game player = self.game.player self.camera = Camera(location=player.location.copy(), screen_size=Size(120, 30)) self.camera.character_focus = player self.game.camera = self.camera
def layout_subviews(self): self.rect_view.apply_springs_and_struts_layout_in_superview() if self.scroll_fraction >= 0: self.scroll_indicator_view.frame = Rect( Point(self.bounds.width - 1, 1 + floor(self.inner_height * self.scroll_fraction)), Size(1, 1)) for i in range(len(self.labels)): is_in_view = self.get_is_in_view(i) if is_in_view: y = 1 + self.bounds.y + i - self.min_row self.labels[i].frame = Rect( Point(self.bounds.x + 1, y), Size(self.bounds.width - self.value_column_width - 2, 1)) self.values[i].frame = Rect( Point(self.bounds.x + 1 + self.bounds.width - self.value_column_width - 2, y), Size(self.value_column_width, 1)) self.labels[i].is_hidden = not is_in_view self.values[i].is_hidden = not is_in_view
def __init__(self, rect, room_type): self.room_id = uuid4().hex self.room_type = room_type # Corridors are always None, rooms are 1-4. Determined by quadrant, i.e. # 2nd-topmost ancestor. self.difficulty = None # If only I had had time to make more room shapes... if room_type.shape == EnumRoomShape.BOX_RANDOM: self.rect = rect.get_random_rect(Size(5, 5)) if room_type.shape == EnumRoomShape.BOX_FULL: self.rect = rect
def __init__(self): self.window = Window() self.message_log = [] self.gutter = [] self.gutter_size = Size( self.window.width - MAP_SIZE.width + MAP_ORIGIN.x, self.window.height - MAP_ORIGIN.y) self.gutter_rect = Rect( Point(self.window.width - self.gutter_size.width, self.window.height - self.gutter_size.height), self.gutter_size) self.log_height = LOG_HEIGHT super().__init__()
def __init__(self, path): self._image = load_xp_file(path) self.size = Size(self._image['width'], self._image['height']) self.num_layers = self._image['layer_count'] self._draw_calls_by_layer = [] for layer_i, layer in enumerate(self._image['layer_data']): calls = [] for x, col in enumerate(layer['cells']): for y, cell in enumerate(col): fg = _read_color(cell, 'fore_r', 'fore_g', 'fore_b') bg = self._read_bg_color(layer_i, x, y) calls.append((Point(x, y), cell['keycode'], fg, bg)) self._draw_calls_by_layer.append(calls)
def generate(self): rooms = [] for leaf in self.bsp_tree.root.leaves: room = leaf.rect.get_random_rect( min_size=Size(self.room_min, self.room_min)) leaf.data['room'] = room leaf.data['connected_to_sibling'] = False rooms.append(room) for room in rooms: self.place_room(room) for siblings in self.bsp_tree.root.sibling_pairs: self.connect_nodes(*siblings) for leaf in self.bsp_tree.root.leaves: self.connect_nodes(leaf, next(self.bsp_tree.root.leaves), connect_parents=True)
def main(): terminal.open() tilemap = TileMap(Size(80, 24)) player_pos = Point(0, 0) for y, row in enumerate(dungeon): for x, char in enumerate(row): cell = tilemap[Point(x, y)] if char == '.': cell.terrain = 0 elif char == '#': cell.terrain = 1 elif char == '@': player_pos = Point(x, y) else: raise ValueError(char) terminal.refresh() terminal.clear() draw(tilemap, player_pos) try: while True: terminal.clear() draw(tilemap, player_pos) terminal.refresh() if terminal.has_input(): char = terminal.read() if char == terminal.TK_Q: break if char == terminal.TK_UP: player_pos = player_pos + Point(0, -1) if char == terminal.TK_DOWN: player_pos = player_pos + Point(0, 1) if char == terminal.TK_LEFT: player_pos = player_pos + Point(-1, 0) if char == terminal.TK_RIGHT: player_pos = player_pos + Point(1, 0) except KeyboardInterrupt: pass finally: terminal.close()
def get_rect(self) -> Rect: width = randint(self.room_min, self.room_max) height = randint(self.room_min, self.room_max) origin = self.find_empty_point() return Rect(origin, Size(width, height))
def in_range(self, other): range_rect = Rect(self.position - Point(self._range, self._range), Size(self._range * 2, self._range * 2)) return range_rect.contains(other.position)
from clubsandwich.blt.nice_terminal import terminal from clubsandwich.director import DirectorLoop from clubsandwich.geom import Size from main_menu_scene import MainMenuScene WINDOW_SIZE = Size(80, 46) class GameLoop(DirectorLoop): def terminal_init(self): super().terminal_init() terminal.set(""" window.resizeable=true; window.size={size.width}x{size.height} """.format(size=WINDOW_SIZE)) def get_initial_scene(self): return MainMenuScene() if __name__ == '__main__': GameLoop().run()
""" Demonstrates BearLibTerminalContext (more convenient rendering) and blt_state """ from clubsandwich.blt.state import blt_state from clubsandwich.blt.context import BearLibTerminalContext from clubsandwich.geom import Rect, Size, Point terminal = BearLibTerminalContext() terminal.open() terminal.bkcolor('#ff0000') # move frame of reference to middle of screen with terminal.translate( (Point(blt_state.width, blt_state.height) / 2).floored): terminal.clear_area(Rect(Point(-1, -1), Size(3, 2))) terminal.refresh() # less verbose than terminal.state(terminal.TK_ESCAPE)! while not blt_state.escape: terminal.read() terminal.close()
def intrinsic_size(self): return self.label_view.intrinsic_size + Size(4, 0) # add space for arrows
# clubsandwich is my roguelike library that wraps bearlibterminal. We need it # for some basics which I'll get to in a moment. from clubsandwich.blt.nice_terminal import terminal from clubsandwich.director import DirectorLoop from clubsandwich.geom import Size # A "scene" is an object that takes over the input and display. The first one # the player sees is the title screen. from ld38.main_menu_scene import MainMenuScene # This is a pathlib.Path object pointing to the game working directory # containing the executable (and the assets) from ld38.const import GAME_ROOT WINDOW_SIZE = Size(100, 46) # This is a subclass of the clubsandwich game loop object, which takes care of # a LOT of stuff for us. We just need to do a little configuration to set the # window size and load the font, and specify what our first scene is. # # Once we've provided those things, the DirectorLoop base class will forward # all input to the active scene, and allow the active scene to draw the screen. class GameLoop(DirectorLoop): def terminal_init(self): super().terminal_init() terminal.set(""" window.resizeable=true; window.size={size.width}x{size.height}; font: {root}/assets/NotoMono-Regular.ttf, size=10x16;
def intrinsic_size(self): width, height = terminal.measure(self.text) return Size(width, height)
import weakref from collections import namedtuple from numbers import Real from clubsandwich.geom import Point, Rect, Size from clubsandwich.blt.context import BearLibTerminalContext from .layout_options import LayoutOptions ZERO_RECT = Rect(Point(0, 0), Size(0, 0)) class View: def __init__(self, frame=None, subviews=None, scene=None, layout_options=None): if isinstance(layout_options, dict): # have pity on the user's imports opts = LayoutOptions()._asdict() opts.update(layout_options) layout_options = LayoutOptions(**opts) self._scene = scene self._superview_weakref = lambda: None self.needs_layout = True self._frame = frame or ZERO_RECT self._bounds = self.frame.with_origin(Point(0, 0)) self.subviews = [] self.add_subviews(subviews or []) self.is_first_responder = False self.is_hidden = False
def intrinsic_size(self): return Size(len(self.text) + 1, 1)
def _apply_springs_and_struts_layout_to_view(view): options = view.layout_options spec = view.layout_spec superview_bounds = view.superview.bounds fields = [ ('left', 'right', 'x', 'width'), ('top', 'bottom', 'y', 'height'), ] final_frame = Rect(Point(-1000, -1000), Size(-1000, -1000)) for field_start, field_end, field_coord, field_size in fields: debug_string = options.get_debug_string_for_keys( [field_start, field_size, field_end]) matches = (options.get_is_defined(field_start), options.get_is_defined(field_size), options.get_is_defined(field_end)) if matches == (True, True, True): raise ValueError( "Invalid spring/strut definition: {}".format(debug_string)) if matches == (False, False, False): raise ValueError( "Invalid spring/strut definition: {}".format(debug_string)) elif matches == (True, False, False): setattr(final_frame, field_coord, options.get_value(field_start, view)) # pretend that size is constant from frame setattr(final_frame, field_size, getattr(spec, field_size)) elif matches == (True, True, False): setattr(final_frame, field_coord, options.get_value(field_start, view)) setattr(final_frame, field_size, options.get_value(field_size, view)) elif matches == (False, True, False): # magical centering! size_val = options.get_value(field_size, view) setattr(final_frame, field_size, size_val) setattr(final_frame, field_coord, getattr(superview_bounds, field_size) / 2 - size_val / 2) elif matches == (False, True, True): size_val = options.get_value(field_size, view) setattr( final_frame, field_coord, getattr(superview_bounds, field_size) - options.get_value(field_end, view) - size_val) setattr(final_frame, field_size, size_val) elif matches == (False, False, True): setattr( final_frame, field_coord, getattr(superview_bounds, field_size) - options.get_value(field_end, view)) # pretend that size is constant from frame setattr(final_frame, field_size, getattr(spec, field_size)) elif matches == (True, False, True): start_val = options.get_value(field_start, view) end_val = options.get_value(field_end, view) setattr(final_frame, field_coord, start_val) setattr( final_frame, field_size, getattr(superview_bounds, field_size) - start_val - end_val) else: raise ValueError("Unhandled case: {}".format(debug_string)) assert (final_frame.x != -1000) assert (final_frame.y != -1000) assert (final_frame.width != -1000) assert (final_frame.height != -1000) view.frame = final_frame.floored
def terminal_update(self, is_active=False): self.view.frame = self.view.frame.with_size( Size(blt_state.width, blt_state.height)) self.view.perform_layout() self.view.perform_draw()
WINDOW_WIDTH = 46 WINDOW_HEIGHT = 32 ENCODING = 'utf-8' SPACING = '1x1' FONT = resource_path(os.path.join(RESOURCE_PATH, 'VeraMono.ttf')) SYMBOLA_FONT = resource_path( os.path.join(RESOURCE_PATH, 'ttf-symbola/Symbola.ttf')) SYMBOLA_CODEPAGE = resource_path( os.path.join(RESOURCE_PATH, 'symbola_codepage.txt')) FONT_SIZE = '18x18' CELL_SIZE = '20x20' FG_COLOR = Color.BASE1 BG_COLOR = Color.BASE03 # Option to draw UI rectangles RECTANGLES = False MAP_SIZE = Size(30, 30) VIEW_SIZE = Size(20, 20) MAP_ORIGIN = Point(1, 7) LOG_HEIGHT = MAP_ORIGIN.y - 1 class Window: __instance = None def __new__(cls): if cls.__instance is None: new_window = super().__new__(cls) new_window.__init() cls.__instance = new_window return cls.__instance
from clubsandwich.geom import Size from .level_generator import generate_dungeon from .level_state import LevelState LEVEL_SIZE = Size(100, 60) # Originally, Rogue Basement was going to have several levels. This was going # to be the object that tracked the current level and let you switch between # them. # # It fulfills that purpose, but there is no actual "change current level" # method! It would be as simple as setting `self.active_id = NEW_VALUE`. # The screen is redrawn completely every frame, so there is no need to do # anything else. # # This object also tracks the score. LevelState keeps a weak reference to this # object, so the active LevelState object is what actually increments the # score. class GameState: def __init__(self): self.level_states_by_id = {} self.score = 0 self.active_id = self.add_level().uuid @property def level(self): return self.level_states_by_id[self.active_id] def add_level(self):