예제 #1
0
    def __init__(self, fps=60):
        Game.instance = self
        self.stopped = False
        self.show_debug_info = False
        self.debug_info = {
            "FPS": 0,
            "FPS Target": 0,
            "Chars Replaced": 0,
        }
        self.screen = Screen()
        self.printer = None
        self.input_controller = None
        self.timer_thread = None
        self.printer = ConsolePrinter()
        self.input_controller = InputController(self)
        self.input_controller.start_watching_key_presses()
        self.printer.clear_screen()
        self._clear_set_empty_screen()

        # FPS and timming
        self.fps_target: int = fps
        self.fps_avg: float = self.fps_target
        self.fps_samples: int = self.fps_target * 2
        self.time_adjustment: float = 0
        self.last_loop_start_time: float = timestamp() - 1 / self.fps_target
예제 #2
0
    def draw_screen(self, screen: Screen):
        ConsolePrinter.replaced = 0
        _terminal_size = os.get_terminal_size()
        if self.terminal_size.columns != _terminal_size.columns or self.terminal_size.lines != _terminal_size.lines:
            self.terminal_size = _terminal_size
            self.previous_screen = self.get_empty_screen()
        # Print over the entire screen with what has been stored
        # in our screen representation.
        # Only prints over characters that have changed since the last print.
        screen_size = screen.size
        rows = min(screen_size.y, self.terminal_size.lines)
        columns = min(screen_size.x, self.terminal_size.columns)
        for row in range(0, rows):
            for column in range(0, columns):
                prev_char = self.previous_screen.get(y=row, x=column)
                new_char = screen.get(y=row, x=column)
                if new_char != prev_char:
                    ConsolePrinter.replaced += 1
                    self.print_character_at(column, row, new_char)

        # So we don't leave the cursor in an annoying place between draws
        # we will draw the final character at the bottom right corner.
        # Also, changes don't seem to reflect on the screen until "enter"
        # is pressed, this adds "end='\n'" which accomplishes that.
        # If "end=''" changes don't draw on the screen any more."
        bottom_right_char = screen.get(y=rows - 1, x=columns - 1)
        self.print_character_at(columns - 1,
                                rows - 2,
                                bottom_right_char,
                                end='\n')

        # Store the current state of the screen so we can
        # use it again next cycle
        self.previous_screen.apply(screen)
예제 #3
0
 def draw_laid_shapes(self, grid: Grid, screen: Screen):
     offset = Vector2(x=grid.position.x, y=grid.position.y)
     for i in range(0, len(self.matrix)):
         row = self.matrix[i]
         for j in range(0, len(row)):
             should_draw = row[j]
             if should_draw:
                 x = j + offset.x
                 y = i + offset.y
                 screen.set(y=y, x=x, char=self.matrix[i][j])
예제 #4
0
 def draw(self, screen: Screen):
 # Note: Shape positions are NOT relative to the grid position
     offset = Vector2(
         x=self.position.x,
         y=self.position.y
     )
     for i in range(0, len(self.matrix)):
         row = self.matrix[i]
         for j in range(0, len(row)):
             should_draw = row[j]
             if should_draw:
                 x = j + offset.x
                 y = i + offset.y
                 # TODO fix this
                 screen.set(y=y, x=x, char=self.char)
