-
Notifications
You must be signed in to change notification settings - Fork 0
/
display.py
304 lines (216 loc) · 9.04 KB
/
display.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
from os import environ
environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' # Disables the pygame initialization prompt
import config
import pygame
import input
from config import *
from pygcurse import PygcurseWindow
from pygame.font import Font, SysFont
pygame.init() # Initialize pygame.
pygame.display.gl_set_attribute(pygame.GL_MULTISAMPLEBUFFERS, 3) # Enables Anti-Aliasing
pygame.display.gl_set_attribute(pygame.GL_ACCELERATED_VISUAL, 1) # Enables Pygame graphical acceleration
# Window object to display output.
try:
window: PygcurseWindow = PygcurseWindow(*RES, fgcolor=WHITE, bgcolor=BACKGROUND_COLOR, font=Font(*FONT))
except FileNotFoundError:
window: PygcurseWindow = PygcurseWindow(*RES, fgcolor=WHITE, bgcolor=BACKGROUND_COLOR, font=SysFont(*SYS_FONT))
def clear() -> None:
"""
Clears the display.
:return:
"""
config.CHOSEN_END = () # Erase end node coordinates.
config.CHOSEN_START = () # Erase starting node coordinates.
config.MAP = [row.copy() for row in BLANK_MAP.copy()] # Restart map values.
config.chosen_hubs = [] # Resart hub values.
window.fill(NODE_SYMBOLS[-1], fgcolor=config.NODE_COLORS[NODE_SYMBOLS[-1]], bgcolor=BACKGROUND_COLOR)
# Clear the window, must be done to avoid a bug with the library fill function.
clear()
# Adds a shadow around the window.
window.addshadow()
def __register_display(char: str, coords: tuple[int, int]) -> None:
"""
Adds a drawn character into the MAP.
Takes scaled coordinates.
:param char:
:param coords:
:return:
"""
try:
config.MAP[coords[1]][coords[0]] = char
if char == NODE_SYMBOLS[3]:
config.chosen_hubs.append(coords)
except TypeError:
return
def __draw_char(char: str, coords: tuple[int, int],
color: COLOR, bg_color: COLOR = BACKGROUND_COLOR,
no_register: bool = False) -> None:
"""
Draws the given char at the given 2D coordinates, and registers the char in the MAP.
:param char:
:param coords:
:return:
"""
# Actual coordinates on the screen.
true_coordinates: tuple[int, int] = __scale_coordinates(coords)
# Register the char in the map.
__register_display(char, true_coordinates) if not no_register else ...
# Displays the character at that given position.
window.write(char, *true_coordinates, fgcolor=color, bgcolor=bg_color)
def visual_char_at(coords: tuple[int, int], scaled: bool = False) -> str:
"""
Returns the character at the given on the screen coordinates in (x, y) format.
:param scaled:
:param coords:
:return:
"""
scaled_coords: tuple[int, int] = __scale_coordinates(coords) if not scaled else coords
return window.getchar(*scaled_coords)
def get_char_at(coords: tuple[int, int], scaled: bool = False) -> str:
"""
Returns the character at the given coordinates in (x, y) format.
:param scaled:
:param coords:
:return:
"""
scaled_coords: tuple[int, int] = __scale_coordinates(coords) if not scaled else coords
return config.MAP[scaled_coords[1]][scaled_coords[0]]
def __scale_coordinates(coords: tuple[int, int]) -> tuple[int, int]:
"""
Returns the scaled coordinates of the char in cells
:param coords:
:return:
"""
return window.getcoordinatesatpixel(*coords) if coords else coords
def __unscale_coordinates(coords: tuple[int, int]) -> tuple[int, int]:
"""
Returns the unscaled coordinates of the given cell coordinates.
:param coords:
:return:
"""
return window.gettopleftpixel(*coords, onscreen=True) if coords else coords
def draw_char(char: str, coords: tuple[int, int], unscale: bool = False) -> None:
"""
Checks the char and coordinates, then
draw the given char at the given 2D coordinates in form (x, y).
Note that each condition has its own draw_char call, due to issues with pygame synchronization.
:param unscale:
:param char:
:param coords:
:return:
"""
try:
if unscale:
coords = __unscale_coordinates(coords)
char_at_coords: str = get_char_at(coords)
if char == NODE_SYMBOLS[-1]: # Empty space
if char_at_coords == NODE_SYMBOLS[0]: # Erase the starting node.
config.CHOSEN_START = ()
elif char_at_coords == NODE_SYMBOLS[1]: # Erase the end node.
config.CHOSEN_END = ()
__draw_char(char, coords, NODE_COLORS[char])
elif char_at_coords in NODE_SYMBOLS[:2]:
return
elif char == NODE_SYMBOLS[0]: # Starting node
input.selected_char = NODE_SYMBOLS[1 + bool(config.CHOSEN_END)]
config.CHOSEN_START = __scale_coordinates(coords)
__draw_char(char, coords, NODE_COLORS[char])
elif char == NODE_SYMBOLS[1]: # End node
input.selected_char = NODE_SYMBOLS[2]
config.CHOSEN_END = __scale_coordinates(coords)
__draw_char(char, coords, NODE_COLORS[char])
elif char == NODE_SYMBOLS[-3]: # Search paths
if char_at_coords == NODE_SYMBOLS[3]:
__draw_char(NODE_SYMBOLS[3], coords, NODE_COLORS[f"{NODE_SYMBOLS[3]}FOUND"], no_register=True)
elif visual_char_at(coords) != NODE_SYMBOLS[-2] and char_at_coords != NODE_SYMBOLS[-2]:
__draw_char(char, coords, NODE_COLORS[char])
elif char == NODE_SYMBOLS[-2]: # Final path
if char_at_coords == NODE_SYMBOLS[3]: # Check if node is hub.
__draw_char(NODE_SYMBOLS[3], coords, NODE_COLORS[f"{NODE_SYMBOLS[3]}PASSED"], no_register=True)
else:
__draw_char(char, coords, NODE_COLORS[char])
elif char_at_coords == NODE_SYMBOLS[-1] or not NODE_SYMBOLS[-1]: # Check if the space is empty, draw char if so.
__draw_char(char, coords, NODE_COLORS[char])
except TypeError:
return
def __valid_line(line: str) -> bool:
"""
Returns True if the characters in the given map are present in the possible_chars.
:param line:
:return:
"""
has_end: bool = False
has_start: bool = False
for c in line:
if c == NODE_SYMBOLS[1]:
if has_end:
return False
has_end = True
elif c == NODE_SYMBOLS[0]:
if has_start:
return False
has_start = True
elif c not in NODE_SYMBOLS:
return False
return True
def mark_cell(coords: tuple[int, int]) -> None:
"""
Displays a search character at the given scaled coords.
:param coords:
:return:
"""
draw_char(NODE_SYMBOLS[-3], coords, unscale=True)
def load_map(file_name: str) -> None:
"""
Loads a map from a txt file.
:param file_name:
:return:
"""
clear()
with open(file_name, 'r') as file:
for line_index in range(RES[1]):
line: str = file.readline().rstrip()
if line is None:
break
if not __valid_line(line):
print("\033[31mIncompatible map with list of chars.\033[0m")
clear()
return
for i, c in enumerate(line):
if RES[0] <= i or line_index == RES[1] - 1 and i == RES[0] - 1:
break
if c == NODE_SYMBOLS[0]:
config.CHOSEN_START = (line_index, i)
elif c == NODE_SYMBOLS[1]:
config.CHOSEN_END = (line_index, i)
draw_char(c, (i, line_index), unscale=True)
def regress() -> None:
"""
Load given map content to display.
:return:
"""
chosen_hubs_copy: list[tuple[int, int]] = [coords for coords in config.chosen_hubs]
config.chosen_hubs.clear()
for row_index, row in enumerate(config.MAP):
for column_index, column in enumerate(row):
if row_index == RES[1] - 1 and column_index == RES[0] - 1:
break
char: str = visual_char_at((column_index, row_index), True)
if char == NODE_SYMBOLS[-3] or char == NODE_SYMBOLS[-2]:
draw_char(NODE_SYMBOLS[-1], (column_index, row_index), unscale=True)
elif char == NODE_SYMBOLS[3]:
draw_char(NODE_SYMBOLS[-1], (column_index, row_index), unscale=True)
draw_char(NODE_SYMBOLS[3], (column_index, row_index), unscale=True)
config.chosen_hubs = chosen_hubs_copy
def virtual_regress() -> None:
"""
Clears the map without clearing the display.
:return:
"""
for row_index, row in enumerate(config.MAP):
for column_index, column in enumerate(row):
if row_index == RES[1] - 1 and column_index == RES[0] - 1:
break
char: str = get_char_at((column_index, row_index), True)
if char == NODE_SYMBOLS[-3] or char == NODE_SYMBOLS[-2]:
__register_display(NODE_SYMBOLS[-1], (column_index, row_index))