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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
|
import curses
from functools import reduce
from enum import Enum
import math
class Ouija:
class Align(Enum):
LEFT = 0
RIGHT = 1
CENTER = 2
DEFAULT_VERT_EDGE = "|"
DEFAULT_HORIZ_EDGE = "~"
DEFAULT_CORNER = ":"
DEFAULT_HORIZ_PAD = 1
DEFAULT_VERT_PAD = 0
DEFAULT_HORIZ_MARGIN = 0
DEFAULT_VERT_MARGIN = 0
DEFAULT_UNIFORM_WIDTH = True
DEFAULT_ALIGN = Align.LEFT
class _OuijaColor:
DEFAULT = -1
BLACK = 0
RED = 1
GREEN = 2
BLUE = 4
CYAN = 6
WHITE = 7
ORANGE = 9
YELLOW = 11
def __init__(self, fg=DEFAULT, bg=DEFAULT):
self.fg = fg
self.bg = bg
def color_pair(self):
# start with an offset of 1 (can't do negatives)
out = 1
# hacky bitwise operator for different fg/bg colors
white_bg = 16
white_fg = 32
black_fg = 64
if self.bg == self.DEFAULT:
return out + self.fg
elif self.bg == self.WHITE:
return out + white_bg + self.fg
elif self.fg == self.WHITE:
return out + white_fg + self.bg
else:
return out + black_fg + self.bg
COLORS = {
"DEFAULT" : _OuijaColor(),
"BLACK" : _OuijaColor(_OuijaColor.BLACK),
"RED" : _OuijaColor(_OuijaColor.RED),
"GREEN" : _OuijaColor(_OuijaColor.GREEN),
"BLUE" : _OuijaColor(_OuijaColor.BLUE),
"CYAN" : _OuijaColor(_OuijaColor.CYAN),
"WHITE" : _OuijaColor(_OuijaColor.WHITE),
"ORANGE" : _OuijaColor(_OuijaColor.ORANGE),
"YELLOW" : _OuijaColor(_OuijaColor.YELLOW),
"RED_ON_WHITE" : _OuijaColor(_OuijaColor.RED, _OuijaColor.WHITE),
"GREEN_ON_WHITE" : _OuijaColor(_OuijaColor.GREEN, _OuijaColor.WHITE),
"BLUE_ON_WHITE" : _OuijaColor(_OuijaColor.BLUE, _OuijaColor.WHITE),
"CYAN_ON_WHITE" : _OuijaColor(_OuijaColor.CYAN, _OuijaColor.WHITE),
"ORANGE_ON_WHITE" : _OuijaColor(_OuijaColor.ORANGE, _OuijaColor.WHITE),
"YELLOW_ON_WHITE" : _OuijaColor(_OuijaColor.YELLOW, _OuijaColor.WHITE),
"WHITE_ON_BLACK" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.BLACK),
"WHITE_ON_RED" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.RED),
"WHITE_ON_GREEN" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.GREEN),
"WHITE_ON_BLUE" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.BLUE),
"WHITE_ON_CYAN" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.CYAN),
"WHITE_ON_ORANGE" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.ORANGE),
"WHITE_ON_YELLOW" : _OuijaColor(_OuijaColor.WHITE, _OuijaColor.YELLOW),
"BLACK_ON_WHITE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.WHITE),
"BLACK_ON_RED" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.RED),
"BLACK_ON_GREEN" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.GREEN),
"BLACK_ON_BLUE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.BLUE),
"BLACK_ON_CYAN" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.CYAN),
"BLACK_ON_ORANGE" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.ORANGE),
"BLACK_ON_YELLOW" : _OuijaColor(_OuijaColor.BLACK, _OuijaColor.YELLOW),
}
def _setup_curses_colors():
# initialize colors for curses
curses.start_color()
curses.use_default_colors()
for c in Ouija.COLORS.keys():
color = Ouija.COLORS[c]
curses.init_pair(color.color_pair(), color.fg, color.bg)
def __init__(self, stdscr, y, x):
self.stdscr = stdscr
self.y = y
self.x = x
# setup default styles
self.style()
# setup the curses colors
Ouija._setup_curses_colors()
# helper method to access style variables
def style(self,
vert_edge=DEFAULT_VERT_EDGE,
horiz_edge=DEFAULT_HORIZ_EDGE,
corner=DEFAULT_CORNER,
horiz_pad=DEFAULT_HORIZ_PAD,
vert_pad=DEFAULT_VERT_PAD,
horiz_margin=DEFAULT_HORIZ_MARGIN,
vert_margin=DEFAULT_VERT_MARGIN,
uniform_width=DEFAULT_UNIFORM_WIDTH,
align=DEFAULT_ALIGN):
self.vert_edge = vert_edge
self.horiz_edge = horiz_edge
self.corner = corner
self.horiz_pad = horiz_pad
self.vert_pad = vert_pad
self.horiz_margin = horiz_margin
self.vert_margin = vert_margin
self.uniform_width = uniform_width
self.align = align
# setup the tiles
def setup_tiles(self, ts):
if type(ts) != list:
return False
self.tiles = {}
for t in ts:
if type(t) != Tile:
return False
self.tiles[t.in_val] = t
print(str(self.tiles))
return True
def calc_cell_width(self, cell_value):
edges = 2 if self.vert_edge is not None else 0
return len(cell_value) + (2 * self.horiz_pad) + edges
def calc_row_width(self, row):
width = 0
for cell in row:
# if there's a cooresponding display value, use its length
# if not, just take the width directly
if cell in self.tiles:
width += self.calc_cell_width(self.tiles[cell].out_val)
else:
width += self.calc_cell_width(cell)
# if there's no margin, we double counted some dividers
if self.horiz_margin == 0:
width -= len(row) - 1
elif self.horiz_margin > 1:
width += (self.horiz_margin - 1) * (len(row) - 1)
return width
# draw the board, optionally highlighting a cell
def draw_board(self, board, hy=-1, hx=-1):
# calculate the longest row
max_row_length = max([len(row) for row in board])
if self.uniform_width:
# get the widest possible cell
out_vals = [i.out_val for i in self.tiles.values()]
max_cell_width = max([len(x) for x in out_vals])
# the max row width is the longest row of the widest cells
max_row_width = self.calc_row_width(["." * max_cell_width] * max_row_length)
else:
max_row_width = max([self.calc_row_width(row) for row in board])
# if specified, make every key as wide as the widest key
if self.uniform_width:
out_vals = [i.out_val for i in self.tiles.values()]
max_width = max([len(x) for x in out_vals])
# keep track of y-coordinate, and board y-index
cur_y = self.y
iy = 0
# loop through all columns in board
for col in board:
if self.uniform_width:
cur_row_width = self.calc_row_width(["." * max_cell_width] * len(col))
else:
cur_row_width = self.calc_row_width(col)
# keep track of x-coordinate, and board x-index
cur_x = self.x
ix = 0
# loop through all cells in the column
for cell in col:
tile = self.tiles[cell]
if self.uniform_width:
cell_width = self.calc_cell_width("." * max_cell_width)
else:
cell_width = self.calc_cell_width(tile.out_val)
target_x = cur_x
if self.align == Ouija.Align.RIGHT:
target_x += max_row_width - cur_row_width
if self.align == Ouija.Align.CENTER:
target_x += math.floor(max_row_width / 2) - math.floor(cur_row_width / 2)
# create the format string with the appropriate width
target_width = max_width if self.uniform_width else len(tile.out_val)
fmt_string = "{:^" + str(target_width) + "}"
# add A_STANDOUT if the cell needs to be highlighted
target_style = tile.style
if hx == ix and hy == iy:
target_style = target_style | curses.A_STANDOUT
# draw the key
#self.draw_rect_key(cur_y, cur_x, fmt_string.format(tile.out_val), tile.color, target_style)
self.draw_rect_key(cur_y, target_x, fmt_string.format(tile.out_val), tile.color, target_style)
# update the x-coordinate, based on width, padding, and margin
# if there's a vertical edge, add an additional position
#cur_x += target_width + (2 * self.horiz_pad) + self.horiz_margin
cur_x += cell_width - 1 + self.horiz_margin
# update the board x-index
ix += 1
# update the y-coordinate, based on padding and margin
# if there's a horizontal edge, add an additional position
cur_y += 1 + (2 * self.vert_pad) + self.vert_margin
if self.horiz_edge is not None:
cur_y += 1
# update the board x-index
iy += 1
# draw a rectangular key, with the top corner at the given (y, x),
# the specified text, color, and style
def draw_rect_key(self, y, x, text, color, style):
# calculate the inner width, including padding
iw = len(text) + (2 * self.horiz_pad)
# outer width and horizontal offset are defaults
ow = iw
h_off = 0
v_off = 1
# if we have a vertical edge, adjust the outer width and horizontal offset
if self.vert_edge is not None:
ow += 2
h_off += 1
# create format strings for the inner and outer widths
owf = "{:^" + str(ow) + "}"
iwf = "{:^" + str(iw) + "}"
# calculate how tall each cell should be,
# and where in the cell the text should appear
max_y = y + 2 + (2 * self.vert_pad)
mid_y = math.ceil((max_y - y) / 2)
# draw the horizontal edges, if defined
if self.horiz_edge is not None:
# top edge begins at (y, x),
# is 'iw' long,
# centered within the 'ow' length
self.stdscr.addstr(y, x, owf.format(self.horiz_edge * iw))
# bottom edge takes into account vertical offset
# and vertical padding
self.stdscr.addstr(y + (2 * v_off) + (2 * self.vert_pad), x, owf.format(self.horiz_edge * iw))
# draw the vertical edges, if defined
if self.vert_edge is not None:
# loop through each row we need to draw
for iy in range(y + 1, max_y):
self.stdscr.addstr(iy, x, self.vert_edge)
self.stdscr.addstr(iy, x + ow - 1, self.vert_edge)
# draw the corners, if defined
if self.corner is not None:
self.stdscr.addstr(y, x, self.corner)
self.stdscr.addstr(y, x + ow - 1, self.corner)
self.stdscr.addstr(max_y, x, self.corner)
self.stdscr.addstr(max_y, x + ow - 1, self.corner)
# draw the inside
for iy in range(y + 1, max_y):
# if we're at the mid-point of the cell, draw the text
# otherwise, don't draw anything
target_text = text if iy == (y + mid_y) else ""
self.stdscr.addstr(iy, x + h_off, iwf.format(target_text), style | curses.color_pair(color.color_pair()))
class Tile:
def __init__(self, in_val, out_val, color=Ouija.COLORS["DEFAULT"], style=0):
self.in_val = in_val
self.out_val = out_val
self.color = color
self.style = style
def __str__(self):
return "[{},{},{}]".format(self.in_val, self.out_val, str(self.color))
# helper function, not sure where it goes yet
def biggest_cell(board):
return max([max(list(map(lambda x: len(x), row))) for row in board])
|