Skip to content

Commit

Permalink
Add files via upload
Browse files Browse the repository at this point in the history
  • Loading branch information
BDadmehr0 authored Oct 17, 2024
1 parent fbdb9a6 commit a51a66d
Show file tree
Hide file tree
Showing 2 changed files with 386 additions and 0 deletions.
384 changes: 384 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,384 @@
import os
import sys
import termios
import signal
import time
import random
import json # Import json module to save player data
from pynput import keyboard

# Character
PLAYER_CH = "P" # character a player
ENEMY_CH = "E" # character a enemy
EMPTY_CH = "_" # character a map empty
BOX_CH = "B" # character a box

# Global variables
player_position = 0 # Initial player position in the map
GAME_STATUS = False # Start with game not running
old_settings = None # Define old_settings globally
keys_pressed = set() # Set to track currently pressed keys
enemies = [] # List to hold enemy positions
boxes = [] # List to hold box positions
score = 0 # Player's score
player_lives = 3 # Player starts with 3 lives
player_data_file = "player_data.json" # File to save player data
show_attack_message = False # To control when to show the attack message
attack_message_shown = False # To make sure message is only shown once

# Allowed keys: only 'w', 'a', 's', 'd', and control keys like Ctrl+Z, Ctrl+C
ALLOWED_KEYS = {
"w",
"a",
"s",
"d",
"e", # Key for attacking
keyboard.Key.ctrl_l,
keyboard.Key.ctrl_r,
keyboard.KeyCode.from_char("x"),
}


# Color definitions
class Colors:
RESET = "\033[0m"
RED = "\033[91m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
BLUE = "\033[94m"
MAGENTA = "\033[95m"
CYAN = "\033[96m"
WHITE = "\033[97m"


class SystemCall:
@staticmethod
def hide_cursor():
sys.stdout.write("\033[?25l")
sys.stdout.flush()

@staticmethod
def show_cursor():
sys.stdout.write("\033[?25h")
sys.stdout.flush()

@staticmethod
def clear_screen():
sys.stdout.write("\033[H\033[J")
sys.stdout.write(f"Player Lives: {player_lives}/3\n") # Display player lives
sys.stdout.flush()

@staticmethod
def get_terminal_size():
return os.get_terminal_size()

@staticmethod
def handle_exit_signal(signum, frame):
SystemCall.show_cursor() # Ensure the cursor is shown
os.system("clear")
global old_settings # Make sure we're using the global old_settings
SystemCall.restore_echo(old_settings)
print("exit")
save_player_data() # Save player data before exiting
sys.exit(0)

@staticmethod
def disable_echo():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
new_settings = termios.tcgetattr(fd)
new_settings[3] = new_settings[3] & ~termios.ECHO # Disable echo
termios.tcsetattr(fd, termios.TCSADRAIN, new_settings)
return old_settings

@staticmethod
def restore_echo(old_settings):
fd = sys.stdin.fileno()
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)


class SystemInputs:
def __init__(self):
self.keys_pressed = set()
self.listener = keyboard.Listener(
on_press=self.on_press, on_release=self.on_release
)
self.listener.start()

def on_press(self, key):
try:
if hasattr(key, "char") and key.char in ALLOWED_KEYS:
self.keys_pressed.add(key.char)
elif key in ALLOWED_KEYS:
self.keys_pressed.add(key)
except AttributeError:
pass

def on_release(self, key):
try:
if hasattr(key, "char") and key.char in ALLOWED_KEYS:
self.keys_pressed.remove(key.char)
elif key in ALLOWED_KEYS:
self.keys_pressed.remove(key)
except KeyError:
pass

def stop(self):
self.listener.stop()


class Map:
def __init__(self):
self.columns, self.lines = (
SystemCall.get_terminal_size()
) # Use the SystemCall method to get terminal size

self.generate_enemies() # Generate enemies when map is created
self.generate_boxes() # Generate boxes when map is created

def generate_boxes(self):
"""Generate boxes with a 0.5% chance on the map."""
global boxes
boxes = [] # Reset boxes for a new game
for i in range(self.columns):
if random.random() < 0.005: # 0.5% chance to spawn a box
boxes.append(i) # Add box at the position i

