code:
#include "bn_memory.h"

#include "state_base.h"

GameState::GameState() :
    bg_x(0), bg_y(0),
    item(cells[0], bn::size(columns, rows))
{
    resetMap();
}

GameState::GameState(int x, int y) :
    bg_x(x), bg_y(x),
    item(cells[0], bn::size(columns, rows))
{
    resetMap();
}

bn::regular_bg_ptr GameState::buildBG()
{
    bn::regular_bg_item i(bn::regular_bg_items::square.tiles_item(), bn::regular_bg_items::square.palette_item(), item);
    bn::regular_bg_builder builder(i);
    builder.set_position(bg_x, bg_y);
    return builder.release_build();
}

bn::optional<bn::sprite_ptr> GameState::buildSprite(const bn::sprite_item &s, int x, int y)
{
    bn::sprite_item i(s.shape_size(), s.tiles_item(), bn::sprite_items::logo.palette_item());
    bn::sprite_builder builder(i);
    builder.set_position(x, y);
    return builder.release_build_optional();
}

const bn::bg_palette_item GameState::getCommonPalette()
{
    return bn::regular_bg_items::square.palette_item();
}

void GameState::setCellTile(int x, int y, int tile_index)
{
    bn::regular_bg_map_cell &current_cell = cells[item.cell_index(x, y)];
    bn::regular_bg_map_cell_info current_cell_info(current_cell);
    current_cell_info.set_tile_index(tile_index);
    current_cell = current_cell_info.cell();
}

bn::regular_bg_map_cell& GameState::retrieveCell(int x, int y)
{
    return cells[item.cell_index(x, y)];
}

void GameState::resetMap()
{
    bn::memory::clear(screen_cells, cells[0]);
}

void GameState::refreshScreen(bn::regular_bg_map_ptr &map)
{
    map.reload_cells_ref();
}

void GameState::scrollScreen(int x_delta, int y_delta, bn::regular_bg_ptr &bg)
{
    bg.set_x(bg.x() + x_delta);
    bg.set_y(bg.y() + y_delta);
    bg.set_position(bg_x, bg_y);
}

void GameState::PaperSheetPattern_Regular()
{
    for (int i = 0; i < columns; ++i)
    {
        for (int j = 0; j < rows; ++j)
        {
            int cell_number = 1;
            if (j < 2 || j > 29)
                cell_number = 32;
            if (j == 2)
                cell_number = 21;
            if (j == 25)
                cell_number = 16;
            if (j == 29)
                cell_number = 22;
            setCellTile(j, i, cell_number);
        }
    }
}

void GameState::PaperSheetPattern_Holed()
{
    for (int i = 0; i < columns; ++i)
    {
        for (int j = 0; j < rows; ++j)
        {
            int cell_number = 1;
            if (j < 1 || j > 30)
                cell_number = 32;
            if (j == 1)
                cell_number = 21;
            if (j == 28)
            {
                if (i % 4 == 0)
                    cell_number = 19;
                if ((i-1)%4 == 0)
                    cell_number = 20;
            }
            if (j == 29)
            {
                if (i % 4 == 0)
                    cell_number = 17;
                if ((i-1)%4 == 0)
                    cell_number = 18;
            }
            //if (j == 25)
              //  cell_number = 16;
            if (j == 30)
                cell_number = 22;
            setCellTile(j, i, cell_number);
        }
    }
}
//title
void GameState::PaperSheetPattern_Scrollable()
{
    for (int i = 0; i < columns; ++i)
    {
        for (int j = 0; j < rows; ++j)
        {
            int cell_number = 1;
            setCellTile(j, i, cell_number);
        }
    }
}
//puzzle selection
void GameState::PaperSheetPattern_PuzzleSelection(int& low_limit, int& up_limit)
{
    for (int i = 0; i < columns; ++i)
    {
        for (int j = 0; j < rows; ++j)
        {
            int cell_number = 1;
            if (i < 7 || i > 24)
                cell_number = 32;
            if (i == 7)
            {
                cell_number = j & 1 ? 25 : 24;
            }
            if (i == 22)
                cell_number = 23;
            if (i >= low_limit && i <= up_limit && i & 1)
            {
                if (j == 15)
                    cell_number = 4;
                if (j == 17)
                    cell_number = 5;
                if (j == 19)
                    cell_number = 6;
                if (j == 21)
                    cell_number = 7;
                if (j == 23)
                    cell_number = 8;
            }
            setCellTile(j, i, cell_number);
        }
    }
}

GameState::~GameState()
{
    resetMap();
    BN_LOG("base class destructor");
}


code:
#include "state_free_play_menu.h"

PuzzleSelect::PuzzleSelect(bn::sprite_text_generator* stg) :
    GameState(), bg(buildBG()), map(bg.map()), text_gnrtr(stg),
    menu_hrz_lower_limit(15), menu_hrz_upper_limit(23), menu_vrt_lower_limit(13), menu_vrt_upper_limit(19),
    cursor_sprite(buildSprite(bn::sprite_items::cursor_circle, 0,0)),
    scribble_left(buildSprite(bn::sprite_items::scribble_star, -68, -40))
{
    bg.set_blending_enabled(true);
    scribble_left->set_blending_enabled(true);
    cursor_sprite->set_blending_enabled(true);

    //bn::blending::set_fade_alpha(1);

    for (int i = 0, k = 0; i < slots_occupied.size(); ++i, k+=18)
    {
        slots_occupied[i] = tool::validateSRAMData(k);
        BN_LOG(i, " slot data valid: ", slots_occupied[i]);
    }

    showMenu();
}

void PuzzleSelect::showMenu()
{
    for (int i = 0; i < slots_occupied.size(); ++i)
    {
        if (slots_occupied[i])
        {
            menu_vrt_upper_limit += 2;
            break;
        }
    }

    resetMap();
    PaperSheetPattern_PuzzleSelection(menu_vrt_lower_limit, menu_vrt_upper_limit);
    refreshScreen(map);

    showMenuText();

}

void PuzzleSelect::showMenuText()
{
    symbols.clear();

    text_gnrtr->generate(tool::cellX2Screen(9, 4), tool::cellY2Screen(10, 4), "Choose a puzzle", symbols);

    text_gnrtr->generate(tool::cellX2Screen(6, 4), tool::cellY2Screen(13, 4), "things", symbols);
    text_gnrtr->generate(tool::cellX2Screen(6, 4), tool::cellY2Screen(15, 4), "nature", symbols);
    text_gnrtr->generate(tool::cellX2Screen(6, 4), tool::cellY2Screen(17, 4), "symbols", symbols);
    text_gnrtr->generate(tool::cellX2Screen(6, 4), tool::cellY2Screen(19, 4), "portraits", symbols);

    for (int i = 0; i < slots_occupied.size(); ++i)
    {
        if (slots_occupied[i])
        {
            text_gnrtr->generate(tool::cellX2Screen(6, 4), tool::cellY2Screen(21, 4), "custom", symbols);
            break;
        }
    }
}

bn::array<bool, 144> const& PuzzleSelect::getSelectedPuzzle()
{
    bn::sound_items::ganbatte_ne.play(1);
    switch(cursor_position[1])
    {
        case 13:
            switch(cursor_position[0])
            {
                case 15:
                    return puzzle::Things_Gameboy;
                case 17:
                    return puzzle::Things_Giftbox;
                case 19:
                    return puzzle::Things_Notepad;
                case 21:
                    return puzzle::Things_Chair;
                case 23:
                    return puzzle::Things_Key;
                default:
                    break;
            }
            break;
        case 15:
            switch(cursor_position[0])
            {
                case 15:
                    return puzzle::Nature_4LClover;
                case 17:
                    return puzzle::Nature_Flower;
                case 19:
                    return puzzle::Nature_Lagomorph;
                case 21:
                    return puzzle::Nature_Duck;
                case 23:
                    return puzzle::Nature_Dog;
                default:
                    break;
            }
            break;
        case 17:
            switch(cursor_position[0])
            {
                case 15:
                    return puzzle::Kanji_Cat;
                case 17:
                    return puzzle::Kanji_Congrats;
                case 19:
                    return puzzle::Kanji_Flower;
                case 21:
                    return puzzle::Writing_Hi;
                case 23:
                    return puzzle::Symbol_Clef;
                default:
                    break;
            }
            break;
        case 19:
            switch(cursor_position[0])
            {
                case 15:
                    return puzzle::Portrait_Nyra;
                case 17:
                    return puzzle::Portrait_Tooru;
                case 19:
                    return puzzle::Portrait_Yuuji;
                case 21:
                    return puzzle::Portrait_Steph;
                case 23:
                    return puzzle::Portrait_Yuli;
                default:
                    break;
            }
            break;
        case 21:
            if (slots_occupied[(cursor_position[0]-15)/2] == true)
            {
                tool::UInt2BoolArray(puzzle::Custom, (cursor_position[0]-15)/2*18);
                return puzzle::Custom;
            }
            break;
        default:
            break;
        }
}

int PuzzleSelect::updateState()
{
    if (bn::keypad::down_pressed() && cursor_position[1] < menu_vrt_upper_limit)
    {
        cursor_position[1]+=2;
    }
    else if (bn::keypad::up_pressed() && cursor_position[1] > menu_vrt_lower_limit)
    {
        cursor_position[1]-=2;
    }
    if (bn::keypad::right_pressed() && cursor_position[0] < menu_hrz_upper_limit)
    {
        cursor_position[0]+=2;
    }
    else if (bn::keypad::left_pressed() && cursor_position[0] > menu_hrz_lower_limit)
    {
        cursor_position[0]-=2;
    }

    if (bn::keypad::a_pressed())
    {
        if (cursor_position[1] == 21)
            if (slots_occupied[(cursor_position[0]-15)/2] == false)
            {
                if (!empty_selection)
                {
                    text_gnrtr->generate(tool::cellX2Screen(11, 4), tool::cellY2Screen(23, 4), "no puzzle found!", symbols);
                    empty_selection = true;
                }
                return -1;
            }
        return 4;
    }
    else if (bn::keypad::select_pressed() || bn::keypad::b_pressed())
    {
        return 0;
    }
    two_frame_anim.update();
    cursor_sprite->set_position(tool::cellX2Screen(cursor_position[0], 8), tool::cellY2Screen(cursor_position[1],8));

    if (empty_selection && ++frame_counter >= 100)
    {
        empty_selection = false;
        frame_counter = 0;
        showMenuText();
    }

    return -1;
}

void PuzzleSelect::toggleStateVisibility(bool show)
{
    bg.set_visible(show);
    cursor_sprite->set_visible(show);
    scribble_left->set_visible(show);
    symbols.clear();
    if (show) showMenuText();
}

void PuzzleSelect::toggleFadeEffect()
{

}

PuzzleSelect::~PuzzleSelect()
{
    symbols.clear();
    BN_LOG("free play menu class destructor");
}


code:
#include "state_main.h"

MainGame::MainGame(bn::sprite_text_generator *_t) :
    GameState(), bg(buildBG()), map(bg.map()), text_gnrtr(_t),//_text(_t),
    frames2skip(0), offset(0), mistake_made(false), puzzle_solved(false), frame_counter(0),
    cursor_sprite(buildSprite(bn::sprite_items::cursor_pen, 0,0)),
    stamp(buildSprite(bn::sprite_items::stamp, -68,48)),
    notono_window(buildSprite(bn::sprite_items::notonochan, -56, -56)),
    //scribble_left(buildSprite(bn::sprite_items::scribble_star.tiles_item(), -88, 56)),
    scribble_right(buildSprite(bn::sprite_items::scribble_heart, 96, -64))
{
    notono_window->set_visible(false);
    stamp->set_visible(false);

    bg.set_blending_enabled(true);
    scribble_right->set_blending_enabled(true);

    createPuzzle();

    BN_LOG("main game sub class constructor (create)");
}

