├── Example Games ├── silence.bin └── silence.src ├── Compiler ├── bin2array.py ├── ita.py └── compiler.py ├── Players ├── player.py └── ArduBoyGTI │ └── ArduBoyGTI.ino ├── README └── LICENSE /Example Games/silence.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spaceneedle/GameTextInterpreter/HEAD/Example Games/silence.bin -------------------------------------------------------------------------------- /Compiler/bin2array.py: -------------------------------------------------------------------------------- 1 | # convert BIN GTI files to C++ arrays for Arduino 2 | 3 | import sys 4 | 5 | if len(sys.argv) != 2: 6 | print "Usage: bin2array.py " 7 | quit() 8 | 9 | input = sys.argv[1] 10 | file = open(input,"r") 11 | 12 | print "const unsigned char gti[] PROGMEM = { " 13 | 14 | col = 0 15 | 16 | while 1==1: 17 | data = file.read(1) 18 | if data == "": break 19 | sys.stdout.write(str(ord(data))+",") 20 | col = col + 1 21 | if col == 90000: 22 | sys.stdout.write("\n") 23 | col = 0 24 | print "};" 25 | 26 | -------------------------------------------------------------------------------- /Compiler/ita.py: -------------------------------------------------------------------------------- 1 | # ITA2 BAUDOT Library 2 | 3 | mode = 0 4 | 5 | letters = "@E\nA SIU$DRJNFCKTZLWHYPQOBG^MXV~" # $ = CR, ^ = shift numbers, ~ = shift letters 6 | # 01 234567890123456789012345678901 7 | # 1 2 3 8 | numbers = "@3\n- $87%=4',!:(5\")2#6019?&^./;~" # $ = bell, = WRU?, ^ = shift numbers, ~ = shift to letters 9 | # 01 234567890123456 789012345678901" 10 | # 1 2 3 11 | 12 | # converts ASCII to ITA2 13 | 14 | def a2ita(character): 15 | if character == "^": return bin(27).split("b")[1].zfill(5) 16 | if character == "~": return bin(31).split("b")[1].zfill(5) 17 | for i in range(0,31): 18 | if mode == 0: 19 | if character == letters[i]: return bin(i).split("b")[1].zfill(5) 20 | if mode == 1: 21 | if character == numbers[i]: return bin(i).split("b")[1].zfill(5) 22 | return "00000" 23 | 24 | # converts a block of 10 characters into 5 byte ITA2 (40 bits) 25 | 26 | def a2itablock(string): 27 | bits = "" 28 | binary = "" 29 | oldstring = string 30 | string = "" 31 | mode = 0 32 | for i in range(0,len(oldstring)): # pre-processor to insert control characters 33 | if mode == 0: 34 | if (oldstring[i].isnumeric() == True) or (oldstring[i] == "-") or (oldstring[i] == ",") or (oldstring[i] == ".") or (oldstring[i] == "\"") or (oldstring[i] == "?"): 35 | string = string + "^" 36 | mode = 1 37 | print "switch to number" 38 | else: 39 | if (oldstring[i].isnumeric() == False) or (oldstring[i] != "\n") or (oldstring[i] != " ") or (oldstring[i] != "\n") or (oldstring[i] != "-") or (oldstring[i] != ",") or (oldstring[i] != ".") or (oldstring[i] != "\"") or (oldstring[i] != "?"): 40 | string = string + "~" 41 | mode = 0 42 | print "switch to string" 43 | string = string + oldstring[i] 44 | mode = 0 45 | for x in range(0,len(string),8): 46 | bits = "" 47 | for i in range(0,9): 48 | try: 49 | bits = bits + a2ita(string[i+x]) 50 | except: 51 | bits = bits + "00000" 52 | for i in range(0,39,8): 53 | binary = binary + chr(int(bits[i:i+8],2)) 54 | if len(binary) % 5 == 0: 55 | binary = binary + chr(0) 56 | return binary 57 | 58 | # function that calls ITA2 lookup table for baudot() 59 | 60 | def b2a(value): 61 | if mode == 0: 62 | return letters[value] 63 | if mode == 1: 64 | return numbers[value] 65 | 66 | 67 | # decodes ITA2 68 | 69 | def baudot(string): 70 | bits = "" 71 | output = "" 72 | for i in range(0,len(string)): 73 | bits = bits + bin(ord(string[i])).split("b")[1].zfill(8) 74 | for i in range(0,len(bits),5): 75 | output = output + b2a(int(bits[i:i+5],2)) 76 | return output 77 | 78 | -------------------------------------------------------------------------------- /Players/player.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # GBI Player 3 | 4 | pc = 0 5 | mode = 0 6 | import ita 7 | col = 0 8 | 9 | import sys 10 | binary = sys.argv 11 | 12 | if len(binary) != 2: 13 | print "Usage: python player.py " 14 | quit() 15 | 16 | binary = binary[1] 17 | 18 | def hasnull(string): 19 | for i in range(0,len(string)): 20 | if ord(string[i]) == 0: return 1 21 | return 0 22 | 23 | f = open(binary) 24 | code = f.read(32768) 25 | f.close() 26 | 27 | while 1==1: 28 | type = ord(code[pc]) << 8 | ord(code[pc+1]) 29 | # print "type: "+str(type) 30 | if type == 65535: 31 | pc = pc + 2 32 | operation = ord(code[pc]) 33 | # print "special operation: "+str(operation) 34 | if operation == 0: 35 | print "** GAME OVER **\n\n" 36 | buffer = "" 37 | while 1==1: 38 | pc = pc + 1 39 | if ord(code[pc]) == 0: break 40 | buffer = buffer + code[pc] 41 | print buffer 42 | a = raw_input("\n[Hit enter to reset]") 43 | pc = 0 44 | if operation == 1: # low resolution image 45 | pc = pc + 22 46 | # image not supported 47 | if operation == 2: # high res image 48 | pc = pc + 1024 49 | # high res iage not supported 50 | if operation == 3: # set variable 51 | var_name = "" 52 | var_val = "" 53 | while 1==1: 54 | pc = pc + 1 55 | if ord(code[pc]) == 0: break 56 | var_name = var_name + code[pc] 57 | while 1==1: 58 | pc = pc + 1 59 | if ord(code[pc]) == 0: break 60 | var_val = var_val + code[pc] 61 | storage[var_name] = var_val 62 | pc = pc + 1 63 | if operation == 4: # evaluate variable 64 | var_name = "" 65 | operator = 0 66 | var_compare = "" 67 | var_branch = "" 68 | while 1==1: 69 | pc = pc + 1 70 | if ord(code[pc]) == 0: break 71 | var_name = var_name + code[pc] 72 | pc = pc + 1 73 | operator = code[pc] 74 | while 1==1: 75 | pc = pc + 1 76 | if ord(code[pc]) == 0: break 77 | var_compare = var_compare + code[pc] 78 | pc = pc + 1 79 | var_branch = ord(code[pc]) << 8 | ord(code[pc+1]) & 0xFF 80 | pc = pc + 2 81 | if operator == "$": 82 | if storage[var_name] == var_compare: 83 | pc = var_branch 84 | if operator == "<": 85 | if int(storage[var_name]) < int(var_compare): 86 | pc = var_branch 87 | if operator == ">": 88 | if storage[var_name] > var_compare: 89 | pc = var_branch 90 | if operator == "l": 91 | if int(storage[var_name]) <= int(var_compare): 92 | pc = var_branch 93 | if operator == "g": 94 | if int(storage[var_name]) >= int(var_compare): 95 | pc = var_branch 96 | if operator == "!": 97 | if storage[var_name] != var_compare: 98 | pc = var_branch 99 | if operation == 5: 100 | pc = pc + 1 101 | var_branch = ord(code[pc]) << 8 | ord(code[pc+1]) & 0xFF 102 | pc = var_branch 103 | if operation == 13: 104 | pc = pc + 2 105 | print "[Unsupported effect]" 106 | if operation == 16: 107 | buffer = "" 108 | while 1==1: 109 | pc = pc + 1 110 | if ord(code[pc]) == 0: break 111 | buffer = buffer + code[pc] 112 | print buffer 113 | raw_input("[Press Enter]") 114 | pc = pc + 1 115 | else: 116 | exit_a = type 117 | pc = pc + 2 118 | exit_b = ord(code[pc]) << 8 | ord(code[pc+1]) 119 | pc = pc + 2 120 | exit_a_description = "" 121 | exit_b_description = "" 122 | text = "" 123 | while 1==1: 124 | if ord(code[pc]) == 0: break 125 | exit_a_description = exit_a_description + code[pc] 126 | pc = pc + 1 127 | pc = pc + 1 128 | while 1==1: 129 | if ord(code[pc]) == 0: break 130 | exit_b_description = exit_b_description + code[pc] 131 | pc = pc + 1 132 | pc = pc +1 133 | while 1==1: 134 | if ord(code[pc]) == 0: break 135 | text = text + code[pc] 136 | pc = pc + 1 137 | pc = pc + 1 138 | print "" 139 | print text 140 | print "" 141 | print "1. "+exit_a_description 142 | print "2. "+exit_b_description 143 | entry = raw_input("> ") 144 | if entry == "1": pc = exit_a 145 | if entry == "2": pc = exit_b 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /Compiler/compiler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # compile CSV into byte code 3 | 4 | #import ita 5 | import sys 6 | 7 | if len(sys.argv) != 3: 8 | print "Usage: python compiler.py " 9 | print "Compiler for GTI converts CSV files into binary byte code" 10 | quit() 11 | 12 | source = sys.argv[1] 13 | destination = sys.argv[2] 14 | 15 | file = open(source,"r") 16 | source = file.readlines() 17 | file.close() 18 | 19 | rooms = [] 20 | room_type = [] 21 | room_label = [] 22 | room_size = [] 23 | room_address = [] 24 | room_exits = [] 25 | address = {} 26 | compiled = "" 27 | mode = 0 28 | 29 | 30 | for i in range(0,len(source)): 31 | line = source[i].split(",") 32 | mode = line[1] 33 | if mode.upper() == "ROOM": 34 | binary = "" 35 | exit_a = line[2] 36 | exit_b = line[3] 37 | exit_a_description = line[4] 38 | exit_b_description = line[5] 39 | description = ",".join(line[6:]).rstrip() 40 | # binary = binary + chr(exit_a >> 8) + chr(exit_a & 0xFF) 41 | # binary = binary + chr(exit_b >> 8) + chr(exit_b & 0xFF) 42 | binary = binary + exit_a_description + chr(0) 43 | binary = binary + exit_b_description + chr(0) 44 | binary = binary + description + chr(0) 45 | rooms.append(binary) 46 | room_type.append("room") 47 | room_label.append(line[0]) 48 | room_exits.append([exit_a,exit_b]) 49 | if mode.upper() == "SPECIAL": 50 | if line[2].upper() == "END": 51 | binary = "" 52 | description = ",".join(line[3:]) 53 | binary = binary + description + chr(0) 54 | rooms.append(binary) 55 | room_type.append("end") 56 | room_label.append(line[0]) 57 | room_exits.append(['special','end']) 58 | if line[2].upper() == "TEXT": 59 | binary = "" 60 | description = ",".join(line[3:]).rstrip() 61 | binary = binary + description + chr(0) 62 | rooms.append(binary) 63 | room_type.append("text") 64 | room_label.append(line[0]) 65 | room_exits.append(['special','text']) 66 | if line[2].upper() == "JUMP": 67 | rooms.append("") 68 | room_type.append("jump") 69 | room_label.append(line[0]) 70 | room_exits.append([line[3].rstrip(),line[3].rstrip()]) 71 | if line[2].upper() == "EFFECT": 72 | rooms.append(line[3].rstrip()) 73 | room_type.append("effect") 74 | room_label.append(line[0]) 75 | room_exits.append([line[3].rstrip(),line[3].rstrip()]) 76 | 77 | 78 | 79 | print "ASCII data converted. Indexing rooms complete." 80 | 81 | # Build a list of field lengths 82 | 83 | for i in range(0,len(rooms)): 84 | size = 2 85 | if room_type[i] == "room": 86 | size = size + 2 87 | size = size + len(rooms[i]) 88 | if room_type[i] == "end": 89 | size = size + 1 90 | size = size + len(rooms[i]) 91 | if room_type[i] == "text": 92 | size = size + 1 93 | size = size + len(rooms[i]) 94 | if room_type[i] == "jump": 95 | size = size + 3 96 | if room_type[i] == "effect": 97 | size = size + 2 98 | room_size.append(size) 99 | 100 | 101 | print "Calculated field lenghs." 102 | 103 | # calculate room addresses 104 | 105 | pc = 0 106 | 107 | for i in range(0,len(rooms)): 108 | room_address.append(pc) 109 | pc = pc + room_size[i] 110 | 111 | print "Calculated field addresses." 112 | 113 | for i in range(0,len(rooms)): 114 | address[room_label[i]] = room_address[i] 115 | 116 | print "Label address dictionary built." 117 | 118 | for i in range(0,len(rooms)): 119 | # try: 120 | if room_type[i] == "room": 121 | print room_exits[i][0]+" "+room_exits[i][1] 122 | compiled = compiled + chr(address[room_exits[i][0]] >> 8) + chr(address[room_exits[i][0]] & 0xFF) 123 | compiled = compiled + chr(address[room_exits[i][1]] >> 8) + chr(address[room_exits[i][1]] & 0xFF) 124 | compiled = compiled + rooms[i].rstrip() 125 | if room_type[i] == "end": 126 | compiled = compiled + chr(255) + chr(255) 127 | compiled = compiled + chr(0) 128 | compiled = compiled + rooms[i].rstrip() 129 | if room_type[i] == "text": 130 | compiled = compiled + chr(255) + chr(255) 131 | compiled = compiled + chr(16) 132 | compiled = compiled + rooms[i].rstrip() 133 | if room_type[i] == "jump": 134 | compiled = compiled + chr(255) + chr(255) 135 | compiled = compiled + chr(5) 136 | compiled = compiled + chr(address[room_exits[i][0]] >> 8) + chr(address[room_exits[i][0]] & 0xFF) 137 | if room_type[i] == "effect": 138 | compiled = compiled + chr(255) + chr(255) 139 | compiled = compiled + chr(13) 140 | compiled = compiled + chr(int(rooms[i].rstrip())) 141 | 142 | 143 | # except: 144 | # print "Cannot find destination label in room "+room_label[i] 145 | # quit() 146 | 147 | print "Compile complete." 148 | o = open(destination,"w") 149 | o.write(compiled) 150 | o.close() 151 | print "Written to "+destination+"." 152 | print "Final length was "+str(len(compiled))+"." 153 | 154 | base = 12722 155 | max = 28672 156 | size = max - base 157 | if len(compiled) > size: print "Arduboy warning: memory size exceeded by "+str(len(compiled) - size)+" bytes!" 158 | 159 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | ==About GTI== 2 | 3 | Write your own text adventure games with GTI! 4 | 5 | GTI, or Game Text Interpreter, is a tiny byte code intended for use with microcontrollers, such as the popular Arduboy or Arduino. GTI files can also be played using the Python player and effort is being made to port GTI to PHP and Javascript for playing on the web. 6 | 7 | GTI Uses a very simple binary format to use as little space as possible. 8 | 9 | ==How to use GTI== 10 | 11 | GTI does not currently have a parser, so it requires a CSV file containing all the elements. 12 | 13 | Step 1: 14 | 15 | Run compiler.py, which converts csv to binary format. 16 | 17 | Step 2: 18 | 19 | Run player.py to execute the binary format file on a desktop for testing. 20 | 21 | Optional Step 3: 22 | 23 | Run bin2array.py to convert the binary file to a C array (for use with Arduino, etc) and play using your Arduboy! 24 | 25 | ==GTI Features== 26 | 27 | GTI supports (or will support) the following: 28 | 29 | * Text + branch (Player picks selection A or B) 30 | * Text only fields 31 | * Jumping 32 | * Variables 33 | * Non-volitle storage 34 | * Math support 35 | * Conditional branching 36 | * 21x8 images 37 | * 128x64 images 38 | * Simple vector graphics 39 | * Melodies 40 | * Sound effects 41 | * Animation features 42 | * 5 bit text support (ITA2), resulting in 40% space reduction 43 | 44 | ==Writing CSV Code== 45 | 46 | Until a parser is written, code must be given to the compiler as a CSV. 47 | 48 | The compiler uses labels to build a jump table which ultimately drives the jump addresses within the game. All lines must have a label and just about any character (except a ,) is supported. Spaces are permitted. 49 | 50 | If a branch is not utilized, the program will advance from one line to the next. 51 | 52 | The two prefixes of importance are