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.
@@ -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,12 @@
1
+
2
+ A simply CLI snake game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
3
+
4
+ ## Install
5
+ ```bash
6
+ pip install funhub-snake
7
+ ```
8
+ ## Quick start
9
+ Write in terminal this command to start game:
10
+ ```bash
11
+ funhub-snake
12
+ ```
@@ -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,9 @@
1
+ README.md
2
+ setup.py
3
+ snake.py
4
+ funhub_snake.egg-info/PKG-INFO
5
+ funhub_snake.egg-info/SOURCES.txt
6
+ funhub_snake.egg-info/dependency_links.txt
7
+ funhub_snake.egg-info/entry_points.txt
8
+ funhub_snake.egg-info/requires.txt
9
+ funhub_snake.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ funhub-snake = snake:main
@@ -0,0 +1,3 @@
1
+ pynput>=1.7.6
2
+ colorama>=0.4.6
3
+ numpy>=1.24.0
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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()