MainGame::MainGame(bn::array<bool, 144> const &puzzle_ref) :
    GameState(), bg(buildBG()), map(bg.map()),
    frames2skip(0), offset(0), mistake_made(false), puzzle_solved(false), frame_counter(0),
    cursor_sprite(buildSprite(bn::sprite_items::cursor_pen, 0,0)),
    stamp(buildSprite(bn::sprite_items::stamp, -68,48)),
    notono_window(buildSprite(bn::sprite_items::notonochan, -56, -56)),
    //scribble_left(buildSprite(bn::sprite_items::scribble_star.tiles_item(), -88, 56)),
    scribble_right(buildSprite(bn::sprite_items::scribble_heart, 96, -64))
{
    stamp->set_visible(false);

    bg.set_blending_enabled(true);
    scribble_right->set_blending_enabled(true);
    notono_window->set_blending_enabled(true);
    stamp->set_blending_enabled(true);

    solvePuzzle(puzzle_ref);
    BN_LOG("main game sub class constructor (solve)");
}

void MainGame::redrawHints()
{
    for (int i = 0; i < 12; ++i)
    {
        for (int j = 0; j < 6; ++j)
        {
            if (grid.grid_hints_up[6*i+j] > 0)
            {
                setCellTile(13+i, 12-j, grid.grid_hints_up[6*i+j]+3);
            }
            else setCellTile(13+i, 12-j, 1);
        }
    }
    for (int i = 0; i < 12; ++i)
    {
        for (int j = 0; j < 6; ++j)
        {
            if (grid.grid_hints_left[6*i+j] > 0)
            {
                setCellTile(12-j, 13+i, grid.grid_hints_left[6*i+j]+3);
            }
            else setCellTile(12-j, 13+i, 1);
        }
    }
}

void MainGame::drawCreatorModeInstructions()
{

    symbols.clear();
    text_gnrtr->generate(-104, -76, "use L and R", symbols);
    text_gnrtr->generate(-104, -68, "to switch", symbols);
    text_gnrtr->generate(-104, -60, "puzzle slots,", symbols);
    text_gnrtr->generate(-104, -52, "START to", symbols);
    text_gnrtr->generate(-104, -44, "save puzzle", symbols);
    text_gnrtr->generate(-104, -36, "using slot", symbols);
}

void MainGame::createPuzzle()
    {
        resetMap();
        PaperSheetPattern_Regular();
        refreshScreen(map);

        drawCreatorModeInstructions();

        setCellTile(11, 11, 4);
        grid.create();
        cursor_sprite->set_position(tool::cellX2Screen(grid.getCursorX(),8), tool::cellY2Screen(grid.getCursorY(),8));
    }

void MainGame::solvePuzzle(bn::array<bool, 144> const &_puzzle)
    {
        resetMap();
        PaperSheetPattern_Regular();
        refreshScreen(map);

        grid.solve();

        grid.updateHints(_puzzle);

        redrawHints();
        refreshScreen(map);

        cursor_sprite->set_position(tool::cellX2Screen(grid.getCursorX(),8), tool::cellY2Screen(grid.getCursorY(),8));
    }

int MainGame::updateState(bn::array<bool, 144> const &current_puzzle)
{
    if (
            !puzzle_solved &&
            ((grid.processDPadContinuousInput(frames2skip) &&
             grid.processKeyContinuousInput(retrieveCell(grid.getCursorX(),
                                                grid.getCursorY()))) ||
             grid.processKeyInput(retrieveCell(grid.getCursorX(),
                                                     grid.getCursorY())))
       )
    {
        if(grid.checkCurrentCell(current_puzzle))
        {
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 2,3);
            mistake_made = true;
        }
        if(grid.checkSolution(current_puzzle))
        {
            cursor_sprite->set_visible(false);
            bn::sound_items::yoku_dekimasita.play(1);
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 4,5);
            puzzle_solved = true;
            if (frame_counter > 0)
            {
                frame_counter = 0;
                mistake_made = false;
            }
        }
        refreshScreen(map);
    }
    for (int i = 0; i < 2; ++i)
    {
        two_frame_anim[i].update();
    }
    if (puzzle_solved)
    {
        if (++frame_counter >= 40)
        {
            if (frame_counter > 200)
                return 0;
            else
            {
                //bn::sound_items::stamp.play(1);
                stamp->set_visible(true);
            }
        }
     }
    if (mistake_made)
    {
        if (++frame_counter >= 60)
        {
            frame_counter = 0;
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 0,1);
            mistake_made = false;
        }
    }
    if (bn::keypad::select_pressed())
    {
        return 0;
    }
    cursor_sprite->set_position(tool::cellX2Screen(grid.getCursorX(), 8), tool::cellY2Screen(grid.getCursorY(),8));
    return -1;
}

int MainGame::updateState()
{
    if (
            (grid.processDPadContinuousInput(frames2skip) &&
             grid.processKeyContinuousInput(retrieveCell(grid.getCursorX(),
                                                grid.getCursorY()))) ||
             grid.processKeyInput(retrieveCell(grid.getCursorX(),
                                                     grid.getCursorY()))
       )
    {
        grid.updateHints(grid.getCurrentGrid());
        redrawHints();
        refreshScreen(map);
    }
    for (int i = 0; i < 2; ++i)
    {
        two_frame_anim[i].update();
    }
    if (bn::keypad::start_pressed())
    {
        //bn::sram::write_offset(grid.getCurrentGrid(), offset*144);
        //tool::write2SRAM(grid.getCurrentGrid(), offset);

        tool::boolArray2UInt(grid.getCurrentGrid(), offset*18);
        if (!puzzle_solved)
        {
            symbols.clear();
            text_gnrtr->generate(-104, -60, "puzzle saved!", symbols);
            text_gnrtr->generate(-104, -36, "using slot", symbols);
            puzzle_solved = true;
        }
    }
    if (bn::keypad::l_pressed())
    {
        if (offset > 0) --offset;
        setCellTile(11, 11, 4+offset);
        refreshScreen(map);
    }
    else if (bn::keypad::r_pressed())
    {
        if (offset < 4) ++offset;
        setCellTile(11, 11, 4+offset);
        refreshScreen(map);
    }
    if (bn::keypad::select_pressed())
    {
        return 0;
    }

    if (puzzle_solved && ++frame_counter >= 120)
    {
        frame_counter = 0;
        puzzle_solved = false;
        drawCreatorModeInstructions();
    }

    cursor_sprite->set_position(tool::cellX2Screen(grid.getCursorX(), 8), tool::cellY2Screen(grid.getCursorY(),8));

    return -1;
}

void MainGame::toggleStateVisibility(bool show)
{
    bg.set_visible(show);
    if (!grid.creator_mode) notono_window->set_visible(show);
    cursor_sprite->set_visible(show);
    symbols.clear();
}

void MainGame::toggleFadeEffect()
{

}

MainGame::~MainGame()
{
    if (!symbols.empty())
        symbols.clear();
    BN_LOG("main game sub class destructor");
}


code:
#include "state_main_timed.h"

ArcadeGame::ArcadeGame() : MainGame(puzzle::StoryMode_Phone),
    completion_time(0), time_limit(800), completion_time_delta(0), one_second_frames(0),
            mistake_counter(0), crossed_square_counter(0), longest_coloring_time_delta(0)
{}

ArcadeGame::ArcadeGame(int limit) : MainGame(puzzle::StoryMode_Phone),
    completion_time(0), time_limit(limit), completion_time_delta(0), one_second_frames(0),
            mistake_counter(0), crossed_square_counter(0), longest_coloring_time_delta(0)
{}

int ArcadeGame::GradeCalculator()
{
    int result = 1;

    if (mistake_counter > 0)
    {
        result = mistake_counter < 5 ? result + 2 : result + 3;
    }

    completion_time_delta = time_limit - completion_time;

    if (completion_time_delta <= 0 || completion_time >= time_limit - 100)
    {
        result += 3;
    }
    else if (completion_time_delta <= completion_time && completion_time < time_limit - 100)
    {
        result += 2;
    }
    BN_LOG("RESULTING RANK ", result);
    return result;
}

int ArcadeGame::updateState()
{
    if (
            !puzzle_solved &&
            ((grid.processDPadContinuousInput(frames2skip) &&
             grid.processKeyContinuousInput(retrieveCell(grid.getCursorX(),
                                                grid.getCursorY()))) ||
             grid.processKeyInput(retrieveCell(grid.getCursorX(),
                                                     grid.getCursorY())))
       )
    {
        if(grid.checkCurrentCell(puzzle::StoryMode_Phone))
        {
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 2,3);
            mistake_made = true;
            ++mistake_counter;
            time_limit -= 60;
        }
        if(grid.checkSolution(puzzle::StoryMode_Phone))
        {
            cursor_sprite->set_visible(false);
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 4,5);
            puzzle_solved = true;
            if (frame_counter > 0)
            {
                frame_counter = 0;
                mistake_made = false;
            }
        }
        refreshScreen(map);
    }
    for (int i = 0; i < 2; ++i)
    {
        two_frame_anim[i].update();
    }
    if (puzzle_solved)
    {
        if (++frame_counter >= 40)
        {
            if (frame_counter > 200)
            {
                return GradeCalculator();
            }
        }
     }
    if (mistake_made)
    {
        if (++frame_counter >= 60)
        {
            frame_counter = 0;
            two_frame_anim[0] = bn::create_sprite_animate_action_forever
                    (*notono_window, 5, bn::sprite_items::notonochan.tiles_item(), 0,1);
            mistake_made = false;
        }
    }
    if (++one_second_frames >= 60)
    {
        one_second_frames = 0;
        ++completion_time;
        BN_LOG("time ", completion_time);
        BN_LOG("limit ", time_limit);
        BN_LOG("mistakes ", mistake_counter);
    }
    cursor_sprite->set_position(tool::cellX2Screen(grid.getCursorX(), 8), tool::cellY2Screen(grid.getCursorY(),8));
    return -1;
}

ArcadeGame::~ArcadeGame()
{

}


code:
#include "state_start_menu.h"

MainMenu::MainMenu(text* stg) : GameState(), bg_pattern(buildBG()), map(bg_pattern.map()),
    _text(new text(stg)), displaying_logo1(true), displaying_logo2(false),displaying_credits(false), returning_from_state(false),
    cursor_sprite(buildSprite(bn::sprite_items::cursor_pen,0,0)),
    logo(buildSprite(bn::sprite_items::logo,0,0)),
    bg_foreground(bn::regular_bg_items::title.create_bg(-20, 15)),
    script_ptr(0), line_ptr(0), symbol_ptr(0)
{
    bg_foreground.set_visible(false);
    cursor_sprite->set_visible(false);
    cursor_sprite->set_horizontal_flip(true);
    cursor_sprite->set_vertical_flip(true);
    initScrollingBG();
    bg_pattern.set_blending_enabled(false);
    bg_foreground.set_blending_enabled(false);
    cursor_sprite->set_blending_enabled(false);
    logo->set_blending_enabled(true);
    bn::blending::set_transparency_alpha(0);
}

void MainMenu::initScrollingBG()
{
    resetMap();
    PaperSheetPattern_Scrollable();
    refreshScreen(map);
}

void MainMenu::displayMainMenu()
{
    bg_foreground.set_visible(true);
    cursor_sprite->set_visible(true);

    _text->outputSingleLine(42, -60, "notenogram");
    _text->outputSingleLine(57, 4, "play");
    _text->outputSingleLine(57, 20, "create");
    _text->outputSingleLine(57, 36, "tutorial");
    _text->outputSingleLine(57, 52, "story mode");
    _text->outputSingleLine(57, 68, "credits");
}

void MainMenu::displayCredits()
{
    _text->output_Typewriter(credits[script_ptr], symbol_ptr, line_ptr, 0);
    if (bn::keypad::a_pressed())
    {
        if (++script_ptr >= 7)
        {
            script_ptr = 0;
        }
        line_ptr = 0;
        symbol_ptr = 0;
        _text->redraw(credits[script_ptr]);
    }
    if (bn::keypad::b_pressed())
    {
        displaying_credits = false;
        logo->set_visible(false);
        _text->clear();
        toggleStateVisibility(true);
        script_ptr = 0;
        line_ptr = 0;
        symbol_ptr = 0;
    }
}

