diff options
author | Nathan Korth <nkorth@nkorth.com> | 2012-02-25 10:34:26 -0500 |
---|---|---|
committer | William Wilgus <wilgus.william@gmail.com> | 2024-04-28 00:21:16 -0400 |
commit | 2c7e47fc121014c1218d58248cd7c49cf2f38f0a (patch) | |
tree | 1900db6cb35b94dcda6b93f0d2d0ba096979e796 /apps/plugins/picross.lua | |
parent | 3ae48284c1dcca3515785d15444d3dd4000cbfe5 (diff) | |
download | rockbox-2c7e47fc121014c1218d58248cd7c49cf2f38f0a.tar.gz rockbox-2c7e47fc121014c1218d58248cd7c49cf2f38f0a.zip |
New plugin: Picross
Picross is a puzzle game also known as Picture Crossword, Nonograms, or
Paint By Numbers. See http://en.wikipedia.org/wiki/Nonogram for
information on how to play.
Update 1: nicer graphics with less images, fixed directory listing,
changed how the board works to make a lot of math more sane
Update 2: added missing rb.yield to viewPicture loop
Update 3: you can now save a game in progress
Update 4: fixed a file pointer leak, improved the numbers font
Update 5: no images, use vector num draw library add zoom, freedraw -- Bilgus
Change-Id: Idc476b46b6eaa10818400fa789701d5bac83467f
Diffstat (limited to 'apps/plugins/picross.lua')
-rw-r--r-- | apps/plugins/picross.lua | 820 |
1 files changed, 820 insertions, 0 deletions
diff --git a/apps/plugins/picross.lua b/apps/plugins/picross.lua new file mode 100644 index 0000000000..26ad57ee74 --- /dev/null +++ b/apps/plugins/picross.lua | |||
@@ -0,0 +1,820 @@ | |||
1 | --[[ | ||
2 | __________ __ ___. | ||
3 | Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | \/ \/ \/ \/ \/ | ||
8 | |||
9 | Port of Picross (aka. Picture Crossword, Nonograms, Paint By Numbers) | ||
10 | Copyright (c) 2012 by Nathan Korth | ||
11 | |||
12 | See http://en.wikipedia.org/wiki/Nonogram for details on how to play, and | ||
13 | http://nkorth.com/picross for more puzzles. | ||
14 | |||
15 | ]]-- | ||
16 | |||
17 | require "actions" | ||
18 | require "luadir" | ||
19 | require("rbsettings") | ||
20 | require("settings") | ||
21 | |||
22 | local _nums = require("draw_num") | ||
23 | local _clr = require("color") -- clrset, clrinc provides device independent colors | ||
24 | local _lcd = require("lcd") -- lcd helper functions | ||
25 | |||
26 | local plugindir = rb.PLUGIN_GAMES_DATA_DIR | ||
27 | local userdir = plugindir .. "/picross" | ||
28 | |||
29 | local wrap = rb.settings.read('global_settings', rb.system.global_settings.list_wraparound) | ||
30 | wrap = (wrap or 1) == 1 | ||
31 | |||
32 | do -- free up some ram by removing items we don't need | ||
33 | local function strip_functions(t, ...) | ||
34 | local t_keep = {...} | ||
35 | local keep | ||
36 | for key, val in pairs(t) do | ||
37 | keep = false | ||
38 | for _, v in ipairs(t_keep) do | ||
39 | if string.find (key, v) then | ||
40 | keep = true; break | ||
41 | end | ||
42 | end | ||
43 | if keep ~= true then | ||
44 | t[key] = nil | ||
45 | end | ||
46 | end | ||
47 | end | ||
48 | |||
49 | strip_functions(rb.actions, "PLA_", "TOUCHSCREEN", "_NONE") | ||
50 | rb.contexts = nil | ||
51 | |||
52 | _clr.inc = nil | ||
53 | rb.metadata = nil -- remove metadata settings | ||
54 | rb.system = nil -- remove system settings | ||
55 | rb.settings = nil --remove setting read/write fns | ||
56 | end | ||
57 | |||
58 | --colors for fg/bg ------------------------ | ||
59 | local WHITE = _clr.set(-1, 255, 255, 255) | ||
60 | local BLACK = _clr.set(0, 0, 0, 0) | ||
61 | ------------------------------------------- | ||
62 | |||
63 | -- set colors on color targets | ||
64 | if rb.lcd_rgbpack ~= nil then | ||
65 | rb.lcd_set_background(rb.lcd_rgbpack(255, 255, 255)) | ||
66 | rb.lcd_set_foreground(rb.lcd_rgbpack(0, 0, 0)) | ||
67 | end | ||
68 | |||
69 | local TEXT_COLOR = BLACK | ||
70 | |||
71 | if rb.LCD_DEPTH == 2 then TEXT_COLOR = bit.bnot(TEXT_COLOR) end | ||
72 | |||
73 | --[[ | ||
74 | -- load images | ||
75 | local img_numbers = rb.read_bmp_file(rb.current_path().."picross_numbers.bmp") | ||
76 | |||
77 | -- image helper function | ||
78 | function draw_image(img, x, y, w, h, tilenum) | ||
79 | |||
80 | local func = rb.lcd_bitmap_transparent_part | ||
81 | or rb.lcd_bitmap_part -- Fallback version for grayscale targets | ||
82 | or rb.lcd_mono_bitmap_part -- Fallback version for mono targets | ||
83 | |||
84 | func(img, 0, (tilenum * h), w, x, y, w, h) | ||
85 | end | ||
86 | ]] | ||
87 | |||
88 | function draw_number(x, y, w, tilenum, scale) | ||
89 | scale = scale or 1 | ||
90 | _nums.print(_LCD, tilenum, x, y, w, TEXT_COLOR, nil, nil, true, scale, scale) | ||
91 | end | ||
92 | |||
93 | function showComplete(self) | ||
94 | if self:isComplete() then | ||
95 | rb.splash(rb.HZ * 2, "Puzzle complete!") | ||
96 | self:saveGame() | ||
97 | self.puzzleh = 50 | ||
98 | self.puzzlew = 50 | ||
99 | local old_boardh, old_boardw = self.boardh, self.boardw | ||
100 | local old_numbersw, old_numbersh = self.numbersw, self.numbersh | ||
101 | while self.numbersh > 0 do -- remove the number rows | ||
102 | table.remove (self.board, 1) | ||
103 | self.numbersh = self.numbersh - 1 | ||
104 | end | ||
105 | self.numbersh = 0 | ||
106 | self.numbersw = 0 | ||
107 | self.solution = nil | ||
108 | self.boardh = self.puzzleh | ||
109 | self.boardw = self.puzzlew | ||
110 | self.freedraw = 0 | ||
111 | -- find a free number | ||
112 | while rb.file_exists(string.format("%s/user_freedraw_%d.picross", | ||
113 | userdir, self.freedraw)) do | ||
114 | self.freedraw = self.freedraw + 1 | ||
115 | end | ||
116 | |||
117 | for r = 1, self.boardh do | ||
118 | local old_row = self.board[r] or {} | ||
119 | |||
120 | self.board[r] = {} | ||
121 | -- copy over the last drawing | ||
122 | for c = 1, self.boardw do | ||
123 | local ch = old_row[c + old_numbersw] | ||
124 | if not ch or ch ~= '*' then | ||
125 | self.board[r][c] = '.' | ||
126 | else | ||
127 | self.board[r][c] = '*' | ||
128 | end | ||
129 | end | ||
130 | end | ||
131 | rb.splash(rb.HZ * 3, "Free Draw!") | ||
132 | self.cursor.x = 1 | ||
133 | self.cursor.y = 1 | ||
134 | self.showComplete = function() end --show once, then remove the reference | ||
135 | end | ||
136 | end | ||
137 | |||
138 | local State = { | ||
139 | puzzlew = 0, | ||
140 | puzzleh = 0, | ||
141 | numbersw = 0, | ||
142 | numbersh = 0, | ||
143 | boardw = 0, | ||
144 | boardh = 0, | ||
145 | board = {}, | ||
146 | solution = {}, | ||
147 | filename = '', | ||
148 | cursor = {x = 0, y = 0}, | ||
149 | scale = 1, | ||
150 | freedraw = false | ||
151 | } | ||
152 | |||
153 | --[[ | ||
154 | |||
155 | Notes on how puzzles work in the code: | ||
156 | |||
157 | The "board" array is bigger than the actual puzzle, so the numbers | ||
158 | above and to the left of it can be stored and do not need to be recalculated | ||
159 | for every draw. (The "solution" array is the same size as the puzzle, however.) | ||
160 | The various width/height variables help keep track of where everything is. | ||
161 | (they should be fairly self-explanatory) | ||
162 | |||
163 | The width/height of the numbers section is always half the width/height of the | ||
164 | puzzle. For odd-number-sized puzzles, the value must be rounded up. This is | ||
165 | because strings of squares must have at least one blank space in between. For | ||
166 | example, on a board 5 wide, the maximum set of row numbers is "1 1 1". | ||
167 | |||
168 | Here are the values used in the "board" array: | ||
169 | ' ': empty space | ||
170 | '.': unfilled square | ||
171 | '*': filled square | ||
172 | [number]: number (not a string) | ||
173 | |||
174 | The .picross puzzle files are text files which look pretty much the same as the | ||
175 | "board" array, with two differences: | ||
176 | |||
177 | - puzzle files should not contain numbers, because they will be generated | ||
178 | based on the puzzle at runtime | ||
179 | - blank lines and lines starting with '#' are ignored | ||
180 | |||
181 | ]]-- | ||
182 | |||
183 | function State:initBoard() | ||
184 | if self.freedraw then | ||
185 | -- clear board (set the puzzle area to '.' and everything else to ' ') | ||
186 | self.board = {} | ||
187 | for r = 1,self.boardh do | ||
188 | self.board[r] = {} | ||
189 | for c = 1,self.boardw do | ||
190 | if r > self.numbersh and c > self.numbersw then | ||
191 | self.board[r][c] = '.' | ||
192 | else | ||
193 | self.board[r][c] = ' ' | ||
194 | end | ||
195 | end | ||
196 | end | ||
197 | |||
198 | -- reset cursor | ||
199 | self.cursor.x = 1 | ||
200 | self.cursor.y = 1 | ||
201 | return | ||
202 | end --freedraw | ||
203 | |||
204 | -- metrics | ||
205 | self.puzzleh = #self.solution | ||
206 | self.puzzlew = #self.solution[1] | ||
207 | self.numbersh = math.floor(self.puzzleh / 2) + 1 | ||
208 | self.numbersw = math.floor(self.puzzlew / 2) + 1 | ||
209 | self.boardh = self.puzzleh + self.numbersh | ||
210 | self.boardw = self.puzzlew + self.numbersw | ||
211 | self.showComplete = showComplete | ||
212 | |||
213 | -- clear board (set the puzzle area to '.' and everything else to ' ') | ||
214 | self.board = {} | ||
215 | for r = 1,self.boardh do | ||
216 | self.board[r] = {} | ||
217 | for c = 1,self.boardw do | ||
218 | if r > self.numbersh and c > self.numbersw then | ||
219 | self.board[r][c] = '.' | ||
220 | else | ||
221 | self.board[r][c] = ' ' | ||
222 | end | ||
223 | end | ||
224 | end | ||
225 | |||
226 | -- reset cursor | ||
227 | self.cursor.x = self.numbersw + 1 | ||
228 | self.cursor.y = self.numbersh + 1 | ||
229 | |||
230 | -- calculate row numbers | ||
231 | local rownums = {} | ||
232 | for r = 1,self.puzzleh do | ||
233 | rownums[r] = {} | ||
234 | local count = 0 | ||
235 | for c = 1,self.puzzlew do | ||
236 | if self.solution[r][c] == '*' then | ||
237 | -- filled square | ||
238 | count = count + 1 | ||
239 | else | ||
240 | -- empty square | ||
241 | if count > 0 then | ||
242 | table.insert(rownums[r], count) | ||
243 | count = 0 | ||
244 | end | ||
245 | end | ||
246 | end | ||
247 | -- if there were no empty squares | ||
248 | if count > 0 then | ||
249 | table.insert(rownums[r], count) | ||
250 | count = 0 | ||
251 | end | ||
252 | end | ||
253 | |||
254 | -- calculate column numbers | ||
255 | local columnnums = {} | ||
256 | for c = 1,self.puzzlew do | ||
257 | columnnums[c] = {} | ||
258 | local count = 0 | ||
259 | for r = 1,self.puzzleh do | ||
260 | if self.solution[r][c] == '*' then | ||
261 | -- filled square | ||
262 | count = count + 1 | ||
263 | else | ||
264 | -- empty square | ||
265 | if count > 0 then | ||
266 | table.insert(columnnums[c], count) | ||
267 | count = 0 | ||
268 | end | ||
269 | end | ||
270 | end | ||
271 | -- if there were no empty squares | ||
272 | if count > 0 then | ||
273 | table.insert(columnnums[c], count) | ||
274 | count = 0 | ||
275 | end | ||
276 | end | ||
277 | |||
278 | -- add row numbers to board | ||
279 | for r = 1,self.puzzleh do | ||
280 | for i,num in ipairs(rownums[r]) do | ||
281 | self.board[self.numbersh + r][self.numbersw - #rownums[r] + i] = num | ||
282 | end | ||
283 | end | ||
284 | |||
285 | -- add column numbers to board | ||
286 | for c = 1,self.puzzlew do | ||
287 | for i,num in ipairs(columnnums[c]) do | ||
288 | self.board[self.numbersh - #columnnums[c] + i][self.numbersw + c] = num | ||
289 | end | ||
290 | end | ||
291 | end | ||
292 | |||
293 | function State:saveGame() | ||
294 | local file | ||
295 | local boardw, boardh = self.boardw, self.boardh | ||
296 | |||
297 | if self.freedraw then | ||
298 | self.filename = string.format("%s/user_freedraw_%d.picross", userdir, self.freedraw) | ||
299 | |||
300 | |||
301 | --remove blank lines from the end | ||
302 | while boardh > 1 and not string.find(table.concat(self.board[boardh]), "\*") do | ||
303 | boardh = boardh - 1 | ||
304 | end | ||
305 | --remove blank lines from right | ||
306 | local max_w = 0 | ||
307 | for r = self.numbersh + 1, boardh do | ||
308 | for c = max_w + 1, boardw do | ||
309 | if self.board[r][c] == '*' then | ||
310 | max_w = c | ||
311 | end | ||
312 | end | ||
313 | end | ||
314 | boardw = max_w | ||
315 | if max_w == 0 then return end--nothing to save | ||
316 | |||
317 | file = io.open(self.filename, 'w') | ||
318 | else | ||
319 | file = io.open(plugindir .. '/picross.sav', 'w') | ||
320 | end | ||
321 | |||
322 | if file then | ||
323 | file:write("#"..self.filename.."\n") | ||
324 | for r = self.numbersh + 1, boardh do | ||
325 | for c = self.numbersw + 1, boardw do | ||
326 | file:write(self.board[r][c]) | ||
327 | end | ||
328 | file:write("\n") | ||
329 | end | ||
330 | file:close() | ||
331 | if self.freedraw then | ||
332 | rb.splash(rb.HZ, "Freedraw saved.") | ||
333 | else | ||
334 | rb.splash(rb.HZ, "Game saved.") | ||
335 | end | ||
336 | return true | ||
337 | else | ||
338 | rb.splash(rb.HZ * 2, "Failed to open save file") | ||
339 | return false | ||
340 | end | ||
341 | end | ||
342 | |||
343 | function State:loadSave() | ||
344 | local file = io.open(plugindir .. '/picross.sav') | ||
345 | if file then | ||
346 | -- first line is commented path of original puzzle | ||
347 | path = file:read('*l') | ||
348 | path = path:sub(2,-1) | ||
349 | -- prepare original puzzle | ||
350 | if self:loadFile(path) then | ||
351 | -- load saved board | ||
352 | contents = file:read('*all') | ||
353 | file:close() | ||
354 | local r = 1 | ||
355 | for line in contents:gmatch("[^\r\n]+") do | ||
356 | local c = 1 | ||
357 | for char in line:gmatch('.') do | ||
358 | self.board[self.numbersh + r][self.numbersw + c] = char | ||
359 | c = c + 1 | ||
360 | end | ||
361 | r = r + 1 | ||
362 | end | ||
363 | |||
364 | return true | ||
365 | else | ||
366 | return false | ||
367 | end | ||
368 | else | ||
369 | return false | ||
370 | end | ||
371 | end | ||
372 | |||
373 | function State:loadDefault() | ||
374 | self:loadFile(userdir .. '/picross_default.picross') | ||
375 | end | ||
376 | |||
377 | function State:loadFile(path) | ||
378 | local file = io.open(path) | ||
379 | if file then | ||
380 | self.freedraw = false | ||
381 | local board = {} | ||
382 | local boardwidth = 0 | ||
383 | local count = 0 | ||
384 | contents = file:read('*all') | ||
385 | |||
386 | for line in contents:gmatch("[^\r\n]+") do | ||
387 | count = count + 1 | ||
388 | -- ignore blank lines and comments | ||
389 | if line ~= '' and line:sub(1, 1) ~= '#' then | ||
390 | table.insert(board, {}) -- add a new row | ||
391 | |||
392 | -- ensure all lines are the same width | ||
393 | if boardwidth == 0 then | ||
394 | boardwidth = #line | ||
395 | elseif #line ~= boardwidth then | ||
396 | -- a line was the wrong width | ||
397 | local err = "Invalid puzzle file!" | ||
398 | local msg = | ||
399 | string.format("%s (wrong line width ln: %d w: %d)", err, count, #line) | ||
400 | rb.splash(rb.HZ * 2, msg) | ||
401 | return false | ||
402 | end | ||
403 | local pos = 0 | ||
404 | for char in line:gmatch('.') do | ||
405 | pos = pos + 1 | ||
406 | if char == '*' or char == '.' then | ||
407 | table.insert(board[#board], char) | ||
408 | else | ||
409 | local err = "Invalid puzzle file!" | ||
410 | local msg = string.format("%s (invalid character ln: %d '%s' @ %d)", | ||
411 | err, count, char, pos) | ||
412 | -- invalid character in puzzle area | ||
413 | rb.splash(rb.HZ * 2, msg) | ||
414 | return false | ||
415 | end | ||
416 | end | ||
417 | else | ||
418 | -- display puzzle comments | ||
419 | --rb.splash(rb.HZ, line:sub(2,#line)) | ||
420 | end | ||
421 | end | ||
422 | |||
423 | if #board == 0 then | ||
424 | -- empty file | ||
425 | rb.splash(rb.HZ * 2, "Invalid puzzle file! (empty)") | ||
426 | return false | ||
427 | end | ||
428 | |||
429 | file:close() | ||
430 | |||
431 | self.solution = board | ||
432 | self.filename = path | ||
433 | if self.puzzleh < 100 and self.puzzlew < 100 then | ||
434 | self:initBoard() | ||
435 | return true | ||
436 | else | ||
437 | -- puzzle too big | ||
438 | rb.splash(rb.HZ * 2, "Invalid puzzle file! (too big)") | ||
439 | return false | ||
440 | end | ||
441 | else | ||
442 | -- file open failed | ||
443 | rb.splash(rb.HZ * 2, "Failed to open file!") | ||
444 | return false | ||
445 | end | ||
446 | end | ||
447 | |||
448 | function State:drawBoard() | ||
449 | local tw, th = 10 * self.scale, 10 * self.scale -- tile width and height (including bottom+right padding) | ||
450 | |||
451 | local ofsx = rb.LCD_WIDTH/2 - 4 - (self.cursor.x * tw) | ||
452 | local ofsy = rb.LCD_HEIGHT/2 - 4 - (self.cursor.y * th) | ||
453 | |||
454 | rb.lcd_clear_display() | ||
455 | |||
456 | -- guide lines | ||
457 | for r = 0, self.puzzleh do | ||
458 | local x1, x2, y = | ||
459 | ofsx + tw - 1, | ||
460 | ofsx + ((self.boardw + 1) * tw) - 1, | ||
461 | ofsy + ((self.numbersh + 1 + r) * th) - 1 | ||
462 | if r % 5 == 0 or r == self.puzzleh then | ||
463 | rb.lcd_hline(x1, x2, y) | ||
464 | else | ||
465 | for x = x1, x2, 2 do | ||
466 | rb.lcd_drawpixel(x, y) | ||
467 | end | ||
468 | end | ||
469 | end | ||
470 | for c = 0, self.puzzlew do | ||
471 | local x, y1, y2 = | ||
472 | ofsx + ((self.numbersw + 1 + c) * tw) - 1, | ||
473 | ofsy + th - 1, | ||
474 | ofsy + ((self.boardh + 1) * th) - 1 | ||
475 | if c % 5 == 0 or c == self.puzzlew then | ||
476 | rb.lcd_vline(x, y1, y2) | ||
477 | else | ||
478 | for y = y1,y2, 2 do | ||
479 | rb.lcd_drawpixel(x, y) | ||
480 | end | ||
481 | end | ||
482 | end | ||
483 | |||
484 | -- cursor | ||
485 | local cx, cy = ofsx + (self.cursor.x * tw) - 1, ofsy + (self.cursor.y * th) - 1 | ||
486 | rb.lcd_drawrect(cx, cy, tw + 1, th + 1) | ||
487 | local n_width = tw / self.scale / 2 - 1 | ||
488 | local xc = (tw - 5 * self.scale) / 2 | ||
489 | -- tiles | ||
490 | for r = 1, self.boardh do | ||
491 | for c = 1, self.boardw do | ||
492 | local x, y = ofsx + (c * tw) + 1, ofsy + (r * th) + 1 | ||
493 | |||
494 | if self.board[r][c] == '.' then | ||
495 | -- unfilled square | ||
496 | elseif self.board[r][c] == '*' then | ||
497 | -- filled square | ||
498 | rb.lcd_fillrect(x, y, tw - 3, th - 3) | ||
499 | elseif self.board[r][c] == 'x' then | ||
500 | -- eliminated square | ||
501 | rb.lcd_drawline(x + 1, y + 1, x + tw - 5, y + th - 5) | ||
502 | rb.lcd_drawline(x + tw - 5, y + 1, x + 1, y + th - 5) | ||
503 | elseif self.board[r][c] == ' ' then | ||
504 | -- empty space | ||
505 | elseif self.board[r][c] > 0 and self.board[r][c] < 100 then | ||
506 | -- number | ||
507 | local num = self.board[r][c] | ||
508 | if num < 10 then | ||
509 | draw_number(x + xc, y, n_width, num, self.scale) | ||
510 | draw_number(x + xc + 1, y, n_width, num, self.scale) | ||
511 | else | ||
512 | draw_number(x, y, n_width, num, self.scale) | ||
513 | draw_number(x + 1, y, n_width, num, self.scale) | ||
514 | end | ||
515 | end | ||
516 | end | ||
517 | end | ||
518 | |||
519 | rb.lcd_update() | ||
520 | end | ||
521 | |||
522 | function State:isComplete() | ||
523 | for r = 1,self.puzzleh do | ||
524 | for c = 1,self.puzzlew do | ||
525 | if self.solution[r][c] == '*' and | ||
526 | self.board[self.numbersh + r][self.numbersw + c] ~= '*' then | ||
527 | return false | ||
528 | end | ||
529 | end | ||
530 | end | ||
531 | |||
532 | return true | ||
533 | end | ||
534 | |||
535 | function State:moveCursor(dir) | ||
536 | -- The cursor isn't allowed to move in the top-left quadrant of the board. | ||
537 | -- This has to be checked in up and left moves. | ||
538 | local in_board_area = (self.cursor.y > (self.numbersh + 1) | ||
539 | and self.cursor.x > self.numbersw + 1) | ||
540 | |||
541 | if dir == 'left' then | ||
542 | if (self.cursor.x > (self.numbersw + 1) or self.cursor.y > self.numbersh) | ||
543 | and self.cursor.x > 1 then | ||
544 | self.cursor.x = self.cursor.x - 1 | ||
545 | elseif wrap == true then | ||
546 | if in_board_area then | ||
547 | self.cursor.x = 1 | ||
548 | else | ||
549 | self.cursor.x = self.boardw | ||
550 | end | ||
551 | dir = 'up' | ||
552 | end | ||
553 | elseif dir == 'right' then | ||
554 | if self.cursor.x < self.boardw then | ||
555 | self.cursor.x = self.cursor.x + 1 | ||
556 | elseif wrap == true then | ||
557 | if in_board_area then | ||
558 | self.cursor.x = 1 | ||
559 | else | ||
560 | self.cursor.x = self.numbersw + 1 | ||
561 | end | ||
562 | dir = 'down' | ||
563 | end | ||
564 | end | ||
565 | |||
566 | if dir == 'up' then | ||
567 | if (self.cursor.y > (self.numbersh + 1) or self.cursor.x > self.numbersw) | ||
568 | and self.cursor.y > 1 then | ||
569 | self.cursor.y = self.cursor.y - 1 | ||
570 | elseif wrap == true then | ||
571 | if in_board_area then | ||
572 | self.cursor.y = 1 | ||
573 | else | ||
574 | self.cursor.y = self.boardh | ||
575 | end | ||
576 | end | ||
577 | elseif dir == 'down' then | ||
578 | if self.cursor.y < self.boardh then | ||
579 | self.cursor.y = self.cursor.y + 1 | ||
580 | elseif wrap == true then | ||
581 | if in_board_area then | ||
582 | self.cursor.y = 1 | ||
583 | else | ||
584 | self.cursor.y = self.numbersh + 1 | ||
585 | end | ||
586 | end | ||
587 | end | ||
588 | end | ||
589 | |||
590 | function State:fillSquare(mode) | ||
591 | mode = mode or 0 | ||
592 | if self.cursor.x > self.numbersw and self.cursor.y > self.numbersh then | ||
593 | if self.board[self.cursor.y][self.cursor.x] == '*' and mode ~= 2 then | ||
594 | -- clear square | ||
595 | self.board[self.cursor.y][self.cursor.x] = '.' | ||
596 | elseif mode ~= 1 then -- '.' or 'x' | ||
597 | -- fill square | ||
598 | local x, y = self.cursor.x - self.numbersw, self.cursor.y - self.numbersh | ||
599 | if not self.solution or self.solution[y][x] == '*' then | ||
600 | self.board[self.cursor.y][self.cursor.x] = '*' | ||
601 | else | ||
602 | rb.splash(rb.HZ * 2, "Invalid move!") | ||
603 | -- "x" square for convenience | ||
604 | self.board[self.cursor.y][self.cursor.x] = 'x' | ||
605 | end | ||
606 | end | ||
607 | end | ||
608 | |||
609 | self:showComplete() | ||
610 | end | ||
611 | |||
612 | function State:eliminateSquare() | ||
613 | if not self.freedraw | ||
614 | and self.cursor.x > self.numbersw | ||
615 | and self.cursor.y > self.numbersh then | ||
616 | if self.board[self.cursor.y][self.cursor.x] == 'x' then | ||
617 | -- clear square | ||
618 | self.board[self.cursor.y][self.cursor.x] = '.' | ||
619 | else-- '.' or '*' | ||
620 | -- "x" square | ||
621 | self.board[self.cursor.y][self.cursor.x] = 'x' | ||
622 | end | ||
623 | else | ||
624 | self.board[self.cursor.y][self.cursor.x] = '.' | ||
625 | end | ||
626 | end | ||
627 | |||
628 | -- main code ------------------------------------------------------------------ | ||
629 | |||
630 | local function mainMenu() | ||
631 | local menu = { | ||
632 | "Resume", | ||
633 | "View picture", | ||
634 | "Restart puzzle", | ||
635 | "Load puzzle", | ||
636 | "Zoom " .. State.scale - 1, | ||
637 | "Save progress", | ||
638 | "Save and quit", | ||
639 | "Quit without saving" | ||
640 | } | ||
641 | local start | ||
642 | |||
643 | if State.freedraw then | ||
644 | menu[6] = "Save freedraw " .. State.freedraw --Save Progress | ||
645 | end | ||
646 | while true do | ||
647 | local s = rb.do_menu("Picross", menu, start, false) | ||
648 | start = s | ||
649 | if s == 0 then | ||
650 | -- resume | ||
651 | return | ||
652 | elseif s == 1 then | ||
653 | -- view picture | ||
654 | viewPicture() | ||
655 | start = 0 --resume | ||
656 | elseif s == 2 then | ||
657 | -- restart | ||
658 | State:initBoard() | ||
659 | return | ||
660 | elseif s == 3 then | ||
661 | -- choose puzzle | ||
662 | if puzzleList() then | ||
663 | return | ||
664 | end | ||
665 | elseif s == 4 then | ||
666 | -- zoom | ||
667 | State.scale = State.scale + 1 | ||
668 | if State.scale > 4 then | ||
669 | State.scale = 1 | ||
670 | end | ||
671 | menu[5] = "Zoom " .. State.scale - 1 | ||
672 | elseif s == 5 then | ||
673 | -- save | ||
674 | if State:saveGame() then | ||
675 | return | ||
676 | end | ||
677 | elseif s == 6 then | ||
678 | -- save and quit | ||
679 | if State:saveGame() then | ||
680 | os.exit() | ||
681 | end | ||
682 | elseif s == 7 then | ||
683 | -- quit | ||
684 | os.exit() | ||
685 | elseif s == -2 then | ||
686 | -- back button pressed | ||
687 | return | ||
688 | else | ||
689 | -- something strange happened | ||
690 | rb.splash(rb.HZ * 2, "Invalid menu index: "..s) | ||
691 | end | ||
692 | end | ||
693 | end | ||
694 | |||
695 | function puzzleList() | ||
696 | if rb.dir_exists(userdir) then | ||
697 | local files = {} | ||
698 | for file in luadir.dir(userdir) do | ||
699 | if file ~= '.' and file ~= '..' then | ||
700 | table.insert(files, file) | ||
701 | end | ||
702 | end | ||
703 | |||
704 | table.sort(files) | ||
705 | local udir = userdir .. "/" | ||
706 | if #files > 0 then | ||
707 | local s = rb.do_menu("Puzzles", files, nil, false) | ||
708 | if s >= 0 and s < #files then | ||
709 | if State:loadFile(udir..files[s+1]) then | ||
710 | return true -- return to puzzle screen | ||
711 | else | ||
712 | -- puzzle failed to load, return to main menu | ||
713 | return false | ||
714 | end | ||
715 | elseif s == -2 then | ||
716 | -- back button pressed, return to main menu | ||
717 | return false | ||
718 | else | ||
719 | -- something strange happened | ||
720 | rb.splash(rb.HZ * 2, "Invalid menu index: "..s) | ||
721 | return false | ||
722 | end | ||
723 | else | ||
724 | rb.splash(rb.HZ * 2, "No puzzles found! Put .picross files in " .. userdir) | ||
725 | return false | ||
726 | end | ||
727 | else | ||
728 | rb.splash(rb.HZ * 2, "Put .picross files in " .. userdir) | ||
729 | return false | ||
730 | end | ||
731 | end | ||
732 | |||
733 | function viewPicture() | ||
734 | rb.lcd_clear_display() | ||
735 | |||
736 | -- draw filled squares as pixels (scaled 2x) | ||
737 | for r = State.numbersh + 1, State.boardh do | ||
738 | for c = State.numbersw + 1, State.boardw do | ||
739 | if State.board[r][c] == '*' then | ||
740 | --rb.lcd_drawpixel(c - State.numbersw, r - State.numbersh) | ||
741 | local px = (c - State.numbersw) * State.scale - State.scale + 1 | ||
742 | local py = (r - State.numbersh) * State.scale - State.scale + 1 | ||
743 | |||
744 | rb.lcd_fillrect(px, py, State.scale, State.scale) | ||
745 | end | ||
746 | end | ||
747 | end | ||
748 | |||
749 | rb.lcd_update() | ||
750 | |||
751 | -- exit on button press | ||
752 | while true do | ||
753 | local action = rb.get_plugin_action(0) | ||
754 | |||
755 | if action == rb.actions.PLA_EXIT | ||
756 | or action == rb.actions.PLA_CANCEL | ||
757 | or action == rb.actions.PLA_SELECT then | ||
758 | return | ||
759 | end | ||
760 | |||
761 | rb.yield() | ||
762 | end | ||
763 | end | ||
764 | |||
765 | if not State:loadSave() then | ||
766 | State:loadDefault() | ||
767 | end | ||
768 | |||
769 | local act = rb.actions | ||
770 | local action = act.ACTION_NONE | ||
771 | local lockdraw = false | ||
772 | |||
773 | while true do | ||
774 | action = rb.get_plugin_action(0) | ||
775 | if action == rb.actions.PLA_EXIT then | ||
776 | lockdraw = false | ||
777 | mainMenu() | ||
778 | elseif action == act.PLA_UP or action == act.PLA_UP_REPEAT then | ||
779 | State:moveCursor('up') | ||
780 | elseif action == act.PLA_DOWN or action == act.PLA_DOWN_REPEAT then | ||
781 | State:moveCursor('down') | ||
782 | elseif action == act.PLA_LEFT or action == act.PLA_LEFT_REPEAT then | ||
783 | State:moveCursor('left') | ||
784 | elseif action == act.PLA_RIGHT or action == act.PLA_RIGHT_REPEAT then | ||
785 | State:moveCursor('right') | ||
786 | elseif action == act.PLA_SELECT then | ||
787 | if lockdraw then | ||
788 | lockdraw = lockdraw - 1 | ||
789 | if lockdraw < 0 then | ||
790 | lockdraw = false | ||
791 | elseif lockdraw == 1 then | ||
792 | rb.splash(50, "clear") | ||
793 | else | ||
794 | rb.splash(50, "invert") | ||
795 | end | ||
796 | else | ||
797 | State:fillSquare() | ||
798 | end | ||
799 | action = act.ACTION_NONE | ||
800 | elseif action == act.PLA_SELECT_REPEAT then | ||
801 | if State.freedraw and not lockdraw then | ||
802 | lockdraw = 2 | ||
803 | rb.splash(50, "draw") | ||
804 | action = act.ACTION_NONE | ||
805 | end | ||
806 | elseif action == act.PLA_CANCEL then | ||
807 | State:eliminateSquare() | ||
808 | action = act.ACTION_NONE | ||
809 | else | ||
810 | action = act.ACTION_NONE | ||
811 | end | ||
812 | |||
813 | if lockdraw and action ~= act.ACTION_NONE then | ||
814 | State:fillSquare(lockdraw) | ||
815 | end | ||
816 | |||
817 | State:drawBoard() | ||
818 | |||
819 | rb.yield() | ||
820 | end | ||