user3777772
user3777772

Reputation:

Finding 5 elements of the same color in a row

I created a two dimensional array [19,19] of labels. On click a user changes the color of a label to either black or white depending on the iteration. I want a message box to pop up when there are five labels of the same color lined up horizontally, vertically, or diagonally. Only the diagonal check is not working and I am not sure why. Any help would be much appreciated.

class gamePlay
{
    const int WinLength = 5;
    const int BoardWidth = 19;
    const int BoardHeight = 19;

    const int kwinningCount = 5;

    public Color? CheckWinner(Label[,] board)
    {
        int columnCount = board.GetLength(1), rowCount = board.GetLength(0);

        for (int row = 0; row < rowCount; row++)
        {
            Color? lineResult = CheckWinnerForLine(board, 0, row, columnCount, 1, 0);

            if (lineResult != null)
            {
                return lineResult;
            }

            if (rowCount - row >= kwinningCount)
            {
                lineResult = CheckWinnerForLine(board, 0, row, rowCount - row, 1, 1);

                if (lineResult != null)
                {
                    return lineResult;
                }
            }
        }

        for (int column = 0; column < columnCount; column++)
        {
            Color? lineResult = CheckWinnerForLine(board, column, 0, rowCount, 0, 1);

            if (lineResult != null)
            {
                return lineResult;
            }

            if (column > 0 && columnCount - column >= kwinningCount)
            {
                lineResult =
                    CheckWinnerForLine(board, column, 0, columnCount - column, 1, 1);

                if (lineResult != null)
                {
                    return lineResult;
                }
            }
        }

        return null;
    }

    Color? CheckWinnerForLine(Label[,] board,
        int column, int row, int count, int columnIncrement, int rowIncrement)
    {
        // Initialize from the first cell
        int colorCount = 1;
        Color currentColor = board[row, column].BackColor;

        while (--count > 0)
        {
            column += columnIncrement;
            row += rowIncrement;

            Color cellColor = board[row, column].BackColor;

            if (currentColor != cellColor)
            {
                // switched colors, so reset for this cell to be the first of the string
                colorCount = 1;
                currentColor = cellColor;
            }
            else if (++colorCount == kwinningCount && cellColor != Color.Transparent)
            {
                return cellColor;
            }
        }

        return null;
    }
public partial class Form1 : Form
{
    int iteration = 0;
    public Label[,] board = new Label[19,19];
    int count;
    int columnIncrement;
    int rowIncrement;
    const int WinLength = 5;
    const int BoardWidth = 19;
    const int BoardHeight = 19;
    gamePlay WinCheck = new gamePlay();

    public Form1()
    {
        InitializeComponent();

        int x = this.Location.X + 10;
        int y = this.Location.Y + 15;

        // create 361 labels, set their properties
        for (int i = 0; i < 19; i++)
        {
            for (int j = 0; j < 19; j++)
            {
                board[i,j] = new Label();
                board[i,j].Parent = this;
                board[i,j].Name = "label" + i;
                //board[i,j].BackColor = System.Drawing.ColorTranslator.FromHtml("#DBB262");
                board[i,j].BackColor = Color.Transparent;
                //board[i, j].StyleChanged = Opacity
                //set size of labels
                board[i,j].Size = new Size(30, 30);
                //initialize click event handler
                this.board[i,j].Click += new System.EventHandler(this.labelClick);
                this.Controls.Add(board[i,j]);
                board[i,j].BringToFront();
            }

        }



        // set the position of the label
        for (int i = 0; i < 19; i++)
        {
            for (int j = 0; j < 19; j++)
            {
                board[i,j].Location = new Point(x, y);
                //set distance between labels
                if (x >= 755)
                {
                    x = this.Location.X + 10;
                    y += 42;
                }

                else
                {
                    x += 43;
                }
            }

        }
    }

