junglejourney
view build.py @ 219:02610f059ac0
Ensured that writable sound data is stored in RAM.
| author | David Boddie <david@boddie.org.uk> |
|---|---|
| date | Tue Oct 18 00:04:04 2011 +0200 |
| parents | 3ae571b43ba0 |
| children |
line source
1 #!/usr/bin/env python
3 """
4 Copyright (C) 2011 David Boddie <david@boddie.org.uk>
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18 """
20 import os, struct, sys
21 import UEFfile
22 from tools import makesprites
24 version = "0.1"
26 def system(command):
28 if os.system(command):
29 sys.exit(1)
31 def read_basic(path):
33 t = open(path).read()
34 t = t.replace("\n", "\r")
35 lines = t.rstrip().split("\r")
36 t = "\r".join(lines) + "\r"
37 return t
39 def encode_text(text):
41 words = text.split(" ")
42 word_dict = {}
44 # Count the number of occurrences of each word.
45 for word in words:
46 word_dict.setdefault(word, 0)
47 word_dict[word] += 1
49 # Sort the words in order of decreasing frequency.
50 frequencies = map(lambda x: (x[1], x[0]), word_dict.items())
51 frequencies.sort()
52 frequencies.reverse()
54 # Create encoding and decoding look up tables.
55 decoding_lookup = {}
56 encoding_lookup = {}
58 i = 0
59 for count, word in frequencies:
61 if i >= 128:
62 j = 1 + i * 2
63 else:
64 j = i * 2
66 encoding_lookup[word] = j
67 decoding_lookup[j] = word
69 i += 1
71 # Encode the text.
72 encoded = []
73 for word in words:
75 encoded.append(encoding_lookup[word])
77 encoded_string = ""
78 for value in encoded:
80 if value & 1 == 0:
81 encoded_string += chr(value)
82 else:
83 encoded_string += struct.pack("<H", value)
85 return decoding_lookup, encoded_string
87 def decode_text(data, lookup):
89 words = ""
90 i = 0
91 while i < len(data):
93 value = ord(data[i])
94 if value & 1 != 0:
95 value += ord(data[i+1]) << 8
96 i += 2
97 else:
98 i += 1
100 words += lookup[value]
101 words += " "
103 return words[:-1]
106 if __name__ == "__main__":
108 if len(sys.argv) != 2:
110 sys.stderr.write("Usage: %s <new UEF file>\n" % sys.argv[0])
111 sys.exit(1)
113 out_uef_file = sys.argv[1]
115 # Memory map
116 # 0EE0 enemy x locations in the current room
117 # 0EF0 enemy x locations in the current room
118 # 0F00 completion screen
119 # 1800 title screen
120 # 1E00 CODE
121 #
122 # 3F00 CHARS (character sprites)
123 # 4 * 2 * 0x30 (player movement)
124 # 4 * 0x30 (player demise)
125 # 4 * 2 * 0x10 (projectile)
126 # 5 * 4 * 2 * 0x40 (enemies) 41C0
127 # 4 * 0x40 (enemy appear)
128 # 4 * 0x40 (enemy demise)
129 # 4 * 0x40 (weapons) 4dC0
130 # 5 * 0x40 (treasure)
131 # 2 * 0x60 (exit) 5000
132 # 2 * 0x60 (final exit)
133 #
134 # 0x3f00 + 4*2*0x30 + 4*0x30 + 4*2*0x10 + 5*4*2*0x40 + 4*0x40 + 4*0x40 + 4*0x40 + 5*0x40 + 2*0x60 + 2*0x60
135 #
136 # 5180 high scores (8 * 12 = 0xe0)
137 # n 3 bytes score + 9 bytes ASCII
138 #
139 # 5200 objects/treasure table (121 entries)
140 # n type
141 #
142 # 5279 space
143 # 5280 character table (0x24/6 = 6 entries + 1 special entry)
144 # n type (0 missing, 1 player, 2 projectile, 3 explosion,
145 # 4 item,
146 # 8 and higher enemy - bits 4,5,6 are enemy type)
147 # n+1 counter/direction
148 # (player: bits 1,2 are direction, bit 0 is animation
149 # projectile: bits 4,5 are direction, bits 1,2 are type,
150 # bit 0 is animation
151 # enemy: bits 2,3 are direction, bits 1,0 are animation
152 # bits 4,5,6,7 are counter for non-homing enemies
153 # emerging, explosion: bits 4,5,6 are enemy type for emerging,
154 # bit 2 is type 0=emerge,1=explode,
155 # bits 0,1 are animation
156 # item: bits 0,1,2,3 are type, bit 2 is weapon/treasure
157 # 0-3 weapons, 4 key, 5-8 treasure)
158 # n+2 y room offset (0-10)
159 # n+3 dy (0-3)
160 # n+4 x room offset (0-10)
161 # n+5 dx (0-3)
162 #
163 # first character is always the player
164 # second character is always the player's weapon
165 # new characters are added after these
166 #
167 # 5300 plot buffer (alternate unplot/plot entries terminating in 255)
168 # n type
169 # n+1,n+2 source address
170 # n+3,n+4 destination address
171 #
172 # 5300 and every 12 bytes is unplot entries
173 # 5306 and every 12 bytes is plot entries
174 #
175 # 53C0 space (assuming 8 unplot and 8 plot operations)
176 #
177 # 5400 SPRITES (map)
178 # 3 * (1 * 0x60 (flowers)
179 # 1 * 0x60 (tree)
180 # 1 * 0x60 (tree))
181 #
182 # 5760 note data (8 bytes)
183 #
184 # 577E joystick support (0=off; 1=on)
185 # 577F weapon counter (0=fire one subtile below; 1=fire two subtiles below)
186 # 5780 item/player flags (128=leave level, 64=player demise,
187 # bits 4,5,6=enemy limit, 2=complete game,
188 # 1=has key)
189 # 5781 weapon/enemy limit (the highest weapon/enemy possible on a level)
190 # 5782 current room (i, j)
191 # 5784 lives (strength)
192 # 5785 delay counter
193 # 5786 score (three bytes)
194 # 5789 projectile type
195 # 578A level
196 # 578B palette workspace (enough for one 5 byte palette entry)
197 # 578D is also projectile counter when in a room
198 # 578E is also motion counter when in a room
199 # 578F is also enemy generation counter when in a room
200 #
201 # 579C room data (generated)
202 # 0 blank
203 # 1 flowers/decoration
204 # 2 tree/wall
205 # 3 tree/wall
206 # 4 exit
207 # 5 open exit
208 # 6 final exit (left)
209 # 7 final exit (right)
210 #
211 # 5800 screen memory
213 files = []
215 system("ophis loader.oph JUNGLE")
216 code = open("JUNGLE").read()
217 code_start = 0x5180
218 files.append(("JUNGLE", code_start, code_start, code))
220 data = makesprites.read_sprites([makesprites.title])
221 completed = makesprites.encode(makesprites.read_sprite(makesprites.completed))
222 overlay = makesprites.read_sprite(makesprites.overlay)
223 combined = makesprites.combine(completed, overlay)
224 data += combined
225 files.append(("TITLE", 0x5AA0, 0x5AA0, data))
227 data = makesprites.read_sprites(makesprites.tiles)
228 files.append(("SPRITES", 0x5400, 0x5400, data))
230 data = makesprites.read_sprites(makesprites.chars)
231 files.append(("CHARS", 0x3f00, 0x3f00, data))
233 system("ophis tapecode.oph CODE")
234 code = open("CODE").read()
235 code_start = 0x1e00
236 files.append(("CODE", code_start, code_start, code))
238 u = UEFfile.UEFfile(creator = 'build.py '+version)
239 u.minor = 6
240 u.target_machine = "Electron"
242 u.import_files(0, files)
244 # Insert a gap before each file.
245 offset = 0
246 for f in u.contents:
248 # Insert a gap and some padding before the file.
249 gap_padding = [(0x112, "\xdc\x05"), (0x110, "\xdc\x05"), (0x100, "\xdc")]
250 u.chunks = u.chunks[:f["position"] + offset] + \
251 gap_padding + u.chunks[f["position"] + offset:]
253 # Each time we insert a gap, the position of the file changes, so we
254 # need to update its position and last position. This isn't really
255 # necessary because we won't read the contents list again.
256 offset += len(gap_padding)
257 f["position"] += offset
258 f["last position"] += offset
260 # Write the new UEF file.
261 try:
262 u.write(out_uef_file, write_emulator_info = False)
263 except UEFfile.UEFfile_error:
264 sys.stderr.write("Couldn't write the new executable to %s.\n" % out_uef_file)
265 sys.exit(1)
267 # Exit
268 sys.exit()
