0% found this document useful (0 votes)
13 views

snake_main.py

Uploaded by

dingklefard
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

snake_main.py

Uploaded by

dingklefard
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 7

import random

from types import FunctionType


from typing import List, Dict, Tuple
import os
import time
from InputHandler import getch_handler as get_input
import threading
import sys

class Position:
def __init__(self, x: int, y: int) -> None:
self.x = x
self.y = y

def __hash__(self) -> int:


return hash((self.x, self.y))

def __eq__(self, other):


return self.x == other.x and self.y == other.y

def __repr__(self):
return f"({self.x}, {self.y})"

class Direction:
def __init__(self, horizontal_component: int, vertical_component: int) -> None:
self.vertical = vertical_component
self.horizontal = horizontal_component

class KeyBinds:
def __init__(self, up: str, right: str, down: str, left: str) -> None:
self.up: str = up
self.right: str = right
self.down: str = down
self.left: str = left

def is_keybind_defined(self, keybind: str) -> bool:


return keybind in [self.up, self.right, self.down, self.left]

def get_key_index(self, keybind: str) -> int:


for index, key in enumerate([self.up, self.right, self.down, self.left]):
if key == keybind:
return index
return -1

def calculate_new_position(position: Position, direction: Direction, delta: int) ->


Position:
return Position(position.x + direction.horizontal * delta, position.y +
direction.vertical * delta)

class Colour:
def __init__(self, red: int, green: int, blue: int) -> None:
self.red: int = red
self.green: int = green
self.blue: int = blue

class Function:
def __init__(self, function, *arguments):
self.function = function
self.arguments = arguments
def execute(self):
try:
self.function(*self.arguments)
except:
raise Exception(self.arguments)

def __eq__(self, other):


return isinstance(other, Function) and self.function == other.function

def generate_random_colour() -> Colour:


return Colour(random.randint(0, 255), random.randint(0, 255), random.randint(0,
255))

class Game:
def __init__(self) -> None:
self.is_running = True
self.width, self.height = os.get_terminal_size();
self.snakes: List[Snake] = []
self.lock = threading.Lock()
self.background_colour = Colour(0, 0, 0)
self.timed_functions: Dict[Function, int] = {}

def register_timed_function(self, timed_function, duration) -> None:


self.timed_functions[timed_function] = duration

def update_timed_objects(self) -> None:


timed_functions_to_delete = []
for timed_function in self.timed_functions:
self.timed_functions[timed_function] -= 1

if self.timed_functions[timed_function] == 0:
timed_function.execute()
timed_functions_to_delete.append(timed_function)

for timed_function in timed_functions_to_delete:


del self.timed_functions[timed_function]

def is_position_in_a_snake(self, position) -> bool:


for snake in self.snakes:
if snake.is_position_in_snake(position):
return True
return False

def register_snake(self, snake) -> None:


self.snakes.append(snake)

def generate_random_position(self, other) -> Position:


while self.is_position_in_a_snake((position := Position(random.randint(0,
game.width - 1), random.randint(0, game.height - 1)))) and position in other:
pass
return position

def move_cursor(position: Position) -> None:


print(f"\033[{position.y};{position.x}H", end="")

def change_text_colour(text_colour: Colour):


