FunHub-minesweeper 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_minesweeper-1.0.0/FunHub_minesweeper.egg-info/PKG-INFO +24 -0
- funhub_minesweeper-1.0.0/FunHub_minesweeper.egg-info/SOURCES.txt +9 -0
- funhub_minesweeper-1.0.0/FunHub_minesweeper.egg-info/dependency_links.txt +1 -0
- funhub_minesweeper-1.0.0/FunHub_minesweeper.egg-info/entry_points.txt +2 -0
- funhub_minesweeper-1.0.0/FunHub_minesweeper.egg-info/requires.txt +3 -0
- funhub_minesweeper-1.0.0/FunHub_minesweeper.egg-info/top_level.txt +1 -0
- funhub_minesweeper-1.0.0/PKG-INFO +24 -0
- funhub_minesweeper-1.0.0/README.md +11 -0
- funhub_minesweeper-1.0.0/minesweeper.py +483 -0
- funhub_minesweeper-1.0.0/setup.cfg +4 -0
- funhub_minesweeper-1.0.0/setup.py +24 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: FunHub-minesweeper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI minesweeper 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
|
+
A simply CLI minesweeper game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
```bash
|
|
18
|
+
pip install funhub-tic-tac-toe
|
|
19
|
+
```
|
|
20
|
+
## Quick start
|
|
21
|
+
Write in terminal this command to start game:
|
|
22
|
+
```bash
|
|
23
|
+
funhub-minesweeper
|
|
24
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
minesweeper.py
|
|
3
|
+
setup.py
|
|
4
|
+
FunHub_minesweeper.egg-info/PKG-INFO
|
|
5
|
+
FunHub_minesweeper.egg-info/SOURCES.txt
|
|
6
|
+
FunHub_minesweeper.egg-info/dependency_links.txt
|
|
7
|
+
FunHub_minesweeper.egg-info/entry_points.txt
|
|
8
|
+
FunHub_minesweeper.egg-info/requires.txt
|
|
9
|
+
FunHub_minesweeper.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
minesweeper
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: FunHub-minesweeper
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: CLI minesweeper 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
|
+
A simply CLI minesweeper game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
```bash
|
|
18
|
+
pip install funhub-tic-tac-toe
|
|
19
|
+
```
|
|
20
|
+
## Quick start
|
|
21
|
+
Write in terminal this command to start game:
|
|
22
|
+
```bash
|
|
23
|
+
funhub-minesweeper
|
|
24
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
A simply CLI minesweeper game for [FunHud app-launcher](https://pypi.org/project/FunHub-app-launcher/)
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
```bash
|
|
5
|
+
pip install funhub-tic-tac-toe
|
|
6
|
+
```
|
|
7
|
+
## Quick start
|
|
8
|
+
Write in terminal this command to start game:
|
|
9
|
+
```bash
|
|
10
|
+
funhub-minesweeper
|
|
11
|
+
```
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import random
|
|
6
|
+
import shutil
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from pynput import keyboard
|
|
10
|
+
import sys
|
|
11
|
+
from colorama import init, Cursor, Fore, Back, Style
|
|
12
|
+
import ctypes
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
init(autoreset=True)
|
|
16
|
+
|
|
17
|
+
if sys.platform == 'win32':
|
|
18
|
+
import msvcrt
|
|
19
|
+
kernel32 = ctypes.windll.kernel32
|
|
20
|
+
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
|
21
|
+
else:
|
|
22
|
+
import termios
|
|
23
|
+
import tty
|
|
24
|
+
fd = sys.stdin.fileno()
|
|
25
|
+
oldSettings = termios.tcgetattr(fd)
|
|
26
|
+
tty.setcbreak(fd)
|
|
27
|
+
|
|
28
|
+
stop_event = threading.Event()
|
|
29
|
+
print_lock = threading.Lock()
|
|
30
|
+
|
|
31
|
+
MIN_TERMINAL_COLS, MIN_TERMINAL_ROWS = 150, 60
|
|
32
|
+
EASY_FIELD_SIZE, MEDIUM_FIELD_SIZE, HARD_FIELD_SIZE, VERY_HARD_FIELD_SIZE = 9, 16, 30, 49
|
|
33
|
+
|
|
34
|
+
backgroundColor = 250
|
|
35
|
+
numbersColors = [12, 10, 9, 4, 94, 14, 0,15]
|
|
36
|
+
|
|
37
|
+
userStatsPath = Path.home() / ".funhub" / "minesweeper" / "userStats.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({"easy":0, "medium":0, "hard":0, "very hard":0}, f, ensure_ascii=False, indent=4)
|
|
44
|
+
|
|
45
|
+
gameField = []
|
|
46
|
+
gameFieldSize = 0
|
|
47
|
+
mines = 0
|
|
48
|
+
selectedCell = 0, 0
|
|
49
|
+
|
|
50
|
+
gameField = np.array([])
|
|
51
|
+
|
|
52
|
+
logo = r'''
|
|
53
|
+
__ __ _
|
|
54
|
+
| \/ (_)_ _ ___ ____ __ _____ ___ _ __ ___ _ _
|
|
55
|
+
| |\/| | | ' \/ -_|_-< V V / -_) -_) '_ \/ -_) '_|
|
|
56
|
+
|_| |_|_|_||_\___/__/\_/\_/\___\___| .__/\___|_|
|
|
57
|
+
|_|
|
|
58
|
+
|
|
59
|
+
'''
|
|
60
|
+
|
|
61
|
+
class cellObject():
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self.isMine = False
|
|
64
|
+
self.isFlag = False
|
|
65
|
+
self.isOpen = False
|
|
66
|
+
self.isSelected = False
|
|
67
|
+
self.neighborMines = 0
|
|
68
|
+
|
|
69
|
+
def getStr(self):
|
|
70
|
+
if not self.isOpen and not self.isFlag:
|
|
71
|
+
return f"\x1b[38;5;{backgroundColor};48;5;{254}m{' # ' if not self.isSelected else '[#]'}\x1b[0m"
|
|
72
|
+
elif not self.isOpen and self.isFlag:
|
|
73
|
+
return f"\x1b[38;5;{11};48;5;{backgroundColor}m{' F ' if not self.isSelected else '[F]'}\x1b[0m"
|
|
74
|
+
elif self.isOpen and not self.isMine and self.neighborMines > 0:
|
|
75
|
+
return f"\x1b[38;5;{numbersColors[self.neighborMines - 1]};48;5;{backgroundColor}m{f' {self.neighborMines} ' if not self.isSelected else f'[{self.neighborMines}]'}\x1b[0m"
|
|
76
|
+
elif self.isOpen and not self.isMine and self.neighborMines == 0:
|
|
77
|
+
return f"\x1b[38;5;{0};48;5;{backgroundColor}m{' ' if not self.isSelected else '[ ]'}\x1b[0m"
|
|
78
|
+
elif self.isOpen and self.isMine:
|
|
79
|
+
return f"\x1b[38;5;{0};48;5;{9}m{' * ' if not self.isSelected else '[*]'}\x1b[0m"
|
|
80
|
+
|
|
81
|
+
def openCell(self):
|
|
82
|
+
if not self.isOpen and not self.isFlag:
|
|
83
|
+
self.isOpen = True
|
|
84
|
+
return True
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def placeFlag(self):
|
|
88
|
+
if not self.isOpen:
|
|
89
|
+
if self.isFlag:
|
|
90
|
+
self.isFlag = False
|
|
91
|
+
else:
|
|
92
|
+
self.isFlag = True
|
|
93
|
+
|
|
94
|
+
def printField(startedTime=0, isWin = False, isDefeat = False):
|
|
95
|
+
with print_lock:
|
|
96
|
+
global gameFieldSize, logo, mines
|
|
97
|
+
termCols, termRows = os.get_terminal_size()
|
|
98
|
+
resulStr = ''
|
|
99
|
+
logo = logo if gameFieldSize < 40 else 'Minesweeper'
|
|
100
|
+
centeredLogo = ''
|
|
101
|
+
|
|
102
|
+
for i in logo.splitlines():
|
|
103
|
+
centeredLogo += ' ' * int((os.get_terminal_size().columns - len(i))//2) + i + '\n'
|
|
104
|
+
|
|
105
|
+
for r in range(gameFieldSize):
|
|
106
|
+
row_str = ''
|
|
107
|
+
for c in range(gameFieldSize):
|
|
108
|
+
cell = gameField[r][c]
|
|
109
|
+
char = cell.getStr()
|
|
110
|
+
row_str += char
|
|
111
|
+
resulStr += row_str + '\n'
|
|
112
|
+
|
|
113
|
+
centeredResulStr = ''
|
|
114
|
+
|
|
115
|
+
for i in resulStr.splitlines():
|
|
116
|
+
centeredResulStr += ' ' * int((termCols - (gameFieldSize * 3))//2) + i + '\n'
|
|
117
|
+
|
|
118
|
+
centeredResulStr = centeredLogo + '\n' + centeredResulStr
|
|
119
|
+
upperSpace = (termRows - len(centeredResulStr.splitlines()))//2
|
|
120
|
+
|
|
121
|
+
info = "time: " + getTime(startedTime, getStr=True) + ' ' + f'mines:{mines}'
|
|
122
|
+
info = " " * ((os.get_terminal_size().columns - len(info))//2) + info
|
|
123
|
+
centeredResulStr += '\n' + info
|
|
124
|
+
|
|
125
|
+
endGameText = ''
|
|
126
|
+
if isWin:
|
|
127
|
+
endGameText = 'You win!'
|
|
128
|
+
elif isDefeat:
|
|
129
|
+
endGameText = 'You lose!'
|
|
130
|
+
|
|
131
|
+
if not endGameText:
|
|
132
|
+
centeredResulStr = '\n' * upperSpace + centeredResulStr + '\n' * (termRows - upperSpace - len(centeredResulStr.splitlines()) - 1) + '↑/↓/←/→ – move, Enter – enter, R - restart, F - place flag, Ctrl + C – quit'
|
|
133
|
+
else:
|
|
134
|
+
endGameText = ' ' * int((termCols - len(endGameText))//2) + endGameText
|
|
135
|
+
centeredResulStr = '\n' * upperSpace + centeredResulStr + '\n\n' + endGameText +'\n' * (termRows - upperSpace - len(centeredResulStr.splitlines()) - 3) + '↑/↓/←/→ – move, Enter – enter, R - restart, F - place flag, Ctrl + C – quit'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
sys.stdout.write(Cursor.POS(1, 1))
|
|
139
|
+
sys.stdout.write(centeredResulStr)
|
|
140
|
+
sys.stdout.flush()
|
|
141
|
+
|
|
142
|
+
def changeSelection(direction):
|
|
143
|
+
global selectedCell
|
|
144
|
+
cols, rows = gameField.shape
|
|
145
|
+
|
|
146
|
+
gameField[selectedCell[0]][selectedCell[1]].isSelected = False
|
|
147
|
+
|
|
148
|
+
if direction == 'up':
|
|
149
|
+
selectedCell = (selectedCell[0] - 1) % cols, selectedCell[1]
|
|
150
|
+
elif direction == 'down':
|
|
151
|
+
selectedCell = (selectedCell[0] + 1) % cols, selectedCell[1]
|
|
152
|
+
elif direction == 'right':
|
|
153
|
+
selectedCell = selectedCell[0], (selectedCell[1] + 1) % rows
|
|
154
|
+
elif direction == 'left':
|
|
155
|
+
selectedCell = selectedCell[0], (selectedCell[1] - 1) % rows
|
|
156
|
+
|
|
157
|
+
gameField[selectedCell[0]][selectedCell[1]].isSelected = True
|
|
158
|
+
|
|
159
|
+
def selectDificulti():
|
|
160
|
+
global gameField, gameFieldSize, mines, selectedCell
|
|
161
|
+
try:
|
|
162
|
+
difilculties = ['Easy', 'Medium', 'Hard', 'Very hard']
|
|
163
|
+
selectedDiff = 0
|
|
164
|
+
|
|
165
|
+
prew_cols, prew_rows = 0, 0
|
|
166
|
+
|
|
167
|
+
with open(userStatsPath, 'r', encoding='utf-8') as f:
|
|
168
|
+
userStats = json.load(f)
|
|
169
|
+
|
|
170
|
+
def printMenu():
|
|
171
|
+
menuToPrint = ''
|
|
172
|
+
termCols, termRows = os.get_terminal_size()
|
|
173
|
+
centeredStr = ''
|
|
174
|
+
nonlocal prew_cols, prew_rows, selectedDiff
|
|
175
|
+
|
|
176
|
+
if termCols != prew_cols or termRows != prew_rows:
|
|
177
|
+
prew_cols, prew_rows = termCols, termRows
|
|
178
|
+
sys.stdout.write('\033[2J\033[H')
|
|
179
|
+
sys.stdout.flush()
|
|
180
|
+
|
|
181
|
+
for i in logo.splitlines():
|
|
182
|
+
centeredStr += ' ' * int((termCols - len(i))//2) + i + '\n'
|
|
183
|
+
|
|
184
|
+
menuToPrint += centeredStr
|
|
185
|
+
|
|
186
|
+
difList = ''
|
|
187
|
+
for i in difilculties:
|
|
188
|
+
if i == difilculties[selectedDiff]:
|
|
189
|
+
difList += f'*{i}' + '\n'
|
|
190
|
+
else:
|
|
191
|
+
difList += f' {i}' + '\n'
|
|
192
|
+
|
|
193
|
+
centeredStr = ''
|
|
194
|
+
|
|
195
|
+
for i in difList.splitlines():
|
|
196
|
+
centeredStr += ' ' * int((termCols - len(i))//2) + i + '\n'
|
|
197
|
+
|
|
198
|
+
menuToPrint += centeredStr
|
|
199
|
+
|
|
200
|
+
upperSpace = (termRows - len(menuToPrint.splitlines()))//2
|
|
201
|
+
|
|
202
|
+
menuToPrint = '\n' * upperSpace + menuToPrint + '\n' * (termRows - upperSpace - len(menuToPrint.splitlines()) - 1) + '↑/↓/ – move, Enter – select, Ctrl + C – quit'
|
|
203
|
+
|
|
204
|
+
sys.stdout.write(Cursor.POS(1, 1))
|
|
205
|
+
sys.stdout.write(menuToPrint)
|
|
206
|
+
sys.stdout.flush()
|
|
207
|
+
|
|
208
|
+
def setDif():
|
|
209
|
+
global gameField, gameFieldSize, mines, gameField
|
|
210
|
+
gameField = [[cellObject() for _ in range(gameFieldSize)] for _ in range(gameFieldSize)]
|
|
211
|
+
gameField = np.array(gameField)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
sys.stdout.write('\033[2J\033[H')
|
|
215
|
+
sys.stdout.flush()
|
|
216
|
+
printMenu()
|
|
217
|
+
with keyboard.Events() as events:
|
|
218
|
+
while True:
|
|
219
|
+
|
|
220
|
+
event = events.get(1.0)
|
|
221
|
+
if event is None:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
if isinstance(event, keyboard.Events.Press):
|
|
225
|
+
key = event.key
|
|
226
|
+
if key == keyboard.Key.up:
|
|
227
|
+
selectedDiff = (selectedDiff - 1) % len(difilculties)
|
|
228
|
+
printMenu()
|
|
229
|
+
elif key == keyboard.Key.down:
|
|
230
|
+
selectedDiff = (selectedDiff + 1) % len(difilculties)
|
|
231
|
+
printMenu()
|
|
232
|
+
elif key == keyboard.Key.enter:
|
|
233
|
+
if difilculties[selectedDiff] == 'Easy':
|
|
234
|
+
gameFieldSize = EASY_FIELD_SIZE
|
|
235
|
+
mines = 10
|
|
236
|
+
elif difilculties[selectedDiff] == 'Medium':
|
|
237
|
+
gameFieldSize = MEDIUM_FIELD_SIZE
|
|
238
|
+
mines = 40
|
|
239
|
+
elif difilculties[selectedDiff] == 'Hard':
|
|
240
|
+
gameFieldSize = HARD_FIELD_SIZE
|
|
241
|
+
mines = 99
|
|
242
|
+
elif difilculties[selectedDiff] == 'Very hard':
|
|
243
|
+
gameFieldSize = VERY_HARD_FIELD_SIZE
|
|
244
|
+
mines = 150
|
|
245
|
+
setDif()
|
|
246
|
+
gameField[gameFieldSize//2][gameFieldSize//2].isSelected = True
|
|
247
|
+
selectedCell = gameFieldSize//2, gameFieldSize//2
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
except KeyboardInterrupt:
|
|
251
|
+
os._exit(0)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
print(e)
|
|
254
|
+
os._exit(1)
|
|
255
|
+
|
|
256
|
+
def forceTerminalSize(cols, rows):
|
|
257
|
+
if sys.platform == 'win32':
|
|
258
|
+
os.system(f'mode con: cols={cols} lines={rows}')
|
|
259
|
+
|
|
260
|
+
kernel32 = ctypes.windll.kernel32
|
|
261
|
+
hOut = kernel32.GetStdHandle(-11)
|
|
262
|
+
res = ctypes.wintypes._COORD(cols, rows)
|
|
263
|
+
kernel32.SetConsoleScreenBufferSize(hOut, res)
|
|
264
|
+
else:
|
|
265
|
+
sys.stdout.write(f"\x1b[8;{rows};{cols}t")
|
|
266
|
+
sys.stdout.flush()
|
|
267
|
+
|
|
268
|
+
def checkTerminalSize():
|
|
269
|
+
cols, rows = shutil.get_terminal_size()
|
|
270
|
+
if cols < MIN_TERMINAL_COLS or rows < MIN_TERMINAL_ROWS:
|
|
271
|
+
forceTerminalSize(MIN_TERMINAL_COLS, MIN_TERMINAL_ROWS)
|
|
272
|
+
cols, rows = shutil.get_terminal_size()
|
|
273
|
+
if cols < MIN_TERMINAL_COLS or rows < MIN_TERMINAL_ROWS:
|
|
274
|
+
return False
|
|
275
|
+
return True
|
|
276
|
+
|
|
277
|
+
def placeMines():
|
|
278
|
+
global gameField, mines, gameFieldSize, selectedCell
|
|
279
|
+
placedMines = []
|
|
280
|
+
coordinates = [(y, x) for y in range(gameFieldSize) for x in range(gameFieldSize)]
|
|
281
|
+
coordinates = random.shuffle(coordinates)
|
|
282
|
+
for i in range(mines):
|
|
283
|
+
mine = coordinates[i] if coordinates[i] != selectedCell and coordinates[i] not in placedMines else coordinates[mines + i]
|
|
284
|
+
gameField[mine].isMine = True
|
|
285
|
+
placedMines.append(mine)
|
|
286
|
+
|
|
287
|
+
def checkDefeat():
|
|
288
|
+
global gameField
|
|
289
|
+
for i in gameField:
|
|
290
|
+
for j in i:
|
|
291
|
+
if j.isOpen and j.isMine:
|
|
292
|
+
return True
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
def checkWin():
|
|
296
|
+
global gameField, mines
|
|
297
|
+
openedCell = 0
|
|
298
|
+
for i in gameField:
|
|
299
|
+
for j in i:
|
|
300
|
+
if j.isOpen and j.isMine:
|
|
301
|
+
return False
|
|
302
|
+
if j.isOpen:
|
|
303
|
+
openedCell += 1
|
|
304
|
+
if openedCell >= (gameField.size - mines):
|
|
305
|
+
return True
|
|
306
|
+
return False
|
|
307
|
+
|
|
308
|
+
def checkNeighbors(tuplePlace):
|
|
309
|
+
global gameField, gameFieldSize
|
|
310
|
+
y, x = tuplePlace
|
|
311
|
+
curCell = gameField[y, x]
|
|
312
|
+
neighborsCells = [(y-1, x), (y-1, x+1), (y, x+1), (y+1, x+1), (y+1, x), (y+1, x-1), (y, x-1), (y-1, x-1)]
|
|
313
|
+
flagedMines = 0
|
|
314
|
+
|
|
315
|
+
if not curCell.isMine:
|
|
316
|
+
if curCell.neighborMines == 0 and not curCell.isMine:
|
|
317
|
+
for i in neighborsCells:
|
|
318
|
+
if (i[0] >= 0 and i[1] >= 0) and (i[0] < gameFieldSize and i[1] < gameFieldSize):
|
|
319
|
+
if gameField[i].isMine:
|
|
320
|
+
curCell.neighborMines += 1
|
|
321
|
+
|
|
322
|
+
for i in neighborsCells:
|
|
323
|
+
if (i[0] >= 0 and i[1] >= 0) and (i[0] < gameFieldSize and i[1] < gameFieldSize):
|
|
324
|
+
if gameField[i].isMine:
|
|
325
|
+
if gameField[i].isFlag:
|
|
326
|
+
flagedMines += 1
|
|
327
|
+
|
|
328
|
+
if curCell.neighborMines == 0:
|
|
329
|
+
for i in neighborsCells:
|
|
330
|
+
if (i[0] >= 0 and i[1] >= 0) and (i[0] < gameFieldSize and i[1] < gameFieldSize):
|
|
331
|
+
if not gameField[i].isMine:
|
|
332
|
+
if gameField[i].openCell() and gameField[i].neighborMines == 0 :
|
|
333
|
+
checkNeighbors(i)
|
|
334
|
+
|
|
335
|
+
if curCell.neighborMines == flagedMines:
|
|
336
|
+
for i in neighborsCells:
|
|
337
|
+
if (i[0] >= 0 and i[1] >= 0) and (i[0] < gameFieldSize and i[1] < gameFieldSize):
|
|
338
|
+
if gameField[i].openCell():
|
|
339
|
+
checkNeighbors(i)
|
|
340
|
+
|
|
341
|
+
def restartProcess():
|
|
342
|
+
global gameField, selectedCell
|
|
343
|
+
for j in gameField:
|
|
344
|
+
for i in j:
|
|
345
|
+
i.isMine = False
|
|
346
|
+
i.isFlag = False
|
|
347
|
+
i.isOpen = False
|
|
348
|
+
i.isSelected = False
|
|
349
|
+
i.neighborMines = 0
|
|
350
|
+
sys.stdout.write('\033[2J\033[H')
|
|
351
|
+
sys.stdout.flush()
|
|
352
|
+
gameField[selectedCell].isSelected = False
|
|
353
|
+
selectedCell = gameFieldSize//2, gameFieldSize//2
|
|
354
|
+
gameField[selectedCell].isSelected = True
|
|
355
|
+
printField()
|
|
356
|
+
|
|
357
|
+
def getTime(gettedTime, getStr=False):
|
|
358
|
+
if gettedTime != 0:
|
|
359
|
+
if not getStr:
|
|
360
|
+
return time.monotonic() - gettedTime
|
|
361
|
+
else:
|
|
362
|
+
return timeToStr(time.monotonic() - gettedTime)
|
|
363
|
+
else:
|
|
364
|
+
return '00:00'
|
|
365
|
+
|
|
366
|
+
def timeToStr(gettedTime):
|
|
367
|
+
minuts = int((gettedTime % 3600) // 60)
|
|
368
|
+
seconds = int(gettedTime % 60)
|
|
369
|
+
return f'{minuts:02d}:{seconds:02d}'
|
|
370
|
+
|
|
371
|
+
def updateView(gameTime):
|
|
372
|
+
while not stop_event.is_set():
|
|
373
|
+
printField(startedTime=gameTime)
|
|
374
|
+
time.sleep(0.1)
|
|
375
|
+
|
|
376
|
+
def flush_input():
|
|
377
|
+
try:
|
|
378
|
+
import termios
|
|
379
|
+
termios.tcflush(sys.stdin, termios.TCIFLUSH)
|
|
380
|
+
except ImportError:
|
|
381
|
+
while msvcrt.kbhit():
|
|
382
|
+
msvcrt.getch()
|
|
383
|
+
|
|
384
|
+
def main():
|
|
385
|
+
global selectedCell, gameField
|
|
386
|
+
moves = 0
|
|
387
|
+
isGameEnd = False
|
|
388
|
+
timeStarted = 0
|
|
389
|
+
try:
|
|
390
|
+
sys.stdout.write('\033[2J\033[H')
|
|
391
|
+
sys.stdout.flush()
|
|
392
|
+
checkTerminalSize()
|
|
393
|
+
selectDificulti()
|
|
394
|
+
sys.stdout.write('\033[2J\033[H')
|
|
395
|
+
sys.stdout.flush()
|
|
396
|
+
printField()
|
|
397
|
+
with keyboard.Events() as events:
|
|
398
|
+
while True:
|
|
399
|
+
event = events.get(1.0)
|
|
400
|
+
if event is None:
|
|
401
|
+
continue
|
|
402
|
+
|
|
403
|
+
if isinstance(event, keyboard.Events.Press):
|
|
404
|
+
key = event.key
|
|
405
|
+
|
|
406
|
+
if not isGameEnd:
|
|
407
|
+
if key == keyboard.Key.up:
|
|
408
|
+
changeSelection('up')
|
|
409
|
+
printField()
|
|
410
|
+
elif key == keyboard.Key.down:
|
|
411
|
+
changeSelection('down')
|
|
412
|
+
printField()
|
|
413
|
+
elif key == keyboard.Key.right:
|
|
414
|
+
changeSelection('right')
|
|
415
|
+
printField()
|
|
416
|
+
elif key == keyboard.Key.left:
|
|
417
|
+
changeSelection('left')
|
|
418
|
+
printField()
|
|
419
|
+
|
|
420
|
+
elif key == keyboard.Key.enter:
|
|
421
|
+
if moves <= 0:
|
|
422
|
+
placeMines()
|
|
423
|
+
timeStarted = time.monotonic()
|
|
424
|
+
timerUpdateThread = threading.Thread(target=updateView, args=(timeStarted,), daemon=True)
|
|
425
|
+
stop_event.clear()
|
|
426
|
+
timerUpdateThread.start()
|
|
427
|
+
|
|
428
|
+
moves += 1
|
|
429
|
+
gameField[selectedCell].openCell()
|
|
430
|
+
checkNeighbors(selectedCell)
|
|
431
|
+
|
|
432
|
+
if checkDefeat():
|
|
433
|
+
printField(isDefeat=True)
|
|
434
|
+
isGameEnd = True
|
|
435
|
+
stop_event.set()
|
|
436
|
+
|
|
437
|
+
elif checkWin():
|
|
438
|
+
printField(isWin=True)
|
|
439
|
+
with open(userStatsPath, 'r', encoding='utf-8') as f:
|
|
440
|
+
records = json.load(f)
|
|
441
|
+
|
|
442
|
+
newTime = getTime(timeStarted)
|
|
443
|
+
if gameFieldSize == EASY_FIELD_SIZE:
|
|
444
|
+
records['easy'] = newTime if records['easy'] > newTime else records['easy']
|
|
445
|
+
elif gameFieldSize == MEDIUM_FIELD_SIZE:
|
|
446
|
+
records['medium'] = newTime if records['medium'] > newTime else records['medium']
|
|
447
|
+
elif gameFieldSize == HARD_FIELD_SIZE:
|
|
448
|
+
records['hard'] = newTime if records['hard'] > newTime else records['hard']
|
|
449
|
+
elif gameFieldSize == VERY_HARD_FIELD_SIZE:
|
|
450
|
+
records['very hard'] = newTime if records['very hard'] > newTime else records['very hard']
|
|
451
|
+
|
|
452
|
+
with open(userStatsPath, 'w') as f:
|
|
453
|
+
json.dump(records, f, ensure_ascii=False, indent=4)
|
|
454
|
+
|
|
455
|
+
isGameEnd = True
|
|
456
|
+
stop_event.set()
|
|
457
|
+
|
|
458
|
+
if (hasattr(key, 'vk') and key.vk == 82) or key == keyboard.KeyCode.from_char('r') or (hasattr(key, 'char') and key.char == 'r'):
|
|
459
|
+
restartProcess()
|
|
460
|
+
timeStarted = 0
|
|
461
|
+
moves = 0
|
|
462
|
+
isGameEnd = False
|
|
463
|
+
stop_event.set()
|
|
464
|
+
printField()
|
|
465
|
+
|
|
466
|
+
elif (hasattr(key, 'vk') and key.vk == 70) or key == keyboard.KeyCode.from_char('f') or (hasattr(key, 'char') and key.char == 'f'):
|
|
467
|
+
gameField[selectedCell].placeFlag()
|
|
468
|
+
|
|
469
|
+
except KeyboardInterrupt:
|
|
470
|
+
flush_input()
|
|
471
|
+
os._exit(0)
|
|
472
|
+
|
|
473
|
+
except Exception as e:
|
|
474
|
+
flush_input()
|
|
475
|
+
print(e)
|
|
476
|
+
os._exit(1)
|
|
477
|
+
finally:
|
|
478
|
+
if sys.platform != 'win32':
|
|
479
|
+
termios.tcsetattr(fd,termios.TCSADRAIN, oldSettings)
|
|
480
|
+
flush_input()
|
|
481
|
+
|
|
482
|
+
if __name__ == "__main__":
|
|
483
|
+
main()
|
|
@@ -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-minesweeper",
|
|
8
|
+
version="1.0.0",
|
|
9
|
+
py_modules=["minesweeper"],
|
|
10
|
+
description="CLI minesweeper 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-minesweeper=minesweeper:main',
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
)
|