int MainMenu::updateState()
{
    if (!displaying_credits)
    {
    if (!displaying_logo1 && !displaying_logo2)
    {
        if (bn::keypad::down_pressed())
        {
            cursor_position[1] = cursor_position[1] >= upper_bound ? lower_bound : cursor_position[1] + 16;
        }
        else if (bn::keypad::up_pressed())
        {
            cursor_position[1] = cursor_position[1] <= lower_bound ? upper_bound : cursor_position[1] - 16;
        }
        if (bn::keypad::a_pressed())
        {
            switch(cursor_position[1])
            {
                case 4:
                toggleStateVisibility(false);
                returning_from_state = true;
                    return 1;
                    break;
                case 20:
                toggleStateVisibility(false);
                returning_from_state = true;
                    return 2;
                    break;
                case 36:
                toggleStateVisibility(false);
                returning_from_state = true;
                    return 6;
                    break;
                case 52:
                    toggleStateVisibility(false);
                    returning_from_state = true;
                    return 5;
                    break;
                case 68:
                displaying_credits = true;
                toggleStateVisibility(false);
                    return 3;
                    break;
                default: break;
            }
        }
        cursor_sprite->set_position(cursor_position[0], cursor_position[1]);
    }
    else
    {
        if (bn::keypad::any_pressed())
        {
            displaying_logo1 = false;
            displaying_logo2 = true;
            refresh_counter = 200;
        }
        if (++refresh_counter >= 200)
        {
            refresh_counter = 0;
            if (!displaying_logo1 && displaying_logo2)
            {
                displaying_logo2 = false;
                bn::blending::set_transparency_alpha(1);
                logo->set_visible(false);
                logo->set_blending_enabled(false);
                bn::sound_items::nootonoguramu.play();
                displayMainMenu();
            }
            else
            {
                logo = buildSprite(bn::sprite_items::logo_butano,0,0);
                logo->set_blending_enabled(true);
                bn::blending::set_transparency_alpha(0);
                displaying_logo1 = false;
                displaying_logo2 = true;
            }
        }
    }}
    else
    {
        logo = buildSprite(bn::sprite_items::scribble_star, 80, 4);
        //logo->set_visible(true);
        displayCredits();
    }
    bg_pattern.set_x(bg_pattern.x()+0.25);
    bg_pattern.set_y(bg_pattern.y()+0.25);
    return -1;
}

void MainMenu::toggleStateVisibility(const bool show)
{
    bg_foreground.set_visible(show);
    cursor_sprite->set_visible(show);
    if (!displaying_credits)
    {
        bg_pattern.set_visible(show);
    }
    else
    {
        _text->redraw(credits[0]);
        return;
    }
    _text->clear();
    if (show)
    {
        displayMainMenu();
    }
}

void MainMenu::toggleFadeEffect()
{
    logo = buildSprite(bn::sprite_items::logo_butano,0,0);
    displaying_logo1 = false;
    displaying_logo2 = true;
}

MainMenu::~MainMenu()
{
    delete _text;
}


code:
#include "state_story.h"

namespace story
{
    bn::string_view intro[] =
    {
        "Hello! My name's Omi and I'm a regular junior high student. I love playing video games and visiting local arcades.",
        "I've been to this particular arcade so many times I managed to beat almost every game it has.",
        "But today I'm visiting it for a slightly different reason.",
        "See, there's this one arcade machine that I've never paid any attention to until recently.",
        "Notenogram, a puzzle game about solving picture crosswords that mimicked the look of a piece of a notebook paper.",
        "And apparently, I wasn't the only one avoiding this game for so long.",
        "Last time I visited the arcade I heard its owner telling someone they will remove this machine soon due to high maintenance costs.",
        "Plus every time I passed the Notenogram machine, I never saw anyone playing it or simply being near it.",
        "And so I've decided to dedicate today's afterschool hour to playing this game for myself before it goes away.",
        "After all, despite me hearing about picross before, I've never really given it a proper shot, and this looked liked the perfect opportunity to get into it.",
        "Maybe this could be my new arcade favorite, an arcade puzzle classic, a hidden gem?..",
        "And here I am in front of the machine, staring at its dusty screen, not knowing why do I even have to do this.",
        "After a brief tutorial, I selected one of the simpler puzzles Notenogram had to offer to see if I could solve it."
    };

    bn::string_view end_nobisu[] =
    {
        "\"You've obtained rank D!\"",
        "Well, so much for my first attempt. Maybe if I keep playing I could try getting a higher rank...",
        "-\"You're also not good at this, huh...\"",
        "I turned around to find the source of the voice and met face-to-face with a boy in a school uniform of about the same age as me.",
        "Despite being somewhat startled, I instinctively replied with what first came to mind.",
        "\"Yeah, I'm kind of new to this picross solving business. And you..?\"",
        "-\"Same for me! I've been trying to solve picross puzzles from this magazine my grandma has recently subscribed to, but they're just so tough!",
        "-\"They're really big and don't have any fully colored rows and I cannot believe there are people who can solve this kind of stuff.",
        "-\"I'm glad I found this arcade machine, though, I've heard it's really good for those starting out. Plus it has a good extended tutorial mode!",
        "-\"I've been keeping a notebook where I note all the strategy tips the game gives during it, and it helped me a lot!",
        "-\"Not so much with the magazine puzzles, but I'll keep trying and gaining experience and someday I may just be able to solve them in a flash!",
        "-\"...Well, not really in a flash, since I have kind of poor reflexes...\"",
        "The boy was obviously starting to trail off a bit, and I let out a fake suppressed cough to clue him in on this.",
        "-\"Ah, please excuse me for that, I have a really bad habit of rambling in the middle of an unrelated conversation.",
        "-\"If you don't mind me asking, what's your story with Notenogram?\"",
        "\"Oh, I just wanted to experience it for myself before the arcade owners get rid of it.\"",
        "-\"Get rid of it? That's sad to hear. I hope they'll at least replace it with a newer one.\"",
        "\"That would be nice, even though this is my first time playing picross and failing miserably, I think I'm already a big fan.\"",
        "-\"I guess we can only hope for the best now, don't we?\"",
        "\"Well, on the other hand, we could talk to the owner and ask if there is a possibility the game could end up not leaving the arcade.",
        "\"Or we can always look up free-to-play online picross games and cheap puzzle magazines, haha.\"",
        "-\"Yeah! By the way, would you be interested in checking out that magazine I mentioned earlier and maybe even solving some puzzles together?",
        "-\"Like they say, two minds are better than one... or something. My name's Nobisu, by the way.\"",
        "\"Nice to meet you, I'm Omi. And yeah, solving complex puzzles together sounds like a good idea. We could even start our own afterschool picross club.",
        "\"Though I'm not sure if students of different schools can do so...\"",
        "-\"Nah, I think a club would be much better without any ties to school. Plus this could greatly expand our community beyond just junior high students!..",
        "-\"Oh, there's me starting to ramble again.\"",
        "-\"Anyways, would you like to meet up here at the same time tomorrow to play more Notenogram before it disappears?\"",
        "\"You bet! Let's exchange phone numbers so we can contact each other just in case.\"",
        "-\"Sure! Looking forward to our meeting!\"",
        "...I may've not done well at that simple puzzle game, but I managed to gain interest in picross overall, and also befriend a like-minded person.",
        "And to me, that's definitely much better than getting a good rank at any game."
    };

    bn::string_view end_notono[] =
    {
        "\"You've obtained rank B!\"",
        "...Woah, not bad for my first try!",
        "-\"Woah, not bad for your first try!\"",
        "As I wondered how could my thoughts have echoed across the arcade hall, I turned around and noticed a girl standing near me, looking at the screen.",
        "Was she standing there the whole time or..?",
        "\"Thanks, but uh... how did you know it was my first time playing picross?\"",
        "-\"Ah, it's easy. You completely missed a row where you could color pixels by overlapping their supposed placements.",
        "-\"And it's also the first time I see you near this machine.\"",
        "\"Oh. You must have been playing the game for a long time to make such a claim.\"",
        "-\"Well, it's not like I'm playing Notenogram ALL the time, so I can't say for sure if you played this before, but I'm glad my guess was spot on!",
        "-\"How do you like it so far?\"",
        "\"Oh, uh, I've only played one level so far, so I myself can't say for sure if I have any opinion based on just one good rank for one puzzle.",
        "\"Picross as a concept seems pretty interesting, though.\"",
        "-\"Ooh, glad to hear that!\"",
        "\"Why do you ask?\"",
        "-\"You know this machine is about to be removed from this arcade, right?\"",
        "\"Yeah, I've heard of something of this sort recently.\"",
        "-\"I'm trying to persuade the owner of this store to donate it to a video game museum and archive I'm currently volunteering for...",
        "-\"or at least sell it to the nearset larger arcade place.",
        "-\"I really like this and other arcade machines by the same developer and it's sad to see them disappear just like that.",
        "-\"Especially considering those are quite rare to come across nowadays.\"",
        "\"So you help preserving video games? That's cool!\"",
        "-\"I know, right? I especially like hunting for unlabeled video game cartridges and disks, sometimes they can hold a treasure trove of unreleased content.",
        "-\"Oh, this reminds me - if you ever find something video game-related that you don't need, consider donating it to our museum!",
        "-\"We even offer monetary bounties for some of the more obscure findings. Here's my business card in case you'd like to contact me.\"",
        "\"Oh, thanks. I'll be sure to contact you whenever.\"",
        "I accepted the business card from her hands and studied it for a minute before carefully placing it between the copybooks in my bag.",
        "It said \"Notono Ramu, apprentice retro game archivist\".",
        "\"So... Notono-san, right?\"",
        "-\"That's my name! What's yours?\"",
        "\"I'm Omi. Pleasure to meet you.\"",
        "-\"Likewise!\"",
        "\"...I'm curious to know, what kind of games has this developer created?\"",
        "-\"Oh, mostly puzzle games with cute aesthetics, like tile stacking games with super-deformed anime characters...",
        "-\"competitive minesweeper with paper-cutout stop-motion-like 3D graphics... and such.",
        "-\"Though they've also made a fighting game about the characters from a dancing rhythm game that was quite popular.",
        "-\"And I think this arcade has it too, actually!\"",
        "\"You mean Seven's Battle Origins? I love that game! I've completed every fighting team's route there!\"",
        "-\"That's great! Care to play a round of versus mode against me? I'll pay for you as a treat.\"",
        "\"Sounds good, bring it on!\"",
        "...Not only did I gain huge interest in picross puzzles, I also made a new friend, whom I later joined as a video game museum volunteer.",
        "And thanks to a certain obscure picross video game I discovered a wonderful world of video game preservation."

    };

