mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-10-14 02:27:39 -04:00
New plugin: Picross
Picross is a puzzle game also known as Picture Crossword, Nonograms, or Paint By Numbers. See http://en.wikipedia.org/wiki/Nonogram for information on how to play. Update 1: nicer graphics with less images, fixed directory listing, changed how the board works to make a lot of math more sane Update 2: added missing rb.yield to viewPicture loop Update 3: you can now save a game in progress Update 4: fixed a file pointer leak, improved the numbers font Update 5: no images, use vector num draw library add zoom, freedraw -- Bilgus Change-Id: Idc476b46b6eaa10818400fa789701d5bac83467f
This commit is contained in:
parent
3ae48284c1
commit
2c7e47fc12
8 changed files with 872 additions and 0 deletions
|
@ -82,6 +82,7 @@ pacbox,games
|
||||||
pdbox,viewers
|
pdbox,viewers
|
||||||
pegbox,games
|
pegbox,games
|
||||||
periodic_table,apps
|
periodic_table,apps
|
||||||
|
picross,games
|
||||||
pictureflow,demos
|
pictureflow,demos
|
||||||
pitch_detector,apps
|
pitch_detector,apps
|
||||||
pitch_screen,viewers
|
pitch_screen,viewers
|
||||||
|
|
|
@ -116,6 +116,7 @@ metronome.c
|
||||||
/* Lua needs at least 160 KB to work in */
|
/* Lua needs at least 160 KB to work in */
|
||||||
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
||||||
boomshine.lua
|
boomshine.lua
|
||||||
|
picross.lua
|
||||||
lua_scripts.lua
|
lua_scripts.lua
|
||||||
#ifdef HAVE_LCD_COLOR
|
#ifdef HAVE_LCD_COLOR
|
||||||
pixel-painter.lua
|
pixel-painter.lua
|
||||||
|
|
|
@ -87,4 +87,5 @@ mpegplayer
|
||||||
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
#if PLUGIN_BUFFER_SIZE >= 0x80000
|
||||||
lua
|
lua
|
||||||
lua_scripts
|
lua_scripts
|
||||||
|
picross
|
||||||
#endif
|
#endif
|
||||||
|
|
820
apps/plugins/picross.lua
Normal file
820
apps/plugins/picross.lua
Normal file
|
@ -0,0 +1,820 @@
|
||||||
|
--[[
|
||||||
|
__________ __ ___.
|
||||||
|
Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
\/ \/ \/ \/ \/
|
||||||
|
|
||||||
|
Port of Picross (aka. Picture Crossword, Nonograms, Paint By Numbers)
|
||||||
|
Copyright (c) 2012 by Nathan Korth
|
||||||
|
|
||||||
|
See http://en.wikipedia.org/wiki/Nonogram for details on how to play, and
|
||||||
|
http://nkorth.com/picross for more puzzles.
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
require "actions"
|
||||||
|
require "luadir"
|
||||||
|
require("rbsettings")
|
||||||
|
require("settings")
|
||||||
|
|
||||||
|
local _nums = require("draw_num")
|
||||||
|
local _clr = require("color") -- clrset, clrinc provides device independent colors
|
||||||
|
local _lcd = require("lcd") -- lcd helper functions
|
||||||
|
|
||||||
|
local plugindir = rb.PLUGIN_GAMES_DATA_DIR
|
||||||
|
local userdir = plugindir .. "/picross"
|
||||||
|
|
||||||
|
local wrap = rb.settings.read('global_settings', rb.system.global_settings.list_wraparound)
|
||||||
|
wrap = (wrap or 1) == 1
|
||||||
|
|
||||||
|
do -- free up some ram by removing items we don't need
|
||||||
|
local function strip_functions(t, ...)
|
||||||
|
local t_keep = {...}
|
||||||
|
local keep
|
||||||
|
for key, val in pairs(t) do
|
||||||
|
keep = false
|
||||||
|
for _, v in ipairs(t_keep) do
|
||||||
|
if string.find (key, v) then
|
||||||
|
keep = true; break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if keep ~= true then
|
||||||
|
t[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
strip_functions(rb.actions, "PLA_", "TOUCHSCREEN", "_NONE")
|
||||||
|
rb.contexts = nil
|
||||||
|
|
||||||
|
_clr.inc = nil
|
||||||
|
rb.metadata = nil -- remove metadata settings
|
||||||
|
rb.system = nil -- remove system settings
|
||||||
|
rb.settings = nil --remove setting read/write fns
|
||||||
|
end
|
||||||
|
|
||||||
|
--colors for fg/bg ------------------------
|
||||||
|
local WHITE = _clr.set(-1, 255, 255, 255)
|
||||||
|
local BLACK = _clr.set(0, 0, 0, 0)
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- set colors on color targets
|
||||||
|
if rb.lcd_rgbpack ~= nil then
|
||||||
|
rb.lcd_set_background(rb.lcd_rgbpack(255, 255, 255))
|
||||||
|
rb.lcd_set_foreground(rb.lcd_rgbpack(0, 0, 0))
|
||||||
|
end
|
||||||
|
|
||||||
|
local TEXT_COLOR = BLACK
|
||||||
|
|
||||||
|
if rb.LCD_DEPTH == 2 then TEXT_COLOR = bit.bnot(TEXT_COLOR) end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
-- load images
|
||||||
|
local img_numbers = rb.read_bmp_file(rb.current_path().."picross_numbers.bmp")
|
||||||
|
|
||||||
|
-- image helper function
|
||||||
|
function draw_image(img, x, y, w, h, tilenum)
|
||||||
|
|
||||||
|
local func = rb.lcd_bitmap_transparent_part
|
||||||
|
or rb.lcd_bitmap_part -- Fallback version for grayscale targets
|
||||||
|
or rb.lcd_mono_bitmap_part -- Fallback version for mono targets
|
||||||
|
|
||||||
|
func(img, 0, (tilenum * h), w, x, y, w, h)
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
function draw_number(x, y, w, tilenum, scale)
|
||||||
|
scale = scale or 1
|
||||||
|
_nums.print(_LCD, tilenum, x, y, w, TEXT_COLOR, nil, nil, true, scale, scale)
|
||||||
|
end
|
||||||
|
|
||||||
|
function showComplete(self)
|
||||||
|
if self:isComplete() then
|
||||||
|
rb.splash(rb.HZ * 2, "Puzzle complete!")
|
||||||
|
self:saveGame()
|
||||||
|
self.puzzleh = 50
|
||||||
|
self.puzzlew = 50
|
||||||
|
local old_boardh, old_boardw = self.boardh, self.boardw
|
||||||
|
local old_numbersw, old_numbersh = self.numbersw, self.numbersh
|
||||||
|
while self.numbersh > 0 do -- remove the number rows
|
||||||
|
table.remove (self.board, 1)
|
||||||
|
self.numbersh = self.numbersh - 1
|
||||||
|
end
|
||||||
|
self.numbersh = 0
|
||||||
|
self.numbersw = 0
|
||||||
|
self.solution = nil
|
||||||
|
self.boardh = self.puzzleh
|
||||||
|
self.boardw = self.puzzlew
|
||||||
|
self.freedraw = 0
|
||||||
|
-- find a free number
|
||||||
|
while rb.file_exists(string.format("%s/user_freedraw_%d.picross",
|
||||||
|
userdir, self.freedraw)) do
|
||||||
|
self.freedraw = self.freedraw + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
for r = 1, self.boardh do
|
||||||
|
local old_row = self.board[r] or {}
|
||||||
|
|
||||||
|
self.board[r] = {}
|
||||||
|
-- copy over the last drawing
|
||||||
|
for c = 1, self.boardw do
|
||||||
|
local ch = old_row[c + old_numbersw]
|
||||||
|
if not ch or ch ~= '*' then
|
||||||
|
self.board[r][c] = '.'
|
||||||
|
else
|
||||||
|
self.board[r][c] = '*'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
rb.splash(rb.HZ * 3, "Free Draw!")
|
||||||
|
self.cursor.x = 1
|
||||||
|
self.cursor.y = 1
|
||||||
|
self.showComplete = function() end --show once, then remove the reference
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local State = {
|
||||||
|
puzzlew = 0,
|
||||||
|
puzzleh = 0,
|
||||||
|
numbersw = 0,
|
||||||
|
numbersh = 0,
|
||||||
|
boardw = 0,
|
||||||
|
boardh = 0,
|
||||||
|
board = {},
|
||||||
|
solution = {},
|
||||||
|
filename = '',
|
||||||
|
cursor = {x = 0, y = 0},
|
||||||
|
scale = 1,
|
||||||
|
freedraw = false
|
||||||
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
|
||||||
|
Notes on how puzzles work in the code:
|
||||||
|
|
||||||
|
The "board" array is bigger than the actual puzzle, so the numbers
|
||||||
|
above and to the left of it can be stored and do not need to be recalculated
|
||||||
|
for every draw. (The "solution" array is the same size as the puzzle, however.)
|
||||||
|
The various width/height variables help keep track of where everything is.
|
||||||
|
(they should be fairly self-explanatory)
|
||||||
|
|
||||||
|
The width/height of the numbers section is always half the width/height of the
|
||||||
|
puzzle. For odd-number-sized puzzles, the value must be rounded up. This is
|
||||||
|
because strings of squares must have at least one blank space in between. For
|
||||||
|
example, on a board 5 wide, the maximum set of row numbers is "1 1 1".
|
||||||
|
|
||||||
|
Here are the values used in the "board" array:
|
||||||
|
' ': empty space
|
||||||
|
'.': unfilled square
|
||||||
|
'*': filled square
|
||||||
|
[number]: number (not a string)
|
||||||
|
|
||||||
|
The .picross puzzle files are text files which look pretty much the same as the
|
||||||
|
"board" array, with two differences:
|
||||||
|
|
||||||
|
- puzzle files should not contain numbers, because they will be generated
|
||||||
|
based on the puzzle at runtime
|
||||||
|
- blank lines and lines starting with '#' are ignored
|
||||||
|
|
||||||
|
]]--
|
||||||
|
|
||||||
|
function State:initBoard()
|
||||||
|
if self.freedraw then
|
||||||
|
-- clear board (set the puzzle area to '.' and everything else to ' ')
|
||||||
|
self.board = {}
|
||||||
|
for r = 1,self.boardh do
|
||||||
|
self.board[r] = {}
|
||||||
|
for c = 1,self.boardw do
|
||||||
|
if r > self.numbersh and c > self.numbersw then
|
||||||
|
self.board[r][c] = '.'
|
||||||
|
else
|
||||||
|
self.board[r][c] = ' '
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset cursor
|
||||||
|
self.cursor.x = 1
|
||||||
|
self.cursor.y = 1
|
||||||
|
return
|
||||||
|
end --freedraw
|
||||||
|
|
||||||
|
-- metrics
|
||||||
|
self.puzzleh = #self.solution
|
||||||
|
self.puzzlew = #self.solution[1]
|
||||||
|
self.numbersh = math.floor(self.puzzleh / 2) + 1
|
||||||
|
self.numbersw = math.floor(self.puzzlew / 2) + 1
|
||||||
|
self.boardh = self.puzzleh + self.numbersh
|
||||||
|
self.boardw = self.puzzlew + self.numbersw
|
||||||
|
self.showComplete = showComplete
|
||||||
|
|
||||||
|
-- clear board (set the puzzle area to '.' and everything else to ' ')
|
||||||
|
self.board = {}
|
||||||
|
for r = 1,self.boardh do
|
||||||
|
self.board[r] = {}
|
||||||
|
for c = 1,self.boardw do
|
||||||
|
if r > self.numbersh and c > self.numbersw then
|
||||||
|
self.board[r][c] = '.'
|
||||||
|
else
|
||||||
|
self.board[r][c] = ' '
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- reset cursor
|
||||||
|
self.cursor.x = self.numbersw + 1
|
||||||
|
self.cursor.y = self.numbersh + 1
|
||||||
|
|
||||||
|
-- calculate row numbers
|
||||||
|
local rownums = {}
|
||||||
|
for r = 1,self.puzzleh do
|
||||||
|
rownums[r] = {}
|
||||||
|
local count = 0
|
||||||
|
for c = 1,self.puzzlew do
|
||||||
|
if self.solution[r][c] == '*' then
|
||||||
|
-- filled square
|
||||||
|
count = count + 1
|
||||||
|
else
|
||||||
|
-- empty square
|
||||||
|
if count > 0 then
|
||||||
|
table.insert(rownums[r], count)
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- if there were no empty squares
|
||||||
|
if count > 0 then
|
||||||
|
table.insert(rownums[r], count)
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- calculate column numbers
|
||||||
|
local columnnums = {}
|
||||||
|
for c = 1,self.puzzlew do
|
||||||
|
columnnums[c] = {}
|
||||||
|
local count = 0
|
||||||
|
for r = 1,self.puzzleh do
|
||||||
|
if self.solution[r][c] == '*' then
|
||||||
|
-- filled square
|
||||||
|
count = count + 1
|
||||||
|
else
|
||||||
|
-- empty square
|
||||||
|
if count > 0 then
|
||||||
|
table.insert(columnnums[c], count)
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- if there were no empty squares
|
||||||
|
if count > 0 then
|
||||||
|
table.insert(columnnums[c], count)
|
||||||
|
count = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add row numbers to board
|
||||||
|
for r = 1,self.puzzleh do
|
||||||
|
for i,num in ipairs(rownums[r]) do
|
||||||
|
self.board[self.numbersh + r][self.numbersw - #rownums[r] + i] = num
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add column numbers to board
|
||||||
|
for c = 1,self.puzzlew do
|
||||||
|
for i,num in ipairs(columnnums[c]) do
|
||||||
|
self.board[self.numbersh - #columnnums[c] + i][self.numbersw + c] = num
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:saveGame()
|
||||||
|
local file
|
||||||
|
local boardw, boardh = self.boardw, self.boardh
|
||||||
|
|
||||||
|
if self.freedraw then
|
||||||
|
self.filename = string.format("%s/user_freedraw_%d.picross", userdir, self.freedraw)
|
||||||
|
|
||||||
|
|
||||||
|
--remove blank lines from the end
|
||||||
|
while boardh > 1 and not string.find(table.concat(self.board[boardh]), "\*") do
|
||||||
|
boardh = boardh - 1
|
||||||
|
end
|
||||||
|
--remove blank lines from right
|
||||||
|
local max_w = 0
|
||||||
|
for r = self.numbersh + 1, boardh do
|
||||||
|
for c = max_w + 1, boardw do
|
||||||
|
if self.board[r][c] == '*' then
|
||||||
|
max_w = c
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
boardw = max_w
|
||||||
|
if max_w == 0 then return end--nothing to save
|
||||||
|
|
||||||
|
file = io.open(self.filename, 'w')
|
||||||
|
else
|
||||||
|
file = io.open(plugindir .. '/picross.sav', 'w')
|
||||||
|
end
|
||||||
|
|
||||||
|
if file then
|
||||||
|
file:write("#"..self.filename.."\n")
|
||||||
|
for r = self.numbersh + 1, boardh do
|
||||||
|
for c = self.numbersw + 1, boardw do
|
||||||
|
file:write(self.board[r][c])
|
||||||
|
end
|
||||||
|
file:write("\n")
|
||||||
|
end
|
||||||
|
file:close()
|
||||||
|
if self.freedraw then
|
||||||
|
rb.splash(rb.HZ, "Freedraw saved.")
|
||||||
|
else
|
||||||
|
rb.splash(rb.HZ, "Game saved.")
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
rb.splash(rb.HZ * 2, "Failed to open save file")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:loadSave()
|
||||||
|
local file = io.open(plugindir .. '/picross.sav')
|
||||||
|
if file then
|
||||||
|
-- first line is commented path of original puzzle
|
||||||
|
path = file:read('*l')
|
||||||
|
path = path:sub(2,-1)
|
||||||
|
-- prepare original puzzle
|
||||||
|
if self:loadFile(path) then
|
||||||
|
-- load saved board
|
||||||
|
contents = file:read('*all')
|
||||||
|
file:close()
|
||||||
|
local r = 1
|
||||||
|
for line in contents:gmatch("[^\r\n]+") do
|
||||||
|
local c = 1
|
||||||
|
for char in line:gmatch('.') do
|
||||||
|
self.board[self.numbersh + r][self.numbersw + c] = char
|
||||||
|
c = c + 1
|
||||||
|
end
|
||||||
|
r = r + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:loadDefault()
|
||||||
|
self:loadFile(userdir .. '/picross_default.picross')
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:loadFile(path)
|
||||||
|
local file = io.open(path)
|
||||||
|
if file then
|
||||||
|
self.freedraw = false
|
||||||
|
local board = {}
|
||||||
|
local boardwidth = 0
|
||||||
|
local count = 0
|
||||||
|
contents = file:read('*all')
|
||||||
|
|
||||||
|
for line in contents:gmatch("[^\r\n]+") do
|
||||||
|
count = count + 1
|
||||||
|
-- ignore blank lines and comments
|
||||||
|
if line ~= '' and line:sub(1, 1) ~= '#' then
|
||||||
|
table.insert(board, {}) -- add a new row
|
||||||
|
|
||||||
|
-- ensure all lines are the same width
|
||||||
|
if boardwidth == 0 then
|
||||||
|
boardwidth = #line
|
||||||
|
elseif #line ~= boardwidth then
|
||||||
|
-- a line was the wrong width
|
||||||
|
local err = "Invalid puzzle file!"
|
||||||
|
local msg =
|
||||||
|
string.format("%s (wrong line width ln: %d w: %d)", err, count, #line)
|
||||||
|
rb.splash(rb.HZ * 2, msg)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local pos = 0
|
||||||
|
for char in line:gmatch('.') do
|
||||||
|
pos = pos + 1
|
||||||
|
if char == '*' or char == '.' then
|
||||||
|
table.insert(board[#board], char)
|
||||||
|
else
|
||||||
|
local err = "Invalid puzzle file!"
|
||||||
|
local msg = string.format("%s (invalid character ln: %d '%s' @ %d)",
|
||||||
|
err, count, char, pos)
|
||||||
|
-- invalid character in puzzle area
|
||||||
|
rb.splash(rb.HZ * 2, msg)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- display puzzle comments
|
||||||
|
--rb.splash(rb.HZ, line:sub(2,#line))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #board == 0 then
|
||||||
|
-- empty file
|
||||||
|
rb.splash(rb.HZ * 2, "Invalid puzzle file! (empty)")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
file:close()
|
||||||
|
|
||||||
|
self.solution = board
|
||||||
|
self.filename = path
|
||||||
|
if self.puzzleh < 100 and self.puzzlew < 100 then
|
||||||
|
self:initBoard()
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
-- puzzle too big
|
||||||
|
rb.splash(rb.HZ * 2, "Invalid puzzle file! (too big)")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
-- file open failed
|
||||||
|
rb.splash(rb.HZ * 2, "Failed to open file!")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:drawBoard()
|
||||||
|
local tw, th = 10 * self.scale, 10 * self.scale -- tile width and height (including bottom+right padding)
|
||||||
|
|
||||||
|
local ofsx = rb.LCD_WIDTH/2 - 4 - (self.cursor.x * tw)
|
||||||
|
local ofsy = rb.LCD_HEIGHT/2 - 4 - (self.cursor.y * th)
|
||||||
|
|
||||||
|
rb.lcd_clear_display()
|
||||||
|
|
||||||
|
-- guide lines
|
||||||
|
for r = 0, self.puzzleh do
|
||||||
|
local x1, x2, y =
|
||||||
|
ofsx + tw - 1,
|
||||||
|
ofsx + ((self.boardw + 1) * tw) - 1,
|
||||||
|
ofsy + ((self.numbersh + 1 + r) * th) - 1
|
||||||
|
if r % 5 == 0 or r == self.puzzleh then
|
||||||
|
rb.lcd_hline(x1, x2, y)
|
||||||
|
else
|
||||||
|
for x = x1, x2, 2 do
|
||||||
|
rb.lcd_drawpixel(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for c = 0, self.puzzlew do
|
||||||
|
local x, y1, y2 =
|
||||||
|
ofsx + ((self.numbersw + 1 + c) * tw) - 1,
|
||||||
|
ofsy + th - 1,
|
||||||
|
ofsy + ((self.boardh + 1) * th) - 1
|
||||||
|
if c % 5 == 0 or c == self.puzzlew then
|
||||||
|
rb.lcd_vline(x, y1, y2)
|
||||||
|
else
|
||||||
|
for y = y1,y2, 2 do
|
||||||
|
rb.lcd_drawpixel(x, y)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- cursor
|
||||||
|
local cx, cy = ofsx + (self.cursor.x * tw) - 1, ofsy + (self.cursor.y * th) - 1
|
||||||
|
rb.lcd_drawrect(cx, cy, tw + 1, th + 1)
|
||||||
|
local n_width = tw / self.scale / 2 - 1
|
||||||
|
local xc = (tw - 5 * self.scale) / 2
|
||||||
|
-- tiles
|
||||||
|
for r = 1, self.boardh do
|
||||||
|
for c = 1, self.boardw do
|
||||||
|
local x, y = ofsx + (c * tw) + 1, ofsy + (r * th) + 1
|
||||||
|
|
||||||
|
if self.board[r][c] == '.' then
|
||||||
|
-- unfilled square
|
||||||
|
elseif self.board[r][c] == '*' then
|
||||||
|
-- filled square
|
||||||
|
rb.lcd_fillrect(x, y, tw - 3, th - 3)
|
||||||
|
elseif self.board[r][c] == 'x' then
|
||||||
|
-- eliminated square
|
||||||
|
rb.lcd_drawline(x + 1, y + 1, x + tw - 5, y + th - 5)
|
||||||
|
rb.lcd_drawline(x + tw - 5, y + 1, x + 1, y + th - 5)
|
||||||
|
elseif self.board[r][c] == ' ' then
|
||||||
|
-- empty space
|
||||||
|
elseif self.board[r][c] > 0 and self.board[r][c] < 100 then
|
||||||
|
-- number
|
||||||
|
local num = self.board[r][c]
|
||||||
|
if num < 10 then
|
||||||
|
draw_number(x + xc, y, n_width, num, self.scale)
|
||||||
|
draw_number(x + xc + 1, y, n_width, num, self.scale)
|
||||||
|
else
|
||||||
|
draw_number(x, y, n_width, num, self.scale)
|
||||||
|
draw_number(x + 1, y, n_width, num, self.scale)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rb.lcd_update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:isComplete()
|
||||||
|
for r = 1,self.puzzleh do
|
||||||
|
for c = 1,self.puzzlew do
|
||||||
|
if self.solution[r][c] == '*' and
|
||||||
|
self.board[self.numbersh + r][self.numbersw + c] ~= '*' then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:moveCursor(dir)
|
||||||
|
-- The cursor isn't allowed to move in the top-left quadrant of the board.
|
||||||
|
-- This has to be checked in up and left moves.
|
||||||
|
local in_board_area = (self.cursor.y > (self.numbersh + 1)
|
||||||
|
and self.cursor.x > self.numbersw + 1)
|
||||||
|
|
||||||
|
if dir == 'left' then
|
||||||
|
if (self.cursor.x > (self.numbersw + 1) or self.cursor.y > self.numbersh)
|
||||||
|
and self.cursor.x > 1 then
|
||||||
|
self.cursor.x = self.cursor.x - 1
|
||||||
|
elseif wrap == true then
|
||||||
|
if in_board_area then
|
||||||
|
self.cursor.x = 1
|
||||||
|
else
|
||||||
|
self.cursor.x = self.boardw
|
||||||
|
end
|
||||||
|
dir = 'up'
|
||||||
|
end
|
||||||
|
elseif dir == 'right' then
|
||||||
|
if self.cursor.x < self.boardw then
|
||||||
|
self.cursor.x = self.cursor.x + 1
|
||||||
|
elseif wrap == true then
|
||||||
|
if in_board_area then
|
||||||
|
self.cursor.x = 1
|
||||||
|
else
|
||||||
|
self.cursor.x = self.numbersw + 1
|
||||||
|
end
|
||||||
|
dir = 'down'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if dir == 'up' then
|
||||||
|
if (self.cursor.y > (self.numbersh + 1) or self.cursor.x > self.numbersw)
|
||||||
|
and self.cursor.y > 1 then
|
||||||
|
self.cursor.y = self.cursor.y - 1
|
||||||
|
elseif wrap == true then
|
||||||
|
if in_board_area then
|
||||||
|
self.cursor.y = 1
|
||||||
|
else
|
||||||
|
self.cursor.y = self.boardh
|
||||||
|
end
|
||||||
|
end
|
||||||
|
elseif dir == 'down' then
|
||||||
|
if self.cursor.y < self.boardh then
|
||||||
|
self.cursor.y = self.cursor.y + 1
|
||||||
|
elseif wrap == true then
|
||||||
|
if in_board_area then
|
||||||
|
self.cursor.y = 1
|
||||||
|
else
|
||||||
|
self.cursor.y = self.numbersh + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:fillSquare(mode)
|
||||||
|
mode = mode or 0
|
||||||
|
if self.cursor.x > self.numbersw and self.cursor.y > self.numbersh then
|
||||||
|
if self.board[self.cursor.y][self.cursor.x] == '*' and mode ~= 2 then
|
||||||
|
-- clear square
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = '.'
|
||||||
|
elseif mode ~= 1 then -- '.' or 'x'
|
||||||
|
-- fill square
|
||||||
|
local x, y = self.cursor.x - self.numbersw, self.cursor.y - self.numbersh
|
||||||
|
if not self.solution or self.solution[y][x] == '*' then
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = '*'
|
||||||
|
else
|
||||||
|
rb.splash(rb.HZ * 2, "Invalid move!")
|
||||||
|
-- "x" square for convenience
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = 'x'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self:showComplete()
|
||||||
|
end
|
||||||
|
|
||||||
|
function State:eliminateSquare()
|
||||||
|
if not self.freedraw
|
||||||
|
and self.cursor.x > self.numbersw
|
||||||
|
and self.cursor.y > self.numbersh then
|
||||||
|
if self.board[self.cursor.y][self.cursor.x] == 'x' then
|
||||||
|
-- clear square
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = '.'
|
||||||
|
else-- '.' or '*'
|
||||||
|
-- "x" square
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = 'x'
|
||||||
|
end
|
||||||
|
else
|
||||||
|
self.board[self.cursor.y][self.cursor.x] = '.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- main code ------------------------------------------------------------------
|
||||||
|
|
||||||
|
local function mainMenu()
|
||||||
|
local menu = {
|
||||||
|
"Resume",
|
||||||
|
"View picture",
|
||||||
|
"Restart puzzle",
|
||||||
|
"Load puzzle",
|
||||||
|
"Zoom " .. State.scale - 1,
|
||||||
|
"Save progress",
|
||||||
|
"Save and quit",
|
||||||
|
"Quit without saving"
|
||||||
|
}
|
||||||
|
local start
|
||||||
|
|
||||||
|
if State.freedraw then
|
||||||
|
menu[6] = "Save freedraw " .. State.freedraw --Save Progress
|
||||||
|
end
|
||||||
|
while true do
|
||||||
|
local s = rb.do_menu("Picross", menu, start, false)
|
||||||
|
start = s
|
||||||
|
if s == 0 then
|
||||||
|
-- resume
|
||||||
|
return
|
||||||
|
elseif s == 1 then
|
||||||
|
-- view picture
|
||||||
|
viewPicture()
|
||||||
|
start = 0 --resume
|
||||||
|
elseif s == 2 then
|
||||||
|
-- restart
|
||||||
|
State:initBoard()
|
||||||
|
return
|
||||||
|
elseif s == 3 then
|
||||||
|
-- choose puzzle
|
||||||
|
if puzzleList() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif s == 4 then
|
||||||
|
-- zoom
|
||||||
|
State.scale = State.scale + 1
|
||||||
|
if State.scale > 4 then
|
||||||
|
State.scale = 1
|
||||||
|
end
|
||||||
|
menu[5] = "Zoom " .. State.scale - 1
|
||||||
|
elseif s == 5 then
|
||||||
|
-- save
|
||||||
|
if State:saveGame() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
elseif s == 6 then
|
||||||
|
-- save and quit
|
||||||
|
if State:saveGame() then
|
||||||
|
os.exit()
|
||||||
|
end
|
||||||
|
elseif s == 7 then
|
||||||
|
-- quit
|
||||||
|
os.exit()
|
||||||
|
elseif s == -2 then
|
||||||
|
-- back button pressed
|
||||||
|
return
|
||||||
|
else
|
||||||
|
-- something strange happened
|
||||||
|
rb.splash(rb.HZ * 2, "Invalid menu index: "..s)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function puzzleList()
|
||||||
|
if rb.dir_exists(userdir) then
|
||||||
|
local files = {}
|
||||||
|
for file in luadir.dir(userdir) do
|
||||||
|
if file ~= '.' and file ~= '..' then
|
||||||
|
table.insert(files, file)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
table.sort(files)
|
||||||
|
local udir = userdir .. "/"
|
||||||
|
if #files > 0 then
|
||||||
|
local s = rb.do_menu("Puzzles", files, nil, false)
|
||||||
|
if s >= 0 and s < #files then
|
||||||
|
if State:loadFile(udir..files[s+1]) then
|
||||||
|
return true -- return to puzzle screen
|
||||||
|
else
|
||||||
|
-- puzzle failed to load, return to main menu
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
elseif s == -2 then
|
||||||
|
-- back button pressed, return to main menu
|
||||||
|
return false
|
||||||
|
else
|
||||||
|
-- something strange happened
|
||||||
|
rb.splash(rb.HZ * 2, "Invalid menu index: "..s)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
rb.splash(rb.HZ * 2, "No puzzles found! Put .picross files in " .. userdir)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
else
|
||||||
|
rb.splash(rb.HZ * 2, "Put .picross files in " .. userdir)
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function viewPicture()
|
||||||
|
rb.lcd_clear_display()
|
||||||
|
|
||||||
|
-- draw filled squares as pixels (scaled 2x)
|
||||||
|
for r = State.numbersh + 1, State.boardh do
|
||||||
|
for c = State.numbersw + 1, State.boardw do
|
||||||
|
if State.board[r][c] == '*' then
|
||||||
|
--rb.lcd_drawpixel(c - State.numbersw, r - State.numbersh)
|
||||||
|
local px = (c - State.numbersw) * State.scale - State.scale + 1
|
||||||
|
local py = (r - State.numbersh) * State.scale - State.scale + 1
|
||||||
|
|
||||||
|
rb.lcd_fillrect(px, py, State.scale, State.scale)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rb.lcd_update()
|
||||||
|
|
||||||
|
-- exit on button press
|
||||||
|
while true do
|
||||||
|
local action = rb.get_plugin_action(0)
|
||||||
|
|
||||||
|
if action == rb.actions.PLA_EXIT
|
||||||
|
or action == rb.actions.PLA_CANCEL
|
||||||
|
or action == rb.actions.PLA_SELECT then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
rb.yield()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if not State:loadSave() then
|
||||||
|
State:loadDefault()
|
||||||
|
end
|
||||||
|
|
||||||
|
local act = rb.actions
|
||||||
|
local action = act.ACTION_NONE
|
||||||
|
local lockdraw = false
|
||||||
|
|
||||||
|
while true do
|
||||||
|
action = rb.get_plugin_action(0)
|
||||||
|
if action == rb.actions.PLA_EXIT then
|
||||||
|
lockdraw = false
|
||||||
|
mainMenu()
|
||||||
|
elseif action == act.PLA_UP or action == act.PLA_UP_REPEAT then
|
||||||
|
State:moveCursor('up')
|
||||||
|
elseif action == act.PLA_DOWN or action == act.PLA_DOWN_REPEAT then
|
||||||
|
State:moveCursor('down')
|
||||||
|
elseif action == act.PLA_LEFT or action == act.PLA_LEFT_REPEAT then
|
||||||
|
State:moveCursor('left')
|
||||||
|
elseif action == act.PLA_RIGHT or action == act.PLA_RIGHT_REPEAT then
|
||||||
|
State:moveCursor('right')
|
||||||
|
elseif action == act.PLA_SELECT then
|
||||||
|
if lockdraw then
|
||||||
|
lockdraw = lockdraw - 1
|
||||||
|
if lockdraw < 0 then
|
||||||
|
lockdraw = false
|
||||||
|
elseif lockdraw == 1 then
|
||||||
|
rb.splash(50, "clear")
|
||||||
|
else
|
||||||
|
rb.splash(50, "invert")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
State:fillSquare()
|
||||||
|
end
|
||||||
|
action = act.ACTION_NONE
|
||||||
|
elseif action == act.PLA_SELECT_REPEAT then
|
||||||
|
if State.freedraw and not lockdraw then
|
||||||
|
lockdraw = 2
|
||||||
|
rb.splash(50, "draw")
|
||||||
|
action = act.ACTION_NONE
|
||||||
|
end
|
||||||
|
elseif action == act.PLA_CANCEL then
|
||||||
|
State:eliminateSquare()
|
||||||
|
action = act.ACTION_NONE
|
||||||
|
else
|
||||||
|
action = act.ACTION_NONE
|
||||||
|
end
|
||||||
|
|
||||||
|
if lockdraw and action ~= act.ACTION_NONE then
|
||||||
|
State:fillSquare(lockdraw)
|
||||||
|
end
|
||||||
|
|
||||||
|
State:drawBoard()
|
||||||
|
|
||||||
|
rb.yield()
|
||||||
|
end
|
24
apps/plugins/picross/picross.make
Normal file
24
apps/plugins/picross/picross.make
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# __________ __ ___.
|
||||||
|
# Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
||||||
|
# Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
||||||
|
# Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
||||||
|
# Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
||||||
|
# \/ \/ \/ \/ \/
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
|
||||||
|
LUASCR_SRCDIR := $(APPSDIR)/plugins/picross
|
||||||
|
LUASCR_BUILDDIR := $(BUILDDIR)/apps/plugins/picross
|
||||||
|
LUASCRS := $(wildcard $(LUASCR_SRCDIR)/*.picross)
|
||||||
|
|
||||||
|
#DUMMY := $(info [${LUASCRS}])
|
||||||
|
|
||||||
|
DUMMY : all
|
||||||
|
|
||||||
|
all: $(subst $(LUASCR_SRCDIR)/,$(LUASCR_BUILDDIR)/,$(LUASCRS))
|
||||||
|
|
||||||
|
$(LUASCR_BUILDDIR)/%.picross: $(LUASCR_SRCDIR)/%.picross | $(LUASCR_BUILDDIR)
|
||||||
|
$(call PRINTS,CP $(subst $(LUASCR_SRCDIR)/,,$<))cp $< $@
|
||||||
|
|
||||||
|
$(LUASCR_BUILDDIR):
|
||||||
|
$(call PRINTS,MKDIR $@)mkdir -p $(LUASCR_BUILDDIR)/
|
7
apps/plugins/picross/picross_default.picross
Executable file
7
apps/plugins/picross/picross_default.picross
Executable file
|
@ -0,0 +1,7 @@
|
||||||
|
# simple default puzzle
|
||||||
|
|
||||||
|
****..*...*..
|
||||||
|
*...*.*...*..
|
||||||
|
****..*.*****
|
||||||
|
*.....*...*..
|
||||||
|
*.....*...*..
|
7
apps/plugins/picross/rb.picross
Normal file
7
apps/plugins/picross/rb.picross
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#open source jukebox firmware
|
||||||
|
|
||||||
|
****...**...***.*..*..............
|
||||||
|
*...*.*..*.*....*.*.*.............
|
||||||
|
****..*..*.*....**..***...**..*..*
|
||||||
|
*..**.*..*.*....*.*.*..*.*..*..**.
|
||||||
|
*...*..**...***.*..****...**..*..*
|
|
@ -182,6 +182,13 @@ sub make_install {
|
||||||
#glob_mkdir("$temp_dir/rocks/demos/lua_scripts");
|
#glob_mkdir("$temp_dir/rocks/demos/lua_scripts");
|
||||||
#glob_copy("$ROOT/apps/plugins/lua_scripts/*.lua", "$temp_dir/rocks/demos/lua_scripts/");
|
#glob_copy("$ROOT/apps/plugins/lua_scripts/*.lua", "$temp_dir/rocks/demos/lua_scripts/");
|
||||||
}
|
}
|
||||||
|
#lua picross puzzles
|
||||||
|
if(-e "$ROOT/apps/plugins/picross") {
|
||||||
|
unless (glob_mkdir("$libdir/rocks/games/picross")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
glob_install("$ROOT/apps/plugins/picross/*.picross", "$libdir/rocks/games/picross");
|
||||||
|
}
|
||||||
|
|
||||||
# all the rest directories
|
# all the rest directories
|
||||||
foreach my $t (@userstuff) {
|
foreach my $t (@userstuff) {
|
||||||
|
@ -533,6 +540,10 @@ sub buildzip {
|
||||||
copy("$ROOT/apps/plugins/sokoban.levels", "$temp_dir/rocks/games/sokoban.levels"); # sokoban levels
|
copy("$ROOT/apps/plugins/sokoban.levels", "$temp_dir/rocks/games/sokoban.levels"); # sokoban levels
|
||||||
copy("$ROOT/apps/plugins/snake2.levels", "$temp_dir/rocks/games/snake2.levels"); # snake2 levels
|
copy("$ROOT/apps/plugins/snake2.levels", "$temp_dir/rocks/games/snake2.levels"); # snake2 levels
|
||||||
copy("$ROOT/apps/plugins/rockbox-fonts.config", "$temp_dir/rocks/viewers/");
|
copy("$ROOT/apps/plugins/rockbox-fonts.config", "$temp_dir/rocks/viewers/");
|
||||||
|
# picross files
|
||||||
|
copy("$ROOT/apps/plugins/picross_default.picross", "$temp_dir/rocks/games/picross_default.picross");
|
||||||
|
copy("$ROOT/apps/plugins/bitmaps/native/picross_numbers.bmp",
|
||||||
|
"$temp_dir/rocks/games/picross_numbers.bmp");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(-e "$temp_dir/rocks/demos/pictureflow.rock") {
|
if(-e "$temp_dir/rocks/demos/pictureflow.rock") {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue