/ld26-basecode

To get this branch, use:
bzr branch /bzr/ld26-basecode
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
-- Class: Cached
-- This helps you re-use assets in your app instead of creating extraneous
-- copies of them. It also hides Love-related calls so that your code is
-- more portable.
--
-- If you're using a class built into Zoetrope, you do not need to use
-- this class directly. They take care of setting things up for you
-- appropriately. However, if you're rolling your own, you'll want to use
-- this to save memory.
--
-- This class is not meant to be created directly. Instead, call
-- methods on Cached directly, e.g. Cached:sound(), Cached:image(), and so on.
--
-- Extends:
--		<Class>

Cached = Class:extend
{
	-- Property: defaultGlyphs
	-- The default character order of a bitmap font, if none is specified
	-- in a <font> call.
	defaultGlyphs = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`' ..
					'abcdefghijklmnopqrstuvwxyz{|}~',

	-- private property: library
	-- a table to store already-instantiated assets
	_library = { image = {}, text = {}, sound = {}, font = {}, binds = {}, },

	-- Method: image
	-- Returns a cached image asset.
	--
	-- Arguments:
	--		path - pathname to image file
	--
	-- Returns:
	--		Love image object

	image = function (self, path)
		if STRICT then
			assert(type(path) == 'string', 'path must be a string')
		end

		local realPath = self:_absolutePath(path)

		if not self._library.image[realPath] then
			self._library.image[realPath] = love.graphics.newImage(realPath)
		end

		return self._library.image[realPath]
	end,

	-- Method: text
	-- Returns a cached text asset.
	--
	-- Arguments:
	--		path - pathname to text file
	--
	-- Returns:
	--		string

	text = function (self, path)
		if STRICT then
			assert(type(path) == 'string', 'path must be a string')
		end

		local realPath = self:_absolutePath(path)

		if not self._library.text[realPath] then
			self._library.text[realPath] = love.filesystem.read(realPath)
		end

		return self._library.text[realPath]
	end,

	-- Method: sound
	-- Returns a cached sound asset.
	--
	-- Arguments:
	--		path - pathname to sound file
	--		length - either 'short' or 'long'. *It's very important to pass
	--				 the correct option here.* A short sound is loaded entirely
	--				 into memory, while a long one is streamed from disk. If you
	--				 mismatch, you'll either hear a delay in the sound (short sounds
	--				 played from disk) or your app will freeze (long sounds played from
	--				 memory).
	-- 
	-- Returns:
	--		Either a Love SoundData object (for short sounds) or a
	--		Love Decoder object (for long sounds). Either can be used to
	--		create a Love Source object.
	--
	-- See Also:
	--		<playSound>, <sound>

	sound = function (self, path, length)
		if STRICT then
			assert(type(path) == 'string', 'path must be a string')
		end

		local realPath = self:_absolutePath(path)

		if not self._library.sound[realPath] then
			if length == 'short' then
				self._library.sound[realPath] = love.sound.newSoundData(realPath)
			elseif length == 'long' then
				self._library.sound[realPath] = love.sound.newDecoder(realPath)
			else
				error('length must be either "short" or "long"')
			end
		end

		return self._library.sound[path]
	end,

	-- Method: font
	-- Returns a cached font asset.
	--
	-- Arguments:
	-- Can be:
	--		* A single number. This uses Love's default outline font at that point size.
	--		* A single string. This uses a bitmap font given by this pathname, and assumes that
	--		  the characters come in
	--		  <printable ASCII order at https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters>.
	--		* A string, then a number. This uses an outline font whose pathname is the first argument,
	--		  at the point size given in the second argument.
	--		* Two strings. The first is treated as a pathname to a bitmap font, the second
	--		  as the character order in the font.
	--
	-- Returns:
	--		Love font object

	font = function (self, ...)
		local arg = {...}
		local libKey = arg[1]

		if type(libKey) == 'string' then
			libKey = self:_absolutePath(libKey)
		end

		if #arg > 1 then libKey = libKey .. arg[2] end

		if not self._library.font[libKey] then
			local font, image

			if #arg == 1 then
				if type(arg[1]) == 'number' then
					font = love.graphics.newFont(arg[1])
				elseif type(arg[1]) == 'string' then
					image = Cached:image(arg[1])
					font = love.graphics.newImageFont(image, self.defaultGlyphs)
				else
					error("don't understand single argument: " .. arg[1])
				end
			elseif #arg == 2 then
				if type(arg[2]) == 'number' then
					font = love.graphics.newFont(arg[1], arg[2])
				elseif type(arg[2]) == 'string' then
					image = Cached:image(arg[1])
					font = love.graphics.newImageFont(image, arg[2])
				else
					error("don't understand arguments: " .. arg[1] .. ", " .. arg[2])
				end
			else
				error("too many arguments; should be at most two")
			end

			self._library.font[libKey] = font
		end

		return self._library.font[libKey]
	end,

	-- Function: bind
	-- Returns a function that's bound to an object so it can be later called with
	-- the correct context. This can be abbreviated as just bind().
	--
	-- Arguments:
	--		obj - object to use as function owner
	--		func - either a string name of a property of obj, or a free-standing
	--			   function.
	--		... - any number of extra arguments 

	bind = function (self, obj, func, ...)
		local arg = {...}

		if STRICT and type(func) == 'string' then
			assert(type(obj[func]) == 'function', 'asked to bind an object to a non-existent method named ' .. func)
		end

		-- look for previous bind
		
		for key, value in pairs(self._library.binds) do
			if key[1] == func and key[2] == obj then
				local match = true

				for i = 1, #arg do
					if key[i + 2] ~= arg[i] then
						match = false
						break
					end
				end

				if match then
					return value
				end
			end
		end

		-- have to create a new one
		-- note that we have to create a compound key, hence the loop above

		local result = function()
			if type(func) == 'string' then
				return obj[func](obj, unpack(arg))
			else
				return func(obj, unpack(arg))
			end
		end
	
		self._library.binds[{func, obj, arg}] = result
		return result
	end,

	-- internal function: _absolutePath
	-- Replaces any .. references in a path, where possible. 
	--
	-- Arguments:
	--		rawPath - string path to expand
	--
	-- Returns:
	--		string

	_absolutePath = function (self, rawPath)
		local matches
		local result = rawPath

		repeat
			result, matches = string.gsub(result, '[^/]+/%.%./', '')
		until matches == 0

		return result
	end
}