        private void labelClick (object sender, EventArgs e)
        {

            Label clickedLabel = (Label)sender;

            if (clickedLabel.BackColor == Color.Transparent)
            {
                if (iteration % 2 == 0)
                {
                    clickedLabel.BackColor = Color.Black;
                }
                else
                {
                    clickedLabel.BackColor = Color.White;
                }
                iteration++;
            }
            else
            {

            }

            for (int row = 0; row < BoardHeight; row++)
            {
                for (int column = 0; column < BoardWidth; column++)
                {
                    if (board[row, column] == clickedLabel)
                    {
                        Color? winner = WinCheck.CheckWinner(board);
                        if (winner == Color.Black)
                        {
                            MessageBox.Show("Black is the winner!");
                        }
                        else if (winner == Color.White)
                        {
                            MessageBox.Show("White is the winner!");
                        }
                    }
                }
            }
        }

        private int[] FindClickedLabelCoordinates(Label[,] board, Label label)
        {
            for (int row = 0; row < BoardHeight; row++)
            {
                for (int column = 0; column < BoardWidth; column++)
                {
                    if (board[row, column] == label)
                        return new int[] { row, column };
                }
            }
            return null;
        }

Upvotes: 2

Views: 371

Answers (4)

Peter Duniho
Peter Duniho

Reputation: 70671

<edit>
As has been made apparent by your follow-up question, Aligning labels in array of same backcolor diagonally in C#, you were unable to generalize the solution offered here to work with the other two directions of alignment you are interested in for your program.

So I have edited the answer to this question to solve your question here in a more general way, such that it also fixes your issue for diagonals and verticals as well. Please see below for the updated code.
</edit>


For sure, there's no need for recursion. I would also suggest that looking for specific patterns with extended if expressions is wasteful, and in the case of the code you wrote, not going to work anyway. In fact, one reason to avoid complicated expressions like that is that they are easy to get wrong.

Here is a method that, for each row, scans the row looking for a string of five of the same color. Once it finds such a string, it returns the Color value that was found (so that the caller can tell whether it was black or white that won). If no such string is found, null is returned. In other words, a non-null value tells you that someone won, and it also tells you which player won.

<edit>
This new version does the above in a generalized way. I.e. the previous paragraph describes how the implementation works when scanning rows. It will do the similar operation for diagonals and horizontals. It does this by having the main method generate appropriate starting points and cell counts for the given horizontal, diagonal, and vertical lines you want to check, and then calling the general-purpose method that actually does the work to look for string of the same color in sequence.
</edit>

GamePlay.cs:

class GamePlay
{
    private readonly int _winLength = 5;

    public GamePlay(int winLength)
    {
        _winLength = winLength;
    }

    public Color? CheckWinner(Label[,] board)
    {
        return CheckWinnerIterator(board).FirstOrDefault(color => color != null);
    }

    private IEnumerable<Color?> CheckWinnerIterator(Label[,] board)
    {
        int columnCount = board.GetLength(1), rowCount = board.GetLength(0);

        for (int row = 0; row < rowCount; row++)
        {
            // Horizontal
            yield return CheckWinnerForLine(board, 0, row, columnCount, 1, 0);
            // Diagonals starting in first column, upper-left to lower-right
            yield return CheckWinnerForLine(board, 0, row, rowCount - row, 1, 1);
            // Diagonals starting in first column, lower-left to upper-right
            yield return CheckWinnerForLine(board, 0, row, row + 1, 1, -1);
        }

        for (int column = 0; column < columnCount; column++)
        {
            // Vertical
            yield return CheckWinnerForLine(board, column, 0, rowCount, 0, 1);
            // Diagonals starting in first row, upper-left to lower-right
            yield return CheckWinnerForLine(board, column, 0, columnCount - column, 1, 1);
            // Diagonals starting in last row, lower-left to upper-right
            yield return CheckWinnerForLine(board, column, rowCount - 1, columnCount - column, 1, -1);
        }
    }

