0

I am trying to find a solution of the puzzle game 'Flood It'. The main idea is to turn a whole N*M game board of k different colors into a single color. I have to start from the top left corner of the board and turn the same colored block into one of the colors of neighboring nodes and thus moving ahead and flooding the whole board into a single color at last. For example:

Initial Board:
 1 1 1 2 2 3
 1 1 2 3 4 5
 1 1 1 1 3 4
 1 4 3 2 1 5
 2 3 4 5 1 2

Final Board:
 1 1 1 1 1 1
 1 1 1 1 1 1
 1 1 1 1 1 1
 1 1 1 1 1 1
 1 1 1 1 1 1

where 1,2,3,4,5 represents different colors. I have prepared a C++ code for finding out the area of same colored block at any position of the board . This can be applied at the top left cell at first and then at the neighboring nodes of it to flood the color. My code is as follows:

#include <cstdint>
#include <vector>
#include <queue>
#include <string>
#include <iostream>

typedef std::vector<int32_t> vec_1d;
typedef std::vector<vec_1d> vec_2d;

// Print the 2d vector with a label
void dump(std::string const& label, vec_2d const& v)
{
    std::cout << label << "\n";
    for (std::size_t y(0); y < v.size(); ++y) {
        for (std::size_t x(0); x < v[0].size(); ++x) {
            std::cout << v[y][x] << " ";
        }
        std::cout << "\n";
    }
    std::cout << "\n";
}

// Recursive implementation of the search
void find_connected_r(int32_t target_color
, std::size_t x
, std::size_t y
, vec_2d const& colors
, vec_2d& result)
{
    if ((result[y][x] == 1) || (colors[y][x] != target_color)) {
        return;
    }

    result[y][x] = 1;

    std::size_t width(colors[0].size());
    std::size_t height(colors.size());

    if (x > 0) {
        find_connected_r(target_color, x - 1, y, colors, result);
    }
    if (y > 0) {
        find_connected_r(target_color, x, y - 1, colors, result);
    }
    if (x < (width - 1)) {
        find_connected_r(target_color, x + 1, y, colors, result);
    }
    if (y < (height - 1)) {
        find_connected_r(target_color, x, y + 1, colors, result);
    }
}


// Entry point to the search, select the implementation with last param
vec_2d find_connected(std::size_t x, std::size_t y, vec_2d const& colors,    bool recursive)
{
   if (colors.empty() || colors[0].empty()) {
       throw std::runtime_error("Invalid input array size");
   }

   int32_t target_color(colors[y][x]);

    vec_2d result(colors.size(), vec_1d(colors[0].size(), 0));

    if (recursive) {
        find_connected_r(target_color, x, y, colors, result);
    }
    else {
        find_connected(target_color, x, y, colors, result);
    }

    return result;
}

void dump_coordinates(std::string const& label, vec_2d const& v)
{
    std::cout << label << "\n";
    for (std::size_t y(0); y < v.size(); ++y) {
        for (std::size_t x(0); x < v[0].size(); ++x) {
            if (v[y][x]) {
                std::cout << "(" << x << ", " << y << ") ";
            }
        }
    }
    std::cout << "\n";
}

int main()
{
    vec_2d colors{
        { 1, 1, 1, 1, 1, 1 }
    , { 2, 2, 2, 3, 3, 1 }
    , { 1, 1, 1, 1, 3, 1 }
    , { 1, 3, 3, 3, 3, 1 }
    , { 1, 1, 1, 1, 1, 1 }
    };
}

How will I turn the whole board/matrix into a single color by examining the neighboring nodes?

