├── 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