    Color? CheckWinnerForLine(Label[,] board,
        int column, int row, int count, int columnIncrement, int rowIncrement)
    {
        if (count < _winLength)
        {
            return null;
        }

        // Initialize from the first cell
        int colorCount = 1;
        Color currentColor = board[row, column].BackColor;

        while (--count > 0)
        {
            column += columnIncrement;
            row += rowIncrement;

            Color cellColor = board[row, column].BackColor;

            if (currentColor != cellColor)
            {
                // switched colors, so reset for this cell to be the first of the string
                colorCount = 1;
                currentColor = cellColor;
            }
            else if (++colorCount == _winLength && cellColor != Color.Transparent)
            {
                return cellColor;
            }
        }

        return null;
    }
}

Form1.cs

public partial class Form1 : Form
{
    const int WinLength = 5;
    const int BoardWidth = 19;
    const int BoardHeight = 19;

    private bool isBlackTurn = true;
    private Label[,] board = new Label[BoardHeight, BoardWidth];
    private GamePlay WinCheck = new GamePlay(WinLength);

    public Form1()
    {
        InitializeComponent();

        int x = this.Location.X + 10;
        int y = this.Location.Y + 15;

        // create 361 labels, set their properties
        for (int i = 0; i < BoardHeight; i++)
        {
            for (int j = 0; j < BoardWidth; j++)
            {
                Label label = new Label();

                label.Parent = this;
                label.Name = "label" + i;
                label.BackColor = Color.Transparent;
                label.BorderStyle = BorderStyle.FixedSingle;
                label.Size = new Size(30, 30);
                label.Click += new System.EventHandler(this.labelClick);
                this.Controls.Add(board[i, j]);
                label.BringToFront();

                board[i, j] = label;
            }

        }

        // set the position of the label
        for (int i = 0; i < BoardHeight; i++)
        {
            for (int j = 0; j < BoardWidth; j++)
            {
                board[i, j].Location = new Point(x, y);
                //set distance between labels
                if (x >= 755)
                {
                    x = this.Location.X + 10;
                    y += 42;
                }

                else
                {
                    x += 43;
                }
            }

        }
    }

    private void labelClick(object sender, EventArgs e)
    {
        Label clickedLabel = (Label)sender;

        if (clickedLabel.BackColor != Color.Transparent)
        {
            return;
        }

        clickedLabel.BackColor = isBlackTurn ? Color.Black : Color.White;
        isBlackTurn = !isBlackTurn;

        Color? winner = WinCheck.CheckWinner(board);
        if (winner == Color.Black)
        {
            MessageBox.Show("Black is the winner!");
        }
        else if (winner == Color.White)
        {
            MessageBox.Show("White is the winner!");
        }
        else
        {
            return;
        }

        foreach (Label label in board)
        {
            label.BackColor = Color.Transparent;
        }
    }
}

<edit>

Note: the above represents not just the corrected version of the win-testing, but a significant clean-up of the code generally:

  • First and foremost, I overlooked half of the diagonal cases, i.e. those that run from lower-left to upper-right. I've fixed that here. That may be the cause of the difficulty you had getting the code to report a diagonal win.
  • The previous version of my code was already getting a bit verbose for my tastes, and adding the two other cases in the loops for win-testing pushed it over the edge for me. I "leaned out" the code a bit by turning the main loop logic into an iterator method, and then using a simple LINQ statement to enumerate the cases and break early if a winner is found.
  • In your code, I removed the FindClickedLabelCoordinates() method entirely, along with some of the fields, as they were all unused.
  • Also in your code, I significantly reduced the complexity of the labelClick() method. It had a fair amount of redundancy and overly verbose implementation details; I have pared the method down to near the bare minimum code required.
  • I also added a border around each Label, to make it easier for me to see where I should/can click.

Finally, in your comment you had the following concern:

after a winner is declared, a message box keeps popping up after each additional click saying white won

And yes, in your version of the code, that will happen, as the code checks for winners after every click that changes the board state (as it should) and once there is a winner there will always be a winner unless you reset the board. So every click results in a winner being detected. I have added code above, in labelClick(), to go ahead and reset the board if a winner is found.

</edit>


For grins (and so I could double-check my work, since the OP did not provide a complete code example with which an answer could be tested to verify it), here's a quick-and-dirty WPF program that incorporates the above method (with some minor modifications):

XAML:

<Window x:Class="TestSO33773260FiveInARow.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:TestSO33773260FiveInARow"
        Title="MainWindow" Height="525" Width="525">

  <ItemsControl ItemsSource="{Binding Board}" Background="AliceBlue">
    <ItemsControl.ItemsPanel>
      <ItemsPanelTemplate>
        <UniformGrid x:Name="uniformGrid1" Rows="{Binding Rows}" Columns="{Binding Columns}" IsItemsHost="True"/>
      </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
      <DataTemplate DataType="l:BoardCell">
        <Rectangle Fill="{Binding Color}" MouseUp="Rectangle_MouseUp"/>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</Window>

C#:

public partial class MainWindow : Window
{
    private const int _kwinningCount = 5;
    public int Rows { get; set; }
    public int Columns { get; set; }
    public List<BoardCell> Board { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Rows = 19;
        Columns = 19;
        Board = new List<BoardCell>(Enumerable.Range(0, Rows * Columns)
            .Select(i => new BoardCell { Color = Brushes.Transparent }));
        DataContext = this;
    }

    private void Rectangle_MouseUp(object sender, MouseButtonEventArgs e)
    {
        BoardCell boardCell = (BoardCell)((FrameworkElement)sender).DataContext;

        boardCell.IncrementColor();

        Color? winningColor = CheckWinner();

        if (winningColor != null)
        {
            MessageBox.Show(winningColor.Value.GetFriendlyName() + " won!");
        }
    }

    private Color _GetColorForBoardCell(int column, int row)
    {
        return Board[column + row * Rows].Color.Color;
    }

    public Color? CheckWinner()
    {
        return CheckWinnerIterator().FirstOrDefault(color => color != null);
    }

    private IEnumerable<Color?> CheckWinnerIterator()
    {
        int columnCount = Columns, rowCount = Rows;

        for (int row = 0; row < rowCount; row++)
        {
            // Horizontal
            yield return CheckWinnerForLine(0, row, columnCount, 1, 0);
            // Diagonals starting in first column, upper-left to lower-right
            yield return CheckWinnerForLine(0, row, rowCount - row, 1, 1);
            // Diagonals starting in first column, lower-left to upper-right
            yield return CheckWinnerForLine(0, row, row + 1, 1, -1);
        }

        for (int column = 0; column < columnCount; column++)
        {
            // Vertical
            yield return CheckWinnerForLine(column, 0, rowCount, 0, 1);
            // Diagonals starting in first row, upper-left to lower-right
            yield return CheckWinnerForLine(column, 0, columnCount - column, 1, 1);
            // Diagonals starting in last row, lower-left to upper-right
            yield return CheckWinnerForLine(column, rowCount - 1, columnCount - column, 1, -1);
        }
    }

    Color? CheckWinnerForLine(int column, int row, int count, int columnIncrement, int rowIncrement)
    {
        // Initialize from the first cell
        int colorCount = 1;
        Color currentColor = _GetColorForBoardCell(column, row);

        while (--count > 0)
        {
            column += columnIncrement;
            row += rowIncrement;

            Color cellColor = _GetColorForBoardCell(column, row);

            if (currentColor != cellColor)
            {
                // switched colors, so reset for this cell to be the first of the string
                colorCount = 1;
                currentColor = cellColor;
            }
            else if (++colorCount == _kwinningCount && cellColor != Colors.Transparent)
            {
                return cellColor;
            }
        }

        return null;
    }
}

public class BoardCell : INotifyPropertyChanged
{
    private static readonly SolidColorBrush[] _colors =
        { Brushes.Transparent, Brushes.White, Brushes.Black };

    private int _colorIndex;

