├── main.py ├── README.md ├── game.py ├── .gitignore ├── board.py └── snake.py /main.py: -------------------------------------------------------------------------------- 1 | from game import Game 2 | 3 | if __name__=="__main__": 4 | game = Game() 5 | game.new_game([15,15]) 6 | game.run() 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # snake_game 2 | Text based simple snake game written in python 3 | 4 | ``` 5 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 6 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 7 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 8 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 9 | |⬜⬜⬜ ▲⬜⬜⬜⬜ ♥⬜⬜⬜⬜⬜⬜| 10 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 11 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 12 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 13 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 14 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 15 | |⬜⬜⬜ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 16 | |⬜⬜⬜ ■ ■ ■⬜⬜⬜⬜⬜⬜⬜⬜⬜| 17 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 18 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 19 | |⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜⬜| 20 | 21 | ``` 22 | Use arrow keys to navigate the snake. If you press any key other than arrow keys the game won't run correctly. 23 | 24 | # How To Run 25 | 1. Download or Clone Project 26 | 2. Navigate To The Root of The Project 27 | 3. Open Terminal 28 | For Linux & Mac, Type ``` python3 main.py ``` & Press Enter 29 | 30 | # Note: 31 | 1. Make Sure Python3 Is Installed On Your System 32 | 2. This program does not work on Windows because it requires the termios module that will work only on Unix. 33 | -------------------------------------------------------------------------------- /game.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | from threading import Thread 3 | from snake import Snake 4 | from board import Board 5 | 6 | 7 | class Game: 8 | 9 | def __init__(self): 10 | pass 11 | 12 | def new_game(self, board_dim = [10, 10], level = "EASY"): 13 | 14 | self.snake = Snake(2, [[2,2],[2,3]], "UP", board_dim) 15 | self.board = Board(board_dim[0], board_dim[1], self.snake.pos) 16 | self.thrd = Thread(target=self.snake.check_arrow_keys) 17 | self.score = 0 18 | 19 | def finish_game(self): 20 | print(f"Your score is {self.score}") 21 | self.thrd.join() 22 | exit() 23 | 24 | def run(self): 25 | 26 | self.thrd.start() 27 | while True: 28 | 29 | print('\033c') 30 | if not self.snake.status: 31 | break 32 | 33 | self.board.food_process(self.snake.normalize_pos()) 34 | if self.board.eaten: 35 | self.score += 1 36 | self.snake.move_toward_direction(increment_size=True) 37 | else: 38 | self.snake.move_toward_direction() 39 | 40 | self.board.board_init(self.snake.normalize_pos()) 41 | self.board.show_board(self.snake) 42 | print(f"score:{self.score}") 43 | sleep(.2) 44 | 45 | self.finish_game() 46 | 47 | 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # I added 132 | *swp* 133 | -------------------------------------------------------------------------------- /board.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | 3 | class Board: 4 | 5 | def __init__(self, columns, rows, fill): 6 | self.board = [[0 for j in range(columns)] for i in range(rows)] 7 | self.fill = fill 8 | self.col = columns 9 | self.rows = rows 10 | self.first = fill[-1] 11 | self.put_food(fill) 12 | 13 | 14 | 15 | def board_init(self, fill): 16 | 17 | self.board = [[0 for j in range(self.col)] for i in range(self.rows)] 18 | self.fill = fill 19 | self.first = fill[-1] 20 | 21 | for i in self.fill: 22 | if i == self.first: 23 | self.board[i[0]%self.rows][i[1]%self.col] = 2 24 | else: 25 | self.board[i[0]%self.rows][i[1]%self.col] = 1 26 | 27 | self.board[self.food[0]][self.food[1]] = 3 28 | 29 | 30 | def food_process(self, fill): 31 | 32 | if self.check_food(fill): 33 | self.eaten = True 34 | self.put_food(fill) 35 | else: 36 | self.eaten = False 37 | 38 | def normalize_fill(self, fill): 39 | return [[i[0]%self.rows, i[1]%self.col] for i in fill] 40 | 41 | def check_food(self, fill): 42 | if self.food in self.normalize_fill(fill): 43 | return True 44 | 45 | return False 46 | 47 | 48 | def put_food(self, fill): 49 | 50 | while True: 51 | x,y = randint(0,self.col-1), randint(0, self.rows-1) 52 | if [x,y] not in self.normalize_fill(fill): 53 | self.board[x][y] = 3 54 | self.food = [x,y] 55 | return 56 | 57 | 58 | 59 | def show_board(self, snake): 60 | 61 | board_ = "|" 62 | for i in self.board: 63 | for j in i: 64 | # Snake 65 | if j==1: 66 | board_ += " ■" 67 | # Arrows of the snake 68 | elif j==2: 69 | if snake.dir == "UP": 70 | board_ += " ▲" 71 | elif snake.dir == "LEFT": 72 | board_ += " ◀" 73 | elif snake.dir == "RIGHT": 74 | board_ += " ▶" 75 | elif snake.dir == "DOWN": 76 | board_ += " ▼" 77 | 78 | elif j==3: 79 | # Eating Stuff 80 | board_ += " ♥" 81 | # Board BackGround 82 | else: 83 | board_ += "⬜" 84 | 85 | board_ += "|\n|" 86 | # board_ += "".join(["_ "*self.col]) 87 | # board_ += "\n`" 88 | 89 | print(board_) 90 | -------------------------------------------------------------------------------- /snake.py: -------------------------------------------------------------------------------- 1 | from random import choice 2 | from threading import Thread 3 | import sys 4 | import select 5 | import tty 6 | import termios 7 | 8 | class Snake: 9 | 10 | def __init__(self, length, pos, direction, board_size): 11 | 12 | if length != len(pos): 13 | raise Exception("Length is not equal to the size of `pos`") 14 | self.len = length 15 | self.pos = pos 16 | self.dir = direction 17 | self.last = pos[-1] 18 | self.first = pos[0] 19 | self.columns= board_size[0] 20 | self.rows = board_size[1] 21 | self.init_l = length 22 | self.status = True 23 | 24 | def normalize_pos(self): 25 | return [ [p[0]%self.rows, p[1]%self.columns] for p in self.pos] 26 | 27 | def move_toward_direction(self, step = 1, increment_size=False): 28 | 29 | temp = self.last[:] 30 | if self.dir.upper() == "UP": 31 | temp[0] -= 1 32 | if self.check(temp): 33 | self.pos.append(temp) 34 | else: 35 | self.__lost() 36 | 37 | elif self.dir.upper() == "DOWN": 38 | temp[0] += 1 39 | if self.check(temp): 40 | self.pos.append(temp) 41 | else: 42 | self.__lost() 43 | 44 | elif self.dir.upper() == "RIGHT": 45 | temp[1] += 1 46 | """ 47 | if temp[1] >= self.columns: 48 | temp[1] = 0 49 | elif temp[1] <= 0: 50 | temp[1] = self.columns-1 51 | """ 52 | if self.check(temp): 53 | self.pos.append(temp) 54 | else: 55 | self.__lost() 56 | 57 | elif self.dir.upper() == "LEFT": 58 | temp[1] -= 1 59 | """ 60 | if temp[1] >= self.columns: 61 | temp[1] = 0 62 | elif temp[1] <= 0: 63 | temp[1] = self.columns-1 64 | """ 65 | if self.check(temp): 66 | self.pos.append(temp) 67 | else: 68 | self.__lost() 69 | 70 | else: 71 | raise Exception(f"Direction not correct!: {self.dir}") 72 | 73 | if not increment_size : 74 | self.pos.remove(self.first) 75 | self.first = self.pos[0] 76 | else: 77 | self.len += 1 78 | self.first = self.pos[0] 79 | self.last = self.pos[-1] 80 | 81 | 82 | 83 | def check(self, tmp): 84 | 85 | if tmp not in self.normalize_pos() and tmp not in self.pos: 86 | return True 87 | else: 88 | return False 89 | 90 | def rand_direction(self): 91 | 92 | counter = 0 93 | 94 | while True: 95 | 96 | tmp = choice(["UP","RIGHT","LEFT","DOWN"]) 97 | #chcs = [i for i in ["UP","RIGHT","LEFT","DOWN"] if self.check(i)] 98 | temp = self.last[:] 99 | if tmp == "UP" : 100 | temp[0] -= 1 101 | 102 | elif tmp == "DOWN" : 103 | temp[0] += 1 104 | 105 | elif tmp == "RIGHT": 106 | temp[1] += 1 107 | 108 | elif tmp == "LEFT" : 109 | temp[1] -= 1 110 | 111 | else: 112 | raise Exception(f"Direction not correct!: {tmp}") 113 | 114 | if self.check(temp): 115 | self.dir = tmp 116 | return 117 | counter += 1 118 | if counter > 32: 119 | raise Exception("No movement is possible") 120 | 121 | 122 | def check_arrow_keys(self): 123 | old_settings = termios.tcgetattr(sys.stdin) 124 | try: 125 | tty.setcbreak(sys.stdin.fileno()) 126 | while 1: 127 | if self.__isData() or self.status: 128 | c = sys.stdin.read(3) 129 | # Up Arrow Key Pressed 130 | if c == '\x1b[A': 131 | if self.dir != "DOWN": 132 | self.dir = "UP" 133 | # Down Arrow Key Pressed 134 | elif c == '\x1b[B': 135 | if self.dir != "UP": 136 | self.dir = "DOWN" 137 | # Left Arrow Key Pressed 138 | elif c == '\x1b[D': 139 | if self.dir != "RIGHT": 140 | self.dir = "LEFT" 141 | # Right Arrow Key Pressed 142 | elif c == '\x1b[C': 143 | if self.dir != "LEFT": 144 | self.dir = "RIGHT" 145 | else: 146 | # TODO: Clear Input Buffer If Arrow Key NOT Pressed to Keep Game Running 147 | pass 148 | else: 149 | return 150 | 151 | finally: 152 | termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) 153 | 154 | def __isData(self): 155 | return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []) 156 | 157 | def __lost(self): 158 | self.status = False 159 | #print(f"You lost the game with score {score}") 160 | #raise Exception(f"You lost the game with score {score}") 161 | 162 | --------------------------------------------------------------------------------