user4650623
  • 17
  • 2
  • 11
  • What's the problem exactly? Error, incorrect output, etc... Also out of curiosity is this a homework assignment? – Chara Apr 01 '16 at 15:16
  • puzzle tag description: _DO NOT USE - prefer constructive questions, or use more descriptive tags. Otherwise, your question might be appropriate for codegolf.stackexchange.com._ – default Apr 01 '16 at 15:16
  • what is the Color class structure you have? a Color, a Pixel, three integers representing RGB, what class? – Khalil Khalaf Apr 01 '16 at 15:16
  • Isn't this the same issue as in your other question here? http://stackoverflow.com/questions/36349593/finding-out-same-colored-block-in-a-2d-matrix – default Apr 01 '16 at 15:18
  • no it's different from that @Default – user4650623 Apr 01 '16 at 15:20
  • no i just want to know the way of performing the flood filling using the code @Chara – user4650623 Apr 01 '16 at 15:22
  • @user4650623 well, what does your code do right now? does it work, and you want to know why it works? – Chara Apr 01 '16 at 15:26
  • my code works and it just finds out same colored blocks at any position in a given matrix. I want to change the color of the block starting at the top left corner into a color of the adjacent nodes and thus turn the whole matrix into a single color. How will I do that @Chara – user4650623 Apr 01 '16 at 15:30
  • why is it `int32_t target_color(colors[y][y]);` instead of `int32_t target_color(colors[y][x]);`? – Chara Apr 01 '16 at 15:45
  • I don't understand the need for an `if` statement. Why not set every matrix slot to same value? – Thomas Matthews Apr 01 '16 at 15:47

2 Answers2

1

A possible top-level algorithm to solve this puzzle is to repeat the following until there is only one color on the whole board:

  • Find all contiguous color regions. Treat the region at (0,0) as primary, all others as secondary.
  • Pick the largest (by count of tiles) secondary region with a color that is different to the primary region's color. Let's name the color of this secondary region the new_color.
  • Recolor the primary region to new_color.

Finding all the regions

We should keep a cumulative_mask to track of all the tiles that are already identified as part of some region.

First we find the primary region, starting search at (0,0), and update our cumulative_mask with the result.

Then repeat until no more regions can be found:

  • Find the position of the first zero tile in the cumulative_mask, which has at least one non-zero tile in the primary region mask.
  • Find the region starting at this position.
  • Update the cumulative_mask with the mask of this region.

Selecting the color

Simply iterate through secondary regions, and find the region with largest count, which has a different color than the primary region.


Code

(also on coliru)

Note: Intentionally written in a way to make it possible to understand the algorithm. This could definitely be refactored, and it's missing a lot of error checking.

#include <cstdint>
#include <vector>
#include <queue>
#include <string>
#include <iostream>

typedef std::vector<int32_t> vec_1d;
typedef std::vector<vec_1d> vec_2d;

typedef std::pair<std::size_t, std::size_t> position;

position const INVALID_POSITION(-1, -1);
int32_t const INVALID_COLOR(0);

// ============================================================================

struct region_info
{
    int32_t color;
    vec_2d mask;

    std::size_t count() const
    {
        std::size_t result(0);
        for (std::size_t y(0); y < mask.size(); ++y) {
            for (std::size_t x(0); x < mask[0].size(); ++x) {
                if (mask[y][x]) {
                    ++result;
                }
            }
        }
        return result;
    }
};

struct region_set
{
    // The region that contains (0, 0)
    region_info primary;

    // All other regions
    std::vector<region_info> secondary;
};

// ============================================================================

// Print the 2D vector with a label
void dump(std::string const& label, vec_2d const& v)
{
    std::cout << label << "\n";
    for (std::size_t y(0); y < v.size(); ++y) {
        for (std::size_t x(0); x < v[0].size(); ++x) {
            std::cout << v[y][x] << " ";
        }
        std::cout << "\n";
    }
    std::cout << "\n";
}

// Print the coordinates of non-zero elements of 2D vector with a label
void dump_coordinates(std::string const& label, vec_2d const& v)
{
    std::cout << label << "\n";
    for (std::size_t y(0); y < v.size(); ++y) {
        for (std::size_t x(0); x < v[0].size(); ++x) {
            if (v[y][x]) {
                std::cout << "(" << x << ", " << y << ") ";
            }
        }
    }
    std::cout << "\n";
}

void dump(region_info const& ri)
{
    std::cout << "Region color: " << ri.color << "\n";
    std::cout << "Region count: " << ri.count() << "\n";
    dump("Region mask:", ri.mask);
}

void dump(region_set const& rs)
{
    std::cout << "Primary Region\n" << "\n";
    dump(rs.primary);

    for (std::size_t i(0); i < rs.secondary.size(); ++i) {
        std::cout << "Secondary Region #" << i << "\n";
        dump(rs.secondary[i]);
    }
}

// ============================================================================

// Find connected tiles - implementation
void find_connected(int32_t target_color
    , std::size_t x
    , std::size_t y
    , vec_2d const& colors
    , vec_2d& result)
{
    std::size_t width(colors[0].size());
    std::size_t height(colors.size());

    std::queue<position> s;

    s.push(position(x, y));

    while (!s.empty()) {
        position pos(s.front());
        s.pop();

        if (result[pos.second][pos.first] == 1) {
            continue;
        }
        if (colors[pos.second][pos.first] != target_color) {
            continue;
        }
        result[pos.second][pos.first] = 1;

        if (pos.first > 0) {
            s.push(position(pos.first - 1, pos.second));
        }
        if (pos.second > 0) {
            s.push(position(pos.first, pos.second - 1));
        }
        if (pos.first < (width - 1)) {
            s.push(position(pos.first + 1, pos.second));
        }
        if (pos.second < (height - 1)) {
            s.push(position(pos.first, pos.second + 1));
        }
    }
}

// Find connected tiles - convenience wrapper
vec_2d find_connected(std::size_t x, std::size_t y, vec_2d const& colors)
{
    if (colors.empty() || colors[0].empty()) {
        throw std::runtime_error("Invalid input array size");
    }

    int32_t target_color(colors[y][x]);

    vec_2d result(colors.size(), vec_1d(colors[0].size(), 0));

    find_connected(target_color, x, y, colors, result);

    return result;
}

// ============================================================================

// Change color of elements at positions with non-zero mask value to new color
vec_2d& change_masked(int32_t new_color
    , vec_2d& colors
    , vec_2d const& mask)
{
    for (std::size_t y(0); y < mask.size(); ++y) {
        for (std::size_t x(0); x < mask[0].size(); ++x) {
            if (mask[y][x]) {
                colors[y][x] = new_color;
            }
        }
    }

    return colors;
}

// Combine two masks
vec_2d combine(vec_2d const& v1, vec_2d const& v2)
{
    vec_2d result(v1);

    for (std::size_t y(0); y < v2.size(); ++y) {
        for (std::size_t x(0); x < v2[0].size(); ++x) {
            if (v2[y][x]) {
                result[y][x] = v2[y][x];
            }
        }
    }

    return result;
}

// Find position of first zero element in mask
position find_first_zero(vec_2d const& mask)
{
    for (std::size_t y(0); y < mask.size(); ++y) {
        for (std::size_t x(0); x < mask[0].size(); ++x) {
            if (!mask[y][x]) {
                return position(x, y);
            }
        }
    }
    return INVALID_POSITION;
}

bool has_nonzero_neighbor(std::size_t x, std::size_t y, vec_2d const& mask)
{
    bool result(false);
    if (x > 0) {
        result |= (mask[y][x - 1] != 0);
    }
    if (y > 0) {
        result |= (mask[y - 1][x] != 0);
    }
    if (x < (mask[0].size() - 1)) {
        result |= (mask[y][x + 1] != 0);
    }
    if (y < (mask.size() - 1)) {
        result |= (mask[y + 1][x] != 0);
    }
    return result;
}

// Find position of first zero element in mask
// which neighbors at least one non-zero element in primary mask
position find_first_zero_neighbor(vec_2d const& mask, vec_2d const& primary_mask)
{
    for (std::size_t y(0); y < mask.size(); ++y) {
        for (std::size_t x(0); x < mask[0].size(); ++x) {
            if (!mask[y][x]) {
                if (has_nonzero_neighbor(x, y, primary_mask)) {
                    return position(x, y);
                }
            }
        }
    }
    return INVALID_POSITION;
}

// ============================================================================

// Find all contiguous color regions in the image
// The region starting at (0,0) is considered the primary region
// All other regions are secondary
// If parameter 'only_neighbors' is true, search only for regions
// adjacent to primary region, otherwise search the entire board
region_set find_all_regions(vec_2d const& colors, bool only_neighbors = false)
{
    region_set result;

    result.primary.color = colors[0][0];
    result.primary.mask = find_connected(0, 0, colors);

    vec_2d cumulative_mask = result.primary.mask;

    for (;;) {
        position pos;
        if (only_neighbors) {
            pos = find_first_zero_neighbor(cumulative_mask, result.primary.mask);
        } else {
            pos = find_first_zero(cumulative_mask);
        }

        if (pos == INVALID_POSITION) {
            break; // No unsearched tiles left
        }

        region_info reg;
        reg.color = colors[pos.second][pos.first];
        reg.mask = find_connected(pos.first, pos.second, colors);

        cumulative_mask = combine(cumulative_mask, reg.mask);

        result.secondary.push_back(reg);
    }

    return result;
}

// ============================================================================

// Select the color to recolor the primary region with
// based on the color of the largest secondary region of non-primary color
int32_t select_color(region_set const& rs)
{
    int32_t selected_color(INVALID_COLOR);
    std::size_t selected_count(0);

    for (auto const& ri : rs.secondary) {
        if (ri.color != rs.primary.color) {
            if (ri.count() > selected_count) {
                selected_count = ri.count();
                selected_color = ri.color;
            }
        }
    }

    return selected_color;
}

// ============================================================================

// Solve the puzzle
// If parameter 'only_neighbors' is true, search only for regions
// adjacent to primary region, otherwise search the entire board
// Returns the list of selected colors representing the solution steps
vec_1d solve(vec_2d colors, bool only_neighbors = false)
{
    vec_1d selected_colors;

    for (int32_t i(0);; ++i) {
        std::cout << "Step #" << i << "\n";
        dump("Game board: ", colors);

        region_set rs(find_all_regions(colors, true));

        dump(rs);

        int32_t new_color(select_color(rs));
        if (new_color == INVALID_COLOR) {
            break;
        }

        std::cout << "Selected color: " << new_color << "\n";
        selected_colors.push_back(new_color);

        change_masked(new_color, colors, rs.primary.mask);

        std::cout << "\n------------------------------------\n\n";
    }

    return selected_colors;
}

// ============================================================================

int main()
{
    vec_2d colors{
        { 1, 1, 1, 1, 1, 1 }
        , { 2, 2, 2, 3, 3, 1 }
        , { 1, 1, 4, 5, 3, 1 }
        , { 1, 3, 3, 4, 3, 1 }
        , { 1, 1, 1, 1, 1, 1 }
    };

    vec_1d steps(solve(colors, true));

    std::cout << "Solved in " << steps.size() << " step(s):\n";
    for (auto step : steps) {
        std::cout << step << " ";
    }
    std::cout << "\n\n";
}

// ============================================================================

Output of the program:

Step #0
Game board: 
1 1 1 1 1 1 
2 2 2 3 3 1 
1 1 4 5 3 1 
1 3 3 4 3 1 
1 1 1 1 1 1 

Primary Region

Region color: 1
Region count: 18
Region mask:
1 1 1 1 1 1 
0 0 0 0 0 1 
1 1 0 0 0 1 
1 0 0 0 0 1 
1 1 1 1 1 1 

Secondary Region #0
Region color: 2
Region count: 3
Region mask:
0 0 0 0 0 0 
1 1 1 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #1
Region color: 3
Region count: 4
Region mask:
0 0 0 0 0 0 
0 0 0 1 1 0 
0 0 0 0 1 0 
0 0 0 0 1 0 
0 0 0 0 0 0 