예제 #5
0
class Game(ABC):
    def __init__(self, fps=60):
        Game.instance = self
        self.stopped = False
        self.show_debug_info = False
        self.debug_info = {
            "FPS": 0,
            "FPS Target": 0,
            "Chars Replaced": 0,
        }
        self.screen = Screen()
        self.printer = None
        self.input_controller = None
        self.timer_thread = None
        self.printer = ConsolePrinter()
        self.input_controller = InputController(self)
        self.input_controller.start_watching_key_presses()
        self.printer.clear_screen()
        self._clear_set_empty_screen()

        # FPS and timming
        self.fps_target: int = fps
        self.fps_avg: float = self.fps_target
        self.fps_samples: int = self.fps_target * 2
        self.time_adjustment: float = 0
        self.last_loop_start_time: float = timestamp() - 1 / self.fps_target

    def _clear_set_empty_screen(self):
        self.screen = self.printer.get_empty_screen()

    @abstractmethod
    def update(self, deltatime: float):
        pass

    @abstractmethod
    def draw(self):
        # The Strategy:
        # Every draw cycle will print to every position in the console.
        # We need to build a 2D array of what should be printed.
        # So we create a "screen" 2D array that is filled with spaces,
        # then replace values at certain positions.
        # Then we only do a print cycle once everything is in place.

        if self.show_debug_info:

            debug_size = {
                "key": 0,
                "value": 0,
            }
            for key in self.debug_info:
                value = self.debug_info[key]
                key_width = len(str(key))
                if key_width > debug_size["key"]:
                    debug_size["key"] = key_width
                val_width = len(str(value)) if value is not None else 0
                if val_width > debug_size["value"]:
                    debug_size["value"] = val_width
            # + 2 for the colon + space we will use
            key_value_width = debug_size["key"] + debug_size["value"] + 2
            i = 0
            screen_width = self.printer.terminal_size.columns
            for key in self.debug_info:
                value = self.debug_info[key]
                _key = str(key).rjust(debug_size["key"])
                _value = str(value if value is not None else " ").ljust(debug_size["value"])
                key_value = f"{_key}: {_value}"
                self.screen.set(screen_width - key_value_width, i, key_value)
                i += 1

        self.printer.draw_screen(self.screen)
        self._clear_set_empty_screen()

    def run(self):
        """
        Called to start the game loop
        """
        # First Loop Setup
        self.last_loop_start_time = timestamp() - 1 / self.fps_target  # Set the last loop_start time, expected value

        # Loop forever
        while True:

            # Record loop time data
            loop_start_time = timestamp()
            loop_delta_time = loop_start_time - self.last_loop_start_time
            self.last_loop_start_time = loop_start_time

            if self.stopped:
                return

            self.game_loop(loop_delta_time)
            if self.show_debug_info:
                self.calculate_debug(loop_delta_time)

            # Calculate the "Cumulative Moving Average" with fixed n
            # https://en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average
            avg = self.fps_avg
            samples = self.fps_samples
            self.fps_avg = (avg * samples + (1.0 / loop_delta_time)) / (samples + 1)

            # Time after game_loop is end
            loop_end_time = timestamp()

            frame_time = 1.0 / self.fps_target  # How long a frame should take
            loop_used = loop_end_time - loop_start_time  # How much time the current loop took.

            if self.fps_target > self.fps_avg:
                self.time_adjustment = self.time_adjustment + 0.00001
            if self.fps_target + 0.1 < self.fps_avg:
                self.time_adjustment = self.time_adjustment - 0.00001
            self.time_adjustment = clamp(self.time_adjustment, -frame_time, frame_time)

            real_wait_time = frame_time - loop_used  # - self.time_adjustment
            real_wait_time = clamp(real_wait_time, 0, frame_time)

            safe_sleep(real_wait_time)

    def game_loop(self, loop_delta_time: float):
        """
        [summary]

        Args:
            loop_delta_time (float): Time elapsed since last game_loop (seconds)
        """
        if self.show_debug_info:
            self.calculate_debug(loop_delta_time)

        self.update(loop_delta_time)
        self.draw()

    def calculate_debug(self, loop_delta_time: float):
        self.debug_info["FPS"] = round(self.fps_avg, 1)
        self.debug_info["FPS Target"] = self.fps_target
        self.debug_info["Chars Replaced"] = ConsolePrinter.replaced

    def end_game(self):
        self.printer.clear_screen()
        self.stopped = True
        self.input_controller.stop_watching_key_presses()

    def set_on_keydown(self, func):
        self.input_controller.set_on_keydown(func)

    def set_on_keyup(self, func):
        self.input_controller.set_on_keyup(func)
예제 #6
0
 def draw(self, screen: Screen):
     screen.draw_matrix(self.matrix, self.position)
     for i in range(len(self.current_shape.matrix)):
         for j in range(len(self.current_shape.matrix[i])):
             if self.current_shape.matrix[i][j]:
                 screen.set(self.current_shape.position.x + j, self.current_shape.position.y + i, self.current_shape.char)
예제 #7
0
 def get_empty_screen(self):
     self.terminal_size = os.get_terminal_size()
     return Screen(rows=self.terminal_size.lines,
                   columns=self.terminal_size.columns)