    public SolidColorBrush Color
    {
        get { return _colors[_colorIndex]; }
        set
        {
            if (value != _colors[_colorIndex])
            {
                _SetColorIndex(Array.IndexOf(_colors, value));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void _OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public void IncrementColor()
    {
        _SetColorIndex(_colorIndex < _colors.Length - 1 ? _colorIndex + 1 : 0);
    }

    private void _SetColorIndex(int colorIndex)
    {
        _colorIndex = colorIndex;
        _OnPropertyChanged("Color");
    }
}

static class Extensions
{
    private static readonly Lazy<Dictionary<Color, string>> _colorToName =
        new Lazy<Dictionary<Color, string>>(() => GetColorToNameDictionary());

    private static Dictionary<Color, string> GetColorToNameDictionary()
    {
        Dictionary<Color, string> colorToName = new Dictionary<Color, string>();

        foreach (PropertyInfo pi in
            typeof(Colors).GetProperties(BindingFlags.Static | BindingFlags.Public))
        {
            if (pi.PropertyType == typeof(Color))
            {
                colorToName[(Color)pi.GetValue(null)] = pi.Name;
            }
        }

        return colorToName;
    }

    public static string GetFriendlyName(this Color color)
    {
        string name;

        if (_colorToName.Value.TryGetValue(color, out name))
        {
            return name;
        }

        return color.ToString();
    }
}

Upvotes: 1

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186698

You have no need in recoursion, but a bit modified loop:

private static Boolean IsWon(Label[,] board) {
  if (null == board)
    return false;

  // Let code not be that rigid: "5" can be easily updated 
  const int atLeast = 5; 

  // Do not use magic numbers - i.e. "19"
  for (int i = 0; i < board.GetLength(0); ++i) {
    Color current = board[i, 0].BackColor;
    int count = 1;

    for (int j = 1; j < board.GetLength(1); ++j) { // note j = 1
      if (current == board[i, j].BackColor) {
        count += 1;

        if (count >= atLeast)   
          return true;
      }      
      else {
        current = board[i, j].BackColor;
        count = 1;
      } 
    }
  } 

  return false;
}

public void checkWinner(Label[,] board) {
  if (IsWon(board))
    MessageBox.Show("You Win!");
}

As a further improvement, separate the model (i.e. gaming field - board[,]) and its representation (Labels on the form).

Upvotes: 1

Deepak Sharma
Deepak Sharma

Reputation: 4170

its not like only for loop can you get that result. you can try with some linq as well. I tried to find the "1" 5 times in a row continuously .. below code is working fine.

int countItemNeed = 5;
string[,] arr = new string[7, 6]
{
    {"0", "1", "1", "1", "0", "1"},
    {"1", "0", "1", "1", "1", "0"},
    {"1", "0", "1", "1", "1", "0"},
    {"1", "0", "1", "1", "1", "0"},
    {"1", "0", "1", "1", "1", "0"},
    {"1", "0", "1", "1", "1", "0"},
    {"1", "0", "1", "1", "1", "1"},
};

//convert the 2d array to list of object - List<new{string,index}>
var oneList = arr.Cast<string>()
    .Select((s,i)=> new {s,i}); 

// convert the List to into sub list. one for each row in 2D array   
var multipleList = new List<List<Tuple<string, int>>>();
//Size of the row - mean column in a row.
int size = arr.GetLength(1);
while (oneList.Any())
{
    multipleList.Add(oneList
                     .Take(size)
                     .Select(c=>Tuple.Create(c.s, c.i%size)).ToList());
    oneList = oneList.Skip(size).ToList();
}

// check each row. then check each item if we have all 5 items as same.
var foundContinueItems = multipleList.Any(list =>
{
    return list.Any(c =>
    {
        if (c.Item2 > size - countItemNeed)
        {
            return false;
        }
        return list.Skip(c.Item2)
                    .Take(countItemNeed)
                    .All(d => d.Item1 == "1");
    });
});

check the fiddle https://dotnetfiddle.net/YSOCJm

Upvotes: 0

Darshan Faldu
Darshan Faldu

Reputation: 1601

private void Check(Label[,] board)
{
    int Checker = 0;

    for (int i = 0; i < 19; i++)
    {
        for (int j = 0; j < 19; j++)
        {
            if (board[i, j].BackColor == Color.Black)
                Checker++;
            else
                Checker = 0;

            if (Checker == 5)
            {
                MessageBox.Show("You WON....!");
                return;
            }
        }
    }
}

Upvotes: 0

Related Questions