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