    bn::string_view end_nonogami[] =
    {
        "\"You've obtained rank S! New high score!\"",
        "...Wow. That wasn't what I expected at all.",
        "The brief starter tutorial on picross was enough for me to land on top of the game's leaderboard, just barely passing someone by the name of...",
        "-\"Unbelievable!!\"",
        "The sudden voice caught me off guard and I nearly jumped at the machine.",
        "I quickly turned around and saw a teenager in a varsity jacket standing behind my back and looking in my direction, clearly not liking whatever they saw.",
        "-\"To see someone passing me, THE Picross Master Nonogami, in what may be considered the hardest picross game to master!!..\"",
        "\"Um, it wasn't that hard actually... I mean, I picked one of the easiest levels...\"",
        "The Picross Master Nonogami casted a thorough glance at the screen, which continued showing my name right above theirs on the leaderboard.",
        "-\"...At least you were being honest. I was just checking to see if completing an incredibly easy 12-by-12 puzzle is enough for someone to boast about.",
        "-\"Still, it's a rare sight to see anyone other than me trying to go for a high score on any picross arcade.\"",
        "\"Probably because they're not that popular?..\"",
        "-\"That, too, can be considered a reason for this.\"",
        "\"Well, at the end of the day, I kinda had fun playing this and I want to check out more levels and see if I can beat the high scores for them, too.\"",
        "-\"I like your competitive spirit. Good luck with that.",
        "-\"Just know this - should you stumble across it, you'll never beat my high score for Picross Kingdom DX.",
        "-\"Lots of people tried, none of them succeded.\"",
        "\"Perhaps so, give me a couple of months and maybe I'll prove you wrong.\"",
        "-\"Hmph.\"",
        "\"...\"",
        "-\"Oh, one more thing.\"",
        "Master's voice startled me once more.",
        "-\"I made a mental promise to myself to treat someone who beats any of my picross high scores to a favorite latte of mine.",
        "-\"That doesn't mean that I'm willing to be friends with the person I treat, it's just my token of rivarly appreciation. What do you say?\"",
        "\"Wait, does that mean you've been observing every single person who played this machine... just to make them drink coffee with you?\"",
        "-\"Maybe I did, maybe I didn't. Is wanting to buy someone a freshly-brewn cup of latte a bad thing? Feel free to decline my offer if you wish.\"",
        "\"Ah, no-no-no, do count me in, I like free fancy coffee. Especially with cinnamon rolls.\"",
        "-\"Then I guess you can buy it yourself at the coffee shop, they have a good variety of tasty bakery as well.\"",
        "\"Right... oh, I almost forgot. My name is Omi. Pleased to meet your acquaintance, Master.\"",
        "-\"...Heh. Our future picross battles will sure be legendary, Omi-dono.\"",
        "...And that was how I had gotten myself into competitive picross.",
        "I did eventually beat Master's high score for Picross Kingdom DX, but it took me a lot of trial and error.",
        "Still, I'm glad I am now a part of the passionate community around picross, all thanks to the Notenogram machine I happened to play on that faithful day."
    };
}

StoryMode::StoryMode(bn::sprite_text_generator *stg) :
    GameState(),  bg_pattern(buildBG()), map(bg_pattern.map()),
    ending(0),script_ptr(0),symbol_ptr(0),line_ptr(0),
    _text(new text(stg))
{
    resetMap();
    PaperSheetPattern_Scrollable();
    refreshScreen(map);
    _text->redraw(story::intro[0]);
}

int StoryMode::drawIntroText()
{
    _text->output_Typewriter(story::intro[script_ptr], symbol_ptr, line_ptr, 0);
    if (bn::keypad::a_pressed())
    {
        line_ptr = 0;
        symbol_ptr = 0;
        if (++script_ptr >= 13)
        {
            script_ptr = 0;
            return 4;
        }
        _text->redraw(story::intro[script_ptr]);
    }
    return -1;
}

int StoryMode::drawEnd1Text()
{
    _text->output_Typewriter(story::end_nobisu[script_ptr], symbol_ptr, line_ptr, 0);
    if (bn::keypad::a_pressed())
    {
        line_ptr = 0;
        symbol_ptr = 0;
        if (++script_ptr >= 32)
        {
            script_ptr = 0;
            return 0;
        }
        _text->redraw(story::end_nobisu[script_ptr]);
    }
    return -1;
}

int StoryMode::drawEnd2Text()
{
    _text->output_Typewriter(story::end_notono[script_ptr], symbol_ptr, line_ptr, 0);
    if (bn::keypad::a_pressed())
    {
        line_ptr = 0;
        symbol_ptr = 0;
        if (++script_ptr >= 42)
        {
            script_ptr = 0;
            return 0;
        }
        _text->redraw(story::end_notono[script_ptr]);
    }
    return -1;
}

int StoryMode::drawEnd3Text()
{
    _text->output_Typewriter(story::end_nonogami[script_ptr], symbol_ptr, line_ptr, 0);
    if (bn::keypad::a_pressed())
    {
        line_ptr = 0;
        symbol_ptr = 0;
        if (++script_ptr >= 33)
        {
            script_ptr = 0;
            return 0;
        }
        _text->redraw(story::end_nonogami[script_ptr]);
    }
    return -1;
}

void StoryMode::setTextOutputOptions(int rank)
{
    if (rank == 1) ending = 3;
    if (rank > 1 && rank < 5) ending = 2;
    if (rank >= 5) ending = 1;
}

void StoryMode::setOptionalBG()
{
    if (line_ptr == 0 && symbol_ptr == 0)
    {
        if (ending == 1 && script_ptr == 3)
        {
            bg_foreground = bn::regular_bg_items::end_nobisu.create_bg_optional(0,0);
        }
        if (ending == 2 && script_ptr == 2)
        {
            bg_foreground = bn::regular_bg_items::end_notono.create_bg_optional(0,0);
        }
        if (ending == 3 && script_ptr == 4)
        {
            bg_foreground = bn::regular_bg_items::end_nonogami.create_bg_optional(0,0);
        }
    }
}

void StoryMode::eraseOptionalBG()
{
    /*if (ending == 1)
    {
        bg_foreground = bn::regular_bg_items::square.create_bg_optional(0,0);
    }
    if (ending == 2)
    {
        bg_foreground = bn::regular_bg_items::square.create_bg_optional(0,0);
    }
    if (ending == 3)
    {
        bg_foreground = bn::regular_bg_items::square.create_bg_optional(0,0);
    }*/
}

int StoryMode::updateState()
{
    switch (ending)
    {
    case 0:
        return drawIntroText();
        break;
    case 1:
        return drawEnd1Text();
        break;
    case 2:
        return drawEnd2Text();
        break;
    case 3:
        return drawEnd3Text();
        break;
    default:
        return -1;
    }
}

void StoryMode::toggleStateVisibility(bool show)
{
    bg_pattern.set_visible(show);
    _text->clear();
}

void StoryMode::toggleFadeEffect()
{

}

StoryMode::~StoryMode()
{
    _text->clear();
}


code:
#include "state_tutorial.h"

namespace tutorial
{
    bn::string_view text[] =
    {
        "Welcome to notenogram's tutorial mode! Press A to go forward, START to quit anytime.",
        "Chances are this is the first time you've ever gave a picross/nonogram game a go, and if so, this tutorial on Picross 101 is here to help!",
        //2 - basic rules
        "The objective of solving a picross puzzle is filling squares/cells in a finite rectangular grid according to numeric hints on the left and the top.",
        //3
        "Kinda like regular crossword, except you get a pixel picture as a result of completion (also \"picross\" = \"picture crossword\", so there you have it).",
        "Each numeric row on the left or column on top represents the number of colored squares in the grid's respective row or column.",
        "So if second row from the top says '5', it means second row from the top of the grid should have five colored squares.",
        "You can color a square by moving the pen cursor to it using the D-pad and pressing A button.",
        //7 - separated
        "If there's more than one number of colored squares hinted at, it means that these groups of squares are separated by at least one empty square.",
        //8
        "After all, if you color the squares without separating them, these groups will mash into one and won't exist separately anymore.",
        "This can be clearly observed when coloring squares in create mode, so feel free to play around with it as well!",
        "Also worth mentioning is that these groups should be placed from left to right row-wise and from top to bottom column-wise.",
        //11 - separated order
        "Example: a column/row of four cells is marked with '1' and '2' number hints.",
        //12
        "For column you need to color the topmost square first, then skip the next cell below, then color the remaining two cells below.",
        //13
        "Same with the row of cells - start with one colored square from the left, then skip the next one to the right, then color the rest.",
        //14
        "These empty squares can be crossed out (using B button) to easily keep track of them.",
        "That's all there is to the basic rules. Now, let's smoothly transition to our first Picross Strategy Tip!",
        "Picross Strategy Tip #1: always color squares that you know should be colored, and always cross out empty squares you know should be empty.",
        //17 tip 1 full
        "If you're solving a 5x5 puzzle and there's a row or a column marked with number '5', it means the entire row or column should be colored.",
        //18 empty
        "Likewise if a row or column has no number hints, this means there are no colored squares and you can cross them out entirely!",
        "This also can be used for the groups of different amounts of squares - ",
        "if they span the entire row or column and can be separated by no more than one empty square inbetween, they can be safely marked accordingly.",
        //21 group
        "Example - a group of '1', '2' and '3' colored square hints next to a row of 8 cells:",
        "first cell from the left colored, next crossed, next two colored, then next one crossed, then the remaining three colored.",
        "That's 6 colored squares + 2 empty = 8 squares in total in this row. A perfect fit!",
        "Here's another, more advanced way in how this tip can be used:",
        //25 advanced
        "if you have some squares from a group already colored and you know that there should be no more groups of colored squares before or after this one,",
        "count the remaining squares that could make up the full group from both sides and cross out the remaining ones.",
        "This is how you can try to guess where to color four squares on an 8-square row if you already have two colored in the middle.",
        "This brings us to Picross Strategy Tip #2: use cell counting for \"predicting\" colored cell placements!",
        //29 tip 2 single 0
        "Often you may not know where exactly to color squares in a certain row or column, in which case deduction might come in handy.",
        "Try and imagine fitting the colored squares on the row/column starting from the left/top, then try fitting them starting from the right/bottom.",
        "Then notice which squares overlap - these are the squares that should be colored!",
        "Cross markings can help you with imaginary placements, just be sure to erase them afterwards.",
        "Example: if you have a row of 10 squares and you need to fit '6' colored squares onto it, here's what you would do:",
        //34 1
        "1. Imagine coloring (but don't color yet) six squares starting from the leftmost one and cross out (for real) the remaining four.",
        "2. Then imagine coloring six squares from the rightmost one and cross out the remaining four closer to the left.",
        "3. The remaining empty squares can now be safely colored and the cross marks removed afterwards.",
        "While this tip can work for several square groups, too, it usually depends on the numbers you're working with.",
        //38 tip 2 multiple 0
        "A working example - predicting the placement of '5' and '3' colored squares on a row of 10 -",
        "first imagine coloring from the left with one square gap between,",
        "then imagine coloring from the right (but starting with group of three), again with one square inbetween.",
        "Here, the remaining squares should be colored 100%, but that may not work for other numbers.",
        "For example, this won't work with single colored square groups or for less than half of the row or column of colored squares.",
        "Last Picross Strategy Tip for now, and it's the only tip that can be applied for video games.",
        "Picross Strategy Tip #3: take risks!",
        "If you're not sure what to do and making a mistake isn't critical, take a guess and color the square you think should be colored!",
        "Whether you're right or not, you'll still gain some valuable information that will help you out in solving the puzzle.",
        "Truth be told, not every picross puzzle pattern is designed to be logically solveable 100% (and this game is no exception).",
        "As long as you expect minimal losses from it, some guesswork can definitely be of use.",
        "...Aaaaand that's it for now! Hope this was easy enough to understand for getting started with notenogram's 12x12 puzzles.",
        "With the basics now in your grasp, try solving puzzle #1 in the Things category, as it should be the easiest one of them!",
        "Let me know if there is anything I should clarify or add to this tutorial via game's itch.io page's comment section.",
        "I myself am still not very well-versed in picross, so in case I made a mistake or something be sure to notify me!",
        "Have fun д"
    };
}

TutorialMode::TutorialMode() : GameState(),
    bg_pattern(buildBG()), map(bg_pattern.map()),
    script_ptr(0),symbol_ptr(0),line_ptr(0),
    bg_redrawn(false), sprites_redrawn(false)
{
    initScreen();
}

TutorialMode::TutorialMode(bn::sprite_text_generator* stg) :
    GameState(), bg_pattern(buildBG()), map(bg_pattern.map()),
    _text(new text(stg)), script_ptr(0),symbol_ptr(0),line_ptr(0),
    bg_redrawn(false), sprites_redrawn(false)
{
    initScreen();
}

TutorialMode::TutorialMode(text* stg) :
    GameState(), bg_pattern(buildBG()), map(bg_pattern.map()),
    _text(new text(stg)), script_ptr(0),symbol_ptr(0),line_ptr(0),
    bg_redrawn(false), sprites_redrawn(false)
{
    initScreen();
}

void TutorialMode::initScreen()
{
    PaperSheetPattern_Holed();
    refreshScreen(map);
    _text->redraw(tutorial::text[0]);
    //sprite.valid(true);
    //sprite->set_visible(false);
}

