Skip to content

Instantly share code, notes, and snippets.

@vmedea
Last active December 15, 2022 09:19
Show Gist options
  • Save vmedea/38a227f34863bec16b7546992b5b8534 to your computer and use it in GitHub Desktop.
Save vmedea/38a227f34863bec16b7546992b5b8534 to your computer and use it in GitHub Desktop.
Command line tarot
#!/usr/bin/env python3
# (C) Mara Huldra 2022
# SPDX-License-Identifier: MIT
'''
Parser for XBin file (ANSI/ASCII/PETSCII art grid).
Convert to unicode and print it out.
'''
import itertools
import struct
import sys
class Flags:
'''
XBin header flags.
'''
PALETTE = 1
FONT = 2
COMPRESS = 4
NONBLINK = 8
CHARS512 = 16
class XBin:
def __init__(self, width, height, flags, pal, font, fontsize, data):
self.width = width
self.height = height
self.flags = flags
self.pal = pal
self.font = font
self.fontsize = fontsize
self.data = data
def sample(self, x, y):
idx = (self.width * y + x) * 2
(glyph, attr) = self.data[idx:idx+2]
return (glyph, attr)
def rgb(self, i):
r = self.pal[i*3+0]
g = self.pal[i*3+1]
b = self.pal[i*3+2]
return ((r<<2) | (r>>4), (g<<2) | (g>>4), (b<<2) | (b>>4))
@classmethod
def parse(cls, f):
hdr = f.read(11)
(fid, width, height, fontsize, flags) = struct.unpack('<5sHHbb', hdr)
if fid != b'XBIN\x1a':
raise Exception('Could not parse XBIN header: invalid magic')
num_chars = 256
if flags & Flags.CHARS512:
num_chars = 512
if flags & Flags.PALETTE:
pal = f.read(48)
else:
pal = None
if flags & Flags.FONT:
font = []
for _ in range(num_chars):
glyph = f.read(fontsize)
if len(glyph) != fontsize:
raise Exception('XBIN font is truncated')
font.append(glyph)
else:
font = None
if flags & Flags.COMPRESS:
raise Exception('Compressed XBIN is not supported')
data = f.read(width * height * 2)
if len(data) != width * height * 2:
raise Exception('XBIN data ends early')
return cls(width, height, flags, pal, font, fontsize, data)
# mapping PETSCII to unicode
petscii_mapping = (
'@' # 0x40 -> COMMERCIAL AT
'A' # 0x41 -> LATIN CAPITAL LETTER A
'B' # 0x42 -> LATIN CAPITAL LETTER B
'C' # 0x43 -> LATIN CAPITAL LETTER C
'D' # 0x44 -> LATIN CAPITAL LETTER D
'E' # 0x45 -> LATIN CAPITAL LETTER E
'F' # 0x46 -> LATIN CAPITAL LETTER F
'G' # 0x47 -> LATIN CAPITAL LETTER G
'H' # 0x48 -> LATIN CAPITAL LETTER H
'I' # 0x49 -> LATIN CAPITAL LETTER I
'J' # 0x4A -> LATIN CAPITAL LETTER J
'K' # 0x4B -> LATIN CAPITAL LETTER K
'L' # 0x4C -> LATIN CAPITAL LETTER L
'M' # 0x4D -> LATIN CAPITAL LETTER M
'N' # 0x4E -> LATIN CAPITAL LETTER N
'O' # 0x4F -> LATIN CAPITAL LETTER O
'P' # 0x50 -> LATIN CAPITAL LETTER P
'Q' # 0x51 -> LATIN CAPITAL LETTER Q
'R' # 0x52 -> LATIN CAPITAL LETTER R
'S' # 0x53 -> LATIN CAPITAL LETTER S
'T' # 0x54 -> LATIN CAPITAL LETTER T
'U' # 0x55 -> LATIN CAPITAL LETTER U
'V' # 0x56 -> LATIN CAPITAL LETTER V
'W' # 0x57 -> LATIN CAPITAL LETTER W
'X' # 0x58 -> LATIN CAPITAL LETTER X
'Y' # 0x59 -> LATIN CAPITAL LETTER Y
'Z' # 0x5A -> LATIN CAPITAL LETTER Z
'[' # 0x5B -> LEFT SQUARE BRACKET
'\xa3' # 0x5C -> POUND SIGN
']' # 0x5D -> RIGHT SQUARE BRACKET
'\u2191' # 0x5E -> UPWARDS ARROW
'\u2190' # 0x5F -> LEFTWARDS ARROW
' ' # 0x20 -> SPACE
'!' # 0x21 -> EXCLAMATION MARK
'"' # 0x22 -> QUOTATION MARK
'#' # 0x23 -> NUMBER SIGN
'$' # 0x24 -> DOLLAR SIGN
'%' # 0x25 -> PERCENT SIGN
'&' # 0x26 -> AMPERSAND
"'" # 0x27 -> APOSTROPHE
'(' # 0x28 -> LEFT PARENTHESIS
')' # 0x29 -> RIGHT PARENTHESIS
'*' # 0x2A -> ASTERISK
'+' # 0x2B -> PLUS SIGN
',' # 0x2C -> COMMA
'-' # 0x2D -> HYPHEN-MINUS
'.' # 0x2E -> FULL STOP
'/' # 0x2F -> SOLIDUS
'0' # 0x30 -> DIGIT ZERO
'1' # 0x31 -> DIGIT ONE
'2' # 0x32 -> DIGIT TWO
'3' # 0x33 -> DIGIT THREE
'4' # 0x34 -> DIGIT FOUR
'5' # 0x35 -> DIGIT FIVE
'6' # 0x36 -> DIGIT SIX
'7' # 0x37 -> DIGIT SEVEN
'8' # 0x38 -> DIGIT EIGHT
'9' # 0x39 -> DIGIT NINE
':' # 0x3A -> COLON
';' # 0x3B -> SEMICOLON
'<' # 0x3C -> LESS-THAN SIGN
'=' # 0x3D -> EQUALS SIGN
'>' # 0x3E -> GREATER-THAN SIGN
'?' # 0x3F -> QUESTION MARK
'\U0001fb79'# 0x60 -> HORIZONTAL ONE EIGHTH BLOCK-5
'\u2660' # 0x61 -> BLACK SPADE SUIT
'\U0001fb72'# 0x62 -> VERTICAL ONE EIGHTH BLOCK-4
'\U0001fb78'# 0x63 -> HORIZONTAL ONE EIGHTH BLOCK-4
'\U0001fb77'# 0x64 -> BOX DRAWINGS LIGHT HORIZONTAL ONE QUARTER UP
'\U0001fb76'# 0x65 -> HORIZONTAL ONE EIGHTH BLOCK-2
'\U0001fb7a'# 0x66 -> HORIZONTAL ONE EIGHTH BLOCK-6
'\U0001fb71'# 0x67 -> VERTICAL ONE EIGHTH BLOCK-3
'\U0001fb74'# 0x68 -> VERTICAL ONE EIGHTH BLOCK-6
'\u256e' # 0x69 -> BOX DRAWINGS LIGHT ARC DOWN AND LEFT
'\u2570' # 0x6A -> BOX DRAWINGS LIGHT ARC UP AND RIGHT
'\u256f' # 0x6B -> BOX DRAWINGS LIGHT ARC UP AND LEFT
'\U0001fb7c'# 0x6C -> LEFT AND LOWER ONE EIGHTH BLOCK
'\u2572' # 0x6D -> BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
'\u2571' # 0x6E -> BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
'\U0001fb7d'# 0x6F -> LEFT AND UPPER ONE EIGHTH BLOCK
'\U0001fb7e'# 0x70 -> RIGHT AND UPPER ONE EIGHTH BLOCK
'\u25cf' # 0x71 -> BLACK CIRCLE
'\U0001fb7b'# 0x72 -> HORIZONTAL ONE EIGHTH BLOCK-7
'\u2665' # 0x73 -> BLACK HEART SUIT
'\U0001fb70'# 0x74 -> VERTICAL ONE EIGHTH BLOCK-6
'\u256d' # 0x75 -> BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
'\u2573' # 0x76 -> BOX DRAWINGS LIGHT DIAGONAL CROSS
'\u25cb' # 0x77 -> WHITE CIRCLE
'\u2663' # 0x78 -> BLACK CLUB SUIT
'\U0001fb75'# 0x79 -> VERTICAL ONE EIGHTH BLOCK-7
'\u2666' # 0x7A -> BLACK DIAMOND SUIT
'\u253c' # 0x7B -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
'\U0001fb8c'# 0x7C -> LEFT HALF MEDIUM SHADE
'\u2502' # 0x7D -> BOX DRAWINGS LIGHT VERTICAL
'\U0001fb96'# 0x7E -> INVERSE CHECKER BOARD FILL
'\U0001fb98'# 0x7F -> UPPER LEFT TO LOWER RIGHT FILL
'\xa0' # 0xA0 -> NO-BREAK SPACE
'\u258c' # 0xA1 -> LEFT HALF BLOCK
'\u2584' # 0xA2 -> LOWER HALF BLOCK
'\u2594' # 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581' # 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f' # 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592' # 0xA6 -> MEDIUM SHADE
'\u2595' # 0xA7 -> RIGHT ONE EIGHTH BLOCK
'\U0001fb8f'# 0xA8 -> LOWER HALF MEDIUM SHADE
'\u25e4' # 0xA9 -> BLACK UPPER LEFT TRIANGLE
'\U0001fb87'# 0xAA -> RIGHT ONE QUARTER BLOCK
'\u251c' # 0xAB -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
'\u2597' # 0xAC -> QUADRANT LOWER RIGHT
'\u2514' # 0xAD -> BOX DRAWINGS LIGHT UP AND RIGHT
'\u2510' # 0xAE -> BOX DRAWINGS LIGHT DOWN AND LEFT
'\u2582' # 0xAF -> LOWER ONE QUARTER BLOCK
'\u250c' # 0xB0 -> BOX DRAWINGS LIGHT DOWN AND RIGHT
'\u2534' # 0xB1 -> BOX DRAWINGS LIGHT UP AND HORIZONTAL
'\u252c' # 0xB2 -> BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
'\u2524' # 0xB3 -> BOX DRAWINGS LIGHT VERTICAL AND LEFT
'\u258e' # 0xB4 -> LEFT ONE QUARTER BLOCK
'\u258d' # 0xB5 -> LEFT THREE EIGTHS BLOCK
'\U0001fb88'# 0xB6 -> RIGHT THREE EIGHTHS BLOCK
'\U0001fb82'# 0xB7 -> UPPER ONE QUARTER BLOCK
'\U0001fb83'# 0xB8 -> UPPER THREE EIGHTHS BLOCK
'\u2583' # 0xB9 -> LOWER THREE EIGHTHS BLOCK
'\U0001fb7f'# 0xBA -> RIGHT AND LOWER ONE EIGHTH BLOCK
'\u2596' # 0xBB -> QUADRANT LOWER LEFT
'\u259d' # 0xBC -> QUADRANT UPPER RIGHT
'\u2518' # 0xBD -> BOX DRAWINGS LIGHT UP AND LEFT
'\u2598' # 0xBE -> QUADRANT UPPER LEFT
'\u259a' # 0xBF -> QUADRANT UPPER LEFT AND LOWER RIGHT
)
# cp437 mapping from rexpaint
cp437_mapping = (
'\u00A0', # 0 (nothing really, but using hard space)
'\u263A', # 1
'\u263B', # 2
'\u2665', # 3
'\u2666', # 4
'\u2663', # 5
'\u2660', # 6
'\u2022', # 7
'\u25DB', # 8
'\u25CB', # 9
'\u25D9', # 10
'\u2642', # 11
'\u2640', # 12
'\u266A', # 13
'\u266B', # 14
'\u263C', # 15
'\u25BA', # 16
'\u25C4', # 17
'\u2195', # 18
'\u203C', # 19
'\u00B6', # 20
'\u00A7', # 21
'\u25AC', # 22
'\u21A8', # 23
'\u2191', # 24
'\u2193', # 25
'\u2192', # 26
'\u2190', # 27
'\u221F', # 28
'\u2194', # 29
'\u25B2', # 30
'\u25BC', # 31
'\u00A0', # 32 (must used hard space instead of actual space, to prevent collapsing)
'!', # 33
'\"', # 34
'#', # 35
'$', # 36
'%', # 37
'&', # 38
'\'', # 39
'(', # 40
')', # 41
'*', # 42
'+', # 43
',', # 44
'-', # 45
'.', # 46
'/', # 47
'0', # 48
'1', # 49
'2', # 50
'3', # 51
'4', # 52
'5', # 53
'6', # 54
'7', # 55
'8', # 56
'9', # 57
':', # 58
';', # 59
'<', # 60
'=', # 61
'>', # 62
'?', # 63
'@', # 64
'A', # 65
'B', # 66
'C', # 67
'D', # 68
'E', # 69
'F', # 70
'G', # 71
'H', # 72
'I', # 73
'J', # 74
'K', # 75
'L', # 76
'M', # 77
'N', # 78
'O', # 79
'P', # 80
'Q', # 81
'R', # 82
'S', # 83
'T', # 84
'U', # 85
'V', # 86
'W', # 87
'X', # 88
'Y', # 89
'Z', # 90
'[', # 91
'\\', # 92
']', # 93
'^', # 94
'_', # 95
'`', # 96
'a', # 97
'b', # 98
'c', # 99
'd', # 100
'e', # 101
'f', # 102
'g', # 103
'h', # 104
'i', # 105
'j', # 106
'k', # 107
'l', # 108
'm', # 109
'n', # 110
'o', # 111
'p', # 112
'q', # 113
'r', # 114
's', # 115
't', # 116
'u', # 117
'v', # 118
'w', # 119
'x', # 120
'y', # 121
'z', # 122
'{', # 123
'|', # 124
'}', # 125
'~', # 126
'\u2302', # 127
'\u00C7', # 128
'\u00FC', # 129
'\u00E9', # 130
'\u00E2', # 131
'\u00E4', # 132
'\u00E0', # 133
'\u00E5', # 134
'\u00E7', # 135
'\u00EA', # 136
'\u00EB', # 137
'\u00E8', # 138
'\u00EF', # 139
'\u00EE', # 140
'\u00EC', # 141
'\u00C4', # 142
'\u00C5', # 143
'\u00C9', # 144
'\u00E6', # 145
'\u00C6', # 146
'\u00F4', # 147
'\u00F6', # 148
'\u00F2', # 149
'\u00FB', # 150
'\u00F9', # 151
'\u00FF', # 152
'\u00D6', # 153
'\u00DC', # 154
'\u00A2', # 155
'\u00A3', # 156
'\u00A5', # 157
'\u20A7', # 158
'\u0192', # 159
'\u00E1', # 160
'\u00ED', # 161
'\u00F3', # 162
'\u00FA', # 163
'\u00F1', # 164
'\u00D1', # 165
'\u00AA', # 166
'\u00BA', # 167
'\u00BF', # 168
'\u2310', # 169
'\u00AC', # 170
'\u00BD', # 171
'\u00BC', # 172
'\u00A1', # 173
'\u00AB', # 174
'\u00BB', # 175
'\u2591', # 176
'\u2592', # 177
'\u2593', # 178
'\u2502', # 179
'\u2524', # 180
'\u2561', # 181
'\u2562', # 182
'\u2556', # 183
'\u2555', # 184
'\u2563', # 185
'\u2551', # 186
'\u2557', # 187
'\u255D', # 188
'\u255C', # 189
'\u255B', # 190
'\u2510', # 191
'\u2514', # 192
'\u2534', # 193
'\u252C', # 194
'\u251C', # 195
'\u2500', # 196
'\u253C', # 197
'\u255E', # 198
'\u255F', # 199
'\u255A', # 200
'\u2554', # 201
'\u2569', # 202
'\u2566', # 203
'\u2560', # 204
'\u2550', # 205
'\u256C', # 206
'\u2567', # 207
'\u2568', # 208
'\u2564', # 209
'\u2565', # 210
'\u2559', # 211
'\u2558', # 212
'\u2552', # 213
'\u2553', # 214
'\u256B', # 215
'\u256A', # 216
'\u2518', # 217
'\u250C', # 218
'\u2588', # 219
'\u2584', # 220
'\u258C', # 221
'\u2590', # 222
'\u2580', # 223
'\u03B1', # 224
'\u00DF', # 225
'\u0393', # 226
'\u03C0', # 227
'\u03A3', # 228
'\u03C3', # 229
'\u00B5', # 230
'\u03C4', # 231
'\u03A6', # 232
'\u0398', # 233
'\u03A9', # 234
'\u03B4', # 235
'\u221E', # 236
'\u03C6', # 237
'\u03B5', # 238
'\u2229', # 239
'\u2261', # 240
'\u00B1', # 241
'\u2265', # 242
'\u2264', # 243
'\u2320', # 244
'\u2321', # 245
'\u00F7', # 246
'\u2248', # 247
'\u00B0', # 248
'\u2219', # 249
'\u00B7', # 250
'\u221A', # 251
'\u207F', # 252
'\u00B2', # 253
'\u25A0', # 254
'\u25A1', # 255 (changed this to be empty version of 254)
)
RESET = '\x1b[0m'
class LineBuilder:
'''
Build a line of text with terminal attributes.
Always puts a RESET token at the beginning and end of the line to make
sure the terminal is in consistent state in-between.
'''
def __init__(self):
self.items = [RESET]
def update_state(self, update):
codes = itertools.chain.from_iterable(update.values())
self.emit_sgr(list(codes))
def emit_sgr(self, codes):
if codes:
# emit SGR sequence
self.items.append('\x1b[')
self.items.append(';'.join((str(code) for code in codes)))
self.items.append('m')
def append(self, text):
self.items.append(text)
def __str__(self):
return ''.join(self.items) + RESET
class OptimizingLineBuilder(LineBuilder):
'''
Build a line of text with terminal attributes. Keep track of current
terminal state to emit escape codes on changes only.
'''
def __init__(self):
super().__init__()
# Default terminal state after reset:
# fg is default color, bg is default color
self.state = {'fg': (39,), 'bg': (49,)}
self.state_updates = {}
def update_state(self, update):
self.state_updates.update(update)
def flush_state(self):
codes = (v for k,v in self.state_updates.items() if v != self.state[k])
codes = list(itertools.chain.from_iterable(codes))
self.emit_sgr(codes)
self.state.update(self.state_updates)
self.state_updates = {}
def append(self, text):
self.flush_state()
self.items.append(text)
class AttrMap:
'''Attribute mapping.'''
@staticmethod
def true(pal, attr, glyph_attr):
'''
Set attribute using true-color ANSI sequence.
'''
fg = pal.rgb(attr & 15)
bg = pal.rgb(attr >> 4)
if glyph_attr & 0x01: # reverse-video bit
(fg, bg) = (bg, fg)
return {'fg': (38, 2, fg[0], fg[1], fg[2]),
'bg': (48, 2, bg[0], bg[1], bg[2])}
@staticmethod
def cga16(lpal, attr, glyph_attr):
'''
Set attribute using 16-color terminal palette, assuming
the xbin file uses default CGA16 palette.
'''
def swizzle(attr):
return (attr&1)*4 + ((attr&2)>>1)*2 + ((attr&4)>>2)*1 + (attr >> 3) * 60
if glyph_attr & 0x01: # reverse-video bit
attr = (attr >> 4) | ((attr << 4) & 0xf0)
return {'fg': (30 + swizzle(attr & 15),),
'bg': (40 + swizzle(attr >> 4),)}
class GlyphMap:
'''Glyph mapping.'''
def cp437(glyph):
'''
General unicode approximation of DOS cp437.
'''
return cp437_mapping[glyph], 0
def petscii(glyph):
'''
General unicode approximation of PETSCII.
'''
return petscii_mapping[glyph & 0x7f], (glyph >> 7) & 1
def c64_pro_mono(glyph):
'''
Use PETSCII glyphs in private use area for the "C64 Pro Mono" font.
'''
return chr(0xee00 + glyph), 0
def dump(xb, attr_map, glyph_map, line_builder=OptimizingLineBuilder):
for y in range(xb.height):
line = line_builder()
for x in range(xb.width):
(glyph, attr) = xb.sample(x, y)
(ch, glyph_attr) = glyph_map(glyph)
line.update_state(attr_map(xb, attr, glyph_attr))
line.append(ch)
print(str(line))
def dump_mult(images, width, attr_map, glyph_map, line_builder=OptimizingLineBuilder):
for y in range(images[0].height):
line = line_builder()
for xb in images:
for x in range(width):
(glyph, attr) = xb.sample(x, y)
(ch, glyph_attr) = glyph_map(glyph)
line.update_state(attr_map(xb, attr, glyph_attr))
line.append(ch)
print(str(line))
def dump_font(xb):
for ch in range(256):
print(f'{ch:02x}')
for y in range(xb.fontsize):
line = []
for x in range(8):
if xb.font[ch][y] & (1 << (7-x)):
line.append('█')
else:
line.append(' ')
print(''.join(line))
def parse_args():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("filename", help="xbin file to display")
parser.add_argument("--optimize", "-O", default="1", choices=["0", "1"], help="Try to optimize output (0 emits full SGR state for every character, 1 only for changes and between lines, default is 1).")
parser.add_argument("--print-font", "-f", help="Print font.", action="store_true")
parser.add_argument("--attr-mapping", "-a",
default="true",
help='Which attribute mapping to use (one of "true", "cga16", default: true).')
parser.add_argument("--glyph-mapping", "-m",
default="petscii",
help='Which glyph mapping to use (one of "cp437", "petscii", "petscii_c64_pro_mono" or "custom:<file.json>", default: petscii).')
return parser.parse_args()
def main():
import json, sys
args = parse_args()
try:
attr_map = getattr(AttrMap, args.attr_mapping)
except AttributeError:
print(f'Error: unknown attribute mapping {args.attr_mapping}.', file=sys.stderr)
exit(1)
if args.glyph_mapping.startswith('custom:'):
with open(args.glyph_mapping[7:], 'r') as f:
mapping = json.load(f)
glyph_map = mapping.__getitem__
else:
try:
glyph_map = getattr(GlyphMap, args.glyph_mapping)
except AttributeError:
print(f'Error: unknown glyph mapping {args.glyph_mapping}.', file=sys.stderr)
exit(1)
line_builder = [LineBuilder, OptimizingLineBuilder][int(args.optimize)]
with open(args.filename, 'rb') as f:
xb = XBin.parse(f)
if args.print_font:
dump_font(xb)
else:
dump(xb, attr_map, glyph_map, line_builder)
if __name__ == '__main__':
main()
#!/usr/bin/env python3
# (C) Mara Huldra 2022
# SPDX-License-Identifier: MIT
'''
Command-line tarot oracle.
Cards by littlebitspace (https://16colo.rs/pack/lbs-tarot/).
'''
from parse_xbin import XBin, dump, dump_mult, AttrMap, GlyphMap
import os
from pathlib import Path
import random
import sys
CARDS = [
'littlebitspace-0major_arcana00.xb',
'littlebitspace-0major_arcana01.xb',
'littlebitspace-0major_arcana02.xb',
'littlebitspace-0major_arcana03.xb',
'littlebitspace-0major_arcana04.xb',
'littlebitspace-0major_arcana05.xb',
'littlebitspace-0major_arcana06.xb',
'littlebitspace-0major_arcana07.xb',
'littlebitspace-0major_arcana08.xb',
'littlebitspace-0major_arcana09.xb',
'littlebitspace-0major_arcana10.xb',
'littlebitspace-0major_arcana11.xb',
'littlebitspace-0major_arcana12.xb',
'littlebitspace-0major_arcana13.xb',
'littlebitspace-0major_arcana14.xb',
'littlebitspace-0major_arcana15.xb',
'littlebitspace-0major_arcana16.xb',
'littlebitspace-0major_arcana17.xb',
'littlebitspace-0major_arcana18.xb',
'littlebitspace-0major_arcana19.xb',
'littlebitspace-0major_arcana20.xb',
'littlebitspace-0major_arcana21.xb',
'littlebitspace-1wands01.xb',
'littlebitspace-1wands02.xb',
'littlebitspace-1wands03.xb',
'littlebitspace-1wands04.xb',
'littlebitspace-1wands05.xb',
'littlebitspace-1wands06.xb',
'littlebitspace-1wands07.xb',
'littlebitspace-1wands08.xb',
'littlebitspace-1wands09.xb',
'littlebitspace-1wands10.xb',
'littlebitspace-1wands11.xb',
'littlebitspace-1wands12.xb',
'littlebitspace-1wands13.xb',
'littlebitspace-1wands14.xb',
'littlebitspace-2cups01.xb',
'littlebitspace-2cups02.xb',
'littlebitspace-2cups03.xb',
'littlebitspace-2cups04.xb',
'littlebitspace-2cups05.xb',
'littlebitspace-2cups06.xb',
'littlebitspace-2cups07.xb',
'littlebitspace-2cups08.xb',
'littlebitspace-2cups09.xb',
'littlebitspace-2cups10.xb',
'littlebitspace-2cups11.xb',
'littlebitspace-2cups12.xb',
'littlebitspace-2cups13.xb',
'littlebitspace-2cups14.xb',
'littlebitspace-3swords01.xb',
'littlebitspace-3swords02.xb',
'littlebitspace-3swords03.xb',
'littlebitspace-3swords04.xb',
'littlebitspace-3swords05.xb',
'littlebitspace-3swords06.xb',
'littlebitspace-3swords07.xb',
'littlebitspace-3swords08.xb',
'littlebitspace-3swords09.xb',
'littlebitspace-3swords10.xb',
'littlebitspace-3swords11.xb',
'littlebitspace-3swords12.xb',
'littlebitspace-3swords13.xb',
'littlebitspace-3swords14.xb',
'littlebitspace-4coins01.xb',
'littlebitspace-4coins02.xb',
'littlebitspace-4coins03.xb',
'littlebitspace-4coins04.xb',
'littlebitspace-4coins05.xb',
'littlebitspace-4coins06.xb',
'littlebitspace-4coins07.xb',
'littlebitspace-4coins08.xb',
'littlebitspace-4coins09.xb',
'littlebitspace-4coins10.xb',
'littlebitspace-4coins11.xb',
'littlebitspace-4coins12.xb',
'littlebitspace-4coins13.xb',
'littlebitspace-4coins14.xb',
]
# width of a card is 17 characters
CARD_W = 17
def main():
parent = os.path.join(Path(__file__).resolve().parent, 'tarot')
cards = CARDS[:]
attr_map = AttrMap.true
# Conversion method from PETSCII to unicode (what is best depends on the terminal font).
glyph_map = GlyphMap.petscii
#glyph_map = GlyphMap.c64_pro_mono
random.shuffle(cards)
if len(sys.argv) > 1:
num = int(sys.argv[1])
images = []
for filename in cards[0:num]:
with open(os.path.join(parent, filename), 'rb') as f:
images.append(XBin.parse(f))
dump_mult(images, CARD_W + 1, attr_map, glyph_map)
else:
with open(os.path.join(parent, cards[0]), 'rb') as f:
xb = XBin.parse(f)
dump(xb, attr_map, glyph_map)
if __name__ == '__main__':
main()
@vmedea
Copy link
Author

vmedea commented Dec 15, 2022

Works wonderfully well! Btw, what is the format for json?

It's an array of 256 entries ["glyph", flags] (one for every font slot). flags can only be 0 or 1 at the moment which indicates reverse-video. But if something turns up with bold or underline or whatever glyphs, bit flags could be added for those.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment