Reputation:
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
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:
FindClickedLabelCoordinates()
method entirely, along with some of the fields, as they were all unused.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.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
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 (Label
s on the form).
Upvotes: 1
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
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