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
|
@ -72,6 +72,7 @@ pegbox,games
|
|||
periodic_table,apps
|
||||
pictureflow,demos
|
||||
pitch_detector,apps
|
||||
pixel-painter,games
|
||||
plasma,demos
|
||||
png,viewers
|
||||
gif,viewers
|
||||
|
|
|
@ -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
|
||||
|
|
701
apps/plugins/pixel-painter.lua
Normal file
|
@ -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
|
|
@ -657,6 +657,7 @@ Adam Sampson
|
|||
William Wilgus
|
||||
Igor Skochinsky
|
||||
Sebastiano Pistore
|
||||
Stefan Schneider-Kennedy
|
||||
|
||||
The libmad team
|
||||
The wavpack team
|
||||
|
|
BIN
manual/plugins/images/ss-pixelpainter-128x128x16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
manual/plugins/images/ss-pixelpainter-128x160x16.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
manual/plugins/images/ss-pixelpainter-128x96x16.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
manual/plugins/images/ss-pixelpainter-132x80x16.png
Normal file
After Width: | Height: | Size: 1 KiB |
BIN
manual/plugins/images/ss-pixelpainter-160x128x16.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
manual/plugins/images/ss-pixelpainter-176x132x16.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
manual/plugins/images/ss-pixelpainter-176x220x16.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
manual/plugins/images/ss-pixelpainter-220x176x16.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
manual/plugins/images/ss-pixelpainter-240x320x16.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
manual/plugins/images/ss-pixelpainter-240x400x16.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
manual/plugins/images/ss-pixelpainter-320x240x16.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
manual/plugins/images/ss-pixelpainter-320x240x24.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
manual/plugins/images/ss-pixelpainter-96x96x16.png
Normal file
After Width: | Height: | Size: 995 B |
|
@ -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}}
|
||||
|
|
24
manual/plugins/pixelpainter.tex
Normal file
|
@ -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}
|