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

snake_main.py

Uploaded by

dingklefard
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
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
We take content rights seriously. If you suspect this is your content, claim it here.
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