Skip to content
Snippets Groups Projects
Commit d2e08ece authored by Muhamed (aider)'s avatar Muhamed (aider)
Browse files

feat: Implementiere ncurses-basierte Terminal-UI mit Farbunterstützung

parent 49191ca3
No related branches found
No related tags found
No related merge requests found
# Terminal UI Abhängigkeiten
windows-curses>=2.3.0; sys_platform == "win32"
# Für Unix/Linux ist curses bereits in Python enthalten
# Weitere Abhängigkeiten
requests>=2.25.0
import os
import sys
import threading
import time
import msvcrt # Für Windows-Tastatureingabe
import curses
from typing import Optional
class TerminalUI:
......@@ -22,18 +21,20 @@ class TerminalUI:
self.last_command = ""
self.status_message = "Bereit für Befehle"
self.rover_direction = "N" # Nord als Standard
# Terminal-Größe ermitteln
try:
self.terminal_width = os.get_terminal_size().columns
self.terminal_height = os.get_terminal_size().lines
except:
self.stdscr = None
self.terminal_width = 80
self.terminal_height = 24
def clear_screen(self) -> None:
"""Löscht den Bildschirm."""
os.system('cls' if os.name == 'nt' else 'clear')
def init_colors(self) -> None:
"""Initialisiert Farbpaare für ncurses."""
if curses.has_colors():
curses.start_color()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) # Normal
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK) # Rover
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) # Hindernisse
curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Besuchte Positionen
curses.init_pair(5, curses.COLOR_CYAN, curses.COLOR_BLACK) # Header
curses.init_pair(6, curses.COLOR_MAGENTA, curses.COLOR_BLACK) # Status
def get_rover_info(self) -> dict:
"""
......@@ -61,48 +62,141 @@ class TerminalUI:
plateau.set_rover_position(position[0], position[1])
def draw_ui(self) -> None:
"""Zeichnet die komplette Benutzeroberfläche."""
self.clear_screen()
"""Zeichnet die komplette Benutzeroberfläche mit ncurses."""
if not self.stdscr:
return
self.stdscr.clear()
height, width = self.stdscr.getmaxyx()
self.terminal_height = height
self.terminal_width = width
current_line = 0
# Header
print("=" * self.terminal_width)
print("MARS ROVER MISSION CONTROL - Terminal UI".center(self.terminal_width))
print("=" * self.terminal_width)
header_text = "MARS ROVER MISSION CONTROL - Terminal UI"
separator = "=" * width
if current_line < height:
self.stdscr.addstr(current_line, 0, separator[:width], curses.color_pair(5) | curses.A_BOLD)
current_line += 1
if current_line < height:
start_col = max(0, (width - len(header_text)) // 2)
self.stdscr.addstr(current_line, start_col, header_text[:width], curses.color_pair(5) | curses.A_BOLD)
current_line += 1
if current_line < height:
self.stdscr.addstr(current_line, 0, separator[:width], curses.color_pair(5) | curses.A_BOLD)
current_line += 1
# Rover-Informationen
rover_info = self.get_rover_info()
print(f"Position: {rover_info['position']} | Richtung: {rover_info['direction']} | Status: {rover_info['status']}")
print(f"Letzter Befehl: {self.last_command} | {self.status_message}")
print("-" * self.terminal_width)
info_line1 = f"Position: {rover_info['position']} | Richtung: {rover_info['direction']} | Status: {rover_info['status']}"
info_line2 = f"Letzter Befehl: {self.last_command} | {self.status_message}"
if current_line < height:
self.stdscr.addstr(current_line, 0, info_line1[:width], curses.color_pair(6))
current_line += 1
if current_line < height:
self.stdscr.addstr(current_line, 0, info_line2[:width], curses.color_pair(6))
current_line += 1
if current_line < height:
self.stdscr.addstr(current_line, 0, "-" * width, curses.color_pair(1))
current_line += 1
# ASCII-Karte
map_start_line = current_line
map_height = height - current_line - 4 # Platz für Steuerungshinweise
map_width = width - 4
if hasattr(self.mission_control, 'mars') and hasattr(self.mission_control.mars, 'plateau'):
plateau = self.mission_control.mars.plateau
if hasattr(plateau, 'get_ascii_map'):
map_height = self.terminal_height - 10
map_width = self.terminal_width - 4
ascii_map = plateau.get_ascii_map(map_width, map_height)
print(ascii_map)
else:
print("Karte nicht verfügbar")
map_lines = ascii_map.split('\n')
for i, line in enumerate(map_lines):
if current_line + i < height - 3:
self.draw_map_line(current_line + i, line)
current_line += len(map_lines)
else:
# Fallback: Einfache Karte
print("+" + "-" * (self.terminal_width - 4) + "+")
for i in range(self.terminal_height - 12):
row = "|"
for j in range(self.terminal_width - 4):
if (j, self.terminal_height - 12 - i) == rover_info['position']:
row += "R"
self.draw_simple_map(current_line, map_width, map_height, rover_info['position'])
current_line += map_height
else:
row += " "
row += "|"
print(row)
print("+" + "-" * (self.terminal_width - 4) + "+")
# Fallback: Einfache Karte
self.draw_simple_map(current_line, map_width, map_height, rover_info['position'])
current_line += map_height
# Steuerungshinweise
print("-" * self.terminal_width)
print("Steuerung: F=Vorwärts, B=Rückwärts, L=Links, R=Rechts, Q=Beenden")
print("Drücken Sie eine Taste...")
controls_line = height - 2
if controls_line > current_line:
self.stdscr.addstr(controls_line, 0, "-" * width, curses.color_pair(1))
self.stdscr.addstr(controls_line + 1, 0, "Steuerung: F=Vorwärts, B=Rückwärts, L=Links, R=Rechts, Q=Beenden", curses.color_pair(1))
self.stdscr.refresh()
def draw_map_line(self, line_num: int, map_line: str) -> None:
"""Zeichnet eine Zeile der Karte mit Farben."""
if line_num >= self.terminal_height:
return
col = 0
for char in map_line:
if col >= self.terminal_width:
break
color_pair = curses.color_pair(1) # Standard
attrs = 0
if char == 'R':
color_pair = curses.color_pair(2) # Grün für Rover
attrs = curses.A_BOLD
elif char == '#':
color_pair = curses.color_pair(3) # Rot für Hindernisse
attrs = curses.A_BOLD
elif char == '.':
color_pair = curses.color_pair(4) # Gelb für besuchte Positionen
elif char in ['+', '-', '|']:
color_pair = curses.color_pair(1) # Weiß für Rahmen
try:
self.stdscr.addch(line_num, col, char, color_pair | attrs)
except curses.error:
pass # Ignoriere Fehler am Rand des Bildschirms
col += 1
def draw_simple_map(self, start_line: int, map_width: int, map_height: int, rover_pos: tuple) -> None:
"""Zeichnet eine einfache Fallback-Karte."""
# Oberer Rand
if start_line < self.terminal_height:
border = "+" + "-" * (map_width - 2) + "+"
self.stdscr.addstr(start_line, 0, border[:self.terminal_width], curses.color_pair(1))
# Karten-Inhalt
for i in range(1, map_height - 1):
if start_line + i >= self.terminal_height:
break
line = "|"
for j in range(1, map_width - 1):
if (j - 1, map_height - 2 - i) == rover_pos:
line += "R"
else:
line += " "
line += "|"
self.stdscr.addstr(start_line + i, 0, line[:self.terminal_width], curses.color_pair(1))
# Unterer Rand
if start_line + map_height - 1 < self.terminal_height:
border = "+" + "-" * (map_width - 2) + "+"
self.stdscr.addstr(start_line + map_height - 1, 0, border[:self.terminal_width], curses.color_pair(1))
def handle_input(self, key: str) -> bool:
"""
......@@ -144,83 +238,73 @@ class TerminalUI:
return True
def get_key_windows(self) -> Optional[str]:
def get_key(self) -> Optional[str]:
"""
Liest eine Taste unter Windows.
Liest eine Taste mit ncurses.
Returns:
Die gedrückte Taste oder None
"""
if msvcrt.kbhit():
key = msvcrt.getch()
if isinstance(key, bytes):
try:
return key.decode('utf-8')
except:
if not self.stdscr:
return None
return str(key)
return None
def get_key_unix(self) -> Optional[str]:
"""
Liest eine Taste unter Unix/Linux.
Returns:
Die gedrückte Taste oder None
"""
import select
import tty
import termios
if select.select([sys.stdin], [], [], 0.1)[0]:
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setraw(sys.stdin.fileno())
key = sys.stdin.read(1)
return key
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
return None
# Setze Timeout für non-blocking input
self.stdscr.timeout(100) # 100ms timeout
key = self.stdscr.getch()
def get_key(self) -> Optional[str]:
"""
Plattformunabhängige Tastatureingabe.
Returns:
Die gedrückte Taste oder None
"""
if os.name == 'nt': # Windows
return self.get_key_windows()
else: # Unix/Linux
return self.get_key_unix()
if key == -1: # Timeout
return None
elif key == 27: # ESC-Sequenz
return 'Q' # Behandle ESC als Quit
else:
return chr(key).upper() if 0 <= key <= 255 else None
except:
return None
def run(self) -> None:
"""Startet die Terminal-UI Hauptschleife."""
"""Startet die Terminal-UI Hauptschleife mit ncurses."""
try:
# Initialisiere ncurses
self.stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
self.stdscr.keypad(True)
self.init_colors()
self.running = True
print("Starte Mars Rover Terminal UI...")
print("Drücken Sie eine beliebige Taste um zu beginnen...")
# Zeige Startbildschirm
self.stdscr.clear()
height, width = self.stdscr.getmaxyx()
start_msg = "Starte Mars Rover Terminal UI..."
self.stdscr.addstr(height // 2, (width - len(start_msg)) // 2, start_msg)
self.stdscr.addstr(height // 2 + 1, (width - 30) // 2, "Drücken Sie eine Taste...")
self.stdscr.refresh()
# Warte auf erste Eingabe
while True:
key = self.get_key()
if key:
break
time.sleep(0.1)
self.stdscr.getch()
# Hauptschleife
while self.running:
self.draw_ui()
# Warte auf Eingabe
key_pressed = False
while not key_pressed and self.running:
key = self.get_key()
if key:
if not self.handle_input(key):
self.running = False
key_pressed = True
time.sleep(0.05) # Kurze Pause um CPU zu schonen
self.clear_screen()
except KeyboardInterrupt:
self.running = False
finally:
# Cleanup ncurses
if self.stdscr:
curses.nocbreak()
self.stdscr.keypad(False)
curses.echo()
curses.endwin()
print("Mars Rover Terminal UI beendet.")
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment