funhub-snake 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- funhub_snake-1.0.0/PKG-INFO +25 -0
- funhub_snake-1.0.0/README.md +12 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/PKG-INFO +25 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/SOURCES.txt +9 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/dependency_links.txt +1 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/entry_points.txt +2 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/requires.txt +3 -0
- funhub_snake-1.0.0/funhub_snake.egg-info/top_level.txt +1 -0
- funhub_snake-1.0.0/setup.cfg +4 -0
- funhub_snake-1.0.0/setup.py +24 -0
- funhub_snake-1.0.0/snake.py +595 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: funhub-snake
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI snake game for FunHud app-launcher
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
Requires-Dist: pynput>=1.7.6
|
|
7
|
+
Requires-Dist: colorama>=0.4.6
|
|
8
|
+
Requires-Dist: numpy>=1.24.0
|
|
9
|
+
Dynamic: description
|
|
10
|
+
Dynamic: description-content-type
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: summary
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
A simply CLI snake game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
```bash
|
|
19
|
+
pip install funhub-snake
|
|
20
|
+
```
|
|
21
|
+
## Quick start
|
|
22
|
+
Write in terminal this command to start game:
|
|
23
|
+
```bash
|
|
24
|
+
funhub-snake
|
|
25
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: funhub-snake
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI snake game for FunHud app-launcher
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
Requires-Dist: pynput>=1.7.6
|
|
7
|
+
Requires-Dist: colorama>=0.4.6
|
|
8
|
+
Requires-Dist: numpy>=1.24.0
|
|
9
|
+
Dynamic: description
|
|
10
|
+
Dynamic: description-content-type
|
|
11
|
+
Dynamic: requires-dist
|
|
12
|
+
Dynamic: summary
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
A simply CLI snake game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
```bash
|
|
19
|
+
pip install funhub-snake
|
|
20
|
+
```
|
|
21
|
+
## Quick start
|
|
22
|
+
Write in terminal this command to start game:
|
|
23
|
+
```bash
|
|
24
|
+
funhub-snake
|
|
25
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
snake
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="funhub-snake",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
py_modules=["snake"],
|
|
10
|
+
description="CLI snake game for FunHud app-launcher",
|
|
11
|
+
long_description=open("README.md", encoding="utf-8").read(),
|
|
12
|
+
long_description_content_type="text/markdown",
|
|
13
|
+
packages=find_packages(),
|
|
14
|
+
install_requires=[
|
|
15
|
+
"pynput>=1.7.6",
|
|
16
|
+
"colorama>=0.4.6",
|
|
17
|
+
"numpy>=1.24.0",
|
|
18
|
+
],
|
|
19
|
+
entry_points={
|
|
20
|
+
'console_scripts': [
|
|
21
|
+
'funhub-snake=snake:main',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
)
|
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from collections import deque
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import random
|
|
7
|
+
import shutil
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from pynput import keyboard
|
|
11
|
+
import sys
|
|
12
|
+
from colorama import init, Cursor, Fore, Back, Style
|
|
13
|
+
import ctypes
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
init(autoreset=True)
|
|
17
|
+
|
|
18
|
+
if sys.platform == 'win32':
|
|
19
|
+
import msvcrt
|
|
20
|
+
kernel32 = ctypes.windll.kernel32
|
|
21
|
+
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
|
22
|
+
else:
|
|
23
|
+
import termios
|
|
24
|
+
import tty
|
|
25
|
+
fd = sys.stdin.fileno()
|
|
26
|
+
oldSettings = termios.tcgetattr(fd)
|
|
27
|
+
tty.setcbreak(fd)
|
|
28
|
+
|
|
29
|
+
stop_event = threading.Event()
|
|
30
|
+
print_lock = threading.Lock()
|
|
31
|
+
pause_event = threading.Event()
|
|
32
|
+
|
|
33
|
+
MIN_TERMINAL_COLS, MIN_TERMINAL_ROWS = 150, 50
|
|
34
|
+
SMALL_FIELD_SIZE, MEDIUM_FIELD_SIZE, BIG_FIELD_SIZE, LARGE_FIELD_SIZE = 10, 20, 30, 40
|
|
35
|
+
SLOW_SNAKE_SPEED, MODERATE_SNAKE_SPEED, FAST_SNAKE_SPEED = 0.15, 0.1, 0.085
|
|
36
|
+
|
|
37
|
+
userStatsPath = Path.home() / ".funhub" / "snake" / "userRecords.json"
|
|
38
|
+
|
|
39
|
+
os.makedirs(os.path.dirname(userStatsPath), exist_ok=True)
|
|
40
|
+
|
|
41
|
+
if not os.path.exists(userStatsPath):
|
|
42
|
+
with open(userStatsPath, 'w') as f:
|
|
43
|
+
json.dump({"small":{"slow":0, "moderate":0, "fast":0}, "medium":{"slow":0, "moderate":0, "fast":0}, "big":{"slow":0, "moderate":0, "fast":0}, "large":{"slow":0, "moderate":0, "fast":0}}, f, ensure_ascii=False)
|
|
44
|
+
|
|
45
|
+
gameField = []
|
|
46
|
+
gameFieldSize = 0
|
|
47
|
+
snakeSpeed = 0
|
|
48
|
+
moveDirection = 'right'
|
|
49
|
+
snakeColor = 'red'
|
|
50
|
+
snakeColorsDict = {'red':(9, 1, 124),'yellow':(178 ,172 , 166) ,'blue':(12, 4, 20), 'light blue':(39, 33, 27), 'purple':(99, 56, 55)}
|
|
51
|
+
|
|
52
|
+
gameField = np.array([])
|
|
53
|
+
|
|
54
|
+
logo = r'''
|
|
55
|
+
██████ ███▄ █ ▄▄▄ ██ ▄█▀▓█████
|
|
56
|
+
▒██ ▒ ██ ▀█ █ ▒████▄ ██▄█▒ ▓█ ▀
|
|
57
|
+
░ ▓██▄ ▓██ ▀█ ██▒▒██ ▀█▄ ▓███▄░ ▒███
|
|
58
|
+
▒ ██▒▓██▒ ▐▌██▒░██▄▄▄▄██ ▓██ █▄ ▒▓█ ▄
|
|
59
|
+
▒██████▒▒▒██░ ▓██░ ▓█ ▓██▒▒██▒ █▄░▒████▒
|
|
60
|
+
▒ ▒▓▒ ▒ ░░ ▒░ ▒ ▒ ▒▒ ▓▒█░▒ ▒▒ ▓▒░░ ▒░ ░
|
|
61
|
+
░ ░▒ ░ ░░ ░░ ░ ▒░ ▒ ▒▒ ░░ ░▒ ▒░ ░ ░ ░
|
|
62
|
+
░ ░ ░ ░ ░ ░ ░ ▒ ░ ░░ ░ ░
|
|
63
|
+
░ ░ ░ ░░ ░ ░ ░
|
|
64
|
+
|
|
65
|
+
'''
|
|
66
|
+
|
|
67
|
+
class cellObject():
|
|
68
|
+
def __init__(self):
|
|
69
|
+
self.isApple = False
|
|
70
|
+
self.isSnakeHead = False
|
|
71
|
+
self.isSnakeBody = False
|
|
72
|
+
self.isStone = False
|
|
73
|
+
self.grassPattern = (random.choice([" "," ", " #", "# ", "▒▒", "▓▓", "* ", " *", '. ', ' .', '▒▓', '▒ ']))
|
|
74
|
+
self.grassColors = (random.choice([2, 28, 34]), random.choice([70, 64, 10]))
|
|
75
|
+
|
|
76
|
+
def changeGrassPattern(self):
|
|
77
|
+
self.grassPattern = (random.choice([" ", " #", "# ", "▒▒", "▓▓", "* ", " *", '..']))
|
|
78
|
+
self.grassColors = (random.choice([ 2, 34]), random.choice([70, 76, 10]))
|
|
79
|
+
|
|
80
|
+
def getStr(self, direction=None):
|
|
81
|
+
snakeHeadColor = snakeColorsDict[snakeColor][0]
|
|
82
|
+
snakeBodyBackgroundColor = snakeColorsDict[snakeColor][1]
|
|
83
|
+
snakeBodyFountColor = snakeColorsDict[snakeColor][2]
|
|
84
|
+
appleBackgroundColor = 88
|
|
85
|
+
appleFontColor = 34
|
|
86
|
+
stoneBacgroundColor = 245
|
|
87
|
+
stoneFontColor = 240
|
|
88
|
+
if self.isSnakeHead and direction:
|
|
89
|
+
match direction:
|
|
90
|
+
case 'up':
|
|
91
|
+
return f"\033[38;5;0m\033[48;5;{snakeHeadColor}m" + "''" + "\033[0m"
|
|
92
|
+
case 'down':
|
|
93
|
+
return f"\033[38;5;0m\033[48;5;{snakeHeadColor}m" + ".." + "\033[0m"
|
|
94
|
+
case 'right':
|
|
95
|
+
return f"\033[38;5;0m\033[48;5;{snakeHeadColor}m" + " :" + "\033[0m"
|
|
96
|
+
case 'left':
|
|
97
|
+
return f"\033[38;5;0m\033[48;5;{snakeHeadColor}m" + ": " + "\033[0m"
|
|
98
|
+
elif self.isSnakeBody:
|
|
99
|
+
return f"\033[38;5;{snakeBodyFountColor}m\033[48;5;{snakeBodyBackgroundColor}m" + "##" + "\033[0m"
|
|
100
|
+
elif self.isApple:
|
|
101
|
+
return f"\033[38;5;{appleFontColor}m\033[48;5;{appleBackgroundColor}m" + "@/" + "\033[0m"
|
|
102
|
+
elif self.isStone:
|
|
103
|
+
return f"\033[38;5;{stoneFontColor}m\033[48;5;{stoneBacgroundColor}m" + "L " + "\033[0m"
|
|
104
|
+
else:
|
|
105
|
+
return f"\033[38;5;{self.grassColors[1]}m\033[48;5;{self.grassColors[0]}m" + self.grassPattern + "\033[0m"
|
|
106
|
+
|
|
107
|
+
class snakeObject():
|
|
108
|
+
def __init__(self):
|
|
109
|
+
self.coordinateHistory = deque([])
|
|
110
|
+
self.direction = 'right'
|
|
111
|
+
self.bodyCount = 2
|
|
112
|
+
|
|
113
|
+
def setNewSnakePosition(self, gameField):
|
|
114
|
+
if not checkWin(gameFieldSize, self) and not checkDefeat(gameFieldSize, self):
|
|
115
|
+
if gameField[self.coordinateHistory[0]].isApple:
|
|
116
|
+
self.bodyCount += 1
|
|
117
|
+
gameField[self.coordinateHistory[0]].isApple = False
|
|
118
|
+
placeApple(gameField)
|
|
119
|
+
|
|
120
|
+
gameField[self.coordinateHistory[0]].isSnakeHead = True
|
|
121
|
+
gameField[self.coordinateHistory[1]].isSnakeHead = False
|
|
122
|
+
gameField[self.coordinateHistory[1]].isSnakeBody = True
|
|
123
|
+
|
|
124
|
+
if self.bodyCount < len(self.coordinateHistory) - 1:
|
|
125
|
+
gameField[self.coordinateHistory.pop()].isSnakeBody = False
|
|
126
|
+
|
|
127
|
+
def move(self, gameField):
|
|
128
|
+
y, x = self.coordinateHistory[0]
|
|
129
|
+
match self.direction:
|
|
130
|
+
case 'up':
|
|
131
|
+
self.coordinateHistory.appendleft((y - 1, x))
|
|
132
|
+
self.setNewSnakePosition(gameField)
|
|
133
|
+
|
|
134
|
+
case 'down':
|
|
135
|
+
self.coordinateHistory.appendleft((y + 1, x))
|
|
136
|
+
self.setNewSnakePosition(gameField)
|
|
137
|
+
|
|
138
|
+
case 'right':
|
|
139
|
+
self.coordinateHistory.appendleft((y, x + 1))
|
|
140
|
+
self.setNewSnakePosition(gameField)
|
|
141
|
+
|
|
142
|
+
case 'left':
|
|
143
|
+
self.coordinateHistory.appendleft((y, x - 1))
|
|
144
|
+
self.setNewSnakePosition(gameField)
|
|
145
|
+
|
|
146
|
+
def changeDirection(self, newDirection):
|
|
147
|
+
turnAroundPositions = {'up':'down', 'down':'up', 'left':'right', 'right':'left'}
|
|
148
|
+
if newDirection != turnAroundPositions[self.direction]:
|
|
149
|
+
self.direction = newDirection
|
|
150
|
+
|
|
151
|
+
def printField(snake, isWin = False, isDefeat = False, isPaused=False):
|
|
152
|
+
with print_lock:
|
|
153
|
+
global gameFieldSize, logo
|
|
154
|
+
termCols, termRows = os.get_terminal_size()
|
|
155
|
+
resulStr = ''
|
|
156
|
+
newlogo = logo if gameFieldSize < 40 else 'snake'
|
|
157
|
+
centeredLogo = ''
|
|
158
|
+
|
|
159
|
+
for i in newlogo.splitlines():
|
|
160
|
+
centeredLogo += ' ' * int((os.get_terminal_size().columns - len(i))//2) + i + '\n'
|
|
161
|
+
|
|
162
|
+
countApples = snake.bodyCount - 2
|
|
163
|
+
|
|
164
|
+
counter = 'Apples eat: ' + str(countApples)
|
|
165
|
+
counter = ' ' * int((os.get_terminal_size().columns - len(counter))//2) + counter + '\n'
|
|
166
|
+
|
|
167
|
+
for r in range(gameFieldSize):
|
|
168
|
+
row_str = ''
|
|
169
|
+
for c in range(gameFieldSize):
|
|
170
|
+
cell = gameField[r][c]
|
|
171
|
+
char = cell.getStr() if not cell.isSnakeHead else cell.getStr(snake.direction)
|
|
172
|
+
row_str += char
|
|
173
|
+
resulStr += row_str + '\n'
|
|
174
|
+
|
|
175
|
+
centeredResulStr = ''
|
|
176
|
+
|
|
177
|
+
for i in resulStr.splitlines():
|
|
178
|
+
centeredResulStr += ' ' * int((termCols - (gameFieldSize * 2))//2) + i + '\n'
|
|
179
|
+
|
|
180
|
+
centeredResulStr = centeredLogo + '\n' + counter + '\n' + centeredResulStr
|
|
181
|
+
upperSpace = (termRows - len(centeredResulStr.splitlines()))//2
|
|
182
|
+
|
|
183
|
+
endGameText = ''
|
|
184
|
+
if isWin:
|
|
185
|
+
endGameText = 'You win!'
|
|
186
|
+
elif isDefeat:
|
|
187
|
+
endGameText = 'You lose!'
|
|
188
|
+
elif isPaused:
|
|
189
|
+
endGameText = 'Game is paused...'
|
|
190
|
+
|
|
191
|
+
if not endGameText:
|
|
192
|
+
centeredResulStr = '\n' * upperSpace + centeredResulStr + '\n' * (termRows - upperSpace - len(centeredResulStr.splitlines()) - 1) + '↑/↓/←/→ – move, ESC - pause, R - restart, Ctrl + C – quit'
|
|
193
|
+
else:
|
|
194
|
+
endGameText = ' ' * int((termCols - len(endGameText))//2) + endGameText
|
|
195
|
+
centeredResulStr = '\n' * upperSpace + centeredResulStr + '\n\n' + endGameText +'\n' * (termRows - upperSpace - len(centeredResulStr.splitlines()) - 3) + '↑/↓/←/→ – move, ESC - pause, R - restart, Ctrl + C – quit'
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
sys.stdout.write(Cursor.POS(1, 1))
|
|
199
|
+
sys.stdout.write(centeredResulStr)
|
|
200
|
+
sys.stdout.flush()
|
|
201
|
+
|
|
202
|
+
def settingsMenu():
|
|
203
|
+
global gameFieldSize, snakeSpeed, gameField, snakeColor
|
|
204
|
+
try:
|
|
205
|
+
prew_cols, prew_rows = 0, 0
|
|
206
|
+
selectedSetting = 0
|
|
207
|
+
|
|
208
|
+
with open(userStatsPath, 'r', encoding='utf-8') as f:
|
|
209
|
+
userStats = json.load(f)
|
|
210
|
+
|
|
211
|
+
class settingsObject():
|
|
212
|
+
def __init__(self, name, content, selectedItem):
|
|
213
|
+
self.isSelected = False
|
|
214
|
+
self.content = content
|
|
215
|
+
self.selectedItem = selectedItem % len(self.content)
|
|
216
|
+
self.name = name
|
|
217
|
+
|
|
218
|
+
def getStr(self):
|
|
219
|
+
strSize = 30
|
|
220
|
+
if self.isSelected:
|
|
221
|
+
name = "*" + self.name
|
|
222
|
+
settings = (" < " if self.selectedItem != 0 else " ") + self.content[self.selectedItem] + (" >" if self.selectedItem < len(self.content) - 1 else " ")
|
|
223
|
+
str = name + ' ' * ((strSize - len(name) - len(settings))//2) + settings
|
|
224
|
+
return str
|
|
225
|
+
else:
|
|
226
|
+
name = " " + self.name
|
|
227
|
+
settings = (" < " if self.selectedItem != 0 else " ") + self.content[self.selectedItem] + (" >" if self.selectedItem < len(self.content) - 1 else " ")
|
|
228
|
+
str = name + ' ' * ((strSize - len(name) - len(settings))//2) + settings
|
|
229
|
+
return str
|
|
230
|
+
|
|
231
|
+
def changeSelectedItem(self, direction):
|
|
232
|
+
if direction == 'next':
|
|
233
|
+
self.selectedItem = (self.selectedItem + 1) % len(self.content)
|
|
234
|
+
elif direction == 'prev':
|
|
235
|
+
self.selectedItem = (self.selectedItem - 1) % len(self.content)
|
|
236
|
+
|
|
237
|
+
settingsMenu = [settingsObject('Field size', ["small", 'medium', 'big', 'large'], 1), settingsObject('Snake speed', ['slow', 'moderate', 'fast'], 1), settingsObject('Snake color', list(snakeColorsDict.keys()), 0),"Start"]
|
|
238
|
+
|
|
239
|
+
def printMenu(changeSelection=None):
|
|
240
|
+
menuToPrint = ''
|
|
241
|
+
termCols, termRows = os.get_terminal_size()
|
|
242
|
+
centeredStr = ''
|
|
243
|
+
nonlocal prew_cols, prew_rows, settingsMenu, selectedSetting
|
|
244
|
+
|
|
245
|
+
if termCols != prew_cols or termRows != prew_rows:
|
|
246
|
+
prew_cols, prew_rows = termCols, termRows
|
|
247
|
+
sys.stdout.write('\033[2J\033[H')
|
|
248
|
+
sys.stdout.flush()
|
|
249
|
+
|
|
250
|
+
if isinstance(settingsMenu[selectedSetting], settingsObject):
|
|
251
|
+
settingsMenu[selectedSetting].isSelected = False
|
|
252
|
+
|
|
253
|
+
if changeSelection:
|
|
254
|
+
if changeSelection == 'up':
|
|
255
|
+
selectedSetting = (selectedSetting - 1) % len(settingsMenu)
|
|
256
|
+
elif changeSelection == 'down':
|
|
257
|
+
selectedSetting = (selectedSetting + 1) % len(settingsMenu)
|
|
258
|
+
|
|
259
|
+
if isinstance(settingsMenu[selectedSetting], settingsObject):
|
|
260
|
+
settingsMenu[selectedSetting].isSelected = True
|
|
261
|
+
|
|
262
|
+
for i in logo.splitlines():
|
|
263
|
+
centeredStr += ' ' * int((termCols - len(i))//2) + i + '\n'
|
|
264
|
+
|
|
265
|
+
menuToPrint += centeredStr
|
|
266
|
+
|
|
267
|
+
record = "Your record: " + str( userStats[settingsMenu[0].content[settingsMenu[0].selectedItem]][settingsMenu[1].content[settingsMenu[1].selectedItem]])
|
|
268
|
+
|
|
269
|
+
menuToPrint += ' ' * int((termCols - len(record))//2) + record + '\n' + '\n'
|
|
270
|
+
|
|
271
|
+
for i in settingsMenu:
|
|
272
|
+
if isinstance(i, settingsObject):
|
|
273
|
+
if i.name != "Snake color":
|
|
274
|
+
menuToPrint += ' ' * int((termCols - len(i.getStr()))//2) + i.getStr() + '\n'
|
|
275
|
+
else:
|
|
276
|
+
menuToPrint += ' ' * int((termCols - len(i.getStr()))//2) + i.getStr() + " " + (f"\033[38;5;{snakeColorsDict[i.content[i.selectedItem]][2]}m\033[48;5;{snakeColorsDict[i.content[i.selectedItem]][1]}m" + "##" + "\033[0m" + f"\033[38;5;0m\033[48;5;{snakeColorsDict[i.content[i.selectedItem]][0]}m" + " :" + "\033[0m") + '\n'
|
|
277
|
+
else:
|
|
278
|
+
if selectedSetting != len(settingsMenu) - 1:
|
|
279
|
+
menuToPrint += '\n' + ' ' * int((termCols - len(i))//2) + ' ' + i + '\n'
|
|
280
|
+
else:
|
|
281
|
+
menuToPrint += '\n' + ' ' * int((termCols - len(i))//2) + '*' + i + '\n'
|
|
282
|
+
|
|
283
|
+
upperSpace = (termRows - len(menuToPrint.splitlines()))//2
|
|
284
|
+
|
|
285
|
+
menuToPrint = '\n' * upperSpace + menuToPrint + '\n' * (termRows - upperSpace - len(menuToPrint.splitlines()) - 1) + '↑/↓/←/→ – move, Enter – select, Ctrl + C – quit'
|
|
286
|
+
|
|
287
|
+
sys.stdout.write('\033[2J\033[H')
|
|
288
|
+
sys.stdout.flush()
|
|
289
|
+
sys.stdout.write(Cursor.POS(1, 1))
|
|
290
|
+
sys.stdout.write(menuToPrint)
|
|
291
|
+
sys.stdout.flush()
|
|
292
|
+
|
|
293
|
+
sys.stdout.write('\033[2J\033[H')
|
|
294
|
+
sys.stdout.flush()
|
|
295
|
+
printMenu()
|
|
296
|
+
with keyboard.Events() as events:
|
|
297
|
+
while True:
|
|
298
|
+
|
|
299
|
+
event = events.get(1.0)
|
|
300
|
+
if event is None:
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
if isinstance(event, keyboard.Events.Press):
|
|
304
|
+
key = event.key
|
|
305
|
+
if key == keyboard.Key.up:
|
|
306
|
+
printMenu(changeSelection='up')
|
|
307
|
+
|
|
308
|
+
elif key == keyboard.Key.down:
|
|
309
|
+
printMenu(changeSelection='down')
|
|
310
|
+
|
|
311
|
+
elif key == keyboard.Key.right:
|
|
312
|
+
if isinstance(settingsMenu[selectedSetting], settingsObject):
|
|
313
|
+
settingsMenu[selectedSetting].changeSelectedItem('next')
|
|
314
|
+
printMenu()
|
|
315
|
+
|
|
316
|
+
elif key == keyboard.Key.left:
|
|
317
|
+
if isinstance(settingsMenu[selectedSetting], settingsObject):
|
|
318
|
+
settingsMenu[selectedSetting].changeSelectedItem('prev')
|
|
319
|
+
printMenu()
|
|
320
|
+
|
|
321
|
+
elif key == keyboard.Key.enter:
|
|
322
|
+
if not isinstance(settingsMenu[selectedSetting], settingsObject):
|
|
323
|
+
match settingsMenu[0].content[settingsMenu[0].selectedItem]:
|
|
324
|
+
case 'small':
|
|
325
|
+
gameFieldSize = SMALL_FIELD_SIZE
|
|
326
|
+
case 'medium':
|
|
327
|
+
gameFieldSize = MEDIUM_FIELD_SIZE
|
|
328
|
+
case 'big':
|
|
329
|
+
gameFieldSize = BIG_FIELD_SIZE
|
|
330
|
+
case 'large':
|
|
331
|
+
gameFieldSize = LARGE_FIELD_SIZE
|
|
332
|
+
|
|
333
|
+
match settingsMenu[1].content[settingsMenu[1].selectedItem]:
|
|
334
|
+
case 'slow':
|
|
335
|
+
snakeSpeed = SLOW_SNAKE_SPEED
|
|
336
|
+
case 'moderate':
|
|
337
|
+
snakeSpeed = MODERATE_SNAKE_SPEED
|
|
338
|
+
case 'fast':
|
|
339
|
+
snakeSpeed = FAST_SNAKE_SPEED
|
|
340
|
+
|
|
341
|
+
snakeColor = settingsMenu[2].content[settingsMenu[2].selectedItem]
|
|
342
|
+
|
|
343
|
+
gameField = np.array([[ cellObject() for _ in range(gameFieldSize)]for _ in range(gameFieldSize)])
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
except KeyboardInterrupt:
|
|
347
|
+
os._exit(0)
|
|
348
|
+
except Exception as e:
|
|
349
|
+
print(e)
|
|
350
|
+
os._exit(1)
|
|
351
|
+
|
|
352
|
+
def forceTerminalSize(cols, rows):
|
|
353
|
+
if sys.platform == 'win32':
|
|
354
|
+
os.system(f'mode con: cols={cols} lines={rows}')
|
|
355
|
+
|
|
356
|
+
kernel32 = ctypes.windll.kernel32
|
|
357
|
+
hOut = kernel32.GetStdHandle(-11)
|
|
358
|
+
res = ctypes.wintypes._COORD(cols, rows)
|
|
359
|
+
kernel32.SetConsoleScreenBufferSize(hOut, res)
|
|
360
|
+
else:
|
|
361
|
+
sys.stdout.write(f"\x1b[8;{rows};{cols}t")
|
|
362
|
+
sys.stdout.flush()
|
|
363
|
+
|
|
364
|
+
def checkTerminalSize():
|
|
365
|
+
cols, rows = shutil.get_terminal_size()
|
|
366
|
+
if cols < MIN_TERMINAL_COLS or rows < MIN_TERMINAL_ROWS:
|
|
367
|
+
forceTerminalSize(MIN_TERMINAL_COLS, MIN_TERMINAL_ROWS)
|
|
368
|
+
cols, rows = shutil.get_terminal_size()
|
|
369
|
+
if cols < MIN_TERMINAL_COLS or rows < MIN_TERMINAL_ROWS:
|
|
370
|
+
return False
|
|
371
|
+
return True
|
|
372
|
+
|
|
373
|
+
def checkDefeat(gameFieldSize, snake):
|
|
374
|
+
y, x = snake.coordinateHistory[0]
|
|
375
|
+
|
|
376
|
+
if y >= gameFieldSize or y < 0:
|
|
377
|
+
return True
|
|
378
|
+
elif x >= gameFieldSize or x < 0:
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
for i in list(snake.coordinateHistory)[1:]:
|
|
382
|
+
if (y,x) == i:
|
|
383
|
+
return True
|
|
384
|
+
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
def checkWin(gameFieldSize, snake):
|
|
388
|
+
if snake.bodyCount + 1 == gameFieldSize ** 2 :
|
|
389
|
+
return True
|
|
390
|
+
return False
|
|
391
|
+
|
|
392
|
+
def restartProcess(snake):
|
|
393
|
+
global gameField, moveDirection
|
|
394
|
+
for j in gameField:
|
|
395
|
+
for i in j:
|
|
396
|
+
i.isApple = False
|
|
397
|
+
i.isSnakeHead = False
|
|
398
|
+
i.isSnakeBody = False
|
|
399
|
+
i.isStone = False
|
|
400
|
+
moveDirection = 'right'
|
|
401
|
+
sys.stdout.write('\033[2J\033[H')
|
|
402
|
+
sys.stdout.flush()
|
|
403
|
+
printField(snake)
|
|
404
|
+
|
|
405
|
+
def writeNewRecord(record):
|
|
406
|
+
with open(userStatsPath, 'r') as f:
|
|
407
|
+
recordJson = json.load(f)
|
|
408
|
+
|
|
409
|
+
snakeSpeedStr = ''
|
|
410
|
+
fieldSizeStr = ''
|
|
411
|
+
|
|
412
|
+
if snakeSpeed == SLOW_SNAKE_SPEED:
|
|
413
|
+
snakeSpeedStr = 'slow'
|
|
414
|
+
elif snakeSpeed == MODERATE_SNAKE_SPEED:
|
|
415
|
+
snakeSpeedStr = 'moderate'
|
|
416
|
+
elif snakeSpeed == FAST_SNAKE_SPEED:
|
|
417
|
+
snakeSpeedStr = 'fast'
|
|
418
|
+
|
|
419
|
+
if gameFieldSize == SMALL_FIELD_SIZE:
|
|
420
|
+
fieldSizeStr = 'small'
|
|
421
|
+
elif gameFieldSize == MEDIUM_FIELD_SIZE:
|
|
422
|
+
fieldSizeStr = 'medium'
|
|
423
|
+
elif gameFieldSize == BIG_FIELD_SIZE:
|
|
424
|
+
fieldSizeStr = 'big'
|
|
425
|
+
elif gameFieldSize == LARGE_FIELD_SIZE:
|
|
426
|
+
fieldSizeStr = 'large'
|
|
427
|
+
|
|
428
|
+
if recordJson[fieldSizeStr][snakeSpeedStr] < record:
|
|
429
|
+
recordJson[fieldSizeStr][snakeSpeedStr] = record
|
|
430
|
+
with open(userStatsPath, 'w', encoding='utf-8') as f:
|
|
431
|
+
json.dump(recordJson, f, ensure_ascii=False)
|
|
432
|
+
|
|
433
|
+
def updateScene(snake):
|
|
434
|
+
global gameFieldSize, moveDirection, gameField
|
|
435
|
+
|
|
436
|
+
while not stop_event.is_set():
|
|
437
|
+
while pause_event.is_set():
|
|
438
|
+
time.sleep(0.1)
|
|
439
|
+
|
|
440
|
+
if snake.direction != moveDirection:
|
|
441
|
+
snake.changeDirection(moveDirection)
|
|
442
|
+
|
|
443
|
+
if checkDefeat(gameFieldSize, snake):
|
|
444
|
+
stop_event.set()
|
|
445
|
+
printField(snake, isDefeat=True)
|
|
446
|
+
writeNewRecord(snake.bodyCount - 2)
|
|
447
|
+
|
|
448
|
+
elif checkWin(gameFieldSize, snake):
|
|
449
|
+
stop_event.set()
|
|
450
|
+
printField(snake, isWin=True)
|
|
451
|
+
writeNewRecord(snake.bodyCount - 2)
|
|
452
|
+
else:
|
|
453
|
+
snake.move(gameField)
|
|
454
|
+
printField(snake)
|
|
455
|
+
|
|
456
|
+
time.sleep(snakeSpeed)
|
|
457
|
+
|
|
458
|
+
def flush_input():
|
|
459
|
+
try:
|
|
460
|
+
import termios
|
|
461
|
+
termios.tcflush(sys.stdin, termios.TCIFLUSH)
|
|
462
|
+
except ImportError:
|
|
463
|
+
while msvcrt.kbhit():
|
|
464
|
+
msvcrt.getch()
|
|
465
|
+
|
|
466
|
+
def startGame(gameField, snake):
|
|
467
|
+
gameField[gameFieldSize//2, gameFieldSize//2 + 2].isApple = True
|
|
468
|
+
gameField[gameFieldSize//2, gameFieldSize//2 - 2].isSnakeHead = True
|
|
469
|
+
gameField[gameFieldSize//2, gameFieldSize//2 - 3].isSnakeBody = True
|
|
470
|
+
gameField[gameFieldSize//2, gameFieldSize//2 - 4].isSnakeBody = True
|
|
471
|
+
snake.coordinateHistory.clear()
|
|
472
|
+
snake.bodyCount = 2
|
|
473
|
+
snake.coordinateHistory.append((gameFieldSize//2, gameFieldSize//2 - 2))
|
|
474
|
+
snake.coordinateHistory.append((gameFieldSize//2, gameFieldSize//2 - 3))
|
|
475
|
+
snake.coordinateHistory.append((gameFieldSize//2, gameFieldSize//2 - 4))
|
|
476
|
+
freePlaces = []
|
|
477
|
+
for i in range(gameFieldSize):
|
|
478
|
+
for j in range(gameFieldSize):
|
|
479
|
+
gameField[i, j].changeGrassPattern()
|
|
480
|
+
if not gameField[i, j].isSnakeHead and not gameField[i, j].isSnakeBody and not gameField[i, j].isApple:
|
|
481
|
+
freePlaces.append((i, j))
|
|
482
|
+
|
|
483
|
+
random.shuffle(freePlaces)
|
|
484
|
+
|
|
485
|
+
for i in range(random.randint(gameFieldSize//4, gameFieldSize//2)):
|
|
486
|
+
gameField[freePlaces[i]].isStone = True
|
|
487
|
+
|
|
488
|
+
def placeApple(gameField):
|
|
489
|
+
global gameFieldSize
|
|
490
|
+
freePlaces = []
|
|
491
|
+
for i in range(gameFieldSize):
|
|
492
|
+
for j in range(gameFieldSize):
|
|
493
|
+
if not gameField[i, j].isSnakeHead and not gameField[i, j].isSnakeBody:
|
|
494
|
+
freePlaces.append((i, j))
|
|
495
|
+
|
|
496
|
+
random.shuffle(freePlaces)
|
|
497
|
+
|
|
498
|
+
if freePlaces:
|
|
499
|
+
gameField[freePlaces[0]].isApple = True
|
|
500
|
+
|
|
501
|
+
def main():
|
|
502
|
+
global gameField, moveDirection
|
|
503
|
+
moves = 0
|
|
504
|
+
snake = snakeObject()
|
|
505
|
+
|
|
506
|
+
try:
|
|
507
|
+
while True:
|
|
508
|
+
updateSceneThread = threading.Thread(target=updateScene, args=(snake,), daemon=True)
|
|
509
|
+
try:
|
|
510
|
+
sys.stdout.write('\033[2J\033[H')
|
|
511
|
+
sys.stdout.flush()
|
|
512
|
+
checkTerminalSize()
|
|
513
|
+
settingsMenu()
|
|
514
|
+
startGame(gameField, snake)
|
|
515
|
+
stop_event.clear()
|
|
516
|
+
sys.stdout.write('\033[2J\033[H')
|
|
517
|
+
sys.stdout.flush()
|
|
518
|
+
printField(snake)
|
|
519
|
+
with keyboard.Events() as events:
|
|
520
|
+
while True:
|
|
521
|
+
event = events.get(1.0)
|
|
522
|
+
if event is None:
|
|
523
|
+
continue
|
|
524
|
+
|
|
525
|
+
if isinstance(event, keyboard.Events.Press):
|
|
526
|
+
key = event.key
|
|
527
|
+
|
|
528
|
+
if not stop_event.is_set():
|
|
529
|
+
if key == keyboard.Key.up:
|
|
530
|
+
if moves == 0:
|
|
531
|
+
updateSceneThread.start()
|
|
532
|
+
moves += 1
|
|
533
|
+
moveDirection = 'up'
|
|
534
|
+
|
|
535
|
+
elif key == keyboard.Key.down:
|
|
536
|
+
if moves == 0:
|
|
537
|
+
updateSceneThread.start()
|
|
538
|
+
moves += 1
|
|
539
|
+
moveDirection = 'down'
|
|
540
|
+
|
|
541
|
+
elif key == keyboard.Key.right:
|
|
542
|
+
if moves == 0:
|
|
543
|
+
updateSceneThread.start()
|
|
544
|
+
moves += 1
|
|
545
|
+
moveDirection = 'right'
|
|
546
|
+
|
|
547
|
+
elif key == keyboard.Key.left:
|
|
548
|
+
if moves == 0:
|
|
549
|
+
updateSceneThread.start()
|
|
550
|
+
moves += 1
|
|
551
|
+
moveDirection = 'left'
|
|
552
|
+
|
|
553
|
+
elif key == keyboard.Key.esc :
|
|
554
|
+
if not pause_event.is_set():
|
|
555
|
+
pause_event.set()
|
|
556
|
+
printField(snake, isPaused=True)
|
|
557
|
+
else:
|
|
558
|
+
pause_event.clear()
|
|
559
|
+
sys.stdout.write('\033[2J\033[H')
|
|
560
|
+
sys.stdout.flush()
|
|
561
|
+
|
|
562
|
+
if ((hasattr(key, 'vk') and key.vk == 82) or key == keyboard.KeyCode.from_char('r') or (hasattr(key, 'char') and key.char == 'r')) and stop_event.is_set():
|
|
563
|
+
moves = 0
|
|
564
|
+
updateSceneThread.join()
|
|
565
|
+
updateSceneThread = threading.Thread(target=updateScene, args=(snake,), daemon=True)
|
|
566
|
+
stop_event.clear()
|
|
567
|
+
pause_event.clear()
|
|
568
|
+
snake.direction = 'right'
|
|
569
|
+
restartProcess(snake)
|
|
570
|
+
startGame(gameField, snake)
|
|
571
|
+
printField(snake)
|
|
572
|
+
|
|
573
|
+
except KeyboardInterrupt:
|
|
574
|
+
stop_event.set()
|
|
575
|
+
if moves != 0:
|
|
576
|
+
updateSceneThread.join()
|
|
577
|
+
moves = 0
|
|
578
|
+
pause_event.clear()
|
|
579
|
+
snake.direction = 'right'
|
|
580
|
+
moveDirection = 'right'
|
|
581
|
+
sys.stdout.write('\033[2J\033[H')
|
|
582
|
+
sys.stdout.flush()
|
|
583
|
+
continue
|
|
584
|
+
|
|
585
|
+
except Exception as e:
|
|
586
|
+
flush_input()
|
|
587
|
+
print(e)
|
|
588
|
+
os._exit(1)
|
|
589
|
+
finally:
|
|
590
|
+
if sys.platform != 'win32':
|
|
591
|
+
termios.tcsetattr(fd,termios.TCSADRAIN, oldSettings)
|
|
592
|
+
flush_input()
|
|
593
|
+
|
|
594
|
+
if __name__ == "__main__":
|
|
595
|
+
main()
|