def generate_enemies(self):
"""Generate enemies with a 20% chance in every 5% of the map."""
global enemies
enemies = [] # Reset enemies for a new game
for i in range(self.columns):
if random.random() < 0.05: # 20% chance to spawn an enemy
enemies.append(i) # Add enemy at the position i

def move_enemies_towards_player(self):
"""Move enemies one step towards the player."""
global enemies, player_lives, GAME_STATUS
new_enemies = []
for enemy_pos in enemies:
if enemy_pos < player_position:
new_pos = enemy_pos + 1 # Move right towards player
elif enemy_pos > player_position:
new_pos = enemy_pos - 1 # Move left towards player
else:
# Enemy reached the player, decrease player's lives
player_lives -= 1
print(
f"{Colors.RED}Enemy hit you! Lives remaining: {player_lives}{Colors.RESET}"
)
if player_lives <= 0:
print(f"{Colors.RED}Game Over!{Colors.RESET}")
GAME_STATUS = False
return
continue # Do not add this enemy to new list, it's dead now

new_enemies.append(new_pos) # Update enemies' positions
enemies = new_enemies
time.sleep(0.1) # Slows down enemy movement

def draw(self):
indexed_line = ""
for i in range(self.columns):
if i == player_position:
indexed_line += PLAYER_CH
elif i in enemies:
indexed_line += f"{Colors.RED}{ENEMY_CH}{Colors.RESET}"
elif i in boxes:
indexed_line += f"{Colors.YELLOW}{BOX_CH}{Colors.RESET}"
elif i == self.columns - 1: # آخرین کاراکتر مپ
indexed_line += ">"
else:
indexed_line += EMPTY_CH

sys.stdout.write(indexed_line + "\n")
sys.stdout.flush()

self.check_attack_option()
# بررسی اینکه آیا بازیکن به مپ جدید منتقل شده
self.check_new_map()

def check_new_map(self):
"""اگر بازیکن به آخرین کاراکتر برسد، او را به مپ جدید منتقل کن."""
global player_position
if player_position == self.columns - 1:
print(f"{Colors.BLUE}You reached the end! Loading new map...{Colors.RESET}")
time.sleep(2)
player_position = 0 # انتقال بازیکن به ابتدای مپ جدید
self.generate_enemies() # دشمن‌های جدید ایجاد می‌کند
self.generate_boxes() # باکس‌های جدید ایجاد می‌کند

def check_attack_option(self):
"""Check if player is next to an enemy or box and show corresponding messages."""
global show_attack_message, attack_message_shown

if (
player_position in enemies
or player_position - 1 in enemies
or player_position + 1 in enemies
):
if not attack_message_shown: # Only show the message once
print(f"{Colors.YELLOW}You can attack by pressing 'E'.{Colors.RESET}")
attack_message_shown = True
show_attack_message = True
time.sleep(2) # Pause for 2 seconds after showing the message
elif player_position in boxes:
print(f"{Colors.GREEN}You found a box! Press 'E' to open it.{Colors.RESET}")


def show_menu():
"""Display the welcome menu and handle user input."""
global GAME_STATUS, old_settings
while True:
os.system("clear") # Clear the screen
print("Welcome to the Game!")
print("1. Start Game")
print("2. Help")
print("3. Exit")

# Enable echo for user input in the menu
old_settings = SystemCall.restore_echo(old_settings)
choice = input("Please choose an option (1, 2 or 3): ")

# Restore echo to disabled after menu input
old_settings = SystemCall.disable_echo()

if choice == "1":
load_player_data() # Load player data before starting
GAME_STATUS = True
break
elif choice == "2":
show_help()
elif choice == "3":
SystemCall.handle_exit_signal(None, None)
else:
print("Invalid choice. Please choose 1, 2 or 3.")
time.sleep(1) # Pause before showing the menu again


