def place_snake_at_starting_location(self): display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') starting_height = int( round((self.__player_index + 1) * (display_height / (self.__settings['num_players'] + 1)), 1)) if self.__settings['num_players'] == 1: starting_width = int(round(display_width / 2, 1)) else: if self.__player_index % 2 == 0: starting_width = int(round(display_width / 3, 1)) else: starting_width = int(round((2 / 3) * display_width, 1)) if self.__player_index % 2 == 0: self.__direction = self.RIGHT else: self.__direction = self.LEFT for x in range(self.__SNAKE_STARTING_LENGTH): if self.__player_index % 2 == 0: coordinate = (starting_height, starting_width - x) else: coordinate = (starting_height, starting_width + x) self.__snake_linked_list.append(coordinate) self.__snake_set.add(coordinate)
def fade_to_frame(self, frame): if (self.__current_frame is None): self.__current_frame = frame return self.play_frame(frame) display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') frame_steps = np.zeros([display_height, display_width, 3], np.int8) for x in range(display_width): for y in range(display_height): for rgb in range(0, 3): frame_steps[y, x, rgb] = ( (frame[y, x, rgb].astype(np.int16) - self.__current_frame[y, x, rgb].astype(np.int16)) / self.__FADE_STEPS).astype(np.int8) for current_step in range(1, self.__FADE_STEPS): new_frame = np.zeros([display_height, display_width, 3], np.uint8) for x in range(display_width): for y in range(display_height): for rgb in range(0, 3): new_frame[y, x, rgb] = self.__current_frame[y, x, rgb] + ( frame_steps[y, x, rgb] * current_step) # no need to sleep since the above calculation takes some small amount of time self.__set_frame_pixels(new_frame) self.__current_frame = frame self.__set_frame_pixels(frame)
def tick(self): was_apple_eaten = False if self.__is_eliminated: return was_apple_eaten old_head_y, old_head_x = self.__snake_linked_list[0] display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') if self.__direction == self.UP: new_head = ((old_head_y - 1) % display_height, old_head_x) elif self.__direction == self.DOWN: new_head = ((old_head_y + 1) % display_height, old_head_x) elif self.__direction == self.LEFT: new_head = (old_head_y, (old_head_x - 1) % display_width) elif self.__direction == self.RIGHT: new_head = (old_head_y, (old_head_x + 1) % display_width) self.__snake_linked_list.insert(0, new_head) # Must call this before placing the apple to ensure the apple is not placed on the new head self.__snake_set.add(new_head) if new_head == self.__snake_game.get_apple(): was_apple_eaten = True else: old_tail = self.__snake_linked_list[-1] del self.__snake_linked_list[-1] if old_tail != new_head: # Prevent edge case when the head is "following" the tail. # If the old_tail is the same as the new_head, we don't want to remove the old_tail from the set # because the call to `self.__snake_set.add(new_head)` would have been a no-op above. self.__snake_set.remove(old_tail) return was_apple_eaten
def __init__(self, clear_screen=True): options = RGBMatrixOptions() options.rows = Config.get_or_throw('leds.display_height') options.cols = Config.get_or_throw('leds.display_width') options.chain_length = 1 options.parallel = 1 options.hardware_mapping = 'adafruit-hat' options.drop_privileges = False self.__matrix = RGBMatrix(options=options) self.__pixels = self.__matrix.CreateFrameCanvas() if clear_screen: self.clear_screen()
def __init__(self): self.__secure = Config.get('server.use_ssl', False) if not self.__secure: self.__server = PifiThreadingHTTPServer(('0.0.0.0', 80), PifiServerRequestHandler) else: self.__server = PifiThreadingHTTPServer(('0.0.0.0', 443), PifiServerRequestHandler) self.__server.socket = ssl.wrap_socket( self.__server.socket, keyfile=Config.get_or_throw('server.keyfile'), certfile=Config.get_or_throw('server.certfile'), server_side=True)
def __place_apple(self): display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') while True: x = random.randint(0, display_width - 1) y = random.randint(0, display_height - 1) is_coordinate_occupied_by_a_snake = False for i in range(self.__settings['num_players']): is_coordinate_occupied_by_a_snake = self.__players[ i].is_coordinate_occupied(y, x) if is_coordinate_occupied_by_a_snake: break if not is_coordinate_occupied_by_a_snake: break self.__apple = (y, x)
def _board_to_frame(self): frame = np.zeros([ Config.get_or_throw('leds.display_height'), Config.get_or_throw('leds.display_width'), 3 ], np.uint8) rgb = self.__game_color_helper.get_rgb(self.__game_color_mode, self.__COLOR_CHANGE_FREQ, self._num_ticks) frame[(self._board[1:-1, 1:-1] == 1)] = rgb if self.__variant == self.__VARIANT_IMMIGRATION: rgb2 = self.__game_color_helper.get_rgb2(self.__game_color_mode, self.__COLOR_CHANGE_FREQ, self._num_ticks) frame[(self._board[1:-1, 1:-1] == 2)] = rgb2 return frame
def __get_ffmpeg_pixel_conversion_cmd(self): pix_fmt = 'gray' if VideoColorMode.is_color_mode_rgb( Config.get('video.color_mode', VideoColorMode.COLOR_MODE_COLOR)): pix_fmt = 'rgb24' return (self.get_standard_ffmpeg_cmd() + ' ' '-i pipe:0 ' + # read input video from stdin '-filter:v ' + shlex.quote( # resize video 'scale=' + str(Config.get_or_throw('leds.display_width')) + 'x' + str(Config.get_or_throw('leds.display_height'))) + " " '-c:a copy ' + # don't process the audio at all '-f rawvideo -pix_fmt ' + shlex.quote(pix_fmt) + " " # output in numpy compatible byte format 'pipe:1' # output to stdout )
def __init__(self, clear_screen=True): self.__pixels = apa102.APA102( num_led=(Config.get_or_throw('leds.display_width') * Config.get_or_throw('leds.display_height')), mosi=self.__MOSI_PIN, sclk=self.__SCLK_PIN, order=self.__LED_ORDER) brightness = Config.get('leds.brightness', 3) self.__pixels.set_global_brightness(brightness) if clear_screen: self.clear_screen() # Look up the order in which to write each color value to the LED strip. # It's 1-indexed, so subtract by 1. self.__color_order = [x - 1 for x in apa102.RGB_MAP[self.__LED_ORDER]] # Calculate the LED start "frame": 3 1 bits followed by 5 brightness bits. See # set_pixel in the apa102 implementation for this calculation. self.__ledstart = (brightness & 0b00011111) | self.__pixels.LED_START
def _seed_hook(self): # Create the board with an extra edge cell on all sides to simplify the # neighborhood calculation and avoid edge checks. shape = [ Config.get_or_throw('leds.display_height') + 2, Config.get_or_throw('leds.display_width') + 2 ] self._board = np.zeros(shape, np.uint8) probability = Config.get('game_of_life.seed_liveness_probability', 1 / 3) if self.__variant == self.__VARIANT_IMMIGRATION: seed = np.random.random_sample([x - 2 for x in shape ]) < (probability / 2) seed2 = np.random.random_sample([x - 2 for x in shape ]) < (probability / 2) self._board[1:-1, 1:-1][seed] = 1 self._board[1:-1, 1:-1][seed2] = 2 else: # __VARIANT_NORMAL seed = np.random.random_sample([x - 2 for x in shape]) < probability self._board[1:-1, 1:-1][seed] = 1
def __show_board(self): frame = np.zeros([ Config.get_or_throw('leds.display_height'), Config.get_or_throw('leds.display_width'), 3 ], np.uint8) for i in range(self.__settings['num_players']): if (not self.__players[i].should_show_snake()): # Blink snakes for the first few ticks after they are eliminated. continue for (y, x) in self.__players[i].get_snake_linked_list(): frame[y, x] = self.__players[i].get_snake_rgb() if self.__apple is not None: apple_rgb = self.__game_color_helper.get_rgb( GameColorHelper.GAME_COLOR_MODE_RAINBOW, self.__APPLE_COLOR_CHANGE_FREQ, self.__num_ticks) frame[self.__apple[0], self.__apple[1]] = apple_rgb self.__led_frame_player.play_frame(frame)
def display_score(self, rgb=[255, 0, 0]): score_string = str(self.__score) digit_component_length = self.__get_digit_component_length() # omitted left corner + component length + omitted right corner + padding digit_width = 1 + digit_component_length + 1 + 1 # omitted top pixel + component length + omitted middle pixel + component length + omitted bottom pixel digit_height = 1 + digit_component_length + 1 + digit_component_length + 1 num_digits = len(score_string) score_width = digit_width * num_digits display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') x = round((display_width - score_width) / 2) y = round((display_height - digit_height) / 2) frame = np.zeros([display_height, display_width, 3], np.uint8) for i in range(0, num_digits): self.__write_digit(x, y, int(score_string[i]), digit_component_length, frame, rgb) x = x + digit_width self.__led_frame_player.play_frame(frame)
def __init__(self, clear_screen=True, video_color_mode=VideoColorMode.COLOR_MODE_COLOR): self.__current_frame = None self.__gamma_controller = Gamma(video_color_mode=video_color_mode) # static gamma curve self.__scale_red_gamma_curve = None self.__scale_green_gamma_curve = None self.__scale_blue_gamma_curve = None # dynamic gamma curves self.__scale_red_gamma_curves = None self.__scale_green_gamma_curves = None self.__scale_blue_gamma_curves = None # Memoizing the specific gamma curve index for static gamma videos enables us to shave # 1 or 2 milliseconds off the loop per frame. See: self.__set_frame_pixels if VideoColorMode.is_color_mode_rgb(video_color_mode): # static gamma self.__scale_red_gamma_curve = self.__gamma_controller.scale_red_curves[ Gamma.DEFAULT_GAMMA_INDEX] self.__scale_green_gamma_curve = self.__gamma_controller.scale_green_curves[ Gamma.DEFAULT_GAMMA_INDEX] self.__scale_blue_gamma_curve = self.__gamma_controller.scale_blue_curves[ Gamma.DEFAULT_GAMMA_INDEX] else: # dynamic gamma self.__scale_red_gamma_curves = self.__gamma_controller.scale_red_curves self.__scale_green_gamma_curves = self.__gamma_controller.scale_green_curves self.__scale_blue_gamma_curves = self.__gamma_controller.scale_blue_curves led_driver = Config.get_or_throw('leds.driver') if led_driver == LedDrivers.DRIVER_APA102: from pifi.led.drivers.driverapa102 import DriverApa102 self.__driver = DriverApa102(clear_screen) elif led_driver == LedDrivers.DRIVER_RGBMATRIX: from pifi.led.drivers.driverrgbmatrix import DriverRgbMatrix self.__driver = DriverRgbMatrix(clear_screen) else: raise Exception(f'Unsupported driver: {led_driver}.') self.__video_color_mode = video_color_mode
def __transform_frame(self, frame): if not (VideoColorMode.is_color_mode_rgb(self.__video_color_mode)): gamma_index = self.__gamma_controller.getGammaIndexForMonochromeFrame( frame) shape = [ Config.get_or_throw('leds.display_height'), Config.get_or_throw('leds.display_width'), 3 ] transformed_frame = np.zeros(shape, np.uint8) # calculate gamma corrected colors if self.__video_color_mode == VideoColorMode.COLOR_MODE_COLOR: transformed_frame[:, :, 0] = np.take(self.__scale_red_gamma_curve, frame[:, :, 0]) transformed_frame[:, :, 1] = np.take(self.__scale_green_gamma_curve, frame[:, :, 1]) transformed_frame[:, :, 2] = np.take(self.__scale_blue_gamma_curve, frame[:, :, 2]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_R: transformed_frame[:, :, 0] = np.take( self.__scale_red_gamma_curves[gamma_index], frame[:, :]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_G: transformed_frame[:, :, 1] = np.take( self.__scale_green_gamma_curves[gamma_index], frame[:, :]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_B: transformed_frame[:, :, 2] = np.take( self.__scale_blue_gamma_curves[gamma_index], frame[:, :]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_BW: transformed_frame[:, :, 0] = np.take( self.__scale_red_gamma_curves[gamma_index], frame[:, :]) transformed_frame[:, :, 1] = np.take( self.__scale_green_gamma_curves[gamma_index], frame[:, :]) transformed_frame[:, :, 2] = np.take( self.__scale_blue_gamma_curves[gamma_index], frame[:, :]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_INVERT_COLOR: transformed_frame[:, :, 0] = np.take(self.__scale_red_gamma_curve, 255 - frame[:, :, 0]) transformed_frame[:, :, 1] = np.take(self.__scale_green_gamma_curve, 255 - frame[:, :, 1]) transformed_frame[:, :, 2] = np.take(self.__scale_blue_gamma_curve, 255 - frame[:, :, 2]) elif self.__video_color_mode == VideoColorMode.COLOR_MODE_INVERT_BW: transformed_frame[:, :, 0] = np.take( self.__scale_red_gamma_curves[gamma_index], 255 - frame[:, :]) transformed_frame[:, :, 1] = np.take( self.__scale_green_gamma_curves[gamma_index], 255 - frame[:, :]) transformed_frame[:, :, 2] = np.take( self.__scale_blue_gamma_curves[gamma_index], 255 - frame[:, :]) else: raise Exception( f'Unexpected color mode: {self.__video_color_mode}.') flips = () if Config.get('leds.flip_y', False): flips += (0, ) if Config.get('leds.flip_x', False): flips += (1, ) if flips: transformed_frame = np.flip(transformed_frame, flips) return transformed_frame
def _seed_hook(self): # Create the board with an extra edge cell on all sides to simplify the # neighborhood calculation and avoid edge checks. shape = [Config.get_or_throw('leds.display_height') + 2, Config.get_or_throw('leds.display_width') + 2] self._board = np.random.randint(0, self.__num_states, shape)
def __clear_board(self): frame = np.zeros([ Config.get_or_throw('leds.display_height'), Config.get_or_throw('leds.display_width'), 3 ], np.uint8) self.__led_frame_player.play_frame(frame)
def __process_and_play_video(self): ffmpeg_to_python_fifo_name = self.__make_fifo( additional_prefix='ffmpeg_to_python') fps_fifo_name = self.__make_fifo(additional_prefix='fps') process_and_play_vid_cmd = self.__get_process_and_play_vid_cmd( ffmpeg_to_python_fifo_name, fps_fifo_name) self.__logger.info('executing process and play cmd: ' + process_and_play_vid_cmd) process_and_play_vid_proc = subprocess.Popen( process_and_play_vid_cmd, shell=True, executable='/usr/bin/bash', start_new_session=True) # Store the PGID separately, because attempting to get the PGID later via `os.getpgid` can # raise `ProcessLookupError: [Errno 3] No such process` if the process is no longer running self.__process_and_play_vid_proc_pgid = os.getpgid( process_and_play_vid_proc.pid) display_width = Config.get_or_throw('leds.display_width') display_height = Config.get_or_throw('leds.display_height') bytes_per_frame = display_width * display_height np_array_shape = [display_height, display_width] if VideoColorMode.is_color_mode_rgb( Config.get('video.color_mode', VideoColorMode.COLOR_MODE_COLOR)): bytes_per_frame = bytes_per_frame * 3 np_array_shape.append(3) vid_start_time = None last_frame = None vid_processing_lag_counter = 0 is_ffmpeg_done_outputting = False frames = ReadOnceCircularBuffer(self.__FRAMES_BUFFER_LENGTH) ffmpeg_to_python_fifo = open(ffmpeg_to_python_fifo_name, 'rb') fps = self.__read_fps_from_fifo(fps_fifo_name) frame_length = 1 / fps pathlib.Path(self.__FPS_READY_FILE).touch() while True: if is_ffmpeg_done_outputting or frames.is_full(): pass else: is_ffmpeg_done_outputting, vid_start_time = self.__populate_frames( frames, ffmpeg_to_python_fifo, vid_start_time, bytes_per_frame, np_array_shape) if vid_start_time is None: # video has not started being processed yet pass else: if self.__init_time: self.__logger.info( f"Started playing video after {round(time.time() - self.__init_time, 3)} s." ) self.__init_time = None is_video_done_playing, last_frame, vid_processing_lag_counter = self.__play_video( frames, vid_start_time, frame_length, is_ffmpeg_done_outputting, last_frame, vid_processing_lag_counter) if is_video_done_playing: break self.__logger.info("Waiting for process_and_play_vid_proc to end...") while True: # Wait for proc to end if process_and_play_vid_proc.poll() is not None: if process_and_play_vid_proc.returncode != 0: raise YoutubeDlException( "The process_and_play_vid_proc process exited non-zero: " + f"{process_and_play_vid_proc.returncode}. This could mean an issue with youtube-dl; " + "it may require updating.") self.__logger.info("The process_and_play_vid_proc proc ended.") break time.sleep(0.1)
def __get_digit_component_length(self): return min(round(3 * Config.get_or_throw('leds.display_width') / 28), round(3 * Config.get_or_throw('leds.display_height') / 18))