I've written a simple text-based (can be run in terminal) snake game in python. This is a personal project and also one of my very first projects in python. I appreciate any advice or suggestion that make my code more efficient and professional.
main.py
from game import Game
if __name__=="__main__":
game = Game()
game.new_game([15,15])
game.run()
game.py
from time import sleep
from threading import Thread
from snake import Snake
from board import Board
class Game:
def __init__(self):
pass
def new_game(self, board_dim = [10, 10], level = "EASY"):
self.snake = Snake(2, [[2,2],[2,3]], "UP", board_dim)
self.board = Board(board_dim[0], board_dim[1], self.snake.pos)
self.thrd = Thread(target=self.snake.check_arrow_keys)
self.score = 0
def finish_game(self):
print(f"Your score is {self.score}")
self.thrd.join()
exit()
def run(self):
self.thrd.start()
while True:
print('\033c')
if not self.snake.status:
break
self.board.food_process(self.snake.normalize_pos())
if self.board.eaten:
self.score += 1
self.snake.move_toward_direction(increment_size=True)
else:
self.snake.move_toward_direction()
self.board.board_init(self.snake.normalize_pos())
self.board.show_board(self.snake)
print(f"score:{self.score}")
sleep(.2)
self.finish_game()
board.py
from random import randint
class Board:
def __init__(self, columns, rows, fill):
self.board = [[0 for j in range(columns)] for i in range(rows)]
self.fill = fill
self.col = columns
self.rows = rows
self.first = fill[-1]
self.put_food(fill)
def board_init(self, fill):
self.board = [[0 for j in range(self.col)] for i in range(self.rows)]
self.fill = fill
self.first = fill[-1]
for i in self.fill:
if i == self.first:
self.board[i[0]%self.rows][i[1]%self.col] = 2
else:
self.board[i[0]%self.rows][i[1]%self.col] = 1
self.board[self.food[0]][self.food[1]] = 3
def food_process(self, fill):
if self.check_food(fill):
self.eaten = True
self.put_food(fill)
else:
self.eaten = False
def normalize_fill(self, fill):
return [[i[0]%self.rows, i[1]%self.col] for i in fill]
def check_food(self, fill):
if self.food in self.normalize_fill(fill):
return True
return False
def put_food(self, fill):
while True:
x,y = randint(0,self.col-1), randint(0, self.rows-1)
if [x,y] not in self.normalize_fill(fill):
self.board[x][y] = 3
self.food = [x,y]
return
def show_board(self, snake):
board_ = ""
for i in self.board:
for j in i:
if j==1:
board_ += "@|"
elif j==2:
if snake.dir == "UP":
board_ += "^|"
elif snake.dir == "LEFT":
board_ += "<|"
elif snake.dir == "RIGHT":
board_ += ">|"
elif snake.dir == "DOWN":
board_ += "˅|"
elif j==3:
board_ += "*|"
else:
board_ += " |"
board_ += "\n"
board_ += "".join(["_ "*self.col])
board_ += "\n"
print(board_)
snake.py
from random import choice
from threading import Thread
import sys
import select
import tty
import termios
class Snake:
def __init__(self, length, pos, direction, board_size):
if length != len(pos):
raise Exception("Length is not equal to the size of `pos`")
self.len = length
self.pos = pos
self.dir = direction
self.last = pos[-1]
self.first = pos[0]
self.columns= board_size[0]
self.rows = board_size[1]
self.init_l = length
self.status = True
def normalize_pos(self):
return [ [p[0]%self.rows, p[1]%self.columns] for p in self.pos]
def move_toward_direction(self, step = 1, increment_size=False):
temp = self.last[:]
if self.dir.upper() == "UP":
temp[0] -= 1
if self.check(temp):
self.pos.append(temp)
else:
self.__lost()
elif self.dir.upper() == "DOWN":
temp[0] += 1
if self.check(temp):
self.pos.append(temp)
else:
self.__lost()
elif self.dir.upper() == "RIGHT":
temp[1] += 1
if self.check(temp):
self.pos.append(temp)
else:
self.__lost()
elif self.dir.upper() == "LEFT":
temp[1] -= 1
if self.check(temp):
self.pos.append(temp)
else:
self.__lost()
else:
raise Exception(f"Direction not correct!: {self.dir}")
if not increment_size :
self.pos.remove(self.first)
self.first = self.pos[0]
else:
self.len += 1
self.first = self.pos[0]
self.last = self.pos[-1]
def check(self, tmp):
if tmp not in self.normalize_pos() and tmp not in self.pos:
return True
else:
return False
def rand_direction(self):
counter = 0
while True:
tmp = choice(["UP","RIGHT","LEFT","DOWN"])
#chcs = [i for i in ["UP","RIGHT","LEFT","DOWN"] if self.check(i)]
temp = self.last[:]
if tmp == "UP" :
temp[0] -= 1
elif tmp == "DOWN" :
temp[0] += 1
elif tmp == "RIGHT":
temp[1] += 1
elif tmp == "LEFT" :
temp[1] -= 1
else:
raise Exception(f"Direction not correct!: {tmp}")
if self.check(temp):
self.dir = tmp
return
counter += 1
if counter > 32:
raise Exception("No movement is possible")
def check_arrow_keys(self):
old_settings = termios.tcgetattr(sys.stdin)
try:
tty.setcbreak(sys.stdin.fileno())
while 1:
if self.__isData() or self.status:
c = sys.stdin.read(3)
if c == '\x1b[A':
if self.dir != "DOWN":
self.dir = "UP"
elif c == '\x1b[B':
if self.dir != "UP":
self.dir = "DOWN"
elif c == '\x1b[D':
if self.dir != "RIGHT":
self.dir = "LEFT"
elif c == '\x1b[C':
if self.dir != "LEFT":
self.dir = "RIGHT"
else:
pass
else:
return
finally:
termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
def __isData(self):
return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])
def __lost(self):
self.status = False
also this is link of project on github. If you enjoyed the project, please give me star on github :D
Edit: I noticed that if I run the program on the tmux, it will looks very better (It does not blink).