int TutorialMode::drawText()
{
    _text->output_Typewriter(tutorial::text[script_ptr], symbol_ptr, line_ptr, 0, text_redraw_start_y);
    if (bn::keypad::a_pressed())
    {
        line_ptr = symbol_ptr = 0;
        if (++script_ptr >= 54)
        {
            script_ptr = 0;
            return 0;
        }
        else
        {
            switch (script_ptr)
            {
            case 2:
                BasicRulesExample(false,false);
                break;
            case 3:
                BasicRulesExample(true,false);
                break;
            case 5:
                BasicRulesExample(true,true);
                break;
            case 7:
                clearBG();
                SeparatedSquaresExample(true);
                break;
            case 8:
                SeparatedSquaresExample(false);
                break;
            case 11:
            case 15:
            case 20:
            case 24:
            case 28:
            case 37:
            case 42:
                clearBG();
                break;
            case 12:
                SeparatedSquaresOrderExample(false, false);
                break;
            case 13:
                SeparatedSquaresOrderExample(true, false);
                break;
            case 14:
                SeparatedSquaresOrderExample(false, true);
                break;
            case 17:
                clearBG();
                StrategyTipOne_Full();
                break;
            case 18:
                clearBG();
                StrategyTipOne_Empty();
                break;
            case 21:
                StrategyTipOne_Group();
                break;
            case 25:
                StrategyTipOne_Advanced();
                break;
            case 29:
                StrategyTipTwo_Single(0);
                break;
            case 34:
                StrategyTipTwo_Single(1);
                break;
            case 35:
                StrategyTipTwo_Single(2);
                break;
            case 36:
                StrategyTipTwo_Single(3);
                break;
            case 38:
                StrategyTipTwo_Multiple(0);
                break;
            case 39:
                StrategyTipTwo_Multiple(1);
                break;
            case 40:
                StrategyTipTwo_Multiple(2);
                break;
            case 41:
                StrategyTipTwo_Multiple(3);
                break;
            default:
                break;
            }
            refreshScreen(map);
            _text->redraw(tutorial::text[script_ptr]);
        }
    }
    if (bn::keypad::start_held())
        return 0;
    return -1;
}

//clearing the screen's area for puzzle examples
void TutorialMode::clearBG()
{
    sprite->set_visible(false);
    for (int i = map_redraw_start_x; i < map_redraw_width; ++i)
    {
        for (int j = map_redraw_start_y; j < map_redraw_height; ++j)
        {
            setCellTile(i, j, 1);
        }
    }
}

//heart picross puzzle
//parameter - checks whether to color in the solution
void TutorialMode::BasicRulesExample(bool squares_colored, bool highlight)
{
    //row hints
    setCellTile(12, 10, 4);
    setCellTile(13, 10, 6);
    setCellTile(14, 10, 6);
    setCellTile(15, 10, 6);
    setCellTile(16, 10, 4);
    //column hints
    setCellTile(10, 11, 4);
    setCellTile(11, 11, 4);
    setCellTile(11, 12, 8);
    setCellTile(11, 13, 6);
    setCellTile(11, 14, 4);
    //puzzle solution
    if (squares_colored)
    {
        setCellTile(12, 12, 3);
        setCellTile(13, 11, 3);
        setCellTile(13, 12, 3);
        setCellTile(13, 13, 3);
        setCellTile(14, 12, 3);
        setCellTile(14, 13, 3);
        setCellTile(14, 14, 3);
        setCellTile(15, 11, 3);
        setCellTile(15, 12, 3);
        setCellTile(15, 13, 3);
        setCellTile(16, 12, 3);
    }

    if (highlight)
    {
        sprite = buildSprite(bn::sprite_items::bar_horz, -9, -8);
        sprite->set_visible(true);
    }
}
//single-row example of separated square groups
//parameter - flag that decides which example to draw (upper is correct, lower is not)
void TutorialMode::SeparatedSquaresExample(bool upper_row)
{
    int row_index = upper_row ? 10 : 14;
    //row hints
    setCellTile(12, row_index, 4);
    setCellTile(13, row_index, 4);
    setCellTile(15, row_index, 4);
    setCellTile(16, row_index, 4);
    //column hints
    setCellTile(10, row_index+1, 5);
    setCellTile(11, row_index+1, 5);
    //colored cells
    setCellTile(12, row_index+1, 3);
    setCellTile(13, row_index+1, 3);
    setCellTile(15, row_index+1, 3);
    setCellTile((upper_row ? 16 : 14), row_index+1, 3);

    if (!upper_row)
    {
        sprite = buildSprite(bn::sprite_items::good, 40, -22);
        sprite->set_visible(true);
    }
}
//single-row/column example of ordering separated square groups
//parameter - flags that decide which example to draw (row or column or crossed squares)
void TutorialMode::SeparatedSquaresOrderExample(bool row, bool cross)
{
    if (row)
    {
        setCellTile(13, 10, 4);
        setCellTile(14, 10, 5);
        setCellTile(15, 10, 3);
        setCellTile(17, 10, 3);
        setCellTile(18, 10, 3);
        sprite = buildSprite(bn::sprite_items::arrow_row, -9, -8);
        sprite->set_visible(true);
    }
    else if (cross)
    {
        setCellTile(16, 10, 2);
        setCellTile(10, 13, 2);
    }
    else
    {

        setCellTile(10, 10, 4);
        setCellTile(10, 11, 5);
        setCellTile(10, 12, 3);
        setCellTile(10, 14, 3);
        setCellTile(10, 15, 3);
        sprite = buildSprite(bn::sprite_items::arrow_col, -9, -8);
        sprite->set_visible(true);
    }

}
//plus sign picture puzzle
void TutorialMode::StrategyTipOne_Full()
{
    //numeric hints
    setCellTile(12, 10, 4);
    setCellTile(13, 10, 4);
    setCellTile(15, 10, 4);
    setCellTile(16, 10, 4);
    setCellTile(14, 10, 8);

    setCellTile(11, 11, 4);
    setCellTile(11, 12, 4);
    setCellTile(11, 13, 8);
    setCellTile(11, 14, 4);
    setCellTile(11, 15, 4);

    //colored square column
    setCellTile(14, 11, 3);
    setCellTile(14, 12, 3);
    setCellTile(14, 13, 3);
    setCellTile(14, 14, 3);
    setCellTile(14, 15, 3);

    //colored square row
    setCellTile(12, 13, 3);
    setCellTile(13, 13, 3);
    setCellTile(14, 13, 3);
    setCellTile(15, 13, 3);
    setCellTile(16, 13, 3);
}

//inverted plus sign picture puzzle
void TutorialMode::StrategyTipOne_Empty()
{
    //numeric hints
    setCellTile(12, 10, 5);
    setCellTile(13, 10, 5);
    setCellTile(15, 10, 5);
    setCellTile(16, 10, 5);

    setCellTile(11, 11, 5);
    setCellTile(11, 12, 5);
    setCellTile(11, 14, 5);
    setCellTile(11, 15, 5);

    //empty column
    setCellTile(14, 11, 2);
    setCellTile(14, 12, 2);
    setCellTile(14, 13, 2);
    setCellTile(14, 14, 2);
    setCellTile(14, 15, 2);

    //empty row
    setCellTile(12, 13, 2);
    setCellTile(13, 13, 2);
    setCellTile(14, 13, 2);
    setCellTile(15, 13, 2);
    setCellTile(16, 13, 2);
}
//one+two+three squares on 8 square row
void TutorialMode::StrategyTipOne_Group()
{
    //numeric hints
    setCellTile(9, 11, 4);
    setCellTile(10, 11, 5);
    setCellTile(11, 11, 6);

    //row
    setCellTile(12, 11, 3);
    setCellTile(13, 11, 2);
    setCellTile(14, 11, 3);
    setCellTile(15, 11, 3);
    setCellTile(16, 11, 2);
    setCellTile(17, 11, 3);
    setCellTile(18, 11, 3);
    setCellTile(19, 11, 3);
}
//four squares on 8 square row
void TutorialMode::StrategyTipOne_Advanced()
{
    //numeric hints
    setCellTile(10, 11, 7);

    //row
    setCellTile(11, 11, 2);
    setCellTile(14, 11, 3);
    setCellTile(15, 11, 3);
    setCellTile(18, 11, 2);
}
//10x3 puzzle with 6 colored squares in the middle
void TutorialMode::StrategyTipTwo_Single(int step)
{
    switch (step)
    {
    //coloring imaginary squares
    case 1:
        for (int i = 16; i < 20; ++i)
            setCellTile(i, 11, 2);
        break;
    case 2:
        for (int i = 10; i < 14; ++i)
            setCellTile(i, 11, 2);
        break;
    //coloring real squares
    case 3:
        for (int i = 10; i < 20; ++i)
            setCellTile(i, 11, 1);
        setCellTile(14, 11, 3);
        setCellTile(15, 11, 3);
        break;
    default:
        //hints
        for (int i = 10; i < 20; ++i)
            setCellTile(i, 9, 4);
        setCellTile(9, 10, 6);
        setCellTile(9, 11, 9);
        setCellTile(9, 12, 4);
        break;
    }
}

//10x3 puzzle with 4 and 3 colored squares in the middle
void TutorialMode::StrategyTipTwo_Multiple(int step)
{
    switch (step)
    {
    //coloring imaginary squares
    case 1:
        setCellTile(15, 11, 2);
        setCellTile(19, 11, 2);
        break;
    case 2:
        setCellTile(10, 11, 2);
        setCellTile(16, 11, 2);
        break;
    //coloring real squares
    case 3:
        setCellTile(11, 11, 3);
        setCellTile(12, 11, 3);
        setCellTile(13, 11, 3);
        setCellTile(14, 11, 3);
        setCellTile(17, 11, 3);
        setCellTile(18, 11, 3);
        break;
    default:
        //hints
        for (int i = 10; i < 20; ++i)
            setCellTile(i, 9, 4);
        setCellTile(9, 10, 4);
        setCellTile(8, 11, 8);
        setCellTile(9, 11, 6);
        setCellTile(9, 12, 5);
        break;
    }
}

int TutorialMode::updateState()
{
    return drawText();
}

void TutorialMode::toggleStateVisibility(bool show)
{
    _text->clear();
}

void TutorialMode::toggleFadeEffect()
{
    _text->clear();
}

TutorialMode::~TutorialMode()
{
    _text->clear();
}


code:
#include "tools.h"

namespace tool
{
    int cellX2Screen(int x, int delta_x)
    {
        return (x * 8) - (32 * 4) + delta_x;
    }

    int cellY2Screen(int y, int delta_y)
    {
        return (y * 8) - (32 * 4) + delta_y;
    }

    int cellX2Screen(bn::fixed x, int delta_x)
    {
        return (x.integer() * 8) - (32 * 4) + delta_x;
    }

    int cellY2Screen(bn::fixed y, int delta_y)
    {
        return (y.integer() * 8) - (32 * 4) + delta_y;
    }

    int writeBit2Array(bn::array<char, 18> & array_, int index, int data)
    {
        if (index >= 0 && index < 144)
        {
            int int_index = index/8;
            int bit_index = index%8;
            array_[int_index] |= data << bit_index;
            return 1;
        }
        else return -1;
    }

    bool validateSRAMData(int offset)
    {
        bn::array<int8_t, 18> data_check;
        bn::sram::read_offset(data_check, offset);
        int sum_of_zeroes = 0;
        int sum_of_ffs = 0;
        for (int i = 0; i < 18; ++i)
        {
            if (data_check[i] == 0) ++sum_of_zeroes;
            else if (data_check[i] == -1) ++sum_of_ffs;
            ++offset;
        }
        return (sum_of_zeroes < 18 && sum_of_ffs < 18) ? true : false;
    }

    void boolArray2UInt(bn::array<bool, 144> const &array_of_bools, int offset)
    {
        for (int i = 0; i < 18; ++i)
        {
            uint8_t data = 0;
            for (int j = 0; j < 8; ++j)
            {
                data |= array_of_bools[8*i+j] << j;
            }
            bn::sram::write_offset(data, offset);
            ++offset;
        }
    }