Secondary Region #2
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 1 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #3
Region color: 3
Region count: 2
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 1 1 0 0 0 
0 0 0 0 0 0 

Secondary Region #4
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 

Selected color: 3

------------------------------------

Step #1
Game board: 
3 3 3 3 3 3 
2 2 2 3 3 3 
3 3 4 5 3 3 
3 3 3 4 3 3 
3 3 3 3 3 3 

Primary Region

Region color: 3
Region count: 24
Region mask:
1 1 1 1 1 1 
0 0 0 1 1 1 
1 1 0 0 1 1 
1 1 1 0 1 1 
1 1 1 1 1 1 

Secondary Region #0
Region color: 2
Region count: 3
Region mask:
0 0 0 0 0 0 
1 1 1 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #1
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 1 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #2
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #3
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 

Selected color: 2

------------------------------------

Step #2
Game board: 
2 2 2 2 2 2 
2 2 2 2 2 2 
2 2 4 5 2 2 
2 2 2 4 2 2 
2 2 2 2 2 2 

Primary Region

Region color: 2
Region count: 27
Region mask:
1 1 1 1 1 1 
1 1 1 1 1 1 
1 1 0 0 1 1 
1 1 1 0 1 1 
1 1 1 1 1 1 

Secondary Region #0
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 1 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #1
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Secondary Region #2
Region color: 4
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 

Selected color: 4

------------------------------------

Step #3
Game board: 
4 4 4 4 4 4 
4 4 4 4 4 4 
4 4 4 5 4 4 
4 4 4 4 4 4 
4 4 4 4 4 4 

Primary Region

Region color: 4
Region count: 29
Region mask:
1 1 1 1 1 1 
1 1 1 1 1 1 
1 1 1 0 1 1 
1 1 1 1 1 1 
1 1 1 1 1 1 

Secondary Region #0
Region color: 5
Region count: 1
Region mask:
0 0 0 0 0 0 
0 0 0 0 0 0 
0 0 0 1 0 0 
0 0 0 0 0 0 
0 0 0 0 0 0 

Selected color: 5

------------------------------------

Step #4
Game board: 
5 5 5 5 5 5 
5 5 5 5 5 5 
5 5 5 5 5 5 
5 5 5 5 5 5 
5 5 5 5 5 5 

Primary Region

Region color: 5
Region count: 30
Region mask:
1 1 1 1 1 1 
1 1 1 1 1 1 
1 1 1 1 1 1 
1 1 1 1 1 1 
1 1 1 1 1 1 

