summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian Leonhardt <sebastian.leonhardt@web.de>2016-09-23 01:25:32 +0200
committerSebastian Leonhardt <sebastian.leonhardt@web.de>2017-08-23 00:25:11 +0200
commit28ae7004446b05f085083669182a81d1cc28c164 (patch)
tree01f3697619da51be5d1b37668df0edb1969d3bb7
parent7944969ff7576c0c87be22a6c361e0fa7c2283ed (diff)
downloadrockbox-28ae7004446b05f085083669182a81d1cc28c164.tar.gz
rockbox-28ae7004446b05f085083669182a81d1cc28c164.zip
FS#11922: Lua game - Pixel Painter
* Changed keymaps to PLA and added to SOURCES and CATEGORIES file * improved keymaps: implement wrap-around and key repeat * change keymap according to screen orientation * fix font size calculation * use blocking button query in main loop * replace tabs with spaces * added manual entry * added original author to CREDITS Change-Id: Id67ae99cbb7a737c7f4608e278b77a389ac2ffa6
-rw-r--r--apps/plugins/CATEGORIES1
-rw-r--r--apps/plugins/SOURCES5
-rw-r--r--apps/plugins/pixel-painter.lua701
-rw-r--r--docs/CREDITS1
-rw-r--r--manual/plugins/images/ss-pixelpainter-128x128x16.pngbin0 -> 1181 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-128x160x16.pngbin0 -> 1139 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-128x96x16.pngbin0 -> 1065 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-132x80x16.pngbin0 -> 1029 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-160x128x16.pngbin0 -> 1158 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-176x132x16.pngbin0 -> 1314 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-176x220x16.pngbin0 -> 1387 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-220x176x16.pngbin0 -> 1563 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-240x320x16.pngbin0 -> 1686 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-240x400x16.pngbin0 -> 1838 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-320x240x16.pngbin0 -> 1708 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-320x240x24.pngbin0 -> 1708 bytes
-rw-r--r--manual/plugins/images/ss-pixelpainter-96x96x16.pngbin0 -> 995 bytes
-rw-r--r--manual/plugins/main.tex2
-rw-r--r--manual/plugins/pixelpainter.tex24
19 files changed, 733 insertions, 1 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES
index 929788f826..1b5bb9a87b 100644
--- a/apps/plugins/CATEGORIES
+++ b/apps/plugins/CATEGORIES
@@ -72,6 +72,7 @@ pegbox,games
72periodic_table,apps 72periodic_table,apps
73pictureflow,demos 73pictureflow,demos
74pitch_detector,apps 74pitch_detector,apps
75pixel-painter,games
75plasma,demos 76plasma,demos
76png,viewers 77png,viewers
77gif,viewers 78gif,viewers
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES
index b3a939695f..9eec04aa22 100644
--- a/apps/plugins/SOURCES
+++ b/apps/plugins/SOURCES
@@ -149,7 +149,10 @@ metronome.c
149/* Lua needs at least 160 KB to work in */ 149/* Lua needs at least 160 KB to work in */
150#if PLUGIN_BUFFER_SIZE >= 0x80000 150#if PLUGIN_BUFFER_SIZE >= 0x80000
151boomshine.lua 151boomshine.lua
152#endif 152#ifdef HAVE_LCD_COLOR
153pixel-painter.lua
154#endif /* HAVE_LCD_COLOR */
155#endif /* PLUGIN_BUFFER_SIZE >= 0x80000 */
153 156
154rockblox1d.c 157rockblox1d.c
155brickmania.c 158brickmania.c
diff --git a/apps/plugins/pixel-painter.lua b/apps/plugins/pixel-painter.lua
new file mode 100644
index 0000000000..d2bd700d74
--- /dev/null
+++ b/apps/plugins/pixel-painter.lua
@@ -0,0 +1,701 @@
1--[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
9
10 Port and extension of Pixel Painter by Pavel Bakhilau
11 (http://js1k.com/2010-first/demo/453) to Rockbox Lua.
12
13 Copyright (C) 2011 by Stefan Schneider-Kennedy
14
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
19
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
22
23]]--
24
25--Number of colours used in the game
26--Hard coded here, in the COLOURS and PIP_COLOURS definitions and in
27--get_colours_count's count_table
28NUM_COLOURS = 6
29
30--Utility function makes a copy of the passed table
31function deepcopy(object)
32 local lookup_table = {}
33 local function _copy(object)
34 if type(object) ~= "table" then
35 return object
36 elseif lookup_table[object] then
37 return lookup_table[object]
38 end
39 local new_table = {}
40 lookup_table[object] = new_table
41 for index, value in pairs(object) do
42 new_table[_copy(index)] = _copy(value)
43 end
44 return setmetatable(new_table, getmetatable(object))
45 end
46 return _copy(object)
47end
48
49--Returns the maximum value of the passed table and its index
50function table_maximum(a)
51 local mi = 1
52 local m = a[mi]
53 for i, val in ipairs(a) do
54 if val > m then
55 mi = i
56 m = val
57 end
58 end
59 return m, mi
60end
61
62--Solves the board using a simple algorithm and returns the number of
63--moves required. Each turn, the function picks the move which fills in
64--the greatest area of board. The number of moves required to complete
65--it is returned.
66function calculate_par(board)
67 local board_dimension = table.getn(board)
68 local test_game_copy = deepcopy(board)
69 local moves = 0
70
71 repeat
72 local non_matching = {}
73 fill_board(test_game_copy, 0, 1, 1, test_game_copy[1][1], non_matching)
74
75 if table.getn(non_matching) > 0 then
76 local count_table = get_colours_count(test_game_copy, non_matching)
77 local max_value, colour = table_maximum(count_table)
78
79 --Corrects the invalid colour values set by
80 --get_colours_count, this also acts as a move
81 for x=1,board_dimension do
82 for y=1,board_dimension do
83 if test_game_copy[x][y] < 0 then
84 test_game_copy[x][y] = test_game_copy[x][y] * -1
85 elseif test_game_copy[x][y] == 0 then
86 test_game_copy[x][y] = colour
87 end
88 end
89 end
90 else
91 return moves
92 end
93 --Manual garbage collection is needed so it doesn't eat into the
94 --audio buffer, see http://forums.rockbox.org/index.php/topic,27120.msg177434.html
95 collectgarbage("collect")
96 moves = moves + 1
97 until false
98end
99
100--Calculates the number of blocks of each colour adjacent to the filled
101--region identified by the passed parameters. A colour indexed table
102--containing the counts is returned. Relies on the board having been
103--flood filled with 0s prior to executing this function.
104--
105--The 'board' table is also adjusted as follows: The filled region's
106--colour index is set to zero and each of the adjacent areas' colour
107--indexes are multiplied by -1. These invalid colour values are later
108--corrected in the calculate_par function.
109function get_colours_count(board, non_matching)
110 local count_table = {0, 0, 0, 0, 0, 0}
111 repeat
112 --Pop the array
113 local current = non_matching[table.getn(non_matching)]
114 table.remove(non_matching)
115 --Check this square hasn't already been filled
116 local curr_colour = board[current[1]][current[2]]
117 if curr_colour > 0 then
118 count_table[curr_colour] = count_table[curr_colour] +
119 fill_board(board, curr_colour * -1, current[1], current[2], curr_colour)
120 end
121 until table.getn(non_matching) == 0
122
123 return count_table
124end
125
126--Returns a randomly coloured board of the indicated dimensions
127function generate_board(board_dimension, seed)
128 math.randomseed(seed)
129
130 local board = {}
131 for x=1,board_dimension do
132 board[x] = {}
133 for y=1,board_dimension do
134 board[x][y] = math.random(1,NUM_COLOURS)
135 end
136 end
137
138 return board
139end
140
141--Flood fills the board from the top left using selected_colour
142--Returns the number of boxes filled
143function fill_board(board, fill_colour, x, y, original_colour, non_matching)
144 local board_dimension = table.getn(board)
145 if x > 0 and y > 0 and x <= board_dimension and y <= board_dimension then
146 if board[x][y] == original_colour then
147 board[x][y] = fill_colour
148 return fill_board(board, fill_colour, x - 1, y, original_colour, non_matching) +
149 fill_board(board, fill_colour, x, y - 1, original_colour, non_matching) +
150 fill_board(board, fill_colour, x + 1, y, original_colour, non_matching) +
151 fill_board(board, fill_colour, x, y + 1, original_colour, non_matching) + 1
152 elseif(non_matching ~= nil and board[x][y] ~= fill_colour) then
153 table.insert(non_matching, {x,y})
154 end
155 end
156
157 return 0
158end
159
160--Checks whether the given board is a single colour
161function check_win(board)
162 for x,col in pairs(board) do
163 for y,value in pairs(col) do
164 if value ~= board[1][1] then
165 return false
166 end
167 end
168 end
169
170 return true
171end
172
173--Attempt to load the game variables stored in the indicated save file.
174--Returns a table containing game variables if the file can be opened,
175--false otherwise.
176--Table keys are: difficulty, par, move_number, selected_colour, board
177function load_game(filename)
178 local f = io.open(filename, "r")
179 if f ~= nil then
180 local rtn = {}
181 rtn["difficulty"] = tonumber(f:read())
182 rtn["par"] = tonumber(f:read())
183 rtn["move_number"] = tonumber(f:read())
184 rtn["selected_colour"] = tonumber(f:read())
185
186 local board={}
187 local dimension = diff_to_dimension(rtn["difficulty"])
188 for x=1,dimension do
189 board[x] = {}
190 local line = f:read()
191 local bits = {line:match(("([^ ]*) "):rep(dimension))}
192 for y=1,dimension do
193 board[x][y] = tonumber(bits[y])
194 end
195 end
196 rtn["board"] = board
197
198 f:close()
199 return rtn
200 else
201 return false
202 end
203end
204
205--Saves the game state to file
206function save_game(state, filename)
207 local f = io.open(filename, "w")
208 if f ~= nil then
209 f:write(state["difficulty"],"\n")
210 f:write(state["par"],"\n")
211 f:write(state["move_number"],"\n")
212 f:write(state["selected_colour"],"\n")
213 local board = state["board"]
214 local dimension = diff_to_dimension(state["difficulty"])
215 for x=1,dimension do
216 for y=1,dimension do
217 f:write(board[x][y]," ")
218 end
219 f:write("\n")
220 end
221 f:close()
222 return true
223 else
224 return false
225 end
226end
227
228--Loads the high scores from file
229--Returns true on success, false otherwise
230function load_scores(filename)
231 local f = io.open(filename, "r")
232 if f ~= nil then
233 local highscores = {}
234 for i=1,3 do
235 local line = f:read()
236 local value = false
237 if line ~= nil then
238 value = tonumber(line)
239 end
240
241 highscores[i] = value
242 end
243 f:close()
244 return highscores
245 else
246 return false
247 end
248end
249
250--Saves the high scores to file
251function save_scores(highscores, filename)
252 local f = io.open(filename, "w")
253 if f ~= nil then
254 for i=1,3 do
255 local value = highscores[i]
256 if value == false then
257 value = ""
258 end
259 f:write(value,"\n")
260 end
261 f:close()
262 return true
263 else
264 return false
265 end
266end
267
268function diff_to_dimension(difficulty)
269 if difficulty == 1 then
270 return 8
271 elseif difficulty == 2 then
272 return 16
273 else
274 return 22
275 end
276end
277
278--Don't run the RB stuff if we're not running under RB
279if rb ~= nil then
280
281 if rb.lcd_rgbpack == nil then
282 rb.splash(2*rb.HZ, "Non RGB targets not currently supported")
283 os.exit()
284 end
285
286 ---------------------
287 --RB Game variables--
288 ---------------------
289
290 --The colours used by the game
291 COLOURS = {
292 rb.lcd_rgbpack(255, 119, 34),
293 rb.lcd_rgbpack(255, 255, 102),
294 rb.lcd_rgbpack(119, 204, 51),
295 rb.lcd_rgbpack(102, 170, 255),
296 rb.lcd_rgbpack(51, 68, 255),
297 rb.lcd_rgbpack(51, 51, 51),
298 }
299 --The colour of the selection pip
300 PIP_COLOURS = {
301 rb.lcd_rgbpack(0, 0, 0),
302 rb.lcd_rgbpack(0, 0, 0),
303 rb.lcd_rgbpack(0, 0, 0),
304 rb.lcd_rgbpack(0, 0, 0),
305 rb.lcd_rgbpack(0, 0, 0),
306 rb.lcd_rgbpack(255, 255, 255),
307 }
308 DEFAULT_DIFFICULTY = 2 --1: Easy, 2: Normal, 3: Hard
309
310 FILES_ROOT = "/.rockbox/rocks/games/"
311 SCORES_FILE = FILES_ROOT.."pixel-painter.score"
312 SAVE_FILE = FILES_ROOT.."pixel-painter.save"
313 r,w,TEXT_LINE_HEIGHT = rb.font_getstringsize(" ", rb.FONT_UI) --Get font height
314 --Determine which layout to use by considering the screen dimensions
315 --the +9 is so we have space for the chooser
316 if rb.LCD_WIDTH > (rb.LCD_HEIGHT + 9) then
317 LAYOUT = 1 --Wider than high, status and chooser on right
318 elseif rb.LCD_HEIGHT > (rb.LCD_WIDTH + 9) then
319 LAYOUT = 2 --Higher than wide, status and chooser below
320 else
321 LAYOUT = 3 --Treat like a square screen, chooser on right, status below
322 end
323
324 --Display variables
325 chooser_pip_dimension = 6 --pixel dimension of the selected colour pip
326 block_width = 0 --pixel dimension of each game square
327 chooser_start_pos = 0 --x or y position of the first block (depending on LAYOUT)
328
329 --Populated by load_scores()
330 highscores = {false, false, false}
331
332 --A table containing the game state, initialised by init_game() or
333 --load_game(), see
334 game_state = {}
335
336 -----------------------------------
337 --Display and interface functions--
338 -----------------------------------
339
340 --Sets up a new game and display variables for the indicated
341 --difficulty
342 function init_game(difficulty)
343 init_display_variables(difficulty)
344 local state = {}
345 local board_dimension = diff_to_dimension(difficulty)
346 state["selected_colour"] = 1
347 state["move_number"] = 0
348 state["difficulty"] = difficulty
349 state["board"] = generate_board(board_dimension, rb.current_tick()+os.time())
350 rb.splash(1, "Calculating par...") --Will stay on screen until it's done
351 state["par"] = calculate_par(state["board"])
352
353 return state
354 end
355
356 --Initialises the display variables for the screen LAYOUT
357 function init_display_variables(difficulty)
358 local board_dimension = diff_to_dimension(difficulty)
359
360 if LAYOUT == 1 then
361 block_width = rb.LCD_HEIGHT / board_dimension
362 chooser_start_pos = (board_dimension)*block_width + 2
363 chooser_width = rb.LCD_WIDTH - chooser_start_pos
364 chooser_height = (rb.LCD_HEIGHT - 3*TEXT_LINE_HEIGHT) / NUM_COLOURS
365 elseif LAYOUT == 2 then
366 block_width = rb.LCD_WIDTH / board_dimension
367 chooser_start_pos = board_dimension*block_width + 2 + TEXT_LINE_HEIGHT
368 chooser_width = rb.LCD_WIDTH / NUM_COLOURS
369 chooser_height = rb.LCD_HEIGHT - chooser_start_pos
370 else
371 if TEXT_LINE_HEIGHT > 9 then
372 block_width = (rb.LCD_HEIGHT - TEXT_LINE_HEIGHT) / board_dimension
373 else
374 block_width = (rb.LCD_HEIGHT - 9) / board_dimension
375 end
376 chooser_start_pos = (board_dimension)*block_width + 1
377 chooser_width = rb.LCD_WIDTH - chooser_start_pos
378 chooser_height = (rb.LCD_HEIGHT - TEXT_LINE_HEIGHT) / NUM_COLOURS
379 end
380 end
381
382 --Draws the game board to screen
383 function draw_board(board)
384 for x,col in pairs(board) do
385 for y,value in pairs(col) do
386 rb.lcd_set_foreground(COLOURS[value])
387 rb.lcd_fillrect((x-1)*block_width, (y-1)*block_width, block_width, block_width)
388 end
389 end
390 end
391
392 --Draw the colour chooser along with selected pip at the appropriate
393 --position
394 function draw_chooser(selected_colour)
395 for i=1,NUM_COLOURS do
396 rb.lcd_set_foreground(COLOURS[i])
397 if LAYOUT == 1 or LAYOUT == 3 then
398 rb.lcd_fillrect(chooser_start_pos, (i - 1)*(chooser_height), chooser_width, chooser_height)
399 elseif LAYOUT == 2 then
400 rb.lcd_fillrect((i - 1)*(chooser_width), chooser_start_pos, chooser_width, chooser_height)
401 end
402 end
403
404 rb.lcd_set_foreground(PIP_COLOURS[selected_colour])
405 local xpos = 0
406 local ypos = 0
407 if LAYOUT == 1 or LAYOUT == 3 then
408 xpos = chooser_start_pos + (chooser_width - chooser_pip_dimension)/2
409 ypos = (selected_colour-1)*(chooser_height) + (chooser_height - chooser_pip_dimension)/2
410 elseif LAYOUT == 2 then
411 xpos = (selected_colour-1)*(chooser_width) + (chooser_width - chooser_pip_dimension)/2
412 ypos = chooser_start_pos + (chooser_height - chooser_pip_dimension)/2
413 end
414 rb.lcd_fillrect(xpos, ypos, chooser_pip_dimension, chooser_pip_dimension)
415 end
416
417 --Draws the current moves, par and high score
418 function draw_status(move_number, par, highscore)
419 local strings = {"Move", move_number, "Par", par}
420 if highscore then
421 table.insert(strings, "Best")
422 table.insert(strings, highscore)
423 end
424
425 if LAYOUT == 1 then
426 local function calc_string(var_len_str, static_str)
427 local avail_width = chooser_width - rb.font_getstringsize(static_str, rb.FONT_UI)
428 local rtn_str = ""
429
430 for i=1,string.len(var_len_str) do
431 local c = string.sub(var_len_str, i, i)
432 local curr_width = rb.font_getstringsize(rtn_str, rb.FONT_UI)
433 local width = rb.font_getstringsize(c, rb.FONT_UI)
434
435 if curr_width + width <= avail_width then
436 rtn_str = rtn_str .. c
437 else
438 break
439 end
440 end
441
442 return rtn_str, rb.font_getstringsize(rtn_str, rb.FONT_UI)
443 end
444
445 local height = NUM_COLOURS*chooser_height
446 colon_width = rb.font_getstringsize(": ", rb.FONT_UI)
447 for i = 1,table.getn(strings),2 do
448 local label, label_width = calc_string(strings[i], ": "..strings[i+1])
449
450 rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0))
451 rb.lcd_putsxy(chooser_start_pos, height, label..": ")
452 rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255))
453 rb.lcd_putsxy(chooser_start_pos + label_width + colon_width, height, strings[i+1])
454 height = height + TEXT_LINE_HEIGHT
455 end
456 else
457 local text_ypos = 0
458 if LAYOUT == 2 then
459 text_ypos = chooser_start_pos - TEXT_LINE_HEIGHT - 1
460 else
461 text_ypos = rb.LCD_HEIGHT - TEXT_LINE_HEIGHT
462 end
463 space_width = rb.font_getstringsize(" ", rb.FONT_UI)
464 local xpos = 0
465 for i = 1,table.getn(strings),2 do
466 rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0))
467 rb.lcd_putsxy(xpos, text_ypos, strings[i]..": ")
468 xpos = xpos + rb.font_getstringsize(strings[i]..": ", rb.FONT_UI)
469 rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255))
470 rb.lcd_putsxy(xpos, text_ypos, strings[i+1])
471 xpos = xpos + rb.font_getstringsize(strings[i+1], rb.FONT_UI) + space_width
472 end
473 end
474 end
475
476 --Convenience function to redraw the whole board to screen
477 function redraw_game(game_state, highscores)
478 rb.lcd_clear_display()
479 draw_board(game_state["board"])
480 draw_chooser(game_state["selected_colour"])
481 draw_status(game_state["move_number"], game_state["par"],
482 highscores[game_state["difficulty"]])
483 rb.lcd_update()
484 end
485
486
487 --Draws help to screen, waits for a keypress to exit
488 function app_help()
489 rb.lcd_clear_display()
490
491 local title = "Pixel painter help"
492 local rtn, title_width, h = rb.font_getstringsize(title, rb.FONT_UI)
493 local title_xpos = (rb.LCD_WIDTH - title_width) / 2
494 local space_width = rb.font_getstringsize(" ", rb.FONT_UI)
495
496 --Draw title
497 function draw_text(y_offset)
498 rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0))
499 rb.lcd_putsxy(title_xpos, y_offset, title)
500 rb.lcd_hline(title_xpos, title_xpos + title_width, TEXT_LINE_HEIGHT + y_offset)
501 rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255))
502
503 local body_text = [[
504The aim is to fill the screen with a single colour. Each move you select a new colour which is then filled in from the top left corner.
505
506The bottom right displays the number of moves taken, the number of moves used by the computer and your best score relative to the computer's.
507 ]]
508 local body_len = string.len(body_text)
509
510 --Draw body text
511 local word_buffer = ""
512 local xpos = 0
513 local ypos = TEXT_LINE_HEIGHT * 2
514 for i=1,body_len do
515 local c = string.sub(body_text, i, i)
516 if c == " " or c == "\n" then
517 local word_length = rb.font_getstringsize(word_buffer, rb.FONT_UI)
518 if (xpos + word_length) > rb.LCD_WIDTH then
519 xpos = 0
520 ypos = ypos + TEXT_LINE_HEIGHT
521 end
522 rb.lcd_putsxy(xpos, ypos + y_offset, word_buffer)
523
524 word_buffer = ""
525 if c == "\n" then
526 xpos = 0
527 ypos = ypos + TEXT_LINE_HEIGHT
528 else
529 xpos = xpos + word_length + space_width
530 end
531 else
532 word_buffer = word_buffer .. c
533 end
534 end
535
536 rb.lcd_update()
537
538 return ypos
539 end
540
541 --Deal with scrolling the help
542 local y_offset = 0
543 local max_y_offset = math.max(draw_text(y_offset) - rb.LCD_HEIGHT, 0)
544 local exit = false
545 repeat
546 local action = rb.get_plugin_action(0)
547 if action == rb.actions.PLA_DOWN then
548 y_offset = math.max(-max_y_offset, y_offset - TEXT_LINE_HEIGHT)
549 elseif action == rb.actions.PLA_UP then
550 y_offset = math.min(0, y_offset + TEXT_LINE_HEIGHT)
551 elseif action == rb.actions.PLA_LEFT or
552 action == rb.actions.PLA_RIGHT or
553 action == rb.actions.PLA_SELECT or
554 action == rb.actions.PLA_EXIT or
555 action == rb.actions.PLA_CANCEL then
556 --This explicit enumeration is needed for targets like
557 --the iriver which send more than one action when
558 --scrolling
559
560 exit = true
561 end
562 rb.lcd_clear_display()
563 draw_text(y_offset)
564 until exit == true
565 end
566
567 --Draws the application menu and handles its logic
568 function app_menu()
569 local options = {"Resume game", "Start new game", "Change difficulty",
570 "Help", "Quit without saving", "Quit"}
571 local item = rb.do_menu("Pixel painter menu", options, nil, false)
572
573 if item == 0 then
574 redraw_game(game_state, highscores)
575 elseif item == 1 then
576 os.remove(SAVE_FILE)
577 game_state = init_game(game_state["difficulty"])
578 redraw_game(game_state, highscores)
579 elseif item == 2 then
580 local diff = rb.do_menu("Difficulty", {"Easy", "Medium", "Hard"}, game_state["difficulty"] - 1, false)
581 if diff < 0 then
582 app_menu()
583 else
584 local difficulty = diff + 1 --lua is 1 indexed
585 os.remove(SAVE_FILE)
586 game_state = init_game(difficulty)
587 redraw_game(game_state, highscores)
588 end
589 elseif item == 3 then
590 app_help()
591 redraw_game(game_state, highscores)
592 elseif item == 4 then
593 os.remove(SAVE_FILE)
594 os.exit()
595 elseif item == 5 then
596 rb.splash(1, "Saving game...") --Will stay on screen till the app exits
597 save_game(game_state,SAVE_FILE)
598 os.exit()
599 end
600 end
601
602 --Determine what victory text to show depending on the relation of the
603 --score to the calculated par value
604 function win_text(delta)
605 if delta < 0 then
606 return "You were "..(-1*delta).." under par"
607 elseif delta > 0 then
608 return "You were "..delta.." over par"
609 else
610 return "You attained par"
611 end
612 end
613
614 ------------------
615 --Game main loop--
616 ------------------
617
618 --Gives the option of testing things without running the game, use:
619 --as_library=true
620 --dofile('pixel-painter.lua')
621 if not as_library then
622 game_state = load_game(SAVE_FILE)
623 if game_state then
624 init_display_variables(game_state["difficulty"])
625 else
626 game_state = init_game(DEFAULT_DIFFICULTY)
627 end
628 loaded_scores = load_scores(SCORES_FILE)
629 if loaded_scores then
630 highscores = loaded_scores
631 end
632 redraw_game(game_state, highscores)
633
634 require("actions")
635 --Set the keys to use for scrolling the chooser
636 if LAYOUT == 1 or LAYOUT == 3 then -- landscape and square screens
637 prev_action = rb.actions.PLA_UP
638 prev_action_repeat = rb.actions.PLA_UP_REPEAT
639 next_action = rb.actions.PLA_DOWN
640 next_action_repeat = rb.actions.PLA_DOWN_REPEAT
641 else -- portrait screens
642 prev_action = rb.actions.PLA_LEFT
643 prev_action_repeat = rb.actions.PLA_LEFT_REPEAT
644 next_action = rb.actions.PLA_RIGHT
645 next_action_repeat = rb.actions.PLA_RIGHT_REPEAT
646 end
647
648 repeat
649 local action = rb.get_plugin_action(-1) -- TIMEOUT_BLOCK
650
651 if action == rb.actions.PLA_SELECT then
652 --Ensure the user has changed the colour before allowing move
653 --TODO: Check that the move would change the board
654
655 if game_state["selected_colour"] ~= game_state["board"][1][1] then
656 fill_board(game_state["board"], game_state["selected_colour"],
657 1, 1, game_state["board"][1][1])
658 game_state["move_number"] = game_state["move_number"] + 1
659 redraw_game(game_state, highscores)
660
661 if check_win(game_state["board"]) then
662 local par_diff = game_state["move_number"] - game_state["par"]
663 if not highscores[game_state["difficulty"]] or
664 par_diff < highscores[game_state["difficulty"]] then
665 --
666 rb.splash(3*rb.HZ, win_text(par_diff)..", a new high score!")
667 highscores[game_state["difficulty"]] = par_diff
668 save_scores(highscores, SCORES_FILE)
669 else
670 rb.splash(3*rb.HZ, win_text(par_diff)..".")
671 end
672 os.remove(SAVE_FILE)
673 os.exit()
674 end
675 else
676 --Will stay on screen until they move
677 rb.splash(1, "Invalid move (wouldn't change board). Change colour to continue.")
678 end
679 elseif action == next_action or action == next_action_repeat then
680 if game_state["selected_colour"] < NUM_COLOURS then
681 game_state["selected_colour"] = game_state["selected_colour"] + 1
682 else
683 game_state["selected_colour"] = 1
684 end
685 redraw_game(game_state, highscores)
686 elseif action == prev_action or action == prev_action_repeat then
687 if game_state["selected_colour"] > 1 then
688 game_state["selected_colour"] = game_state["selected_colour"] - 1
689 else
690 game_state["selected_colour"] = NUM_COLOURS
691 end
692 redraw_game(game_state, highscores)
693 elseif action == rb.actions.PLA_CANCEL then
694 app_menu()
695 end
696 until action == rb.actions.PLA_EXIT
697 --This is executed if the user presses PLA_EXIT
698 rb.splash(1, "Saving game...") --Will stay on screen till the app exits
699 save_game(game_state,SAVE_FILE)
700 end
701end
diff --git a/docs/CREDITS b/docs/CREDITS
index 81a93d651f..b14847b129 100644
--- a/docs/CREDITS
+++ b/docs/CREDITS
@@ -657,6 +657,7 @@ Adam Sampson
657William Wilgus 657William Wilgus
658Igor Skochinsky 658Igor Skochinsky
659Sebastiano Pistore 659Sebastiano Pistore
660Stefan Schneider-Kennedy
660 661
661The libmad team 662The libmad team
662The wavpack team 663The wavpack team
diff --git a/manual/plugins/images/ss-pixelpainter-128x128x16.png b/manual/plugins/images/ss-pixelpainter-128x128x16.png
new file mode 100644
index 0000000000..927880a00c
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-128x128x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-128x160x16.png b/manual/plugins/images/ss-pixelpainter-128x160x16.png
new file mode 100644
index 0000000000..125c6f652b
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-128x160x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-128x96x16.png b/manual/plugins/images/ss-pixelpainter-128x96x16.png
new file mode 100644
index 0000000000..84819dd52c
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-128x96x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-132x80x16.png b/manual/plugins/images/ss-pixelpainter-132x80x16.png
new file mode 100644
index 0000000000..a1e0830cb5
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-132x80x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-160x128x16.png b/manual/plugins/images/ss-pixelpainter-160x128x16.png
new file mode 100644
index 0000000000..f5c20ef95d
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-160x128x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-176x132x16.png b/manual/plugins/images/ss-pixelpainter-176x132x16.png
new file mode 100644
index 0000000000..06cdaf6d9d
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-176x132x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-176x220x16.png b/manual/plugins/images/ss-pixelpainter-176x220x16.png
new file mode 100644
index 0000000000..c41a3f6f00
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-176x220x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-220x176x16.png b/manual/plugins/images/ss-pixelpainter-220x176x16.png
new file mode 100644
index 0000000000..a92eefeba8
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-220x176x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-240x320x16.png b/manual/plugins/images/ss-pixelpainter-240x320x16.png
new file mode 100644
index 0000000000..ceb7c4f7a6
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-240x320x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-240x400x16.png b/manual/plugins/images/ss-pixelpainter-240x400x16.png
new file mode 100644
index 0000000000..08c467c131
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-240x400x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-320x240x16.png b/manual/plugins/images/ss-pixelpainter-320x240x16.png
new file mode 100644
index 0000000000..82d1d68984
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-320x240x16.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-320x240x24.png b/manual/plugins/images/ss-pixelpainter-320x240x24.png
new file mode 100644
index 0000000000..82d1d68984
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-320x240x24.png
Binary files differ
diff --git a/manual/plugins/images/ss-pixelpainter-96x96x16.png b/manual/plugins/images/ss-pixelpainter-96x96x16.png
new file mode 100644
index 0000000000..eb0f5bf668
--- /dev/null
+++ b/manual/plugins/images/ss-pixelpainter-96x96x16.png
Binary files differ
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex
index 540a0efe5c..bf5bcd3dac 100644
--- a/manual/plugins/main.tex
+++ b/manual/plugins/main.tex
@@ -70,6 +70,8 @@ text files%
70 70
71\opt{lcd_bitmap}{\input{plugins/pegbox.tex}} 71\opt{lcd_bitmap}{\input{plugins/pegbox.tex}}
72 72
73\opt{lcd_color}{\opt{large_plugin_buffer}{\input{plugins/pixelpainter.tex}}}
74
73\opt{lcd_bitmap}{\input{plugins/pong.tex}} 75\opt{lcd_bitmap}{\input{plugins/pong.tex}}
74 76
75\opt{lcd_bitmap}{\input{plugins/reversi.tex}} 77\opt{lcd_bitmap}{\input{plugins/reversi.tex}}
diff --git a/manual/plugins/pixelpainter.tex b/manual/plugins/pixelpainter.tex
new file mode 100644
index 0000000000..2b6a315480
--- /dev/null
+++ b/manual/plugins/pixelpainter.tex
@@ -0,0 +1,24 @@
1\subsection{Pixel Painter}
2\screenshot{plugins/images/ss-pixelpainter}{Pixel Painter}{img:pixelpainter}
3This game is written in LUA and based on the game of the same name by
4Pavel Bakhilau (\url{http://js1k.com/2010-first/demo/453}).
5
6Select a colour to flood-fill the board with that colour, starting from the
7top-left pixel (meaning that any pixel which is connected to the top-left
8through other pixels of the same colour will be changed to the selected colour).
9Try to paint the entire board with as few moves as possible.
10
11\begin{btnmap}
12 \ifnum\dapdisplaywidth<\dapdisplayheight
13 \PluginLeft{} / \PluginRight
14 \else
15 \PluginUp{} / \PluginDown
16 \fi
17 & Move colour selector\\
18
19 \PluginSelect
20 & Fill screen with selected colour\\
21
22 \PluginCancel, \PluginExit
23 & Enter game menu\\
24\end{btnmap}