    void UInt2BoolArray(bn::array<bool, 144> &array_of_bools, int offset)
    {
        for (int i = 0; i < 18; ++i)
        {
            uint8_t data = 0;
            bn::sram::read_offset(data, offset);
            for (int j = 0; j < 8; ++j)
            {
                uint8_t temp = data;
                array_of_bools[8*i+j] = (temp >> j)&1;
            }
            ++offset;
        }
    }

}


code:
#include "vn_text_output.h"

text::text()
{
    BN_LOG("text output class default constructor");
}

text::text(bn::sprite_text_generator *_text_gnrtr)
{
    text_gnrtr = _text_gnrtr;
    BN_LOG("text output class param constructor");
}

text::text(bn::string_view const& script_line, bn::sprite_text_generator *_text_gnrtr)
{
    initTextOutput(script_line);
    text_gnrtr = _text_gnrtr;
    BN_LOG("text output class param constructor 2");
}

text::text(text* text_copy)
{
    this->text_gnrtr = text_copy->text_gnrtr;
    BN_LOG("text output class copy constructor");
}

void text::initTextOutput(bn::string_view const& script_line)
{
    line_counter = script_line.length() / MAX_WIDTH;
    //quick and dirty fix for the "disappearing last line" bug that the code above produces
    if (line_counter == 0 || line_counter < MAX_ROWS_8) ++line_counter;

    breakIntoMultipleLines(script_line);
}

bool text::output_Typewriter(bn::string_view const &line, int &symbol_ptr, int &line_ptr, int const &frames2skip)
{
    if (line_ptr >= line_counter)
    {
        return false;
    }

    symbols.clear();
    if (line_ptr > 0)
        for (int i = 0; i < line_ptr; ++i)
        {
            outputSingleLine(8*i+TEXT_Y, line.substr(line_starters.at(i), line_breakers.at(i)));
        }
    outputSingleLine(8*line_ptr+TEXT_Y, line.substr(line_starters.at(line_ptr), symbol_ptr));

    //indicates start of a new line
    if (++symbol_ptr > line_breakers.at(line_ptr))
    {
        symbol_ptr = 0;
        ++line_ptr;
    }
    return true;
}

bool text::output_Typewriter(bn::string_view const &line, int &symbol_ptr, int &line_ptr, int const &frames2skip, int const& y_pos)
{
    if (line_ptr >= line_counter)
    {
        return false;
    }

    symbols.clear();
    if (line_ptr > 0)
        for (int i = 0; i < line_ptr; ++i)
        {
            outputSingleLine(8*i+y_pos, line.substr(line_starters.at(i), line_breakers.at(i)));
        }
    outputSingleLine(8*line_ptr+y_pos, line.substr(line_starters.at(line_ptr), symbol_ptr));

    //indicates start of a new line
    if (++symbol_ptr > line_breakers.at(line_ptr))
    {
        symbol_ptr = 0;
        ++line_ptr;
    }
    return true;
}

bool text::output_NoEffect(bn::string_view const &line)
{
    symbols.clear();
    for (int i = 0; i < line_counter; ++i)
    {
        outputSingleLine(8*i+TEXT_Y, line.substr(line_starters.at(i), line_breakers.at(i)));
    }
    line_starters.clear();
    line_breakers.clear();
    return false;
}

void text::outputSingleLine(bn::string_view const &line)
{
    text_gnrtr->generate(TEXT_X, TEXT_Y, line, symbols);
}

void text::outputSingleLine(int y, bn::string_view const &line)
{
    text_gnrtr->generate(TEXT_X, y, line, symbols);
}

void text::outputSingleLine(int x, int y, bn::string_view const &line)
{
    text_gnrtr->generate(x, y, line, symbols);
}

void text::breakIntoMultipleLines(bn::string_view const &lines)
{
    int start = 0;
    int substr_length = 0;
    for(int i = 0; i < line_counter && i < MAX_ROWS_8; ++i)
    {
        for (int j = start, k = 0; j <= (start + MAX_WIDTH) && j < lines.length(); ++j, ++k)
        {
            if (lines.at(j) == ' ' || lines.at(j) == lines.back())
            {
                substr_length = k + 1;
            }
            if (substr_length == 0 && k >= MAX_WIDTH)
            {
                substr_length = MAX_WIDTH;
            }
        }
        line_starters.push_back(start);
        line_breakers.push_back(substr_length);
        start += substr_length;
    }
}

bn::fixed_point text::getLastSymbolCoords()
{
    if (!line_breakers.empty())
    {
        bn::fixed_point blinker_point(TEXT_X+8*line_breakers[line_counter-1]+2, TEXT_Y+8*(line_counter-1));
        return blinker_point;
    }
}

void text::clear()
{
    symbols.clear();
    BN_LOG("stg symbols cleared!");
}

void text::redraw(bn::string_view const& script_line)
{
    symbols.clear();
    line_starters.clear();
    line_breakers.clear();

    line_counter = script_line.length() / MAX_WIDTH;
    if (line_counter == 0 || line_counter < MAX_ROWS_8) ++line_counter;

    breakIntoMultipleLines(script_line);
}

text::~text()
{
    symbols.clear();
    line_starters.clear();
    line_breakers.clear();
    delete text_gnrtr;
    BN_LOG("text output class destructor");
}


code:
# dmenu - dynamic menu
# See LICENSE file for copyright and license details.

include config.mk

SRC = drw.c dmenu.c stest.c util.c
OBJ = $(SRC:.c=.o)

all: options dmenu stest

options:
	@echo dmenu build options:
	@echo "CFLAGS   = $(CFLAGS)"
	@echo "LDFLAGS  = $(LDFLAGS)"
	@echo "CC       = $(CC)"

.c.o:
	$(CC) -c $(CFLAGS) $<

config.h:
	cp config.def.h $@

$(OBJ): arg.h config.h config.mk drw.h

dmenu: dmenu.o drw.o util.o
	$(CC) -o $@ dmenu.o drw.o util.o $(LDFLAGS)

stest: stest.o
	$(CC) -o $@ stest.o $(LDFLAGS)

clean:
	rm -f dmenu stest $(OBJ) dmenu-$(VERSION).tar.gz

dist: clean
	mkdir -p dmenu-$(VERSION)
	cp LICENSE Makefile README arg.h config.def.h config.mk dmenu.1\
		drw.h util.h dmenu_path dmenu_run stest.1 $(SRC)\
		dmenu-$(VERSION)
	tar -cf dmenu-$(VERSION).tar dmenu-$(VERSION)
	gzip dmenu-$(VERSION).tar
	rm -rf dmenu-$(VERSION)

install: all
	mkdir -p $(DESTDIR)$(PREFIX)/bin
	cp -f dmenu dmenu_path dmenu_run stest $(DESTDIR)$(PREFIX)/bin
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_path
	chmod 755 $(DESTDIR)$(PREFIX)/bin/dmenu_run
	chmod 755 $(DESTDIR)$(PREFIX)/bin/stest
	mkdir -p $(DESTDIR)$(MANPREFIX)/man1
	sed "s/VERSION/$(VERSION)/g" < dmenu.1 > $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
	sed "s/VERSION/$(VERSION)/g" < stest.1 > $(DESTDIR)$(MANPREFIX)/man1/stest.1
	chmod 644 $(DESTDIR)$(MANPREFIX)/man1/dmenu.1
	chmod 644 $(DESTDIR)$(MANPREFIX)/man1/stest.1

uninstall:
	rm -f $(DESTDIR)$(PREFIX)/bin/dmenu\
		$(DESTDIR)$(PREFIX)/bin/dmenu_path\
		$(DESTDIR)$(PREFIX)/bin/dmenu_run\
		$(DESTDIR)$(PREFIX)/bin/stest\
		$(DESTDIR)$(MANPREFIX)/man1/dmenu.1\
		$(DESTDIR)$(MANPREFIX)/man1/stest.1

.PHONY: all options clean dist install uninstall

repo_name: jandourekjaroslav/SucklessToolsBuild

license: mit


code:
dmenu - dynamic menu
====================
dmenu is an efficient dynamic menu for X.

Requirements
------------
In order to build dmenu you need the Xlib header files.

Installation
------------
Edit config.mk to match your local setup (dmenu is installed into
the /usr/local namespace by default).

Afterwards enter the following command to build and install dmenu
(if necessary as root):

    make clean install

Running dmenu
-------------
See the man page for details.


code:
/*
 * Copy me if you can.
 * by 20h
 */

#ifndef ARG_H__
#define ARG_H__

extern char *argv0;

/* use main(int argc, char *argv[]) */
#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\
					argv[0] && argv[0][0] == '-'\
					&& argv[0][1];\
					argc--, argv++) {\
				char argc_;\
				char **argv_;\
				int brk_;\
				if (argv[0][1] == '-' && argv[0][2] == '\0') {\
					argv++;\
					argc--;\
					break;\
				}\
				for (brk_ = 0, argv[0]++, argv_ = argv;\
						argv[0][0] && !brk_;\
						argv[0]++) {\
					if (argv_ != argv)\
						break;\
					argc_ = argv[0][0];\
					switch (argc_)

#define ARGEND			}\
			}

#define ARGC()		argc_

#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\
				((x), abort(), (char *)0) :\
				(brk_ = 1, (argv[0][1] != '\0')?\
					(&argv[0][1]) :\
					(argc--, argv++, argv[0])))

#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\
				(char *)0 :\
				(brk_ = 1, (argv[0][1] != '\0')?\
					(&argv[0][1]) :\
					(argc--, argv++, argv[0])))

#endif


code:
/* See LICENSE file for copyright and license details. */
/* Default settings; can be overriden by command line. */

static int topbar = 1;                      /* -b  option; if 0, dmenu appears at bottom     */
/* -fn option overrides fonts[0]; default X11 font or font set */
static const char *fonts[] = {
	"monospace:size=10"
};
static const char *prompt      = NULL;      /* -p  option; prompt to the left of input field */
static const char *colors[SchemeLast][2] = {
	/*     fg         bg       */
	[SchemeNorm] = { "#bbbbbb", "#222222" },
	[SchemeSel] = { "#eeeeee", "#005577" },
	[SchemeOut] = { "#000000", "#00ffff" },
};
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
static unsigned int lines      = 0;

/*
 * Characters not considered part of a word while deleting words
 * for example: " /?\"&[]"
 */
static const char worddelimiters[] = " ";


code:
# dmenu version
VERSION = 5.0

# paths
PREFIX = /usr/local
MANPREFIX = $(PREFIX)/share/man

X11INC = /usr/X11R6/include
X11LIB = /usr/X11R6/lib

# Xinerama, comment if you don't want it
XINERAMALIBS  = -lXinerama
XINERAMAFLAGS = -DXINERAMA

# freetype
FREETYPELIBS = -lfontconfig -lXft
FREETYPEINC = /usr/include/freetype2
# OpenBSD (uncomment)
#FREETYPEINC = $(X11INC)/freetype2

# includes and libs
INCS = -I$(X11INC) -I$(FREETYPEINC)
LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)

# flags
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
CFLAGS   = -std=c99 -pedantic -Wall -Os $(INCS) $(CPPFLAGS)
LDFLAGS  = $(LIBS)

# compiler and linker
CC = cc


