diff --git a/submissions/terminal-art/README.md b/submissions/terminal-art/README.md new file mode 100644 index 00000000..3a0c5226 --- /dev/null +++ b/submissions/terminal-art/README.md @@ -0,0 +1,220 @@ +# Terminal Video Simulations + +This project contains several Python programs that create video-like simulations and interactive games in the terminal using ASCII characters and the curses library. + +## Menu + +A menu program is included to easily launch any of the simulations: + +``` +python menu.py +``` + +Use the arrow keys to navigate, Enter to select a simulation, and Q to quit. + +## Requirements + +- Python 3.6 or higher +- curses library (included in standard library for Unix/Linux/macOS; for Windows, install windows-curses) + +If you're on Windows, you'll need to install the windows-curses package: + +``` +pip install windows-curses +``` + +## Programs + +### Basic Simulations + +#### 1. Basic Video Simulation + +A simple wave-like animation using ASCII characters. + +``` +python video_terminal.py +``` + +#### 2. Matrix Effect + +A simulation of the famous "digital rain" effect from The Matrix movie. + +``` +python matrix_terminal.py +``` + +#### 3. Fire Effect + +A simulation of fire using ASCII characters and colors. + +``` +python fire_terminal.py +``` + +#### 4. Dancing Man + +A simulation of a dancing stick figure with disco lights. + +``` +python dancing_man.py +``` + +#### 5. Bouncing Ball + +A physics-based simulation of bouncing balls with trails. + +``` +python bouncing_ball.py +``` + +#### 6. Dance and Bounce + +A combined simulation featuring both the dancing man and bouncing balls. + +``` +python dance_and_bounce.py +``` + +#### 7. Starfield + +A simulation of flying through space with stars moving toward you. + +``` +python starfield.py +``` + +#### 8. Rain + +A simulation of rain falling with splashes when raindrops hit the ground. + +``` +python rain.py +``` + +#### 9. Conway's Game of Life + +An implementation of Conway's Game of Life cellular automaton with interactive controls. + +``` +python game_of_life.py +``` + +#### 10. Fireworks Display + +A colorful fireworks simulation with rising rockets and particle explosions. + +``` +python fireworks.py +``` + +#### 11. Digital Clock + +A customizable digital clock with ASCII art digits and various display options. + +``` +python digital_clock.py +``` + +### Interactive Games + +#### 12. Snake Game + +A classic snake game where you control a snake to eat food and grow longer. + +``` +python snake_game.py +``` + +#### 13. Typing Test + +A typing speed and accuracy test with WPM (words per minute) calculation. + +``` +python typing_test.py +``` + +## Controls + +### Common Controls +- Press `q` to quit any simulation +- Press `Ctrl+C` to force quit + +### Specific Controls + +#### Bouncing Ball & Dance and Bounce +- Press `Space` to add a new ball +- Press `+`/`-` to adjust dancing speed (Dance and Bounce only) + +#### Dancing Man +- Press `+`/`-` to adjust dancing speed + +#### Starfield +- Press `+`/`-` to adjust flying speed + +#### Rain +- Press `+`/`-` to adjust rain intensity + +#### Conway's Game of Life +- Press `r` to randomize the grid +- Press `c` to clear the grid +- Press `Space` to pause/resume the simulation +- Press `+`/`-` to adjust simulation speed +- Click on cells to toggle their state (if mouse support is available) + +#### Fireworks Display +- Press `Space` to manually launch a firework +- Press `+`/`-` to adjust the automatic launch rate + +#### Digital Clock +- Press `c` to change the color scheme +- Press `s` to toggle seconds display +- Press `d` to toggle date display +- Press `a` to toggle AM/PM display +- Press `h` to toggle between 12h and 24h format +- Press `r` to toggle rainbow mode +- Press `m` to cycle through animation modes +- Press `*` to toggle background stars + +#### Snake Game +- Use arrow keys to control the snake +- Press `r` to restart after game over + +#### Typing Test +- Type the displayed text +- Press `ESC` to restart the test + +## How It Works + +These simulations use the curses library to control the terminal display. Each program: + +1. Initializes the terminal screen for drawing +2. Creates a simulation loop that generates frames +3. Renders each frame to the terminal +4. Cleans up the terminal state when exiting + +The simulations use different algorithms to create their effects: + +- **video_terminal.py**: Uses wave patterns and noise to create a dynamic display +- **matrix_terminal.py**: Creates falling columns of characters that mimic the Matrix digital rain +- **fire_terminal.py**: Uses a cellular automaton approach to simulate fire rising from the bottom of the screen +- **dancing_man.py**: Uses pre-defined ASCII art frames to animate a dancing stick figure +- **bouncing_ball.py**: Uses physics calculations to simulate realistic ball bouncing with gravity +- **dance_and_bounce.py**: Combines the dancing man and bouncing ball simulations +- **starfield.py**: Creates a 3D effect of stars moving toward the viewer +- **rain.py**: Simulates raindrops falling with realistic splash effects +- **game_of_life.py**: Implements Conway's Game of Life cellular automaton with rules for cell birth, survival, and death +- **fireworks.py**: Simulates fireworks with physics for rocket launch, explosion, and particle movement with gravity +- **digital_clock.py**: Displays the current time using ASCII art digits with various display options and animations +- **snake_game.py**: Implements the classic snake game with collision detection +- **typing_test.py**: Measures typing speed and accuracy with real-time feedback + +## Customization + +You can modify these programs to create your own terminal animations by changing: + +- The characters used for display +- The colors (if supported by your terminal) +- The algorithms that generate the patterns +- The speed of the animation + +Enjoy the terminal video simulations and games! \ No newline at end of file diff --git a/submissions/terminal-art/bouncing_ball.py b/submissions/terminal-art/bouncing_ball.py new file mode 100644 index 00000000..481c1ad7 --- /dev/null +++ b/submissions/terminal-art/bouncing_ball.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import math +import random +BALL_CHARS = { + 'small': 'o', + 'medium': 'O', + 'large': '@' +} +TRAIL_CHARS = '.·' +class Ball: + """A class representing a bouncing ball.""" + def __init__(self, x, y, x_vel, y_vel, size='medium', color=1): + self.x = x + self.y = y + self.x_vel = x_vel + self.y_vel = y_vel + self.size = size + self.color = color + self.gravity = 0.2 + self.elasticity = 0.8 + self.trail = [] + self.max_trail_length = 10 + def update(self, width, height): + """Update the ball's position and velocity.""" + self.trail.append((self.x, self.y)) + if len(self.trail) > self.max_trail_length: + self.trail.pop(0) + self.x += self.x_vel + self.y += self.y_vel + self.y_vel += self.gravity + if self.x < 0: + self.x = 0 + self.x_vel = -self.x_vel * self.elasticity + elif self.x >= width: + self.x = width - 1 + self.x_vel = -self.x_vel * self.elasticity + if self.y < 0: + self.y = 0 + self.y_vel = -self.y_vel * self.elasticity + elif self.y >= height - 1: + self.y = height - 2 + self.y_vel = -self.y_vel * self.elasticity + if abs(self.y_vel) > 0.5: + self.x_vel += (random.random() - 0.5) * 0.5 + self.x_vel *= 0.99 + if abs(self.y_vel) < 0.3 and self.y >= height - 2: + self.y_vel = -1.5 + def draw(self, screen): + """Draw the ball and its trail.""" + for i, (trail_x, trail_y) in enumerate(self.trail): + if i < len(self.trail) - 2: + try: + char_index = i % len(TRAIL_CHARS) + screen.addch(int(trail_y), int(trail_x), TRAIL_CHARS[char_index], + curses.color_pair(self.color) | curses.A_DIM) + except curses.error: + pass + try: + screen.addch(int(self.y), int(self.x), BALL_CHARS[self.size], + curses.color_pair(self.color) | curses.A_BOLD) + except curses.error: + pass +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + colors = [curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, + curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN, + curses.COLOR_WHITE] + for i, color in enumerate(colors, start=1): + curses.init_pair(i, color, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_floor(screen, width, height): + """Draw a floor for the ball to bounce on.""" + floor_y = height - 1 + try: + screen.addstr(floor_y, 0, "=" * width) + except curses.error: + pass +def draw_walls(screen, width, height): + """Draw walls on the sides.""" + for y in range(height - 1): + try: + screen.addch(y, 0, '|') + screen.addch(y, width - 1, '|') + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + balls = [ + Ball(width // 4, height // 4, 1.0, 0.0, 'large', 1), + Ball(width // 2, height // 3, -0.8, 0.5, 'medium', 2), + Ball(3 * width // 4, height // 2, 0.5, -1.0, 'small', 3) + ] + frame_num = 0 + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord(' '): + x = random.randint(5, width - 5) + y = random.randint(5, height // 2) + x_vel = random.uniform(-1.5, 1.5) + y_vel = random.uniform(-1.0, 0.0) + size = random.choice(['small', 'medium', 'large']) + color = random.randint(1, 7) + balls.append(Ball(x, y, x_vel, y_vel, size, color)) + screen.clear() + draw_floor(screen, width, height) + draw_walls(screen, width, height) + for ball in balls: + ball.update(width, height) + ball.draw(screen) + try: + screen.addstr(0, 2, "Press 'q' to quit, SPACE to add ball") + except curses.error: + pass + screen.refresh() + frame_num += 1 + time.sleep(0.03) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Bouncing ball simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/dance_and_bounce.py b/submissions/terminal-art/dance_and_bounce.py new file mode 100644 index 00000000..06de33b7 --- /dev/null +++ b/submissions/terminal-art/dance_and_bounce.py @@ -0,0 +1,228 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import math +import random +DANCING_MAN_FRAMES = [ + [ + " o ", + " /|\ ", + " / \ ", + " / \ " + ], + [ + " \o/ ", + " | ", + " | ", + " / \ " + ], + [ + " o/ ", + " /| ", + " / \ ", + " / \ " + ], + [ + " \o ", + " |\ ", + " / \ ", + " / \ " + ], + [ + " o ", + " /|\ ", + " | ", + " / \ " + ], + [ + " o ", + " /|\ ", + " / \ ", + " / \ " + ], + [ + " o ", + " |\ ", + " | ", + " / \ " + ], + [ + " o ", + " /| ", + " | ", + " / \ " + ] +] +class Ball: + """A class representing a bouncing ball.""" + def __init__(self, x, y, x_vel, y_vel, size='medium', color=1): + self.x = x + self.y = y + self.x_vel = x_vel + self.y_vel = y_vel + self.size = size + self.color = color + self.gravity = 0.2 + self.elasticity = 0.8 + self.trail = [] + self.max_trail_length = 5 + self.ball_chars = { + 'small': 'o', + 'medium': 'O', + 'large': '@' + } + self.trail_chars = '.·' + def update(self, width, height, obstacles=None): + """Update the ball's position and velocity.""" + self.trail.append((self.x, self.y)) + if len(self.trail) > self.max_trail_length: + self.trail.pop(0) + self.x += self.x_vel + self.y += self.y_vel + self.y_vel += self.gravity + if self.x < 1: + self.x = 1 + self.x_vel = -self.x_vel * self.elasticity + elif self.x >= width - 1: + self.x = width - 2 + self.x_vel = -self.x_vel * self.elasticity + if self.y < 1: + self.y = 1 + self.y_vel = -self.y_vel * self.elasticity + elif self.y >= height - 2: + self.y = height - 3 + self.y_vel = -self.y_vel * self.elasticity + if abs(self.y_vel) > 0.5: + self.x_vel += (random.random() - 0.5) * 0.5 + self.x_vel *= 0.99 + self.y_vel *= 0.99 + if abs(self.y_vel) < 0.3 and self.y >= height - 3: + self.y_vel = -1.5 + def draw(self, screen): + """Draw the ball and its trail.""" + for i, (trail_x, trail_y) in enumerate(self.trail): + if i < len(self.trail) - 2: + try: + char_index = i % len(self.trail_chars) + screen.addch(int(trail_y), int(trail_x), self.trail_chars[char_index], + curses.color_pair(self.color) | curses.A_DIM) + except curses.error: + pass + try: + screen.addch(int(self.y), int(self.x), self.ball_chars[self.size], + curses.color_pair(self.color) | curses.A_BOLD) + except curses.error: + pass +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + colors = [curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, + curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN, + curses.COLOR_WHITE] + for i, color in enumerate(colors, start=1): + curses.init_pair(i, color, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_dancing_man(screen, x, y, frame_index): + """Draw the dancing man at the specified position with the given frame.""" + frame = DANCING_MAN_FRAMES[frame_index % len(DANCING_MAN_FRAMES)] + for i, line in enumerate(frame): + try: + screen.addstr(y + i, x, line, curses.color_pair(4) | curses.A_BOLD) + except curses.error: + pass +def draw_floor(screen, width, height): + """Draw a floor for the dancing man and balls.""" + floor_y = height - 2 + try: + screen.addstr(floor_y, 0, "=" * width) + except curses.error: + pass +def draw_walls(screen, width, height): + """Draw walls on the sides.""" + for y in range(height - 1): + try: + screen.addch(y, 0, '|') + screen.addch(y, width - 1, '|') + except curses.error: + pass +def draw_disco_lights(screen, width, height, frame_num): + """Draw disco lights at the top of the screen.""" + for x in range(2, width - 2, 4): + color_index = ((x + frame_num) % 7) + 1 + try: + screen.addch(0, x, '*', curses.color_pair(color_index) | curses.A_BOLD) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + balls = [ + Ball(width // 4, height // 4, 1.0, 0.0, 'large', 1), + Ball(width // 2, height // 3, -0.8, 0.5, 'medium', 2), + Ball(3 * width // 4, height // 2, 0.5, -1.0, 'small', 3) + ] + frame_num = 0 + dance_speed = 3 + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord(' '): + x = random.randint(5, width - 5) + y = random.randint(5, height // 2) + x_vel = random.uniform(-1.5, 1.5) + y_vel = random.uniform(-1.0, 0.0) + size = random.choice(['small', 'medium', 'large']) + color = random.randint(1, 7) + balls.append(Ball(x, y, x_vel, y_vel, size, color)) + elif key == ord('+') or key == ord('='): + dance_speed = max(1, dance_speed - 1) + elif key == ord('-'): + dance_speed = min(10, dance_speed + 1) + screen.clear() + draw_floor(screen, width, height) + draw_walls(screen, width, height) + draw_disco_lights(screen, width, height, frame_num) + man_x = width // 2 - 5 + man_y = height - 8 + dance_frame = (frame_num // dance_speed) % len(DANCING_MAN_FRAMES) + draw_dancing_man(screen, man_x, man_y, dance_frame) + for ball in balls: + ball.update(width, height) + ball.draw(screen) + try: + screen.addstr(height-1, 2, "Press 'q' to quit, SPACE to add ball, '+'/'-' to adjust speed") + except curses.error: + pass + screen.refresh() + frame_num += 1 + time.sleep(0.03) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Dance and bounce simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/dancing_man.py b/submissions/terminal-art/dancing_man.py new file mode 100644 index 00000000..38dd84e4 --- /dev/null +++ b/submissions/terminal-art/dancing_man.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import math +DANCING_MAN_FRAMES = [ + [ + " o ", + " /|\ ", + " / \ ", + " / \ " + ], + [ + " \o/ ", + " | ", + " | ", + " / \ " + ], + [ + " o/ ", + " /| ", + " / \ ", + " / \ " + ], + [ + " \o ", + " |\ ", + " / \ ", + " / \ " + ], + [ + " o ", + " /|\ ", + " | ", + " / \ " + ], + [ + " o ", + " /|\ ", + " / \ ", + " / \ " + ], + [ + " o ", + " |\ ", + " | ", + " / \ " + ], + [ + " o ", + " /| ", + " | ", + " / \ " + ] +] +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_dancing_man(screen, x, y, frame_index): + """Draw the dancing man at the specified position with the given frame.""" + frame = DANCING_MAN_FRAMES[frame_index % len(DANCING_MAN_FRAMES)] + for i, line in enumerate(frame): + try: + screen.addstr(y + i, x, line, curses.color_pair(1) | curses.A_BOLD) + except curses.error: + pass +def draw_floor(screen, width, height): + """Draw a floor for the dancing man.""" + floor_y = height - 2 + try: + screen.addstr(floor_y, 0, "_" * width) + except curses.error: + pass +def draw_disco_lights(screen, width, height, frame_num): + """Draw disco lights at the top of the screen.""" + colors = [curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, + curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN] + for i, color in enumerate(colors, start=2): + curses.init_pair(i, color, curses.COLOR_BLACK) + for x in range(0, width, 4): + color_index = ((x + frame_num) % len(colors)) + 2 + try: + screen.addstr(0, x, "*", curses.color_pair(color_index) | curses.A_BOLD) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + frame_num = 0 + dance_speed = 3 + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord('+') or key == ord('='): + dance_speed = max(1, dance_speed - 1) + elif key == ord('-'): + dance_speed = min(10, dance_speed + 1) + screen.clear() + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + man_x = width // 2 - 5 + int(math.sin(frame_num / 10) * 10) + man_y = height - 8 + draw_disco_lights(screen, width, height, frame_num) + draw_floor(screen, width, height) + dance_frame = (frame_num // dance_speed) % len(DANCING_MAN_FRAMES) + draw_dancing_man(screen, man_x, man_y, dance_frame) + try: + screen.addstr(height-1, 0, "Press 'q' to quit, '+'/'-' to adjust speed") + except curses.error: + pass + screen.refresh() + frame_num += 1 + time.sleep(0.05) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Dancing man simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/digital_clock.py b/submissions/terminal-art/digital_clock.py new file mode 100644 index 00000000..1c114446 --- /dev/null +++ b/submissions/terminal-art/digital_clock.py @@ -0,0 +1,318 @@ +#!/usr/bin/env python3 +import curses +import time +import os +import random +from datetime import datetime +DIGITS = { + '0': [ + " ##### ", + "# #", + "# #", + "# #", + "# #", + "# #", + " ##### " + ], + '1': [ + " # ", + " ## ", + " # # ", + " # ", + " # ", + " # ", + "#######" + ], + '2': [ + " ##### ", + "# #", + " #", + " ##### ", + "# ", + "# ", + "#######" + ], + '3': [ + " ##### ", + "# #", + " #", + " ##### ", + " #", + "# #", + " ##### " + ], + '4': [ + "# #", + "# #", + "# #", + "#######", + " #", + " #", + " #" + ], + '5': [ + "#######", + "# ", + "# ", + "###### ", + " #", + "# #", + " ##### " + ], + '6': [ + " ##### ", + "# #", + "# ", + "###### ", + "# #", + "# #", + " ##### " + ], + '7': [ + "#######", + " #", + " # ", + " # ", + " # ", + " # ", + " # " + ], + '8': [ + " ##### ", + "# #", + "# #", + " ##### ", + "# #", + "# #", + " ##### " + ], + '9': [ + " ##### ", + "# #", + "# #", + " ######", + " #", + "# #", + " ##### " + ], + ':': [ + " ", + " # ", + " # ", + " ", + " # ", + " # ", + " " + ], + 'A': [ + " ##### ", + "# #", + "# #", + "#######", + "# #", + "# #", + "# #" + ], + 'P': [ + "###### ", + "# #", + "# #", + "###### ", + "# ", + "# ", + "# " + ], + 'M': [ + "# #", + "## ##", + "# # # #", + "# # #", + "# #", + "# #", + "# #" + ] +} +COLOR_SCHEMES = [ + (curses.COLOR_RED, curses.COLOR_BLACK), + (curses.COLOR_GREEN, curses.COLOR_BLACK), + (curses.COLOR_YELLOW, curses.COLOR_BLACK), + (curses.COLOR_BLUE, curses.COLOR_BLACK), + (curses.COLOR_MAGENTA, curses.COLOR_BLACK), + (curses.COLOR_CYAN, curses.COLOR_BLACK), + (curses.COLOR_WHITE, curses.COLOR_BLACK), +] +class DigitalClock: + """A digital clock display using ASCII art.""" + def __init__(self, height, width): + self.height = height + self.width = width + self.digit_width = 7 + self.digit_height = 7 + self.color_scheme = 0 + self.show_seconds = True + self.show_date = False + self.show_am_pm = True + self.use_24h = False + self.rainbow_mode = False + self.animation_mode = 0 + self.animation_frame = 0 + def get_time_string(self): + """Get the current time as a string.""" + now = datetime.now() + if self.use_24h: + if self.show_seconds: + return now.strftime("%H:%M:%S") + else: + return now.strftime("%H:%M") + else: + if self.show_seconds: + return now.strftime("%I:%M:%S") + else: + return now.strftime("%I:%M") + def get_ampm_string(self): + """Get AM/PM indicator.""" + return datetime.now().strftime("%p") + def get_date_string(self): + """Get the current date as a string.""" + return datetime.now().strftime("%Y-%m-%d") + def draw_digit(self, screen, digit, x, y, color_pair): + """Draw a single digit at the specified position.""" + if digit not in DIGITS: + return + digit_art = DIGITS[digit] + for i, line in enumerate(digit_art): + for j, char in enumerate(line): + attr = curses.A_NORMAL + if self.animation_mode == 1: + if self.animation_frame % 10 < 5: + attr = curses.A_BOLD + elif self.animation_mode == 2: + wave_pos = (self.animation_frame + j) % 14 + if wave_pos < 7: + attr = curses.A_BOLD + if char != ' ': + try: + if self.rainbow_mode: + rainbow_color = (color_pair + j + i) % 7 + 1 + screen.addch(y + i, x + j, '#', + curses.color_pair(rainbow_color) | attr) + else: + screen.addch(y + i, x + j, '#', + curses.color_pair(color_pair) | attr) + except curses.error: + pass + def draw_clock(self, screen): + """Draw the digital clock on the screen.""" + time_str = self.get_time_string() + total_width = len(time_str) * (self.digit_width + 1) - 1 + start_x = (self.width - total_width) // 2 + start_y = (self.height - self.digit_height) // 2 + x = start_x + for digit in time_str: + color_pair = (self.color_scheme % 7) + 1 + self.draw_digit(screen, digit, x, start_y, color_pair) + x += self.digit_width + 1 + if self.show_am_pm and not self.use_24h: + ampm = self.get_ampm_string() + ampm_x = start_x + total_width + 2 + ampm_y = start_y + 2 + for i, char in enumerate(ampm): + self.draw_digit(screen, char, ampm_x, ampm_y + i*8, + (self.color_scheme % 7) + 1) + if self.show_date: + date_str = self.get_date_string() + date_y = start_y + self.digit_height + 2 + date_x = (self.width - len(date_str)) // 2 + try: + screen.addstr(date_y, date_x, date_str, + curses.color_pair((self.color_scheme % 7) + 1) | + curses.A_BOLD) + except curses.error: + pass + self.animation_frame += 1 +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + for i, (fg, bg) in enumerate(COLOR_SCHEMES, start=1): + curses.init_pair(i, fg, bg) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_stars(screen, height, width, num_stars=30): + """Draw random stars in the background.""" + for _ in range(num_stars): + x = random.randint(0, width - 1) + y = random.randint(0, height - 1) + char = random.choice(['*', '.', '+']) + color = random.randint(1, 7) + try: + screen.addch(y, x, char, curses.color_pair(color) | curses.A_DIM) + except curses.error: + pass +def draw_info(screen, height, width, clock): + """Draw information about controls.""" + info_text = [ + "Controls: c=color, s=seconds, d=date, a=AM/PM, h=24h, r=rainbow, m=animation, q=quit" + ] + for i, text in enumerate(info_text): + try: + screen.addstr(height - 1 - len(info_text) + i, 0, text.ljust(width), + curses.color_pair(7) | curses.A_BOLD) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + clock = DigitalClock(height, width) + running = True + show_stars = False + while running: + screen.clear() + if show_stars: + draw_stars(screen, height, width) + clock.draw_clock(screen) + draw_info(screen, height, width, clock) + screen.refresh() + key = screen.getch() + if key == ord('q'): + running = False + elif key == ord('c'): + clock.color_scheme = (clock.color_scheme + 1) % len(COLOR_SCHEMES) + elif key == ord('s'): + clock.show_seconds = not clock.show_seconds + elif key == ord('d'): + clock.show_date = not clock.show_date + elif key == ord('a'): + clock.show_am_pm = not clock.show_am_pm + elif key == ord('h'): + clock.use_24h = not clock.use_24h + elif key == ord('r'): + clock.rainbow_mode = not clock.rainbow_mode + elif key == ord('m'): + clock.animation_mode = (clock.animation_mode + 1) % 3 + elif key == ord('*'): + show_stars = not show_stars + time.sleep(0.1) + except KeyboardInterrupt: + pass + finally: + cleanup_screen(screen) +if __name__ == "__main__": + curses.wrapper(main) \ No newline at end of file diff --git a/submissions/terminal-art/fire_terminal.py b/submissions/terminal-art/fire_terminal.py new file mode 100644 index 00000000..acfe18bd --- /dev/null +++ b/submissions/terminal-art/fire_terminal.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +import os +import time +import random +import curses +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_MAGENTA, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +class FireSimulation: + """A class for simulating fire in the terminal.""" + def __init__(self, width, height): + self.width = width + self.height = height + self.buffer = [[0 for _ in range(width)] for _ in range(height)] + for x in range(width): + self.buffer[height-1][x] = random.randint(80, 100) + def update(self): + """Update the fire simulation.""" + for x in range(self.width): + self.buffer[self.height-1][x] = random.randint(80, 100) + for y in range(self.height-2, -1, -1): + for x in range(self.width): + decay = random.randint(1, 4) + below = self.buffer[y+1][x] + below_left = self.buffer[y+1][(x-1) % self.width] + below_right = self.buffer[y+1][(x+1) % self.width] + new_value = (below + below_left + below_right) // 3 - decay + self.buffer[y][x] = max(0, new_value) + def render(self, screen): + """Render the fire simulation on the screen.""" + for y in range(self.height): + for x in range(self.width): + value = self.buffer[y][x] + if value > 80: + char = '@' + color_pair = 1 + elif value > 60: + char = '#' + color_pair = 2 + elif value > 40: + char = '+' + color_pair = 3 + elif value > 20: + char = '.' + color_pair = 4 + else: + char = ' ' + color_pair = 0 + try: + screen.addch(y, x, char, curses.color_pair(color_pair)) + except curses.error: + pass + screen.refresh() +def main(screen): + """Main function.""" + try: + height, width = screen.getmaxyx() + fire = FireSimulation(width, height) + while True: + key = screen.getch() + if key == ord('q'): + break + fire.update() + fire.render(screen) + time.sleep(0.05) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Fire simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/fireworks.py b/submissions/terminal-art/fireworks.py new file mode 100644 index 00000000..33bfd0e4 --- /dev/null +++ b/submissions/terminal-art/fireworks.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +import curses +import random +import time +import math +import os +class Particle: + """A particle in a firework explosion.""" + def __init__(self, x, y, angle, speed, color, char='*', lifespan=20): + self.x = x + self.y = y + self.angle = angle + self.speed = speed + self.color = color + self.char = char + self.lifespan = lifespan + self.age = 0 + self.dx = math.cos(angle) * speed + self.dy = math.sin(angle) * speed + self.gravity = 0.05 + def update(self): + """Update the particle's position and age.""" + self.x += self.dx + self.y += self.dy + self.dy += self.gravity + self.age += 1 + self.dx *= 0.97 + self.dy *= 0.97 + return self.age <= self.lifespan + def draw(self, screen, height, width): + """Draw the particle on the screen.""" + screen_x = int(self.x) + screen_y = int(self.y) + if 0 <= screen_x < width and 0 <= screen_y < height: + brightness = 1.0 - (self.age / self.lifespan) + if brightness > 0.7: + attr = curses.A_BOLD + elif brightness > 0.3: + attr = curses.A_NORMAL + else: + attr = curses.A_DIM + try: + screen.addch(screen_y, screen_x, self.char, + curses.color_pair(self.color) | attr) + except curses.error: + pass +class Firework: + """A firework that explodes into particles.""" + def __init__(self, width, height): + self.width = width + self.height = height + self.x = random.randint(width // 4, width * 3 // 4) + self.y = height - 1 + self.target_y = random.randint(height // 8, height // 2) + self.speed = random.uniform(0.3, 0.7) + self.particles = [] + self.exploded = False + self.color = random.randint(1, 7) + def update(self): + """Update the firework's position and particles.""" + if not self.exploded: + self.y -= self.speed + if self.y <= self.target_y: + self.explode() + else: + self.particles = [p for p in self.particles if p.update()] + return self.exploded and not self.particles + def explode(self): + """Create an explosion of particles.""" + self.exploded = True + num_particles = random.randint(20, 40) + for i in range(num_particles): + angle = random.uniform(0, 2 * math.pi) + speed = random.uniform(0.3, 1.2) + char = random.choice(['*', '+', '.', '•', '°']) + lifespan = random.randint(15, 30) + self.particles.append(Particle( + self.x, self.y, angle, speed, self.color, char, lifespan + )) + def draw(self, screen): + """Draw the firework on the screen.""" + if not self.exploded: + try: + screen.addch(int(self.y), int(self.x), '|', + curses.color_pair(self.color) | curses.A_BOLD) + except curses.error: + pass + else: + for particle in self.particles: + particle.draw(screen, self.height, self.width) +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + colors = [curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, + curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN, + curses.COLOR_WHITE] + for i, color in enumerate(colors, start=1): + curses.init_pair(i, color, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(50) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_ground(screen, height, width): + """Draw the ground at the bottom of the screen.""" + for x in range(width): + try: + screen.addch(height - 1, x, '_', curses.color_pair(7)) + except curses.error: + pass +def draw_stars(screen, height, width, num_stars=50): + """Draw background stars.""" + for _ in range(num_stars): + x = random.randint(0, width - 1) + y = random.randint(0, height - 2) + char = random.choice(['.', '*', '+']) + attr = random.choice([curses.A_DIM, curses.A_NORMAL]) + try: + screen.addch(y, x, char, curses.color_pair(7) | attr) + except curses.error: + pass +def draw_info(screen, height, width, fireworks_count, launch_rate): + """Draw information about the simulation.""" + info_text = [ + f"Fireworks: {fireworks_count} | Launch Rate: {launch_rate:.2f}/s", + "Controls: Space=launch, +/-=rate, q=quit" + ] + for i, text in enumerate(info_text): + try: + screen.addstr(i, 0, text, curses.color_pair(7) | curses.A_BOLD) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + fireworks = [] + launch_rate = 0.5 + last_launch = time.time() + running = True + while running: + screen.clear() + draw_stars(screen, height, width) + draw_ground(screen, height, width) + current_time = time.time() + if current_time - last_launch > 1.0 / launch_rate: + fireworks.append(Firework(width, height)) + last_launch = current_time + fireworks = [fw for fw in fireworks if not fw.update()] + for firework in fireworks: + firework.draw(screen) + draw_info(screen, height, width, len(fireworks), launch_rate) + screen.refresh() + key = screen.getch() + if key == ord('q'): + running = False + elif key == ord(' '): + fireworks.append(Firework(width, height)) + elif key == ord('+') or key == ord('='): + launch_rate = min(2.0, launch_rate + 0.1) + elif key == ord('-'): + launch_rate = max(0.1, launch_rate - 0.1) + time.sleep(0.05) + except KeyboardInterrupt: + pass + finally: + cleanup_screen(screen) +if __name__ == "__main__": + curses.wrapper(main) \ No newline at end of file diff --git a/submissions/terminal-art/game_of_life.py b/submissions/terminal-art/game_of_life.py new file mode 100644 index 00000000..d1be682c --- /dev/null +++ b/submissions/terminal-art/game_of_life.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +import curses +import random +import time +import os +class GameOfLife: + """Conway's Game of Life simulation in the terminal.""" + def __init__(self, height, width, density=0.3): + self.height = height + self.width = width + self.grid = [[0 for _ in range(width)] for _ in range(height)] + self.next_grid = [[0 for _ in range(width)] for _ in range(height)] + self.density = density + self.generation = 0 + self.population = 0 + def randomize(self): + """Initialize the grid with random cells.""" + self.grid = [[1 if random.random() < self.density else 0 + for _ in range(self.width)] + for _ in range(self.height)] + self.count_population() + def count_population(self): + """Count the current population of live cells.""" + self.population = sum(sum(row) for row in self.grid) + def get_neighbors(self, row, col): + """Count the number of live neighbors for a cell.""" + count = 0 + for i in range(max(0, row-1), min(self.height, row+2)): + for j in range(max(0, col-1), min(self.width, col+2)): + if (i, j) != (row, col) and self.grid[i][j] == 1: + count += 1 + return count + def update(self): + """Update the grid based on Conway's Game of Life rules.""" + for i in range(self.height): + for j in range(self.width): + neighbors = self.get_neighbors(i, j) + if self.grid[i][j] == 1: + if neighbors < 2 or neighbors > 3: + self.next_grid[i][j] = 0 + else: + self.next_grid[i][j] = 1 + else: + if neighbors == 3: + self.next_grid[i][j] = 1 + else: + self.next_grid[i][j] = 0 + self.grid, self.next_grid = self.next_grid, self.grid + self.generation += 1 + self.count_population() + def draw(self, screen): + """Draw the current state of the grid on the screen.""" + for i in range(self.height): + for j in range(self.width): + if self.grid[i][j] == 1: + try: + screen.addch(i, j, '■', curses.A_BOLD | curses.color_pair(2)) + except curses.error: + pass + else: + try: + screen.addch(i, j, ' ') + except curses.error: + pass +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_info(screen, game, height, width, speed): + """Draw information about the simulation.""" + info_text = [ + f"Conway's Game of Life | Gen: {game.generation} | Pop: {game.population}", + "Controls: r=randomize, c=clear, +/-=speed, q=quit" + ] + for i, text in enumerate(info_text): + try: + screen.addstr(height - len(info_text) + i, 0, text.ljust(width), + curses.color_pair(3) | curses.A_BOLD) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + height = min(height, 40) + width = min(width, 80) + game_height = height - 2 + game = GameOfLife(game_height, width) + game.randomize() + speed = 0.1 + running = True + paused = False + while running: + screen.clear() + game.draw(screen) + draw_info(screen, game, height, width, speed) + screen.refresh() + key = screen.getch() + if key == ord('q'): + running = False + elif key == ord('r'): + game.randomize() + elif key == ord('c'): + game.grid = [[0 for _ in range(width)] for _ in range(game_height)] + game.count_population() + elif key == ord('+') or key == ord('='): + speed = max(0.01, speed - 0.05) + elif key == ord('-'): + speed = min(1.0, speed + 0.05) + elif key == ord(' '): + paused = not paused + elif key == curses.KEY_MOUSE: + try: + _, mx, my, _, _ = curses.getmouse() + if my < game_height and mx < width: + game.grid[my][mx] = 1 - game.grid[my][mx] + game.count_population() + except: + pass + if not paused: + game.update() + time.sleep(speed) + except KeyboardInterrupt: + pass + finally: + cleanup_screen(screen) +if __name__ == "__main__": + try: + curses.mousemask(curses.ALL_MOUSE_EVENTS) + except: + pass + curses.wrapper(main) \ No newline at end of file diff --git a/submissions/terminal-art/matrix_terminal.py b/submissions/terminal-art/matrix_terminal.py new file mode 100644 index 00000000..10838237 --- /dev/null +++ b/submissions/terminal-art/matrix_terminal.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +import os +import time +import random +import curses +import string +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +class MatrixColumn: + """A class representing a column in the Matrix effect.""" + def __init__(self, height, x_pos): + self.x_pos = x_pos + self.height = height + self.speed = random.randint(1, 3) + self.head_pos = random.randint(-height, 0) + self.length = random.randint(5, 15) + self.chars = [] + self.update_chars() + def update_chars(self): + """Update the characters in the column.""" + charset = string.ascii_letters + string.digits + '!@#$%^&*()' + self.chars = [random.choice(charset) for _ in range(self.length)] + def update(self): + """Update the column position.""" + self.head_pos += self.speed + for i in range(len(self.chars)): + if random.random() < 0.1: + self.chars[i] = random.choice(string.ascii_letters + string.digits + '!@#$%^&*()') + if self.head_pos - self.length > self.height: + self.head_pos = random.randint(-self.length, 0) + self.speed = random.randint(1, 3) + self.length = random.randint(5, 15) + self.update_chars() +def render_matrix(screen, columns): + """Render the Matrix effect on the screen.""" + screen.clear() + for column in columns: + if 0 <= column.head_pos < column.height: + screen.addstr(column.head_pos, column.x_pos, column.chars[0], + curses.color_pair(1) | curses.A_BOLD) + for i in range(1, column.length): + pos = column.head_pos - i + if 0 <= pos < column.height: + if i < column.length // 3: + attr = curses.color_pair(1) | curses.A_BOLD + elif i < column.length * 2 // 3: + attr = curses.color_pair(1) + else: + attr = curses.color_pair(1) | curses.A_DIM + screen.addstr(pos, column.x_pos, column.chars[i % len(column.chars)], attr) + screen.refresh() +def main(screen): + """Main function.""" + try: + height, width = screen.getmaxyx() + columns = [] + for x in range(0, width - 1, 2): + columns.append(MatrixColumn(height, x)) + while True: + key = screen.getch() + if key == ord('q'): + break + for column in columns: + column.update() + render_matrix(screen, columns) + time.sleep(0.05) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Matrix simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/menu.py b/submissions/terminal-art/menu.py new file mode 100644 index 00000000..ad07a320 --- /dev/null +++ b/submissions/terminal-art/menu.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +import curses +import os +import subprocess +import sys +import platform +PROGRAMS = [ + {"name": "Basic Video Simulation", "file": "video_terminal.py", "description": "A simple wave-like animation using ASCII characters."}, + {"name": "Matrix Effect", "file": "matrix_terminal.py", "description": "A simulation of the famous \"digital rain\" effect from The Matrix movie."}, + {"name": "Fire Effect", "file": "fire_terminal.py", "description": "A simulation of fire using ASCII characters and colors."}, + {"name": "Dancing Man", "file": "dancing_man.py", "description": "A simulation of a dancing stick figure with disco lights."}, + {"name": "Bouncing Ball", "file": "bouncing_ball.py", "description": "A physics-based simulation of bouncing balls with trails."}, + {"name": "Dance and Bounce", "file": "dance_and_bounce.py", "description": "A combined simulation featuring both the dancing man and bouncing balls."}, + {"name": "Starfield", "file": "starfield.py", "description": "A simulation of flying through space with stars moving toward you."}, + {"name": "Rain", "file": "rain.py", "description": "A simulation of rain falling with splashes when raindrops hit the ground."}, + {"name": "Conway's Game of Life", "file": "game_of_life.py", "description": "An implementation of Conway's Game of Life cellular automaton with interactive controls."}, + {"name": "Fireworks Display", "file": "fireworks.py", "description": "A colorful fireworks simulation with rising rockets and particle explosions."}, + {"name": "Digital Clock", "file": "digital_clock.py", "description": "A customizable digital clock with ASCII art digits and various display options."}, + {"name": "Snake Game", "file": "snake_game.py", "description": "A classic snake game where you control a snake to eat food and grow longer."}, + {"name": "Typing Test", "file": "typing_test.py", "description": "A typing speed and accuracy test with WPM (words per minute) calculation."}, +] +def draw_menu(screen, selected_idx, start_idx, max_visible): + """Draw the menu with the current selection.""" + height, width = screen.getmaxyx() + visible_count = min(max_visible, len(PROGRAMS)) + title = "TERMINAL VIDEO SIMULATIONS MENU" + screen.addstr(1, (width - len(title)) // 2, title, curses.A_BOLD) + instructions = "Use UP/DOWN arrows to navigate, ENTER to select, Q to quit" + screen.addstr(3, (width - len(instructions)) // 2, instructions) + for i in range(visible_count): + idx = (start_idx + i) % len(PROGRAMS) + program = PROGRAMS[idx] + y_pos = i + 5 + if idx == selected_idx: + attr = curses.A_REVERSE | curses.A_BOLD + else: + attr = curses.A_NORMAL + menu_text = f"{idx + 1}. {program['name']}" + screen.addstr(y_pos, 4, menu_text, attr) + if idx == selected_idx: + desc_text = f"> {program['description']}" + if len(desc_text) > width - 8: + desc_text = desc_text[:width - 11] + "..." + screen.addstr(y_pos + 1, 6, desc_text, curses.A_DIM) + if len(PROGRAMS) > visible_count: + scrollbar_height = int((visible_count / len(PROGRAMS)) * visible_count) + scrollbar_pos = int((start_idx / len(PROGRAMS)) * visible_count) + for i in range(visible_count): + if scrollbar_pos <= i < scrollbar_pos + scrollbar_height: + screen.addstr(i + 5, width - 2, "█") + else: + screen.addstr(i + 5, width - 2, "│") + footer = "Press 'q' to quit" + screen.addstr(height - 2, (width - len(footer)) // 2, footer) +def run_program(program_file): + """Run the selected program.""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + program_path = os.path.join(current_dir, program_file) + if not os.path.exists(program_path): + return f"Error: {program_file} not found!" + try: + python_exe = sys.executable + result = subprocess.run([python_exe, program_path], capture_output=True, text=True) + if result.returncode != 0: + return f"Program exited with error code {result.returncode}\n{result.stderr}" + return "Program completed successfully." + except Exception as e: + return f"Error running program: {str(e)}" +def main(screen): + """Main function.""" + curses.curs_set(0) + curses.start_color() + curses.use_default_colors() + screen.keypad(True) + selected_idx = 0 + start_idx = 0 + max_visible = 10 + running = True + status_message = "" + while running: + screen.clear() + height, width = screen.getmaxyx() + draw_menu(screen, selected_idx, start_idx, max_visible) + if status_message: + if len(status_message) > width - 4: + status_message = status_message[:width - 7] + "..." + screen.addstr(height - 4, 2, status_message, curses.A_BOLD) + screen.refresh() + key = screen.getch() + if key == ord('q') or key == ord('Q'): + running = False + elif key == curses.KEY_UP: + selected_idx = (selected_idx - 1) % len(PROGRAMS) + if selected_idx < start_idx: + start_idx = selected_idx + elif selected_idx >= start_idx + max_visible: + start_idx = selected_idx - max_visible + 1 + elif key == curses.KEY_DOWN: + selected_idx = (selected_idx + 1) % len(PROGRAMS) + if selected_idx < start_idx: + start_idx = selected_idx + elif selected_idx >= start_idx + max_visible: + start_idx = selected_idx - max_visible + 1 + elif key == curses.KEY_ENTER or key == 10 or key == 13: + screen.clear() + screen.addstr(0, 0, f"Running {PROGRAMS[selected_idx]['name']}...") + screen.refresh() + curses.endwin() + program_file = PROGRAMS[selected_idx]['file'] + print(f"\nRunning {program_file}...\n") + try: + current_dir = os.path.dirname(os.path.abspath(__file__)) + program_path = os.path.join(current_dir, program_file) + python_exe = sys.executable + subprocess.call([python_exe, program_path]) + print(f"\nReturned from {program_file}. Press any key to continue...") + input() + status_message = f"Returned from {PROGRAMS[selected_idx]['name']}" + except Exception as e: + status_message = f"Error: {str(e)}" + screen = curses.initscr() + curses.noecho() + curses.cbreak() + screen.keypad(True) + curses.curs_set(0) +def check_dependencies(): + """Check if required dependencies are installed.""" + try: + import curses + return True + except ImportError: + if platform.system() == "Windows": + print("The 'windows-curses' package is required to run this program.") + print("Please install it using: pip install windows-curses") + else: + print("The 'curses' package is required to run this program.") + return False +if __name__ == "__main__": + if check_dependencies(): + try: + curses.wrapper(main) + except KeyboardInterrupt: + print("\nExiting...") + except Exception as e: + print(f"\nAn error occurred: {str(e)}") \ No newline at end of file diff --git a/submissions/terminal-art/rain.py b/submissions/terminal-art/rain.py new file mode 100644 index 00000000..7f0600d8 --- /dev/null +++ b/submissions/terminal-art/rain.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import random +class Raindrop: + """A class representing a raindrop.""" + def __init__(self, x, y, speed, length, color=4): + self.x = x + self.y = y + self.speed = speed + self.length = length + self.color = color + self.char = '|' + self.trail_chars = ['.'] + self.splash_active = False + self.splash_frame = 0 + self.splash_frames = ['_', '-', '.', ' '] + def update(self, height): + """Update the raindrop's position.""" + self.y += self.speed + if self.y >= height - 2 and not self.splash_active: + self.splash_active = True + self.splash_frame = 0 + if self.splash_active: + self.splash_frame += 1 + if self.splash_frame >= len(self.splash_frames) * 2: + self.y = random.randint(-20, -1) + self.x = random.randint(0, 79) + self.speed = random.uniform(0.5, 2.0) + self.length = random.randint(3, 8) + self.splash_active = False + self.splash_frame = 0 + def draw(self, screen, height): + """Draw the raindrop on the screen.""" + if self.splash_active: + splash_char = self.splash_frames[(self.splash_frame // 2) % len(self.splash_frames)] + try: + screen.addch(height - 2, int(self.x), splash_char, + curses.color_pair(self.color) | curses.A_BOLD) + if self.length > 5 and int(self.x) > 0: + screen.addch(height - 2, int(self.x) - 1, splash_char, + curses.color_pair(self.color) | curses.A_DIM) + if self.length > 5 and int(self.x) < 79: + screen.addch(height - 2, int(self.x) + 1, splash_char, + curses.color_pair(self.color) | curses.A_DIM) + except curses.error: + pass + else: + for i in range(self.length): + trail_y = int(self.y) - i + if 0 <= trail_y < height - 1: + try: + if i == 0: + screen.addch(trail_y, int(self.x), self.char, + curses.color_pair(self.color) | curses.A_BOLD) + else: + trail_char = self.trail_chars[0] + screen.addch(trail_y, int(self.x), trail_char, + curses.color_pair(self.color) | curses.A_DIM) + except curses.error: + pass +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def draw_ground(screen, width, height): + """Draw the ground at the bottom of the screen.""" + try: + screen.addstr(height - 1, 0, "_" * width, curses.color_pair(1)) + except curses.error: + pass +def draw_clouds(screen, width, frame_num): + """Draw clouds at the top of the screen.""" + cloud_chars = [' ', '.', ':', '=', '#'] + cloud_pattern = [] + for x in range(width): + wave = (x + frame_num // 5) % width + distance = abs(wave - x) / width + density = 0.7 + 0.3 * (1 - distance) + density += random.random() * 0.2 + char_index = min(int(density * len(cloud_chars)), len(cloud_chars) - 1) + cloud_pattern.append(cloud_chars[char_index]) + try: + screen.addstr(0, 0, ''.join(cloud_pattern), curses.color_pair(1) | curses.A_DIM) + except curses.error: + pass +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + raindrops = [] + for _ in range(100): + x = random.randint(0, width - 1) + y = random.randint(-20, height - 3) + speed = random.uniform(0.5, 2.0) + length = random.randint(3, 8) + color = random.randint(1, 4) + raindrops.append(Raindrop(x, y, speed, length, color)) + frame_num = 0 + rain_intensity = 100 + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord('+') or key == ord('='): + rain_intensity = min(200, rain_intensity + 10) + while len(raindrops) < rain_intensity: + x = random.randint(0, width - 1) + y = random.randint(-20, 0) + speed = random.uniform(0.5, 2.0) + length = random.randint(3, 8) + color = random.randint(1, 4) + raindrops.append(Raindrop(x, y, speed, length, color)) + elif key == ord('-'): + rain_intensity = max(10, rain_intensity - 10) + while len(raindrops) > rain_intensity: + raindrops.pop() + screen.clear() + draw_clouds(screen, width, frame_num) + draw_ground(screen, width, height) + for raindrop in raindrops: + raindrop.update(height) + raindrop.draw(screen, height) + try: + screen.addstr(height-1, 0, f"Raindrops: {len(raindrops)} - Press '+'/'-' to adjust, 'q' to quit") + except curses.error: + pass + screen.refresh() + frame_num += 1 + time.sleep(0.03) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Rain simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/snake_game.py b/submissions/terminal-art/snake_game.py new file mode 100644 index 00000000..5375a615 --- /dev/null +++ b/submissions/terminal-art/snake_game.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import random +from collections import deque +class SnakeGame: + """A class representing the snake game.""" + def __init__(self, width, height): + self.width = width + self.height = height + self.score = 0 + self.game_over = False + self.win = False + self.snake = deque([(width // 2, height // 2)]) + self.direction = 1 + self.food = self.create_food() + self.speed = 0.1 + self.max_score = 20 + def create_food(self): + """Create a new food at a random position.""" + while True: + food = (random.randint(1, self.width - 2), random.randint(1, self.height - 2)) + if food not in self.snake: + return food + def change_direction(self, key): + """Change the snake's direction based on key press.""" + if key == curses.KEY_UP and self.direction != 2: + self.direction = 0 + elif key == curses.KEY_RIGHT and self.direction != 3: + self.direction = 1 + elif key == curses.KEY_DOWN and self.direction != 0: + self.direction = 2 + elif key == curses.KEY_LEFT and self.direction != 1: + self.direction = 3 + def move_snake(self): + """Move the snake in the current direction.""" + head_x, head_y = self.snake[0] + if self.direction == 0: + new_head = (head_x, head_y - 1) + elif self.direction == 1: + new_head = (head_x + 1, head_y) + elif self.direction == 2: + new_head = (head_x, head_y + 1) + elif self.direction == 3: + new_head = (head_x - 1, head_y) + if (new_head[0] <= 0 or new_head[0] >= self.width - 1 or + new_head[1] <= 0 or new_head[1] >= self.height - 1): + self.game_over = True + return + if new_head in list(self.snake)[:-1]: + self.game_over = True + return + self.snake.appendleft(new_head) + if new_head == self.food: + self.score += 1 + if self.score >= self.max_score: + self.win = True + return + self.food = self.create_food() + self.speed = max(0.05, self.speed * 0.95) + else: + self.snake.pop() + def draw(self, screen): + """Draw the game on the screen.""" + screen.clear() + for x in range(self.width): + try: + screen.addch(0, x, '#') + screen.addch(self.height - 1, x, '#') + except curses.error: + pass + for y in range(self.height): + try: + screen.addch(y, 0, '#') + screen.addch(y, self.width - 1, '#') + except curses.error: + pass + for i, (x, y) in enumerate(self.snake): + try: + if i == 0: + screen.addch(y, x, '@', curses.color_pair(1) | curses.A_BOLD) + else: + screen.addch(y, x, 'O', curses.color_pair(2)) + except curses.error: + pass + try: + screen.addch(self.food[1], self.food[0], '*', curses.color_pair(3) | curses.A_BOLD) + except curses.error: + pass + score_text = f" Score: {self.score} " + try: + screen.addstr(0, self.width // 2 - len(score_text) // 2, score_text) + except curses.error: + pass + if self.game_over: + game_over_text = " GAME OVER - Press 'r' to restart or 'q' to quit " + try: + screen.addstr(self.height // 2, self.width // 2 - len(game_over_text) // 2, + game_over_text, curses.color_pair(4) | curses.A_BOLD) + except curses.error: + pass + elif self.win: + win_text = " YOU WIN! - Press 'r' to restart or 'q' to quit " + try: + screen.addstr(self.height // 2, self.width // 2 - len(win_text) // 2, + win_text, curses.color_pair(3) | curses.A_BOLD) + except curses.error: + pass + screen.refresh() +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_CYAN, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.init_pair(4, curses.COLOR_RED, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + game = SnakeGame(width, height) + last_update = time.time() + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord('r'): + game = SnakeGame(width, height) + last_update = time.time() + elif key in [curses.KEY_UP, curses.KEY_RIGHT, curses.KEY_DOWN, curses.KEY_LEFT]: + game.change_direction(key) + current_time = time.time() + if current_time - last_update > game.speed and not game.game_over and not game.win: + game.move_snake() + last_update = current_time + game.draw(screen) + time.sleep(0.03) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Snake game ended.") \ No newline at end of file diff --git a/submissions/terminal-art/starfield.py b/submissions/terminal-art/starfield.py new file mode 100644 index 00000000..b1fe3b32 --- /dev/null +++ b/submissions/terminal-art/starfield.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import random +import math +class Star: + """A class representing a star in the starfield.""" + def __init__(self, x, y, z, color=7): + self.x = x + self.y = y + self.z = z + self.prev_x = x + self.prev_y = y + self.prev_z = z + self.color = color + def update(self, speed): + """Update the star's position.""" + self.prev_x = self.x + self.prev_y = self.y + self.prev_z = self.z + self.z -= speed + if self.z <= 0: + self.x = random.uniform(-1.0, 1.0) + self.y = random.uniform(-1.0, 1.0) + self.z = 1.0 + def draw(self, screen, width, height): + """Draw the star on the screen.""" + if self.z > 0: + screen_x = int((self.x / self.z) * width / 2 + width / 2) + screen_y = int((self.y / self.z) * height / 2 + height / 2) + if 0 <= screen_x < width and 0 <= screen_y < height: + brightness = 1.0 - self.z + if brightness > 0.8: + char = '@' + attr = curses.A_BOLD + elif brightness > 0.5: + char = '*' + attr = curses.A_BOLD + elif brightness > 0.3: + char = '+' + attr = curses.A_NORMAL + else: + char = '.' + attr = curses.A_DIM + try: + screen.addch(screen_y, screen_x, char, + curses.color_pair(self.color) | attr) + except curses.error: + pass + if self.prev_z > 0 and brightness > 0.5: + prev_x = int((self.prev_x / self.prev_z) * width / 2 + width / 2) + prev_y = int((self.prev_y / self.prev_z) * height / 2 + height / 2) + if (0 <= prev_x < width and 0 <= prev_y < height and + (prev_x != screen_x or prev_y != screen_y)): + try: + screen.addch(prev_y, prev_x, '.', + curses.color_pair(self.color) | curses.A_DIM) + except curses.error: + pass +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + colors = [curses.COLOR_RED, curses.COLOR_GREEN, curses.COLOR_YELLOW, + curses.COLOR_BLUE, curses.COLOR_MAGENTA, curses.COLOR_CYAN, + curses.COLOR_WHITE] + for i, color in enumerate(colors, start=1): + curses.init_pair(i, color, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + stars = [] + for _ in range(100): + x = random.uniform(-1.0, 1.0) + y = random.uniform(-1.0, 1.0) + z = random.uniform(0.0, 1.0) + color = random.randint(1, 7) + stars.append(Star(x, y, z, color)) + speed = 0.01 + while True: + key = screen.getch() + if key == ord('q'): + break + elif key == ord('+') or key == ord('='): + speed = min(0.05, speed + 0.005) + elif key == ord('-'): + speed = max(0.001, speed - 0.005) + screen.clear() + for star in stars: + star.update(speed) + star.draw(screen, width, height) + try: + screen.addstr(0, 0, f"Speed: {speed:.3f} - Press '+'/'-' to adjust, 'q' to quit") + except curses.error: + pass + screen.refresh() + time.sleep(0.03) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Starfield simulation ended.") \ No newline at end of file diff --git a/submissions/terminal-art/typing_test.py b/submissions/terminal-art/typing_test.py new file mode 100644 index 00000000..6e050b37 --- /dev/null +++ b/submissions/terminal-art/typing_test.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python3 +import os +import time +import curses +import random +from datetime import datetime +SAMPLE_TEXTS = [ + "The quick brown fox jumps over the lazy dog.", + "Programming is the art of telling another human what one wants the computer to do.", + "The best way to predict the future is to invent it.", + "Simplicity is the ultimate sophistication.", + "Code is like humor. When you have to explain it, it's bad.", + "First, solve the problem. Then, write the code.", + "Any fool can write code that a computer can understand. Good programmers write code that humans can understand.", + "Experience is the name everyone gives to their mistakes.", + "The only way to learn a new programming language is by writing programs in it.", + "Sometimes it pays to stay in bed on Monday, rather than spending the rest of the week debugging Monday's code." +] +class TypingTest: + """A class representing a typing test.""" + def __init__(self, width, height): + self.width = width + self.height = height + self.reset_test() + def reset_test(self): + """Reset the typing test with a new text.""" + self.text = random.choice(SAMPLE_TEXTS) + self.user_input = "" + self.start_time = None + self.end_time = None + self.current_pos = 0 + self.errors = 0 + self.test_complete = False + self.wpm = 0 + self.accuracy = 100.0 + def start(self): + """Start the typing test.""" + self.start_time = datetime.now() + def add_char(self, char): + """Add a character to the user input.""" + if self.start_time is None: + self.start() + if not self.test_complete: + self.user_input += char + if len(self.user_input) <= len(self.text) and char != self.text[self.current_pos]: + self.errors += 1 + self.current_pos += 1 + if self.current_pos >= len(self.text): + self.end_time = datetime.now() + self.test_complete = True + self.calculate_results() + def remove_char(self): + """Remove the last character from user input.""" + if self.start_time is not None and not self.test_complete and self.user_input: + self.user_input = self.user_input[:-1] + self.current_pos -= 1 + def calculate_results(self): + """Calculate typing speed and accuracy.""" + if self.start_time and self.end_time: + time_taken = (self.end_time - self.start_time).total_seconds() / 60 + self.wpm = int(len(self.text) / 5 / time_taken) + correct_chars = len(self.text) - self.errors + self.accuracy = round(correct_chars / len(self.text) * 100, 1) + def draw(self, screen): + """Draw the typing test on the screen.""" + screen.clear() + for x in range(self.width): + try: + screen.addch(0, x, '-') + screen.addch(self.height - 1, x, '-') + except curses.error: + pass + for y in range(self.height): + try: + screen.addch(y, 0, '|') + screen.addch(y, self.width - 1, '|') + except curses.error: + pass + title = " Terminal Typing Test " + try: + screen.addstr(1, self.width // 2 - len(title) // 2, title, curses.A_BOLD) + except curses.error: + pass + instructions = " Type the text below | Press ESC to restart | Ctrl+C to quit " + try: + screen.addstr(2, self.width // 2 - len(instructions) // 2, instructions) + except curses.error: + pass + try: + screen.addstr(3, 1, "-" * (self.width - 2)) + except curses.error: + pass + wrapped_text = [] + current_line = "" + for word in self.text.split(): + if len(current_line) + len(word) + 1 <= self.width - 4: + if current_line: + current_line += " " + word + else: + current_line = word + else: + wrapped_text.append(current_line) + current_line = word + if current_line: + wrapped_text.append(current_line) + for i, line in enumerate(wrapped_text): + try: + screen.addstr(5 + i, 2, line) + except curses.error: + pass + try: + screen.addstr(7 + len(wrapped_text), 1, "-" * (self.width - 2)) + except curses.error: + pass + user_input_y = 9 + len(wrapped_text) + for i, char in enumerate(self.user_input): + if i < len(self.text): + if char == self.text[i]: + attr = curses.color_pair(1) + else: + attr = curses.color_pair(2) + try: + screen.addch(user_input_y, 2 + i % (self.width - 4), char, attr) + if (i + 1) % (self.width - 4) == 0: + user_input_y += 1 + except curses.error: + pass + if not self.test_complete: + cursor_x = 2 + self.current_pos % (self.width - 4) + cursor_y = user_input_y + self.current_pos // (self.width - 4) + try: + screen.addch(cursor_y, cursor_x, '_', curses.A_BLINK) + except curses.error: + pass + if self.test_complete: + results = f" Results: {self.wpm} WPM | Accuracy: {self.accuracy}% " + try: + screen.addstr(self.height - 3, self.width // 2 - len(results) // 2, + results, curses.color_pair(3) | curses.A_BOLD) + except curses.error: + pass + restart_msg = " Press ESC to try again " + try: + screen.addstr(self.height - 2, self.width // 2 - len(restart_msg) // 2, restart_msg) + except curses.error: + pass + screen.refresh() +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) + curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) + curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK) + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def main(screen): + """Main function.""" + try: + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + typing_test = TypingTest(width, height) + while True: + typing_test.draw(screen) + key = screen.getch() + if key == 27: + typing_test.reset_test() + elif key == curses.KEY_BACKSPACE or key == 127: + typing_test.remove_char() + elif 32 <= key <= 126: + typing_test.add_char(chr(key)) + time.sleep(0.01) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Typing test ended.") \ No newline at end of file diff --git a/submissions/terminal-art/video_terminal.py b/submissions/terminal-art/video_terminal.py new file mode 100644 index 00000000..9f44b133 --- /dev/null +++ b/submissions/terminal-art/video_terminal.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import os +import time +import sys +import random +import curses +def initialize_screen(): + """Initialize the curses screen.""" + screen = curses.initscr() + curses.start_color() + curses.use_default_colors() + curses.curs_set(0) + curses.noecho() + curses.cbreak() + screen.keypad(True) + screen.timeout(100) + return screen +def cleanup_screen(screen): + """Clean up the curses screen.""" + screen.keypad(False) + curses.nocbreak() + curses.echo() + curses.endwin() +def get_terminal_size(): + """Get the terminal size.""" + return os.get_terminal_size() +def generate_frame(width, height, frame_num): + """Generate an ASCII art frame.""" + ascii_chars = ' .:-=+*#%@' + frame = [] + for y in range(height): + line = [] + for x in range(width): + wave = (x + frame_num) % width + distance = abs(wave - x) / width + noise = random.random() * 0.3 + intensity = (0.5 + 0.5 * (1 - distance)) + noise + char_index = min(int(intensity * len(ascii_chars)), len(ascii_chars) - 1) + line.append(ascii_chars[char_index]) + frame.append(''.join(line)) + return frame +def display_frame(screen, frame): + """Display a frame on the screen.""" + for y, line in enumerate(frame): + try: + screen.addstr(y, 0, line) + except curses.error: + pass + screen.refresh() +def main(screen): + """Main function.""" + try: + frame_num = 0 + while True: + key = screen.getch() + if key == ord('q'): + break + width, height = get_terminal_size() + width = min(width, 80) + height = min(height, 24) + frame = generate_frame(width, height, frame_num) + display_frame(screen, frame) + frame_num += 1 + time.sleep(0.05) + except KeyboardInterrupt: + pass +if __name__ == "__main__": + try: + screen = initialize_screen() + main(screen) + finally: + cleanup_screen(screen) + print("Video simulation ended.") \ No newline at end of file