From 28ae7004446b05f085083669182a81d1cc28c164 Mon Sep 17 00:00:00 2001 From: Sebastian Leonhardt Date: Fri, 23 Sep 2016 01:25:32 +0200 Subject: [PATCH] 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 --- apps/plugins/CATEGORIES | 1 + apps/plugins/SOURCES | 5 +- apps/plugins/pixel-painter.lua | 701 ++++++++++++++++++ docs/CREDITS | 1 + .../images/ss-pixelpainter-128x128x16.png | Bin 0 -> 1181 bytes .../images/ss-pixelpainter-128x160x16.png | Bin 0 -> 1139 bytes .../images/ss-pixelpainter-128x96x16.png | Bin 0 -> 1065 bytes .../images/ss-pixelpainter-132x80x16.png | Bin 0 -> 1029 bytes .../images/ss-pixelpainter-160x128x16.png | Bin 0 -> 1158 bytes .../images/ss-pixelpainter-176x132x16.png | Bin 0 -> 1314 bytes .../images/ss-pixelpainter-176x220x16.png | Bin 0 -> 1387 bytes .../images/ss-pixelpainter-220x176x16.png | Bin 0 -> 1563 bytes .../images/ss-pixelpainter-240x320x16.png | Bin 0 -> 1686 bytes .../images/ss-pixelpainter-240x400x16.png | Bin 0 -> 1838 bytes .../images/ss-pixelpainter-320x240x16.png | Bin 0 -> 1708 bytes .../images/ss-pixelpainter-320x240x24.png | Bin 0 -> 1708 bytes .../images/ss-pixelpainter-96x96x16.png | Bin 0 -> 995 bytes manual/plugins/main.tex | 2 + manual/plugins/pixelpainter.tex | 24 + 19 files changed, 733 insertions(+), 1 deletion(-) create mode 100644 apps/plugins/pixel-painter.lua create mode 100644 manual/plugins/images/ss-pixelpainter-128x128x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-128x160x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-128x96x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-132x80x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-160x128x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-176x132x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-176x220x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-220x176x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-240x320x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-240x400x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-320x240x16.png create mode 100644 manual/plugins/images/ss-pixelpainter-320x240x24.png create mode 100644 manual/plugins/images/ss-pixelpainter-96x96x16.png create mode 100644 manual/plugins/pixelpainter.tex 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 periodic_table,apps pictureflow,demos pitch_detector,apps +pixel-painter,games plasma,demos png,viewers gif,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 /* Lua needs at least 160 KB to work in */ #if PLUGIN_BUFFER_SIZE >= 0x80000 boomshine.lua -#endif +#ifdef HAVE_LCD_COLOR +pixel-painter.lua +#endif /* HAVE_LCD_COLOR */ +#endif /* PLUGIN_BUFFER_SIZE >= 0x80000 */ rockblox1d.c brickmania.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 @@ +--[[ + __________ __ ___. + Open \______ \ ____ ____ | | _\_ |__ _______ ___ + Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + \/ \/ \/ \/ \/ + $Id$ + + Port and extension of Pixel Painter by Pavel Bakhilau + (http://js1k.com/2010-first/demo/453) to Rockbox Lua. + + Copyright (C) 2011 by Stefan Schneider-Kennedy + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + KIND, either express or implied. + +]]-- + +--Number of colours used in the game +--Hard coded here, in the COLOURS and PIP_COLOURS definitions and in +--get_colours_count's count_table +NUM_COLOURS = 6 + +--Utility function makes a copy of the passed table +function deepcopy(object) + local lookup_table = {} + local function _copy(object) + if type(object) ~= "table" then + return object + elseif lookup_table[object] then + return lookup_table[object] + end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do + new_table[_copy(index)] = _copy(value) + end + return setmetatable(new_table, getmetatable(object)) + end + return _copy(object) +end + +--Returns the maximum value of the passed table and its index +function table_maximum(a) + local mi = 1 + local m = a[mi] + for i, val in ipairs(a) do + if val > m then + mi = i + m = val + end + end + return m, mi +end + +--Solves the board using a simple algorithm and returns the number of +--moves required. Each turn, the function picks the move which fills in +--the greatest area of board. The number of moves required to complete +--it is returned. +function calculate_par(board) + local board_dimension = table.getn(board) + local test_game_copy = deepcopy(board) + local moves = 0 + + repeat + local non_matching = {} + fill_board(test_game_copy, 0, 1, 1, test_game_copy[1][1], non_matching) + + if table.getn(non_matching) > 0 then + local count_table = get_colours_count(test_game_copy, non_matching) + local max_value, colour = table_maximum(count_table) + + --Corrects the invalid colour values set by + --get_colours_count, this also acts as a move + for x=1,board_dimension do + for y=1,board_dimension do + if test_game_copy[x][y] < 0 then + test_game_copy[x][y] = test_game_copy[x][y] * -1 + elseif test_game_copy[x][y] == 0 then + test_game_copy[x][y] = colour + end + end + end + else + return moves + end + --Manual garbage collection is needed so it doesn't eat into the + --audio buffer, see http://forums.rockbox.org/index.php/topic,27120.msg177434.html + collectgarbage("collect") + moves = moves + 1 + until false +end + +--Calculates the number of blocks of each colour adjacent to the filled +--region identified by the passed parameters. A colour indexed table +--containing the counts is returned. Relies on the board having been +--flood filled with 0s prior to executing this function. +-- +--The 'board' table is also adjusted as follows: The filled region's +--colour index is set to zero and each of the adjacent areas' colour +--indexes are multiplied by -1. These invalid colour values are later +--corrected in the calculate_par function. +function get_colours_count(board, non_matching) + local count_table = {0, 0, 0, 0, 0, 0} + repeat + --Pop the array + local current = non_matching[table.getn(non_matching)] + table.remove(non_matching) + --Check this square hasn't already been filled + local curr_colour = board[current[1]][current[2]] + if curr_colour > 0 then + count_table[curr_colour] = count_table[curr_colour] + + fill_board(board, curr_colour * -1, current[1], current[2], curr_colour) + end + until table.getn(non_matching) == 0 + + return count_table +end + +--Returns a randomly coloured board of the indicated dimensions +function generate_board(board_dimension, seed) + math.randomseed(seed) + + local board = {} + for x=1,board_dimension do + board[x] = {} + for y=1,board_dimension do + board[x][y] = math.random(1,NUM_COLOURS) + end + end + + return board +end + +--Flood fills the board from the top left using selected_colour +--Returns the number of boxes filled +function fill_board(board, fill_colour, x, y, original_colour, non_matching) + local board_dimension = table.getn(board) + if x > 0 and y > 0 and x <= board_dimension and y <= board_dimension then + if board[x][y] == original_colour then + board[x][y] = fill_colour + return fill_board(board, fill_colour, x - 1, y, original_colour, non_matching) + + fill_board(board, fill_colour, x, y - 1, original_colour, non_matching) + + fill_board(board, fill_colour, x + 1, y, original_colour, non_matching) + + fill_board(board, fill_colour, x, y + 1, original_colour, non_matching) + 1 + elseif(non_matching ~= nil and board[x][y] ~= fill_colour) then + table.insert(non_matching, {x,y}) + end + end + + return 0 +end + +--Checks whether the given board is a single colour +function check_win(board) + for x,col in pairs(board) do + for y,value in pairs(col) do + if value ~= board[1][1] then + return false + end + end + end + + return true +end + +--Attempt to load the game variables stored in the indicated save file. +--Returns a table containing game variables if the file can be opened, +--false otherwise. +--Table keys are: difficulty, par, move_number, selected_colour, board +function load_game(filename) + local f = io.open(filename, "r") + if f ~= nil then + local rtn = {} + rtn["difficulty"] = tonumber(f:read()) + rtn["par"] = tonumber(f:read()) + rtn["move_number"] = tonumber(f:read()) + rtn["selected_colour"] = tonumber(f:read()) + + local board={} + local dimension = diff_to_dimension(rtn["difficulty"]) + for x=1,dimension do + board[x] = {} + local line = f:read() + local bits = {line:match(("([^ ]*) "):rep(dimension))} + for y=1,dimension do + board[x][y] = tonumber(bits[y]) + end + end + rtn["board"] = board + + f:close() + return rtn + else + return false + end +end + +--Saves the game state to file +function save_game(state, filename) + local f = io.open(filename, "w") + if f ~= nil then + f:write(state["difficulty"],"\n") + f:write(state["par"],"\n") + f:write(state["move_number"],"\n") + f:write(state["selected_colour"],"\n") + local board = state["board"] + local dimension = diff_to_dimension(state["difficulty"]) + for x=1,dimension do + for y=1,dimension do + f:write(board[x][y]," ") + end + f:write("\n") + end + f:close() + return true + else + return false + end +end + +--Loads the high scores from file +--Returns true on success, false otherwise +function load_scores(filename) + local f = io.open(filename, "r") + if f ~= nil then + local highscores = {} + for i=1,3 do + local line = f:read() + local value = false + if line ~= nil then + value = tonumber(line) + end + + highscores[i] = value + end + f:close() + return highscores + else + return false + end +end + +--Saves the high scores to file +function save_scores(highscores, filename) + local f = io.open(filename, "w") + if f ~= nil then + for i=1,3 do + local value = highscores[i] + if value == false then + value = "" + end + f:write(value,"\n") + end + f:close() + return true + else + return false + end +end + +function diff_to_dimension(difficulty) + if difficulty == 1 then + return 8 + elseif difficulty == 2 then + return 16 + else + return 22 + end +end + +--Don't run the RB stuff if we're not running under RB +if rb ~= nil then + + if rb.lcd_rgbpack == nil then + rb.splash(2*rb.HZ, "Non RGB targets not currently supported") + os.exit() + end + + --------------------- + --RB Game variables-- + --------------------- + + --The colours used by the game + COLOURS = { + rb.lcd_rgbpack(255, 119, 34), + rb.lcd_rgbpack(255, 255, 102), + rb.lcd_rgbpack(119, 204, 51), + rb.lcd_rgbpack(102, 170, 255), + rb.lcd_rgbpack(51, 68, 255), + rb.lcd_rgbpack(51, 51, 51), + } + --The colour of the selection pip + PIP_COLOURS = { + rb.lcd_rgbpack(0, 0, 0), + rb.lcd_rgbpack(0, 0, 0), + rb.lcd_rgbpack(0, 0, 0), + rb.lcd_rgbpack(0, 0, 0), + rb.lcd_rgbpack(0, 0, 0), + rb.lcd_rgbpack(255, 255, 255), + } + DEFAULT_DIFFICULTY = 2 --1: Easy, 2: Normal, 3: Hard + + FILES_ROOT = "/.rockbox/rocks/games/" + SCORES_FILE = FILES_ROOT.."pixel-painter.score" + SAVE_FILE = FILES_ROOT.."pixel-painter.save" + r,w,TEXT_LINE_HEIGHT = rb.font_getstringsize(" ", rb.FONT_UI) --Get font height + --Determine which layout to use by considering the screen dimensions + --the +9 is so we have space for the chooser + if rb.LCD_WIDTH > (rb.LCD_HEIGHT + 9) then + LAYOUT = 1 --Wider than high, status and chooser on right + elseif rb.LCD_HEIGHT > (rb.LCD_WIDTH + 9) then + LAYOUT = 2 --Higher than wide, status and chooser below + else + LAYOUT = 3 --Treat like a square screen, chooser on right, status below + end + + --Display variables + chooser_pip_dimension = 6 --pixel dimension of the selected colour pip + block_width = 0 --pixel dimension of each game square + chooser_start_pos = 0 --x or y position of the first block (depending on LAYOUT) + + --Populated by load_scores() + highscores = {false, false, false} + + --A table containing the game state, initialised by init_game() or + --load_game(), see + game_state = {} + + ----------------------------------- + --Display and interface functions-- + ----------------------------------- + + --Sets up a new game and display variables for the indicated + --difficulty + function init_game(difficulty) + init_display_variables(difficulty) + local state = {} + local board_dimension = diff_to_dimension(difficulty) + state["selected_colour"] = 1 + state["move_number"] = 0 + state["difficulty"] = difficulty + state["board"] = generate_board(board_dimension, rb.current_tick()+os.time()) + rb.splash(1, "Calculating par...") --Will stay on screen until it's done + state["par"] = calculate_par(state["board"]) + + return state + end + + --Initialises the display variables for the screen LAYOUT + function init_display_variables(difficulty) + local board_dimension = diff_to_dimension(difficulty) + + if LAYOUT == 1 then + block_width = rb.LCD_HEIGHT / board_dimension + chooser_start_pos = (board_dimension)*block_width + 2 + chooser_width = rb.LCD_WIDTH - chooser_start_pos + chooser_height = (rb.LCD_HEIGHT - 3*TEXT_LINE_HEIGHT) / NUM_COLOURS + elseif LAYOUT == 2 then + block_width = rb.LCD_WIDTH / board_dimension + chooser_start_pos = board_dimension*block_width + 2 + TEXT_LINE_HEIGHT + chooser_width = rb.LCD_WIDTH / NUM_COLOURS + chooser_height = rb.LCD_HEIGHT - chooser_start_pos + else + if TEXT_LINE_HEIGHT > 9 then + block_width = (rb.LCD_HEIGHT - TEXT_LINE_HEIGHT) / board_dimension + else + block_width = (rb.LCD_HEIGHT - 9) / board_dimension + end + chooser_start_pos = (board_dimension)*block_width + 1 + chooser_width = rb.LCD_WIDTH - chooser_start_pos + chooser_height = (rb.LCD_HEIGHT - TEXT_LINE_HEIGHT) / NUM_COLOURS + end + end + + --Draws the game board to screen + function draw_board(board) + for x,col in pairs(board) do + for y,value in pairs(col) do + rb.lcd_set_foreground(COLOURS[value]) + rb.lcd_fillrect((x-1)*block_width, (y-1)*block_width, block_width, block_width) + end + end + end + + --Draw the colour chooser along with selected pip at the appropriate + --position + function draw_chooser(selected_colour) + for i=1,NUM_COLOURS do + rb.lcd_set_foreground(COLOURS[i]) + if LAYOUT == 1 or LAYOUT == 3 then + rb.lcd_fillrect(chooser_start_pos, (i - 1)*(chooser_height), chooser_width, chooser_height) + elseif LAYOUT == 2 then + rb.lcd_fillrect((i - 1)*(chooser_width), chooser_start_pos, chooser_width, chooser_height) + end + end + + rb.lcd_set_foreground(PIP_COLOURS[selected_colour]) + local xpos = 0 + local ypos = 0 + if LAYOUT == 1 or LAYOUT == 3 then + xpos = chooser_start_pos + (chooser_width - chooser_pip_dimension)/2 + ypos = (selected_colour-1)*(chooser_height) + (chooser_height - chooser_pip_dimension)/2 + elseif LAYOUT == 2 then + xpos = (selected_colour-1)*(chooser_width) + (chooser_width - chooser_pip_dimension)/2 + ypos = chooser_start_pos + (chooser_height - chooser_pip_dimension)/2 + end + rb.lcd_fillrect(xpos, ypos, chooser_pip_dimension, chooser_pip_dimension) + end + + --Draws the current moves, par and high score + function draw_status(move_number, par, highscore) + local strings = {"Move", move_number, "Par", par} + if highscore then + table.insert(strings, "Best") + table.insert(strings, highscore) + end + + if LAYOUT == 1 then + local function calc_string(var_len_str, static_str) + local avail_width = chooser_width - rb.font_getstringsize(static_str, rb.FONT_UI) + local rtn_str = "" + + for i=1,string.len(var_len_str) do + local c = string.sub(var_len_str, i, i) + local curr_width = rb.font_getstringsize(rtn_str, rb.FONT_UI) + local width = rb.font_getstringsize(c, rb.FONT_UI) + + if curr_width + width <= avail_width then + rtn_str = rtn_str .. c + else + break + end + end + + return rtn_str, rb.font_getstringsize(rtn_str, rb.FONT_UI) + end + + local height = NUM_COLOURS*chooser_height + colon_width = rb.font_getstringsize(": ", rb.FONT_UI) + for i = 1,table.getn(strings),2 do + local label, label_width = calc_string(strings[i], ": "..strings[i+1]) + + rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0)) + rb.lcd_putsxy(chooser_start_pos, height, label..": ") + rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255)) + rb.lcd_putsxy(chooser_start_pos + label_width + colon_width, height, strings[i+1]) + height = height + TEXT_LINE_HEIGHT + end + else + local text_ypos = 0 + if LAYOUT == 2 then + text_ypos = chooser_start_pos - TEXT_LINE_HEIGHT - 1 + else + text_ypos = rb.LCD_HEIGHT - TEXT_LINE_HEIGHT + end + space_width = rb.font_getstringsize(" ", rb.FONT_UI) + local xpos = 0 + for i = 1,table.getn(strings),2 do + rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0)) + rb.lcd_putsxy(xpos, text_ypos, strings[i]..": ") + xpos = xpos + rb.font_getstringsize(strings[i]..": ", rb.FONT_UI) + rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255)) + rb.lcd_putsxy(xpos, text_ypos, strings[i+1]) + xpos = xpos + rb.font_getstringsize(strings[i+1], rb.FONT_UI) + space_width + end + end + end + + --Convenience function to redraw the whole board to screen + function redraw_game(game_state, highscores) + rb.lcd_clear_display() + draw_board(game_state["board"]) + draw_chooser(game_state["selected_colour"]) + draw_status(game_state["move_number"], game_state["par"], + highscores[game_state["difficulty"]]) + rb.lcd_update() + end + + + --Draws help to screen, waits for a keypress to exit + function app_help() + rb.lcd_clear_display() + + local title = "Pixel painter help" + local rtn, title_width, h = rb.font_getstringsize(title, rb.FONT_UI) + local title_xpos = (rb.LCD_WIDTH - title_width) / 2 + local space_width = rb.font_getstringsize(" ", rb.FONT_UI) + + --Draw title + function draw_text(y_offset) + rb.lcd_set_foreground(rb.lcd_rgbpack(255,0,0)) + rb.lcd_putsxy(title_xpos, y_offset, title) + rb.lcd_hline(title_xpos, title_xpos + title_width, TEXT_LINE_HEIGHT + y_offset) + rb.lcd_set_foreground(rb.lcd_rgbpack(255,255,255)) + + local body_text = [[ +The 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. + +The 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. + ]] + local body_len = string.len(body_text) + + --Draw body text + local word_buffer = "" + local xpos = 0 + local ypos = TEXT_LINE_HEIGHT * 2 + for i=1,body_len do + local c = string.sub(body_text, i, i) + if c == " " or c == "\n" then + local word_length = rb.font_getstringsize(word_buffer, rb.FONT_UI) + if (xpos + word_length) > rb.LCD_WIDTH then + xpos = 0 + ypos = ypos + TEXT_LINE_HEIGHT + end + rb.lcd_putsxy(xpos, ypos + y_offset, word_buffer) + + word_buffer = "" + if c == "\n" then + xpos = 0 + ypos = ypos + TEXT_LINE_HEIGHT + else + xpos = xpos + word_length + space_width + end + else + word_buffer = word_buffer .. c + end + end + + rb.lcd_update() + + return ypos + end + + --Deal with scrolling the help + local y_offset = 0 + local max_y_offset = math.max(draw_text(y_offset) - rb.LCD_HEIGHT, 0) + local exit = false + repeat + local action = rb.get_plugin_action(0) + if action == rb.actions.PLA_DOWN then + y_offset = math.max(-max_y_offset, y_offset - TEXT_LINE_HEIGHT) + elseif action == rb.actions.PLA_UP then + y_offset = math.min(0, y_offset + TEXT_LINE_HEIGHT) + elseif action == rb.actions.PLA_LEFT or + action == rb.actions.PLA_RIGHT or + action == rb.actions.PLA_SELECT or + action == rb.actions.PLA_EXIT or + action == rb.actions.PLA_CANCEL then + --This explicit enumeration is needed for targets like + --the iriver which send more than one action when + --scrolling + + exit = true + end + rb.lcd_clear_display() + draw_text(y_offset) + until exit == true + end + + --Draws the application menu and handles its logic + function app_menu() + local options = {"Resume game", "Start new game", "Change difficulty", + "Help", "Quit without saving", "Quit"} + local item = rb.do_menu("Pixel painter menu", options, nil, false) + + if item == 0 then + redraw_game(game_state, highscores) + elseif item == 1 then + os.remove(SAVE_FILE) + game_state = init_game(game_state["difficulty"]) + redraw_game(game_state, highscores) + elseif item == 2 then + local diff = rb.do_menu("Difficulty", {"Easy", "Medium", "Hard"}, game_state["difficulty"] - 1, false) + if diff < 0 then + app_menu() + else + local difficulty = diff + 1 --lua is 1 indexed + os.remove(SAVE_FILE) + game_state = init_game(difficulty) + redraw_game(game_state, highscores) + end + elseif item == 3 then + app_help() + redraw_game(game_state, highscores) + elseif item == 4 then + os.remove(SAVE_FILE) + os.exit() + elseif item == 5 then + rb.splash(1, "Saving game...") --Will stay on screen till the app exits + save_game(game_state,SAVE_FILE) + os.exit() + end + end + + --Determine what victory text to show depending on the relation of the + --score to the calculated par value + function win_text(delta) + if delta < 0 then + return "You were "..(-1*delta).." under par" + elseif delta > 0 then + return "You were "..delta.." over par" + else + return "You attained par" + end + end + + ------------------ + --Game main loop-- + ------------------ + + --Gives the option of testing things without running the game, use: + --as_library=true + --dofile('pixel-painter.lua') + if not as_library then + game_state = load_game(SAVE_FILE) + if game_state then + init_display_variables(game_state["difficulty"]) + else + game_state = init_game(DEFAULT_DIFFICULTY) + end + loaded_scores = load_scores(SCORES_FILE) + if loaded_scores then + highscores = loaded_scores + end + redraw_game(game_state, highscores) + + require("actions") + --Set the keys to use for scrolling the chooser + if LAYOUT == 1 or LAYOUT == 3 then -- landscape and square screens + prev_action = rb.actions.PLA_UP + prev_action_repeat = rb.actions.PLA_UP_REPEAT + next_action = rb.actions.PLA_DOWN + next_action_repeat = rb.actions.PLA_DOWN_REPEAT + else -- portrait screens + prev_action = rb.actions.PLA_LEFT + prev_action_repeat = rb.actions.PLA_LEFT_REPEAT + next_action = rb.actions.PLA_RIGHT + next_action_repeat = rb.actions.PLA_RIGHT_REPEAT + end + + repeat + local action = rb.get_plugin_action(-1) -- TIMEOUT_BLOCK + + if action == rb.actions.PLA_SELECT then + --Ensure the user has changed the colour before allowing move + --TODO: Check that the move would change the board + + if game_state["selected_colour"] ~= game_state["board"][1][1] then + fill_board(game_state["board"], game_state["selected_colour"], + 1, 1, game_state["board"][1][1]) + game_state["move_number"] = game_state["move_number"] + 1 + redraw_game(game_state, highscores) + + if check_win(game_state["board"]) then + local par_diff = game_state["move_number"] - game_state["par"] + if not highscores[game_state["difficulty"]] or + par_diff < highscores[game_state["difficulty"]] then + -- + rb.splash(3*rb.HZ, win_text(par_diff)..", a new high score!") + highscores[game_state["difficulty"]] = par_diff + save_scores(highscores, SCORES_FILE) + else + rb.splash(3*rb.HZ, win_text(par_diff)..".") + end + os.remove(SAVE_FILE) + os.exit() + end + else + --Will stay on screen until they move + rb.splash(1, "Invalid move (wouldn't change board). Change colour to continue.") + end + elseif action == next_action or action == next_action_repeat then + if game_state["selected_colour"] < NUM_COLOURS then + game_state["selected_colour"] = game_state["selected_colour"] + 1 + else + game_state["selected_colour"] = 1 + end + redraw_game(game_state, highscores) + elseif action == prev_action or action == prev_action_repeat then + if game_state["selected_colour"] > 1 then + game_state["selected_colour"] = game_state["selected_colour"] - 1 + else + game_state["selected_colour"] = NUM_COLOURS + end + redraw_game(game_state, highscores) + elseif action == rb.actions.PLA_CANCEL then + app_menu() + end + until action == rb.actions.PLA_EXIT + --This is executed if the user presses PLA_EXIT + rb.splash(1, "Saving game...") --Will stay on screen till the app exits + save_game(game_state,SAVE_FILE) + end +end diff --git a/docs/CREDITS b/docs/CREDITS index 81a93d651f..b14847b129 100644 --- a/docs/CREDITS +++ b/docs/CREDITS @@ -657,6 +657,7 @@ Adam Sampson William Wilgus Igor Skochinsky Sebastiano Pistore +Stefan Schneider-Kennedy The libmad team The 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 0000000000000000000000000000000000000000..927880a00c890d0c431dfdf2a723c5b4cc3a06c8 GIT binary patch literal 1181 zcmV;O1Y-M%P)+s2!R3eEltKX!^r?Ozll4!tbVON;Z_6FdzT|05 z@0>`cF6L@feJOLCp(sI^M=p`Iy$BH61?o!cIxmY=-FHdPh4r z5~nFWfDi--z&+rEOXN-EF%vodzFk)f!+AFoET^BWHuL~O5Fh{o00JNYw6vieEu&n? z$jRU5p|y7XIMGgIE*G(R_oH?AK8n}M?=I*8gdjiw1ONm;0BC7Lwda$M={#61qs~Xl z$(;x5iPo&6q{hmh#Fp`$aKZan?7rBl+SR(DaXRin0ItE_ky9uLLEHlX0T2KXU}!c} z=Qs~j<t<{9cX`stcWoVy7gX||MJ%A7d2!H^900;o> z7=3-tjOJ2Kg_LvQrIV9e+VfU2$0?oh_)eHh`NE{BdEHRCd-5PwhVrWy*`)LULJ%MT z0ssOG42C58$G#}Lb1slu$4m9<%SSaoQjS)ud7yGR&q(GI<=cr4V)t}sp4@r6RyA3( zp^^HJ^!r6ugdpw#fB*;p2!H@^o>8OJT^D4MQc`1B#qhlxRktTTwc}Jn?O|-q+HPh( zd>|W22m%B^06+i)fO9tVo5~%^XLoesQ)LA8dyJaAUCT&hPS7@>+uQ>mY_Oq(AV7d? zfg#BYaE#~ygdi}6AelRq?+21kwd;i9TqP8@YvVb|X`n9NcXFJ{T*}1f)I5hZnn&~i zLJ%MT0ssOa0JLKi$_K-p^roF2C(j)X=$~jBiWzK2fq0A--lvhUUiOdTUY7e zZ&Wu3veSRN^tG4P&zqpw9Hq}y0kRIQpMQS4x}pCN%TD!>t&^Q=wbFIh^fHv^kkwQ- z&}+P&An)wi>EBWystZbadH%uo(Ibl?$k*4^sJa1O{;uiN#F9>}14Z5(@`CmCk{BDk zcyJ&1{ObTU2&#?S6yK`#?XjYG;kP^ZkmJ%#i{{P@7inLu-(A{aMjlfL@|AG>#x`f~ v7IlqsR*Yg5S$+SgJ9shjT-_}i;D6^Yow9o%#gB+A00000NkvXXu0mjf`+yk| literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-128x160x16.png b/manual/plugins/images/ss-pixelpainter-128x160x16.png new file mode 100644 index 0000000000000000000000000000000000000000..125c6f652b9c641a3b5a7146771ab315c4a2eceb GIT binary patch literal 1139 zcmeAS@N?(olHy`uVBq!ia0vp^4M4nrg9%7_fB3u#NO2Z;L>4nJ@ErkR#;MwT(hLkN zs-7;6Ar-gY&Q+Xt%R%CJ`@Rkj7yf{ai;S2AJ0mu-ueiaiVBz4}*YJW-Kqi5eeMLlL zPlRESt1EwlgIh?*m%Sz}@ArQY{5EmJ{`oH!JC_N4O14Xzl&`k<)$i#A-A_JV-TJp= z;_Rp0I>x7;zg`+C7hd-F>G_?J|1=MTIx;W`FmO09C_G4>#PiHUJox@M{zdllYPa66 z*wo!E@b=$63G) z1{&G$uVCjMh3BP}t*6dw#v9%LXz^V53IDfW_U|g$7X4Fli(*jZ5NKckyRBAnXGHy- zoA<+4{0g;?UHg5f@K5`~qn@_W36?f{bj9Bg=tFGH1&;n7<(V%TGqw-KzjMC3H zv+u87_j~nEmdDn+GB4b^oG-+#v3+ar}D=FcBm_wS!R zwzF@7RX&PBL%s<=?UU~1+<3N5cE#7?E5%jb&#i1;?cDdOYOl+aulEB{WGLPrAzj5ge2Zb0gOu=y&)9CVa|8b|k zop<}gK39m&Tou3Oy8im#_Fb>PD*bi0Wsg|TzpJ=b&-CAkG?0_v@zK0TUSa$DpWUU}y>CSjeS#SKn6WrN`IhUX!=f-K|-zx4!)E|Fy;6ude#J zVM%<*Z~5I{?|0An`^+<+)51pL^VaWguCMpMUn*bicy%BD=Qkn8&qW=7Kev)+>Wx_c z{f}oY$$NA4-~P1@OPYdW1B%)GQ@*dhEcC4K>f0x|*1lhQ4B6hX z^Q@QV0%yIn4ep0t_TPWY_}oFx)}NaJ5)3acaW6XW%neQD(a-fgoSfa4=_@b+gNMP> L)z4*}Q$iB}s+0g- literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-128x96x16.png b/manual/plugins/images/ss-pixelpainter-128x96x16.png new file mode 100644 index 0000000000000000000000000000000000000000..84819dd52c48b197331d84fb7ba490d45d676fd9 GIT binary patch literal 1065 zcmV+^1lIeBP)u8BNP-G1PtUXN7Gdz*>%#*`CVk|IEw9?@9OH)pG@+r)|@Nq zeCCyu^Iu-c>c$#l+86(L9f$e9HP#eSb}@edeq5iMoZ^1t%v|52tWV?00*1dq1*Jbn}Y_>AS9B(BZ5r71pc}I*_Fch{6TL0<{CI}Z1fCM2y zac^+l$MdLDT7O;o*GTTAew1&od_QBJ{hG@Msd?eoquHvY=OO}-Ab<<{AJDI}Mg$;1 zXD&g3883M}LsB)3Z#ounXQB?Le31N%iGRD+92mtj{&P)lWL}Uw#ojTyl)W5@03--N z00aOC7+pt9T}R4Tl3o+2N5-1A*SqFA;)IA&j(wUEd}KPdrK{J=dA#;UR;KG1l4fbr;7M{elkQdAQ>csAQ_2ce1+d>HLw1am!&xRKr*fleN>@CB|}CJHG?1- z6!7*$k?#YJs+fIU>)x4>L68iRL9nz906M{;lc8$zY$^t;xK$Oq{3TA#z9_YTP59@> zG(|E9l0hz&)CHjY$;cqmj11^|gUsqK8Z5Uy={;Y}s2K#wAQ=S7pn&^B8sp_w$ZR&1 zwdP95>|BA=s1N;-9;PWRIF5h1%Io?ISG!z~ ziBKj6O@^%zV^0imoe%ojMy-W*AJ3$o;+sTvfO#9sg^xh5x|w?8)>R@@xGHA zsk)i)=aW(#g|7Hqw%?&NfRACz=9C3u4otWjD}B^p1C0!;4z5)N#_Rr-yyUMUG<|g7 z1j$GoEe@PG&s-N_z$z1~)?f}?-(3OX5BM=HO;gLZ8W1j||IwJBDBw{!%6Xh~Dl8f&Fqo!- z+aboU9pc~oh*1t!Q*_{jkAUj{)diUad54MXmOYK?#5L3ctBo-8`C{U4$R8JclxmU| z&xM>H)FA0*!UsJEZsX=t6)S!$u}DU2I|M?fTX%$|ZAS;Ln?4)f9qASsBlmVMTr}*s zmP@$d38w)4@oCVF2cHa@tT)&}?csg(9p(oCua7>AvN9#^lcu_va60hjUwUhUy_eM? zOjD+|{-bd%h^zhQ?A;NzLwwsI{zKcjNyTa^A&m8Bq%5~`4*XerBATw8h1^EAd=V$l zc5*NGR;wdSt|>O23=TYDG8h>ogCH3sgYd;)tn6$sidA6M00000NkvXXu0mjf#R1(4 literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-160x128x16.png b/manual/plugins/images/ss-pixelpainter-160x128x16.png new file mode 100644 index 0000000000000000000000000000000000000000..f5c20ef95d377a71ad231abca68b81961a023dc0 GIT binary patch literal 1158 zcmeAS@N?(olHy`uVBq!ia0vp^3xK$Rg9%77Gk)~}Qk(@Ik;M!Qd`Cc-ajG_-Gy?;R zg{O;SNX4zUa~Brfb`WvBF3arLT<75ExUivNL8GJHf&&Hz4s2N9Fh4;eLm)t60zU_b zfe1&0!~}m04iOHG4@WLXyx&!~^39qz#mg>#%n@?^bTayNR`}|J{p~hiW-xJ@9*KFG!fB)>;x8fIbb+M!%Y60EGof3K zcb=YU+ixZQ_~7EqmsWbmni&r^BmiB}xH+Rj{*}x;uC{%l1#@qI{~C7i+~QaJqVw0T z_}x=%IKT4C)#>G-ef_NS4<1PWU|Dc=mwNL2tCA}cl7tjMZfj#UcyZb)QY-%1=Dcn4 zu6}FP&!_#~Q&IBt@!jXg+BZ9}LfrMiuwbPT=am3s1vdKi`vO_39|&iqbVs zTa9M#eeF8^;lozAZR_O#*|ieEzPa%^twe>b(aCU26l{OUXFKdS2g%gO4M;S1;a%Pc?B&BoIF zW>F>gqzC1mtl*GtyKHrG(&ol@$CN6LE#Y&pWnZ~R;PcF*-}SZc|JZm(-t_#RXCzqyMb6d-=F1)aAp0&#H%}49Bp6p7vxZSTba>}FH1x*hHM9)M|V49dN z&eZhVceDHH+3WY2{5Dat2s}RbhHF85x3zRy?(Vm4EX!TLz1mc{`^1O;K5JxGh|YRq z6)dY*ylqDR+H1LCl_9q5C+D6%`t9nD^;UYOYx9c#-Pyx+?^nIM{$9TAH|4i}U-(+(wE>ThXT|E8{Tq-{ebbBDuA{^8A)} zdTP!~e=FH~Hd{72aE6}M%aSW^WEu_pmuz2TAUY@M-dPvN^j*`P1b>xzJb< zyCdf0t4PQ6f3Gh^i!T+t^L&59HI90-GuQr`P4yL@Thf2`&(}q^7q+~cTt26uRra;( z>YvB%6$bCLn8dD>^)k1)%#6jzjQW(k~=82 z9SkZaK0dhf^Uj?`u}}Lxt#(?QtonJ&t-j)WrQzSrC!-_`t$GH=?|E}_`8m3Q1q*|x LtDnm{r-UW|)gmc< literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-176x132x16.png b/manual/plugins/images/ss-pixelpainter-176x132x16.png new file mode 100644 index 0000000000000000000000000000000000000000..06cdaf6d9d5e2b58d7af5c7f6027af8fc608f68c GIT binary patch literal 1314 zcmeAS@N?(olHy`uVBq!ia0vp^8-Tclg9%9Pw=>%Wq&N#aB8wRq_>O=u<5X=vX$A(C zE1oWnAr-gY&RrNL9VpP&KCeStu(pAVrO5TjDGnC?28S>OC2g&D$}S-af*oEg`~iZ3 zJDE0aV!Fugpb)O0RkYxC%0E?Mrj*0q=gv7?6j?U?iM?@o+s@+e`|gT-lRH!Nw69-n zskNQvpXWOL`gcA)Iz#*IqU2%$lG7j&5uAXKPSk zaAas=U}5B75@6XQ)%<Nm(`mt6?U1`btzUK+^&zh_NduS0)g|yl2Ia1uL znC!Xq)!`Yr|3l?No=S(rI)!%F99;Q-&iC#AYO^n1H=6(J``pf2t52!n`yAJD9dv>R zBFMcPf?GZdX!V7c3T6F2lM&zk$>)jmk^N!al6VcqLi=r+hN5%zJs+!SL-A%R&l#-nV)9sn-(Tx8o_ZO z70KEgwPxFHgde@X*!!aX!&B$wZ%njb^KEv(S4p*Nkt=GN@0>SY{i}0g{jQ_eI+tjQ zp<5_@+Iht;#;tLOkNo03Sz!EHJZ10E&Uo>lYjv@%g|fT%MXgVqb|%H#2Na%2F=_2; z!LRsRo5M>mF@0iKK&j}}-WSq^Z?2oK*e8-TpSx7{=K7?FWJQMQ;9!NiHtCHifBfJ1 zPiES$yQX}lE=KMyr_K0ek z-JbUF_RrO}C-%*~y|nX7Tb4)jmYSZ{7wrMFrnyL+?=ngHb3Y|@g4MpGsX59oKQ_l0 zh`#)IwPe<`Z=9KT(+{M62{q3Y)=PWme(M^OW6sO=y~p&|Eo@6YzHzygirZ_35E=c? zjEnLYpVD4Y<6!bG{IB+6m*3%9=j897SC`e1(b?VK_c+gRWv7bX;_1TOe8wM2`n0Eq zKI&61ROrzC^NGqVvzB1zhxF+pITkm|E;xoNf4B>Mb+N#p>pXHj!(L zdLwT43zWUPTeB_TwL8nEx<_hZ{{_!RNBIBpOi)BBD-<|@g&e~Tqkl|s V-mPyQR>4nJ@ErkR#;MwT(hLl& zvYsxEAr-gY-d$L9J3^$ru=2Qrfdc=A1q&`H02x45LZV}1;z1_+riV_=b`B153ltOr z6dX1j=noLk5ZIuQoA%n|m-W6fndSTQjbuYFy@>OSU$^A)Gq*4(I&cUuL8pcD3qPZg_zyDj72?3!%2FAvo>K;DLWx5=CB-b@mj z6@OTyb7IV?UpsVj7X1|}TK3qacO$=Hi@d+_IhIUp`u^T+e`uQ)yZw9Iv6r07)fFFC z76wP1RrcFt@!MJ^(T+3m@uwSQF4s)aJyyWblf1)3E%bOt4o}_jplcg%xJU23|8;8D z#FwUgg|cN9e2hOly;k)JRw*Mz6(ls76J&%dpNlI>S;fAqd3WH3fbHsZ+dMUM2Zx<#qYsgZ-OdKYY70;C3}K`Zg|S!uy0}rulD@;BNEL_g+2g z$d@k>8?A+&2lR+*2?_7+MM+6XsaN&* zOn2j`NCC$ZJO?l^G;DX-e{9wzwRgXEJX`p(eSYulUH>H`niX9Be~I6obokU+i5<4J zGpB3I{VC!#`+q9pXX@XlcVAtOU30uN=JOr9Fa9RxHBZvF{;oFqSGRQ4s{cRFzdBy@ zHKX!r-c@`3De<4$tM?QgIG(%q{++)!96Cx{BPNUHmwh=DxV(JUubJET?|=P%y1dwg z))n*R*ydIL-}bL7*#7vQ-8=v7RbLgx_P=g7jbUU+h<+j!EIsMehkUm`^47BpcjuHB-iQdva^7cZ{WAA@ z=qJU1UpG0y1q)I>j!yq`?A-JFldsml=_rmo3=iITN#-BR3}D3&QmEY%`_IVytEeb* T%HMmy(v!i{)z4*}Q$iB}-XcN0 literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-220x176x16.png b/manual/plugins/images/ss-pixelpainter-220x176x16.png new file mode 100644 index 0000000000000000000000000000000000000000..a92eefeba86e50acd46ddceaeb0269654339efc2 GIT binary patch literal 1563 zcmZ8hc{tR07$3PS$Jj`0J1DKBvCeTsekfNnZi7MON(tp0Oj+xUWn)B)V`z+{1{<3z zl3_x4B)5s&Y8dA;7{-p&KD+zJ`+fg-pZEK|@8|P*-$V<9k-$-jqaYASz{J?litWeQ zR>^mS{eBV5WUw87fU!d$2qZ{8Fpi81f|4K*cejZlU=udFM8$`U8H@8+1e-^>>Oouq zJv&Kzb-{OWn)kWNH zlbEC_e*opATXZ%eVmmd8)^gjC_e>x4L#12{LLX=5R6djPM#x67+_W7-CF1*LYo{VSvB5{nxnLUIjpJW%dwJoxA ztgXpSIi{#=2c{60So`WV*qi4yxy&mUD;|q*o)-Q-cFg`ZF6}wG*$Nsi>0KWjrwrY! zi{&;;U%M?%oV_~VU!8rk{_O}n``K{Z??0@k5$9hnUVKR3>_Tf54q|>|4=jZh#~6&* zqs$VQCBFhW16#R)I#ZmRo$|gt@$OiwYWgodoXKm3EjCj*sV)S`yLMra!iJsA^l z91%!fzch4(L@;Cs{DWd8JbqQv6TL}OAQ^>A^awMqBnN`aeVqDK-*x1jKdgMnUgd40e)E^H#H>huYuc#&l-U~VkBK)Z@)CrX)?CC7H!$0y$|L?Bu*34!40Q)~t8 zdyQky-zPM_Dl0FO$W^TVHU{sz6Dajl8n@2(vC%yc)JjQTXx?0?%g#^J<(0FO4vwm7 zV)E{#>pO69L_4=x4jWZWxNTSwkHQ5t#YW3+)m`Xl|GW_4vB>|FC55Fm+==X9pnSL$ z2SF5Dxs-1IbBprRJZYbf!xveP(0@*q)RKpAF_NN0Z9CiNgzq~sOzDF)jw%HA5TS_d$cIRAH3enOw9X z##y}0jn?(ZzK0gRaRmk)cWh>c=a+@=JwT?Sw@l65>rRjdkkwdUV6Vv8$+t8Bik*3_ zvl^-zllH2MB-A@n`GT|qfgDqo$37FdzKdVqYI2VgcpW@JJI!9(78ThXho`*c$T)~C znaN8TK*ppAP_!hE6CjUoE#G&+cv71&Ra-3NCqkw_PW+{guS>jy>$skBp7fx8zAKYL zn~fX-8pm>~9IX+N;)4VUck=+GK`FDinzv#|6_>Q3*;lXY%z*nasM`gaXA{i8js6Nu z6dXST7VxHuc1x#b7_`F}k!Q(%BA2%Kt;|d;{hPM7<9kAaO6g7hXXx!(2HDr}9t)hF z1KQ6VNSM$z1fv@-!d}7SLBj!CxID#j4)@%=Ds->)oMpP$LV{?c9HOcm+8Ryh-w>NV zht--6e~P|3TRpmo$#Q0^n9{)V96+9S7++S+wdWebtk=#3R?Vd*<&O>J2@Ow+J2BwP zEt&Gv-0N*6F!|VHvZ=IAWIdh0+ub&~`<&_L$xtDndU=$Ve1Y;7=a2{B_bRfIAzvgF z(V|^bj;3+C*3iwNTq@&+{d%2^*hy3M#nOm+de7xS?jnCGEW(I=D2-X12z2R%zUYu* Q_O%C@TtOI;^j)L>1_}7|vH$=8 literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-240x320x16.png b/manual/plugins/images/ss-pixelpainter-240x320x16.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb7c4f7a62a327b4aaf7d445fcef42af25139e8 GIT binary patch literal 1686 zcmeAS@N?(olHy`uVBq!ia0vp^9~c-I9XOakB3)@LK#H@#BeIx*f$sXV^_8Br_7$OMR)(-xSDnE{{z=ep(_y@ zQ7v-3aN+2ui;K%t4c&llSk zrDfbHYCsM&Q22qJd3b@_o!XL6R_{F~9R4osOUKX3O(#C@iDG$d!K?Q@ zN9v88)qVr#$J3Zm{l?~~42}`DDZ28#VJ+wOv1vQKe&79MZgcwX>!BZ5zP-5l?s&!? z)4jf%P88P(u1UHZ$3Fcw9_Jf%%GK`EY4TM*EV}TZ*U?96H)~5w`T6o@E#BR7@z;%; z*H>>`_rSG)b@8jTlct7n2SHo`bQ>&*?3yztx9P3M;#!6C=JVa9z9dPlX*^SQ)MwsX zxszsjvTtv`oA`D6lNbJ9J)T=eWoNY=(`5%o157_WWJ_kcXgeuymS1ek@y~t3kAgi{ zr`}w-n@8;OwfISeA)o&pcRRnBU*1rj$1>VyH_x#gd?uNkw43mCdV>6kB;)t>IbS~f zdQtfHXlg;uwkKbMi4UTG-PGFAbn)LtMD#pSI=Nmp>*E z*DPk6Df24dV%Zw8?zjBzymd|GePuqqD#>v^lMVfwlV^8lTsU~@_S@22xF=!p4-a`B z^*gmDl`E>Ytl1c)c3R4R@mjCu)|IPYNY6RvwoB__(ya$`XNMX*%h12(DwtzdT^FUA ze)#ZfoOywP;lOH3&8|hdT}9XTzpbuycx#_)so8w}{r_~!BMkqlSO1pZp3iVz-nk?! zzkPf01RJxP<{+w>jc=!y7ALg#F5L0otNKMs;fKu1<;EqI^~*B<*FXKJ^5sj*md_`q z?jirjd1Ki2Y7 z=EKAbPv>^-d{f?9CRhKVH}3rDux}l2RO?;jea={)4?17-{+_D#kF{qmod2Wyc0=aN zm#gA(o@syIeOX#r?TY7OWN5fve(B%O+plYiwg`VNSoC%g`1(x=L>Ehuk-Z)Rx}Kru6{1-oD!M<-%|9o literal 0 HcmV?d00001 diff --git a/manual/plugins/images/ss-pixelpainter-240x400x16.png b/manual/plugins/images/ss-pixelpainter-240x400x16.png new file mode 100644 index 0000000000000000000000000000000000000000..08c467c131b567404e81b2ddf3e3118fdcdb9e6f GIT binary patch literal 1838 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 R^nL&U002ovPDHLkV1miNy}AGZ literal 0 HcmV?d00001 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% \opt{lcd_bitmap}{\input{plugins/pegbox.tex}} +\opt{lcd_color}{\opt{large_plugin_buffer}{\input{plugins/pixelpainter.tex}}} + \opt{lcd_bitmap}{\input{plugins/pong.tex}} \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 @@ +\subsection{Pixel Painter} +\screenshot{plugins/images/ss-pixelpainter}{Pixel Painter}{img:pixelpainter} +This game is written in LUA and based on the game of the same name by +Pavel Bakhilau (\url{http://js1k.com/2010-first/demo/453}). + +Select a colour to flood-fill the board with that colour, starting from the +top-left pixel (meaning that any pixel which is connected to the top-left +through other pixels of the same colour will be changed to the selected colour). +Try to paint the entire board with as few moves as possible. + +\begin{btnmap} + \ifnum\dapdisplaywidth<\dapdisplayheight + \PluginLeft{} / \PluginRight + \else + \PluginUp{} / \PluginDown + \fi + & Move colour selector\\ + + \PluginSelect + & Fill screen with selected colour\\ + + \PluginCancel, \PluginExit + & Enter game menu\\ +\end{btnmap}