code:
.TH DMENU 1 dmenu\-VERSION
.SH NAME
dmenu \- dynamic menu
.SH SYNOPSIS
.B dmenu
.RB [ \-bfiv ]
.RB [ \-l
.IR lines ]
.RB [ \-m
.IR monitor ]
.RB [ \-p
.IR prompt ]
.RB [ \-fn
.IR font ]
.RB [ \-nb
.IR color ]
.RB [ \-nf
.IR color ]
.RB [ \-sb
.IR color ]
.RB [ \-sf
.IR color ]
.RB [ \-w
.IR windowid ]
.P
.BR dmenu_run " ..."
.SH DESCRIPTION
.B dmenu
is a dynamic menu for X, which reads a list of newline\-separated items from
stdin.  When the user selects an item and presses Return, their choice is printed
to stdout and dmenu terminates.  Entering text will narrow the items to those
matching the tokens in the input.
.P
.B dmenu_run
is a script used by
.IR dwm (1)
which lists programs in the user's $PATH and runs the result in their $SHELL.
.SH OPTIONS
.TP
.B \-b
dmenu appears at the bottom of the screen.
.TP
.B \-f
dmenu grabs the keyboard before reading stdin if not reading from a tty. This
is faster, but will lock up X until stdin reaches end\-of\-file.
.TP
.B \-i
dmenu matches menu items case insensitively.
.TP
.BI \-l " lines"
dmenu lists items vertically, with the given number of lines.
.TP
.BI \-m " monitor"
dmenu is displayed on the monitor number supplied. Monitor numbers are starting
from 0.
.TP
.BI \-p " prompt"
defines the prompt to be displayed to the left of the input field.
.TP
.BI \-fn " font"
defines the font or font set used.
.TP
.BI \-nb " color"
defines the normal background color.
.IR #RGB ,
.IR #RRGGBB ,
and X color names are supported.
.TP
.BI \-nf " color"
defines the normal foreground color.
.TP
.BI \-sb " color"
defines the selected background color.
.TP
.BI \-sf " color"
defines the selected foreground color.
.TP
.B \-v
prints version information to stdout, then exits.
.TP
.BI \-w " windowid"
embed into windowid.
.SH USAGE
dmenu is completely controlled by the keyboard.  Items are selected using the
arrow keys, page up, page down, home, and end.
.TP
.B Tab
Copy the selected item to the input field.
.TP
.B Return
Confirm selection.  Prints the selected item to stdout and exits, returning
success.
.TP
.B Ctrl-Return
Confirm selection.  Prints the selected item to stdout and continues.
.TP
.B Shift\-Return
Confirm input.  Prints the input text to stdout and exits, returning success.
.TP
.B Escape
Exit without selecting an item, returning failure.
.TP
.B Ctrl-Left
Move cursor to the start of the current word
.TP
.B Ctrl-Right
Move cursor to the end of the current word
.TP
.B C\-a
Home
.TP
.B C\-b
Left
.TP
.B C\-c
Escape
.TP
.B C\-d
Delete
.TP
.B C\-e
End
.TP
.B C\-f
Right
.TP
.B C\-g
Escape
.TP
.B C\-h
Backspace
.TP
.B C\-i
Tab
.TP
.B C\-j
Return
.TP
.B C\-J
Shift-Return
.TP
.B C\-k
Delete line right
.TP
.B C\-m
Return
.TP
.B C\-M
Shift-Return
.TP
.B C\-n
Down
.TP
.B C\-p
Up
.TP
.B C\-u
Delete line left
.TP
.B C\-w
Delete word left
.TP
.B C\-y
Paste from primary X selection
.TP
.B C\-Y
Paste from X clipboard
.TP
.B M\-b
Move cursor to the start of the current word
.TP
.B M\-f
Move cursor to the end of the current word
.TP
.B M\-g
Home
.TP
.B M\-G
End
.TP
.B M\-h
Up
.TP
.B M\-j
Page down
.TP
.B M\-k
Page up
.TP
.B M\-l
Down
.SH SEE ALSO
.IR dwm (1),
.IR stest (1)


code:
/* See LICENSE file for copyright and license details. */
#include <ctype.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <X11/Xutil.h>
#ifdef XINERAMA
#include <X11/extensions/Xinerama.h>
#endif
#include <X11/Xft/Xft.h>

#include "drw.h"
#include "util.h"

/* macros */
#define INTERSECT(x,y,w,h,r)  (MAX(0, MIN((x)+(w),(r).x_org+(r).width)  - MAX((x),(r).x_org)) \
                             * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
#define LENGTH(X)             (sizeof X / sizeof X[0])
#define TEXTW(X)              (drw_fontset_getwidth(drw, (X)) + lrpad)

/* enums */
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */

struct item {
	char *text;
	struct item *left, *right;
	int out;
};

static char text[BUFSIZ] = "";
static char *embed;
static int bh, mw, mh;
static int inputw = 0, promptw;
static int lrpad; /* sum of left and right padding */
static size_t cursor;
static struct item *items = NULL;
static struct item *matches, *matchend;
static struct item *prev, *curr, *next, *sel;
static int mon = -1, screen;

static Atom clip, utf8;
static Display *dpy;
static Window root, parentwin, win;
static XIC xic;

static Drw *drw;
static Clr *scheme[SchemeLast];

#include "config.h"

static int (*fstrncmp)(const char *, const char *, size_t) = strncmp;
static char *(*fstrstr)(const char *, const char *) = strstr;

static void
appenditem(struct item *item, struct item **list, struct item **last)
{
	if (*last)
		(*last)->right = item;
	else
		*list = item;

	item->left = *last;
	item->right = NULL;
	*last = item;
}

static void
calcoffsets(void)
{
	int i, n;

	if (lines > 0)
		n = lines * bh;
	else
		n = mw - (promptw + inputw + TEXTW("<") + TEXTW(">"));
	/* calculate which items will begin the next page and previous page */
	for (i = 0, next = curr; next; next = next->right)
		if ((i += (lines > 0) ? bh : MIN(TEXTW(next->text), n)) > n)
			break;
	for (i = 0, prev = curr; prev && prev->left; prev = prev->left)
		if ((i += (lines > 0) ? bh : MIN(TEXTW(prev->left->text), n)) > n)
			break;
}

static void
cleanup(void)
{
	size_t i;

	XUngrabKey(dpy, AnyKey, AnyModifier, root);
	for (i = 0; i < SchemeLast; i++)
		free(scheme[i]);
	drw_free(drw);
	XSync(dpy, False);
	XCloseDisplay(dpy);
}

static char *
cistrstr(const char *s, const char *sub)
{
	size_t len;

	for (len = strlen(sub); *s; s++)
		if (!strncasecmp(s, sub, len))
			return (char *)s;
	return NULL;
}

static int
drawitem(struct item *item, int x, int y, int w)
{
	if (item == sel)
		drw_setscheme(drw, scheme[SchemeSel]);
	else if (item->out)
		drw_setscheme(drw, scheme[SchemeOut]);
	else
		drw_setscheme(drw, scheme[SchemeNorm]);

	return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
}

static void
drawmenu(void)
{
	unsigned int curpos;
	struct item *item;
	int x = 0, y = 0, w;

	drw_setscheme(drw, scheme[SchemeNorm]);
	drw_rect(drw, 0, 0, mw, mh, 1, 1);

	if (prompt && *prompt) {
		drw_setscheme(drw, scheme[SchemeSel]);
		x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0);
	}
	/* draw input field */
	w = (lines > 0 || !matches) ? mw - x : inputw;
	drw_setscheme(drw, scheme[SchemeNorm]);
	drw_text(drw, x, 0, w, bh, lrpad / 2, text, 0);

	curpos = TEXTW(text) - TEXTW(&text[cursor]);
	if ((curpos += lrpad / 2 - 1) < w) {
		drw_setscheme(drw, scheme[SchemeNorm]);
		drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
	}

	if (lines > 0) {
		/* draw vertical list */
		for (item = curr; item != next; item = item->right)
			drawitem(item, x, y += bh, mw - x);
	} else if (matches) {
		/* draw horizontal list */
		x += inputw;
		w = TEXTW("<");
		if (curr->left) {
			drw_setscheme(drw, scheme[SchemeNorm]);
			drw_text(drw, x, 0, w, bh, lrpad / 2, "<", 0);
		}
		x += w;
		for (item = curr; item != next; item = item->right)
			x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
		if (next) {
			w = TEXTW(">");
			drw_setscheme(drw, scheme[SchemeNorm]);
			drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
		}
	}
	drw_map(drw, win, 0, 0, mw, mh);
}

static void
grabfocus(void)
{
	struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000  };
	Window focuswin;
	int i, revertwin;

	for (i = 0; i < 100; ++i) {
		XGetInputFocus(dpy, &focuswin, &revertwin);
		if (focuswin == win)
			return;
		XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
		nanosleep(&ts, NULL);
	}
	die("cannot grab focus");
}

static void
grabkeyboard(void)
{
	struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000  };
	int i;

	if (embed)
		return;
	/* try to grab keyboard, we may have to wait for another process to ungrab */
	for (i = 0; i < 1000; i++) {
		if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync,
		                  GrabModeAsync, CurrentTime) == GrabSuccess)
			return;
		nanosleep(&ts, NULL);
	}
	die("cannot grab keyboard");
}

static void
match(void)
{
	static char **tokv = NULL;
	static int tokn = 0;

	char buf[sizeof text], *s;
	int i, tokc = 0;
	size_t len, textsize;
	struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;

	strcpy(buf, text);
	/* separate input text into tokens to be matched individually */
	for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
		if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv)))
			die("cannot realloc %u bytes:", tokn * sizeof *tokv);
	len = tokc ? strlen(tokv[0]) : 0;

	matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
	textsize = strlen(text) + 1;
	for (item = items; item && item->text; item++) {
		for (i = 0; i < tokc; i++)
			if (!fstrstr(item->text, tokv[i]))
				break;
		if (i != tokc) /* not all tokens match */
			continue;
		/* exact matches go first, then prefixes, then substrings */
		if (!tokc || !fstrncmp(text, item->text, textsize))
			appenditem(item, &matches, &matchend);
		else if (!fstrncmp(tokv[0], item->text, len))
			appenditem(item, &lprefix, &prefixend);
		else
			appenditem(item, &lsubstr, &substrend);
	}
	if (lprefix) {
		if (matches) {
			matchend->right = lprefix;
			lprefix->left = matchend;
		} else
			matches = lprefix;
		matchend = prefixend;
	}
	if (lsubstr) {
		if (matches) {
			matchend->right = lsubstr;
			lsubstr->left = matchend;
		} else
			matches = lsubstr;
		matchend = substrend;
	}
	curr = sel = matches;
	calcoffsets();
}

static void
insert(const char *str, ssize_t n)
{
	if (strlen(text) + n > sizeof text - 1)
		return;
	/* move existing text out of the way, insert new text, and update cursor */
	memmove(&text[cursor + n], &text[cursor], sizeof text - cursor - MAX(n, 0));
	if (n > 0)
		memcpy(&text[cursor], str, n);
	cursor += n;
	match();
}

static size_t
nextrune(int inc)
{
	ssize_t n;

	/* return location of next utf8 rune in the given direction (+1 or -1) */
	for (n = cursor + inc; n + inc >= 0 && (text[n] & 0xc0) == 0x80; n += inc)
		;
	return n;
}

static void
movewordedge(int dir)
{
	if (dir < 0) { /* move cursor to the start of the word*/
		while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
			cursor = nextrune(-1);
		while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
			cursor = nextrune(-1);
	} else { /* move cursor to the end of the word */
		while (text[cursor] && strchr(worddelimiters, text[cursor]))
			cursor = nextrune(+1);
		while (text[cursor] && !strchr(worddelimiters, text[cursor]))
			cursor = nextrune(+1);
	}
}