print(f"\033[38;2;{ text_colour.red };{ text_colour.green };
{ text_colour.blue};m", end="")

def change_background_colour(background_colour: Colour):


print(f"\033[48;2;{ background_colour.red };{ background_colour.green };
{ background_colour.blue};m", end="")

def create_game():
global game
game = Game()
create_game()

def delete_pixel(position: Position):


move_cursor(position)
change_background_colour(game.background_colour)
print(' ')

def render_pixel(position: Position, background_colour: Colour):


move_cursor(position)
change_background_colour(background_colour)
print(' ')

def render(position, character, text_colour = None, background_colour = None) ->


None:
move_cursor(position)
if text_colour != None:
change_text_colour(text_colour)
if background_colour != None:
change_background_colour(background_colour)
print(character)

class Consumables:
def __init__(self) -> None:
self.consumables: Dict[Position, Consumable] = {}
self.consumable_types: List[type[Consumable]] = []

def delete(self, consumable):


del self.consumables[consumable.position]

def generate_consumable(self):
position = game.generate_random_position(self.consumables)
random.choice(self.consumable_types)(position)

def register_consumable(self, consumable_type):


self.consumable_types.append(consumable_type)

consumables = Consumables()

class Consumable:
def generate(self) -> None:
pass

def consume(self, consumer) -> None:


pass

def move(self, new_position: Position) -> None:


consumables.delete(self)
delete_pixel(self.position)
consumables.consumables[new_position] = self
self.position = new_position
self.render()

def render(self) -> None:


pass

def delete(self) -> None:


consumables.delete(self)
delete_pixel(self.position)

def __init__(self, position: Position):


self.position = position
consumables.consumables[position] = self
self.generate()
self.render()

class Apple(Consumable):
def generate(self) -> None:
pass

def consume(self, consumer) -> None:


self.delete()
consumer.grow()

def render(self) -> None:


render(self.position, ' ', background_colour=Colour(220,20,60))

def attract(snake, magnet_distance) -> None:


for position in list(consumables.consumables):
if position not in consumables.consumables:
continue
consumable = consumables.consumables[position]
if (snake.get_position().x - consumable.position.x) ** 2 +
(snake.get_position().y - consumable.position.y) ** 2 <= magnet_distance ** 2:
dir = Direction(int(snake.get_position().x - consumable.position.x),
int(snake.get_position().y - consumable.position.y))

if abs(dir.horizontal) > abs(dir.vertical):


dir = Direction(int(dir.horizontal/abs(dir.horizontal)) * 1, 0)
elif abs(dir.vertical) > abs(dir.horizontal):
dir = Direction(0, int(dir.vertical/abs(dir.vertical)) * 1)
else:
dir = Direction(int(dir.horizontal/abs(dir.horizontal)),
int((dir.vertical/abs(dir.vertical))))

new_pos = consumable.position
for _ in range(2):
new_pos = calculate_new_position(new_pos, dir, 1)
if new_pos == snake.get_position():
consumable.consume(snake)
break
elif not game.is_position_in_a_snake(new_pos) and not new_pos in
consumables.consumables:
consumable.move(new_pos)
else:
break

class Magnet(Consumable):
def generate(self) -> None:
self.range = random.randint(1, game.width)

def consume(self, consumer) -> None:


if (func := Function(attract, consumer, self.range)) in consumer.functions:
index = consumer.functions.index(func)
other_func = consumer.functions[index]
if other_func.arguments[1] < self.range:
consumer.delete_function(index)
consumer.add_function(func)
else:
consumer.add_function(func)
self.delete()

def render(self) -> None:


render(self.position, ' ', background_colour=Colour(30,144,255))

class Snake:
def __init__(self, position: Position, direction: Direction, keybinds:
KeyBinds, functions = []) -> None:
self.direction: Direction = direction
self.colour: Colour = generate_random_colour()
self.speed: int = 1
self.functions: List[Function | None] = functions
self.free_function_indexes: List[int] = []
self.functions_to_add: List[Tuple[Function, int | None]] = []
self.snake_segments: List[Position] = [position]
self.assign_keybinds(keybinds)
self.alive = True
self.counter = 0
game.register_snake(self)

def get_position(self) -> Position:


return self.snake_segments[-1]

def assign_keybinds(self, keybinds: KeyBinds) -> None:


for snake in game.snakes:
for keybind in [keybinds.up, keybinds.right, keybinds.down,
keybinds.left]:
if snake.keybinds.is_keybind_defined(keybind):
raise Exception(f"Keybind {keybind} already assigned")
self.keybinds: KeyBinds = keybinds

def move_snake(self):
with game.lock:
if not self.alive:
self.counter -= 1
render(Position(0, 5), f"time: {self.counter}")
if self.counter == 0:
self.snake_segments = [game.generate_random_position([])]
self.alive = True
else:
return
if self.grow():
delete_pixel(self.snake_segments.pop(0))
else:
return
if self.alive and len(self.snake_segments) != 0:
for function in self.functions:
if function:
function.execute()

self.register_functions()

def grow(self):
if not game.is_position_in_a_snake(c_pos :=
calculate_new_position(self.snake_segments[-1], self.direction, 1)) and 1 <=
c_pos.x < game.width and 1 <= c_pos.y < game.height:
self.snake_segments.append(c_pos)
if self.snake_segments[-1] in consumables.consumables:
consumables.consumables[self.snake_segments[-1]].consume(self)
render_pixel(c_pos, self.colour)
return True
else:
for segment in self.snake_segments:
delete_pixel(segment)
self.snake_segments = []
render(Position(1, 0), f"Player {game.snakes.index(self) + 1} died!",
Colour(255, 0, 0))
self.alive = False
self.counter = 100
return False

def add_function(self, function: Function) -> int:


if len(self.free_function_indexes) == 0:
self.functions_to_add.append((function, None))
return len(self.free_function_indexes) - 1
index = self.free_function_indexes.pop()
self.functions_to_add.append((function, index))
return index

def register_functions(self) -> None:


for function_index_pair in self.functions_to_add:
if function_index_pair[1] == None:
self.functions.append(function_index_pair[0])
else:
self.functions[function_index_pair[1]] = function_index_pair[0]
self.functions_to_add = []

def delete_function(self, index: int) -> None:


if index == len(self.functions) - 1:
self.functions.pop()
return
self.functions[index] = None
self.free_function_indexes.append(index)

def is_position_in_snake(self, position: Position) -> bool:


return position in self.snake_segments

def movement():
dirs = list(map(lambda x: Direction(*x), [(0, -1), (-1, 0), (0, 1), (1, 0)]))
while True:
key = get_input()
with game.lock:
for snake in game.snakes:
if snake.keybinds.is_keybind_defined(key):
new_dir = dirs[snake.keybinds.get_key_index(key)]
if not (new_dir.vertical + snake.direction.vertical == 0 and
new_dir.horizontal + snake.direction.horizontal == 0):
snake.direction = new_dir
break

def get_keybinds():
string_directions = ["up", "right", "down", "left"]
keybind_list = []
for _ in range(4):
keybind_list.append(get_key_as_a_keybind(string_directions[_],
keybind_list))
return KeyBinds(*keybind_list)

def get_key_as_a_keybind(string, current):


while True:
print(f"For player {len(game.snakes) + 1}, enter the preferred keybind for
the {string} key")
key = get_input()

if key in current:
continue
else:
for index, snake in enumerate(game.snakes):
if snake.keybinds.is_keybind_defined(key):
print(f"Keybind is already defined for player {index + 1}")
continue
break
return key

def main():

snake = Snake(Position(15, 15), Direction(0, -1), KeyBinds('w', 'a', 's', 'd'))


other = Snake(Position(12, 12), Direction(0, -1), get_keybinds())
os.system('clear')
movement_thread = threading.Thread(target = movement)
movement_thread.start()

consumables.register_consumable(Apple)
consumables.register_consumable(Magnet)
while True:
constant = 1/60
p = Position(0, 0)
frame_start = time.time()
for snake in game.snakes:
snake.move_snake()

if random.randint(1, 10) == 1 and len(consumables.consumables) < 50:


consumables.generate_consumable()

if (frame_end := (time.time())) - frame_start < constant:


time.sleep(constant - (frame_end - frame_start))
#render(p, str((time.time() - frame_start)))

main()

You might also like