def show_help():
"""Display help information and handle pagination."""
help_texts = [
"Help - Page 1: This is a simple game where you control a character.",
"Use 'a' to move left and 'd' to move right.",
"Press 'E' to attack enemies.",
"Press Ctrl+C to exit the game at any time.",
"",
"Help - Page 2: The objective of the game is to navigate through the map.",
"You can explore different areas and avoid obstacles.",
"Have fun playing!",
"",
"Press any key to return to the main menu.",
]

for page in range(len(help_texts)):
os.system("clear") # Clear the screen
print(help_texts[page])
if page < len(help_texts) - 1:
input("Press Enter to go to the next page...")
else:
input("Press Enter to return to the main menu...")


def save_player_data():
"""Save player position, score, lives, and enemies to a JSON file."""
player_data = {
"position": player_position,
"score": score,
"lives": player_lives,
"enemies": enemies, # Save enemies positions
}
with open(player_data_file, "w") as file:
json.dump(player_data, file)


def load_player_data():
"""Load player position, score, lives, and enemies from a JSON file."""
global player_position, score, player_lives, enemies
if os.path.exists(player_data_file):
with open(player_data_file, "r") as file:
player_data = json.load(file)
player_position = player_data.get(
"position", 0
) # Default to 0 if not found
score = player_data.get("score", 0) # Default to 0 if not found
player_lives = player_data.get(
"lives", 3
) # Default to 3 lives if not found
enemies = player_data.get(
"enemies", []
) # Default to empty list if not found


# Register signal handlers
signal.signal(signal.SIGTSTP, SystemCall.handle_exit_signal) # Handles Ctrl+Z (SIGTSTP)
signal.signal(signal.SIGINT, SystemCall.handle_exit_signal) # Handles Ctrl+C (SIGINT)

# Disable input and save old settings
old_settings = SystemCall.disable_echo()

# Create an instance of SystemInputs
input_handler = SystemInputs()

map_instance = Map() # Create a new map with enemies

# Show the welcome menu
show_menu()

while GAME_STATUS:
SystemCall.hide_cursor() # Hide the cursor
try:
SystemCall.clear_screen() # Clears the screen and shows player lives

map_instance.draw() # Draw the game map

# Move enemies towards player each frame
map_instance.move_enemies_towards_player()

# Check for attack or box opening
if "e" in input_handler.keys_pressed:
if player_position in enemies:
enemies.remove(player_position) # حذف دشمن
score += 100 # افزایش امتیاز
elif player_position - 1 in enemies:
enemies.remove(player_position - 1) # حذف دشمن سمت چپ
score += 100
elif player_position + 1 in enemies:
enemies.remove(player_position + 1) # حذف دشمن سمت راست
score += 100
elif player_position in boxes:
boxes.remove(player_position) # حذف باکس بعد از باز کردن
reward = random.choice(["Extra Life", "Score Boost", "Nothing"])
reward = random.choice(["Extra Life", "Score Boost", "Speed Boost", "Penalty"])
if reward == "Extra Life":
player_lives += 1
print(f"{Colors.GREEN}You received an extra life! Lives: {player_lives}{Colors.RESET}")
elif reward == "Score Boost":
score += 50
print(f"{Colors.GREEN}You received a score boost! Score: {score}{Colors.RESET}")
elif reward == "Speed Boost":
# بهبود سرعت حرکت بازیکن برای مدت کوتاهی
speed_boost = True
elif reward == "Penalty":
player_lives -= 1
print(f"{Colors.RED}The box was cursed! You lost a life! Lives: {player_lives}{Colors.RESET}")
else:
print(f"{Colors.YELLOW}The box was empty!{Colors.RESET}")
time.sleep(0.5)

# Adjust player position based on key presses
if "a" in input_handler.keys_pressed and player_position > 0:
player_position -= 1
elif (
"d" in input_handler.keys_pressed
and player_position < map_instance.columns - 1
):
player_position += 1

time.sleep(0.01) # Slow down the game loop a bit
except KeyboardInterrupt:
SystemCall.handle_exit_signal(None, None)


input_handler.stop() # Stop the listener
SystemCall.restore_echo(old_settings)
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
colorama
pynput

0 comments on commit a51a66d

Please sign in to comment.