Solved in 4 step(s):
3 2 4 5 
Dan Mašek
  • 17,852
  • 6
  • 57
  • 85
  • I tried to solve this board using the above code: vec_2d colors{ { 1, 2, 3, 4, 3, 1 } , { 3, 2, 4, 2, 2, 5 } , { 2, 3, 2, 4, 1, 3 } , { 3, 4, 2, 3, 2, 4 } , { 3, 5, 4, 5, 4, 3 } , { 2, 3, 2, 5, 4, 2 }}; which takes 12 steps to be solved. But in the original game site, the maximum allowable steps was 8. Can we reduce the number of steps by any technique? Is it possible to make some changes in the code to solve this using fewer steps? will you please check it? @Dan Mašek – user4650623 Apr 02 '16 at 01:24
  • Yes, I just wanted to show you a working solution, not necessarily optimal one, so you can learn from the code. You need to devise a different method of selecting the color yourself -- just going for the first color with largest continuous region is obviously not the most effective one. Sorry, but I believe I answered your question to the full extent. – Dan Mašek Apr 02 '16 at 01:41
  • If I choose a color, that minimizes distance to furthest component and if several colors minimizes distance to one of the furthest components, then I choose color, which has most points in it, will it be faster? @Dan Mašek – user4650623 Apr 02 '16 at 07:20
  • sir, can you please help me in [this question?](http://stackoverflow.com/questions/36371271/solving-a-mn-board-of-different-colors-into-a-single-color-optimally). I am accepting this answer. @Dan Mašek – user4650623 Apr 02 '16 at 12:43
  • sir, are you there? @Dan Mašek – user4650623 Apr 02 '16 at 18:46
  • what will be the complexity of this? @Dan Mašek – user4650623 Apr 03 '16 at 20:13
0

There's a bunch of things I don't understand in your code so instead of trying to fix them I'll create a new function and you can compare the two.

// this function is called when the user inputs the x and y values
// the colors vector will be modified in place by reference
void change_color(int x, int y, vec_2d& colors)
{
    int target_color = colors[x][y];
    // call the recursive flood fill function
    flood_fill(0, 0, target_color, colors);
}  

//this function is the recursive flood fill
void flood_fill(int x, int y, const int target_color, vec_2d& colors)
{
    // if the current tile is already the target color, do nothing
    if (colors[x][y] == target_color) return;

    // only need to go right and down, since starting from top left
    // Also, only goes to the next tile if the next tile's color is 
    // the same as the current tile's color
    if (x < colors.size()-1 && colors[x+1][y] == colors[x][y])
    {
        flood_fill(x+1, y, target_color, colors);
    }
    if (y < colors[0].size()-1 && colors[x][y+1] == colors[x][y])
    {
        flood_fill(x, y+1, target_color, colors);
    }
    // finally, fill in the current tile with target_color
    colors[x][y] = target_color;
}

EDIT: Since you meant you wanted to solve the game instead of implementing the game...

Keep track of which colors are still available on the board at all times. On each "turn", find the color that will fill the most tile area starting from the top left. Repeat until all tiles are filled with the same color. This is more of a brute force approach, and there is probably a more optimized method, but this is the most basic one in my opinion.

Chara
  • 1,045
  • 10
  • 21
  • how will these two functions be called inside main function? how will the target color be chosen? There may be different colored neighbor blocks at adjacent positions, In this case which one will be selected? @Chara – user4650623 Apr 01 '16 at 16:26
  • I'm out at lunch right now I'll edit my answer when I get back. – Chara Apr 01 '16 at 16:28
  • You can follow [this link](http://unixpapa.com/floodit/). I am trying to solve this game. @Chara – user4650623 Apr 01 '16 at 16:32
  • Yep, I know. I had to code this game for a homework assignment before and we used that as a model as well. – Chara Apr 01 '16 at 16:58
  • I want to solve this game. Is it possible using the code? @Chara – user4650623 Apr 01 '16 at 16:59
  • you want to create an algorithm that solves this game without user input? – Chara Apr 01 '16 at 17:00
  • user input will be only the 2D array which we are calling game board as I have shown and It will be solved optimally @Chara – user4650623 Apr 01 '16 at 17:04
  • ahh I see. my understanding was simply to imitate the gameplay, not provide an algorithm to solve it. – Chara Apr 01 '16 at 17:14
  • You have said that you had done this as assignment before. Can you please share the code so that I can check it for better understanding? @Chara – user4650623 Apr 01 '16 at 17:17
  • I created the game as an assignment, but I did not implement an algorithm to SOLVE the game. My program basically let you play Flood-It as if you were playing on unixpapa.com. I edited my answer to share my approach to your problem, hopefully that can get you started. – Chara Apr 01 '16 at 17:18
  • how will I find the color that will fill the most tile area starting from the top left? Can you provide some code with my code to do this? @Chara – user4650623 Apr 01 '16 at 17:23
  • I can't provide code because I'm busy, but the idea is to loop through the list of available colours, use mine/your recursive method while keeping track of a counter for how many tiles have been reached. Then, compare the values of the counters and select the color that filled the most tiles. Starting from top left just means your initial call to the recursive function is with x = 0 and y = 0. – Chara Apr 01 '16 at 17:25