static void
keypress(XKeyEvent *ev)
{
	char buf[32];
	int len;
	KeySym ksym;
	Status status;

	len = XmbLookupString(xic, ev, buf, sizeof buf, &ksym, &status);
	switch (status) {
	default: /* XLookupNone, XBufferOverflow */
		return;
	case XLookupChars:
		goto insert;
	case XLookupKeySym:
	case XLookupBoth:
		break;
	}

	if (ev->state & ControlMask) {
		switch(ksym) {
		case XK_a: ksym = XK_Home;      break;
		case XK_b: ksym = XK_Left;      break;
		case XK_c: ksym = XK_Escape;    break;
		case XK_d: ksym = XK_Delete;    break;
		case XK_e: ksym = XK_End;       break;
		case XK_f: ksym = XK_Right;     break;
		case XK_g: ksym = XK_Escape;    break;
		case XK_h: ksym = XK_BackSpace; break;
		case XK_i: ksym = XK_Tab;       break;
		case XK_j: /* fallthrough */
		case XK_J: /* fallthrough */
		case XK_m: /* fallthrough */
		case XK_M: ksym = XK_Return; ev->state &= ~ControlMask; break;
		case XK_n: ksym = XK_Down;      break;
		case XK_p: ksym = XK_Up;        break;

		case XK_k: /* delete right */
			text[cursor] = '\0';
			match();
			break;
		case XK_u: /* delete left */
			insert(NULL, 0 - cursor);
			break;
		case XK_w: /* delete word */
			while (cursor > 0 && strchr(worddelimiters, text[nextrune(-1)]))
				insert(NULL, nextrune(-1) - cursor);
			while (cursor > 0 && !strchr(worddelimiters, text[nextrune(-1)]))
				insert(NULL, nextrune(-1) - cursor);
			break;
		case XK_y: /* paste selection */
		case XK_Y:
			XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
			                  utf8, utf8, win, CurrentTime);
			return;
		case XK_Left:
			movewordedge(-1);
			goto draw;
		case XK_Right:
			movewordedge(+1);
			goto draw;
		case XK_Return:
		case XK_KP_Enter:
			break;
		case XK_bracketleft:
			cleanup();
			exit(1);
		default:
			return;
		}
	} else if (ev->state & Mod1Mask) {
		switch(ksym) {
		case XK_b:
			movewordedge(-1);
			goto draw;
		case XK_f:
			movewordedge(+1);
			goto draw;
		case XK_g: ksym = XK_Home;  break;
		case XK_G: ksym = XK_End;   break;
		case XK_h: ksym = XK_Up;    break;
		case XK_j: ksym = XK_Next;  break;
		case XK_k: ksym = XK_Prior; break;
		case XK_l: ksym = XK_Down;  break;
		default:
			return;
		}
	}

	switch(ksym) {
	default:
insert:
		if (!iscntrl(*buf))
			insert(buf, len);
		break;
	case XK_Delete:
		if (text[cursor] == '\0')
			return;
		cursor = nextrune(+1);
		/* fallthrough */
	case XK_BackSpace:
		if (cursor == 0)
			return;
		insert(NULL, nextrune(-1) - cursor);
		break;
	case XK_End:
		if (text[cursor] != '\0') {
			cursor = strlen(text);
			break;
		}
		if (next) {
			/* jump to end of list and position items in reverse */
			curr = matchend;
			calcoffsets();
			curr = prev;
			calcoffsets();
			while (next && (curr = curr->right))
				calcoffsets();
		}
		sel = matchend;
		break;
	case XK_Escape:
		cleanup();
		exit(1);
	case XK_Home:
		if (sel == matches) {
			cursor = 0;
			break;
		}
		sel = curr = matches;
		calcoffsets();
		break;
	case XK_Left:
		if (cursor > 0 && (!sel || !sel->left || lines > 0)) {
			cursor = nextrune(-1);
			break;
		}
		if (lines > 0)
			return;
		/* fallthrough */
	case XK_Up:
		if (sel && sel->left && (sel = sel->left)->right == curr) {
			curr = prev;
			calcoffsets();
		}
		break;
	case XK_Next:
		if (!next)
			return;
		sel = curr = next;
		calcoffsets();
		break;
	case XK_Prior:
		if (!prev)
			return;
		sel = curr = prev;
		calcoffsets();
		break;
	case XK_Return:
	case XK_KP_Enter:
		puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
		if (!(ev->state & ControlMask)) {
			cleanup();
			exit(0);
		}
		if (sel)
			sel->out = 1;
		break;
	case XK_Right:
		if (text[cursor] != '\0') {
			cursor = nextrune(+1);
			break;
		}
		if (lines > 0)
			return;
		/* fallthrough */
	case XK_Down:
		if (sel && sel->right && (sel = sel->right) == next) {
			curr = next;
			calcoffsets();
		}
		break;
	case XK_Tab:
		if (!sel)
			return;
		strncpy(text, sel->text, sizeof text - 1);
		text[sizeof text - 1] = '\0';
		cursor = strlen(text);
		match();
		break;
	}

draw:
	drawmenu();
}

static void
paste(void)
{
	char *p, *q;
	int di;
	unsigned long dl;
	Atom da;

	/* we have been given the current selection, now insert it into input */
	if (XGetWindowProperty(dpy, win, utf8, 0, (sizeof text / 4) + 1, False,
	                   utf8, &da, &di, &dl, &dl, (unsigned char **)&p)
	    == Success && p) {
		insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p));
		XFree(p);
	}
	drawmenu();
}

static void
readstdin(void)
{
	char buf[sizeof text], *p;
	size_t i, imax = 0, size = 0;
	unsigned int tmpmax = 0;

	/* read each line from stdin and add it to the item list */
	for (i = 0; fgets(buf, sizeof buf, stdin); i++) {
		if (i + 1 >= size / sizeof *items)
			if (!(items = realloc(items, (size += BUFSIZ))))
				die("cannot realloc %u bytes:", size);
		if ((p = strchr(buf, '\n')))
			*p = '\0';
		if (!(items[i].text = strdup(buf)))
			die("cannot strdup %u bytes:", strlen(buf) + 1);
		items[i].out = 0;
		drw_font_getexts(drw->fonts, buf, strlen(buf), &tmpmax, NULL);
		if (tmpmax > inputw) {
			inputw = tmpmax;
			imax = i;
		}
	}
	if (items)
		items[i].text = NULL;
	inputw = items ? TEXTW(items[imax].text) : 0;
	lines = MIN(lines, i);
}

static void
run(void)
{
	XEvent ev;

	while (!XNextEvent(dpy, &ev)) {
		if (XFilterEvent(&ev, win))
			continue;
		switch(ev.type) {
		case DestroyNotify:
			if (ev.xdestroywindow.window != win)
				break;
			cleanup();
			exit(1);
		case Expose:
			if (ev.xexpose.count == 0)
				drw_map(drw, win, 0, 0, mw, mh);
			break;
		case FocusIn:
			/* regrab focus from parent window */
			if (ev.xfocus.window != win)
				grabfocus();
			break;
		case KeyPress:
			keypress(&ev.xkey);
			break;
		case SelectionNotify:
			if (ev.xselection.property == utf8)
				paste();
			break;
		case VisibilityNotify:
			if (ev.xvisibility.state != VisibilityUnobscured)
				XRaiseWindow(dpy, win);
			break;
		}
	}
}

static void
setup(void)
{
	int x, y, i, j;
	unsigned int du;
	XSetWindowAttributes swa;
	XIM xim;
	Window w, dw, *dws;
	XWindowAttributes wa;
	XClassHint ch = {"dmenu", "dmenu"};
#ifdef XINERAMA
	XineramaScreenInfo *info;
	Window pw;
	int a, di, n, area = 0;
#endif
	/* init appearance */
	for (j = 0; j < SchemeLast; j++)
		scheme[j] = drw_scm_create(drw, colors[j], 2);

	clip = XInternAtom(dpy, "CLIPBOARD",   False);
	utf8 = XInternAtom(dpy, "UTF8_STRING", False);

	/* calculate menu geometry */
	bh = drw->fonts->h + 2;
	lines = MAX(lines, 0);
	mh = (lines + 1) * bh;
#ifdef XINERAMA
	i = 0;
	if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
		XGetInputFocus(dpy, &w, &di);
		if (mon >= 0 && mon < n)
			i = mon;
		else if (w != root && w != PointerRoot && w != None) {
			/* find top-level window containing current input focus */
			do {
				if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws)
					XFree(dws);
			} while (w != root && w != pw);
			/* find xinerama screen with which the window intersects most */
			if (XGetWindowAttributes(dpy, pw, &wa))
				for (j = 0; j < n; j++)
					if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) {
						area = a;
						i = j;
					}
		}
		/* no focused window is on screen, so use pointer location instead */
		if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du))
			for (i = 0; i < n; i++)
				if (INTERSECT(x, y, 1, 1, info[i]))
					break;

		x = info[i].x_org;
		y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
		mw = info[i].width;
		XFree(info);
	} else
#endif
	{
		if (!XGetWindowAttributes(dpy, parentwin, &wa))
			die("could not get embedding window attributes: 0x%lx",
			    parentwin);
		x = 0;
		y = topbar ? 0 : wa.height - mh;
		mw = wa.width;
	}
	promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
	inputw = MIN(inputw, mw/3);
	match();

	/* create menu window */
	swa.override_redirect = True;
	swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
	swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
	win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
	                    CopyFromParent, CopyFromParent, CopyFromParent,
	                    CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
	XSetClassHint(dpy, win, &ch);

	/* input methods */
	if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL)
		die("XOpenIM failed: could not open input device");

	xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
	                XNClientWindow, win, XNFocusWindow, win, NULL);

	XMapRaised(dpy, win);
	if (embed) {
		XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask);
		if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) {
			for (i = 0; i < du && dws[i] != win; ++i)
				XSelectInput(dpy, dws[i], FocusChangeMask);
			XFree(dws);
		}
		grabfocus();
	}
	drw_resize(drw, mw, mh);
	drawmenu();
}

static void
usage(void)
{
	fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
	      "             [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
	exit(1);
}

int
main(int argc, char *argv[])
{
	XWindowAttributes wa;
	int i, fast = 0;

	for (i = 1; i < argc; i++)
		/* these options take no arguments */
		if (!strcmp(argv[i], "-v")) {      /* prints version information */
			puts("dmenu-"VERSION);
			exit(0);
		} else if (!strcmp(argv[i], "-b")) /* appears at the bottom of the screen */
			topbar = 0;
		else if (!strcmp(argv[i], "-f"))   /* grabs keyboard before reading stdin */
			fast = 1;
		else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
			fstrncmp = strncasecmp;
			fstrstr = cistrstr;
		} else if (i + 1 == argc)
			usage();
		/* these options take one argument */
		else if (!strcmp(argv[i], "-l"))   /* number of lines in vertical list */
			lines = atoi(argv[++i]);
		else if (!strcmp(argv[i], "-m"))
			mon = atoi(argv[++i]);
		else if (!strcmp(argv[i], "-p"))   /* adds prompt to left of input field */
			prompt = argv[++i];
		else if (!strcmp(argv[i], "-fn"))  /* font or font set */
			fonts[0] = argv[++i];
		else if (!strcmp(argv[i], "-nb"))  /* normal background color */
			colors[SchemeNorm][ColBg] = argv[++i];
		else if (!strcmp(argv[i], "-nf"))  /* normal foreground color */
			colors[SchemeNorm][ColFg] = argv[++i];
		else if (!strcmp(argv[i], "-sb"))  /* selected background color */
			colors[SchemeSel][ColBg] = argv[++i];
		else if (!strcmp(argv[i], "-sf"))  /* selected foreground color */
			colors[SchemeSel][ColFg] = argv[++i];
		else if (!strcmp(argv[i], "-w"))   /* embedding window id */
			embed = argv[++i];
		else
			usage();

	if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())
		fputs("warning: no locale support\n", stderr);
	if (!(dpy = XOpenDisplay(NULL)))
		die("cannot open display");
	screen = DefaultScreen(dpy);
	root = RootWindow(dpy, screen);
	if (!embed || !(parentwin = strtol(embed, NULL, 0)))
		parentwin = root;
	if (!XGetWindowAttributes(dpy, parentwin, &wa))
		die("could not get embedding window attributes: 0x%lx",
		    parentwin);
	drw = drw_create(dpy, screen, root, wa.width, wa.height);
	if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
		die("no fonts could be loaded.");
	lrpad = drw->fonts->h;

#ifdef __OpenBSD__
	if (pledge("stdio rpath", NULL) == -1)
		die("pledge");
#endif

	if (fast && !isatty(0)) {
		grabkeyboard();
		readstdin();
	} else {
		readstdin();
		grabkeyboard();
	}
	setup();
	run();

	return 1; /* unreachable */
}


code:
#!/bin/sh

cachedir="${XDG_CACHE_HOME:-"$HOME/.cache"}"
cache="$cachedir/dmenu_run"

[ ! -e "$cachedir" ] && mkdir -p "$cachedir"

IFS=:
if stest -dqr -n "$cache" $PATH; then
	stest -flx $PATH | sort -u | tee "$cache"
else
	cat "$cache"
fi


code:
#!/bin/sh
dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &