here is the game board just to give you an idea of how it looks like (this board will be expanded to a 7x6)
what i want to do is detect a winner when 2 colors are in a row similar to the game "conmect four" taking into account diagonal combos too. BUT i want to do this with out using brute-force enumeration..
this the code that goes behind the program i have made I'm not asking for solution i just need a bit of help on an effective algorithm
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Button[] btns;
private Button[] btns2;
public Form1()
{
InitializeComponent();
btns = new Button[] { button2, button3 };
btns2 = new Button[] { button4, button5 };
}
private void Form1_Load(object sender, EventArgs e)
{
foreach (var btn in btns)
{
btn.Enabled = false;
btn.BackColor = Color.LightCyan;
}
foreach (var btn in btns2)
{
btn.Enabled = false;
btn.BackColor = Color.LightCyan;
}
}
public int state;
int cc = 0;
private void button1_Click(object sender, EventArgs e)
{
foreach (var btn in btns)
{
{
if (!btn.Enabled)
{
btn.Enabled = true;
if (cc == 0)
{
cc = 1;
btn.BackColor = Color.Red;
}
else
{
cc = 0;
btn.BackColor = Color.Yellow;
}
return;
}
}
}
}
private void button6_Click(object sender, EventArgs e)
{
foreach (var btn in btns2)
{
if (!btn.Enabled)
{
btn.Enabled = true;
if (cc == 0)
{
cc = 1;
btn.BackColor = Color.Red;
}
else
{
cc = 0;
btn.BackColor = Color.Yellow;
}
return;
}
}
}
}
}
First, for efficiency and sanity, I'd keep the state of my board in a 2D array.
Second, for detecting win states, given that you start the game with a (presumably) empty board, you can only get into a win state when a button changes state. And if the button changing state puts you into a win state, then that button must be involved in that win state (i.e. it must be part of you line).
So...you don't need to brute-force the whole board. You only need to determine if the button that just changed state is part of a line. In other words, look only at the buttons to above, below, to the left and to the right (and maybe diagonal, your question wasn't clear if you included diagonals) to see if they are the same color as the one you changed. If any one of them is, then this is a win state. This is where using a 2D array will make you life much easier. If the button at (x, y) is changed, then you only need to check (x-1, y), (x+1, y), (x, y-1) and (x, y+1), (and maybe diagonals) making sure to do appropriate boundary checks, of course.
Extending this to 3, 4 or more in a row isn't much more difficult, except you need to remember you might be in the middle of a row rather than one end or the other.
Unoptimized Pseudo Code for 2 in a row (note, I've switched to compass points to avoid up-left, up-right, etc because I feel it gets a bit unwieldy):
// cell is the cell that last changes, it has an x and y property and a color property
// board is a member variable, a 2D array of cells. Note [0,0] is the upper-left (NW) corner of the board.
// boardHeight and boardWidth are member variable with the dimensions of the board
// board[boardWidth-1, boardHeight-1] is the lower-right (SE) corner of the board
// returns true for a win, false otherwise
function checkWin(cell) returns bool {
// check west
if (cell.x > 0 && board[cell.x - 1, cell.y].color == cell.color)
return true;
// check northwest
if (cell.x > 0 && cell.y > 0 && board[cell.x-1, cell.y-1].color == cell.color)
return true;
// check north
if (cell.y > 0 && board[cell.x, cell.y-1].color == cell.color)
return true;
// check northeast
if (cell.y > 0 && cell.x < boardWidth && board[cell.x+1, cell.y-1].color == cell.color)
return true;
// checking the other directions is left as an exercise for the reader, hopefully you get the point
return false;
}
If you are doing more than 2, I'd think about a recursive function to count the number of matching cells to the left, right, up, down, and diagnoals
// k is the number of cells in a row for a win
function checkWin(cell) returns bool {
// check west / east
int count = checkWest(cell);
if (count > k)
return true;
count += checkEast(cell);
if (count > k)
return true;
// check nw / se
count = checkNW(cell);
if (count > k)
return true;
count += checkSE(cell);
if (count > k)
return true;
// and so on, checking N/S and NE/SW
return false;
}
function checkWest(cell) returns int {
// base case, check the boundaries!
if (cell.x == 0)
return 0;
// base case, the cell next to this one doesn't match
if (board[cell.x-1,cell.y].color != cell.color)
return 0;
// recursion, check the next cell in the line
return 1 + checkWest(board[cell.x-1,cell.y]);
}
For an n by m board and a winning combo of k in a row:
int n, m, k;
byte[,] color = new byte[n, m]; // for two colors, a 0 would correspond to blue, 1 would be red, or however you like
for (int i = 0; i <= n - k; i++) // don't check all the way to the right because there's no room to win
{
for (int j = 0; j <= m - k; j++) // don't check all the way down because there's no room to win
{
// Check here for a win. Check for a win to the right, down right, and down
}
}
Related
I have a checkeboxlist with 100 items. Obviously user can check items one by one as many as he need, but I would like to give to user option check range of items (let's say with Shift hold button). So, user check one of the items (let's say item index 5) and then press and hold shift button and check next item (index 10), so I range of the items should be checked from 5...10
I have not found anything about such implementation, looks like it doesn't exist and no one did such kind of things.
How to do it?
Keep track of your last index:
int lastIndex = -1;
In your form's constructor, wire things up:
public Form1() {
InitializeComponent();
checkedListBox1.CheckOnClick = true;
checkedListBox1.SelectedIndexChanged += CheckedListBox1_SelectedIndexChanged;
checkedListBox1.MouseDown += CheckedListBox1_MouseDown;
}
And then use these methods to change the items in the range:
private void CheckedListBox1_SelectedIndexChanged(object sender, EventArgs e) {
lastIndex = checkedListBox1.SelectedIndex;
}
private void CheckedListBox1_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left && Control.ModifierKeys == Keys.Shift) {
var useIndex = Math.Max(lastIndex, 0);
var x = checkedListBox1.IndexFromPoint(e.Location);
if (x > -1 && x != useIndex) {
if (useIndex > x) {
for (int i = useIndex - 1; i > x; i--) {
checkedListBox1.SetItemChecked(i, !checkedListBox1.GetItemChecked(i));
}
} else {
for (int i = useIndex + 1; i < x; i++) {
checkedListBox1.SetItemChecked(i, !checkedListBox1.GetItemChecked(i));
}
}
}
}
}
Problem in short: Load saved map that was saved as an textfile where 0 = empty button/tile, 1 = wall, 2 = character and it back up on another form.
Longer more detailed explanation:
I have been working on a winform game for a bit, I know it isn't the best way to make a game but I am just trying to get accustomed. Currently I have two forms, one to create a map, another to load it up. On my map creation form I am able to say the length and width of my map, when I generate it, it displays tiles(that are buttons) with the length and width dimensions specified( 3 buttons wide, 4 buttons long.)
Now I am able to save it as numbers that were given values for that certain image ( 0 is for no image just the button, 1 is for a button with an wall, 2 is for a button with a character.). What I am trying to do now is read the text file and load up the tiles with the correct dimensions and image that was originally given.
My images are placed onto radio buttons which determine which image to play, I did tags for this
public SaveForm()
{
InitializeComponent();
rNoImage.Tag = 0;
rNoImage.Click += Check;
rCharacter.Tag = 1;
rCharacter.Click += Check;
rWall.Tag = 2;
rWall.Click += Check;
}
private void square_Click(object sender, EventArgs e)
{
Map square = (Map)sender;
Map.Type = (MapType)selected;
switch (selected)
{
case 0:
square.Image = null;
break;
case 1:
square.Image = Properties.Resources.Character;
break;
case 2:
square.Image = Properties.Resources.Wall;
break;
}
}
private void Check(object sender, EventArgs e)
{
RadioButton toolBtn = (RadioButton)sender;
selectedTool = (int)toolBtn.Tag;
}
When saving I have done the following:
private void saveButton(object sender, EventArgs e)
{
SaveFileDialog sfg = new SaveFileDialog();
sfg.Filter = "Game File(*.game)|*.game";
if (sfg.ShowDialog() == DialogResult.OK)
{
using (StreamWriter sw = new StreamWriter(sfg.FileName))
{
sw.WriteLine($"{rows},{cols}");
foreach (Map square in (pnlGameBoard.Controls))
{
sw.WriteLine(square.GetString());
}
var output = MessageBox.Show("saved", "saved success", MessageBoxButtons.OK);
}
}
}
I have a component class set up for this called Map in this I have an enum to show the types
public enum MapType
{
None,
Character,
Wall,
}
Now I have my map being made as an Button, when clicked based on selected radio button image changes on the button.
public partial class Map : Button
{
int row;
int col;
public MapType Type { get; set; }
const int OFFSET = 20;
const int MapSize = 50;
public Map()
{
}
public Map(int row, int col)
{
this.row = row;
this.col = col;
this.Size = new Size(MapSize, MapSize);
this.Location = new Point(OFFSET + col * MapSize, OFFSET + row * MapSize);
Type = MapType.None;
}
public string GetString()
{
return $"{(int)Type}";
}
}
So I am able to save it, but my issue now is loading it up on the second form which I am struggling to do.
What I have managed so far is:
private void openButton_Click(object sender, EventArgs e)
{
OpenFileDialog ofg = new OpenFileDialog();
ofg.Filter = "Game File(*.game)|*.game;"
if(ofg.ShowDialog() == DialogResult.OK)
{
File.ReadAllLines(ofg.FileName);
//Not Sure what to do next here
}
}
Apologize for the lengthy post but I wanted to make sure that I am clear enough, thanks.
The layout for the textfile that I saved would be:
3,3
1
1
1
0
2
0
1
1
1
3,3 represents the length and width that was chosen, I will most likely remove this as I am only trying to read the numbers below, top row is wall,wall,wall(1,1,1), middle row is empty button, character, empty button, bottom row is wall,wall,wall.
I have looked at some different examples here that involve reading 2D matrixes from files to 2D int arrays but I am not sure how to actually implement this in my above code or how it works.
You've created a way to represent a Map as a string, but not the other way around. One way to create a Map from a string is to write a public static Map Parse(string input) method that takes in a string and returns a Map. One tricky part is that we will also need to pass in the row and col, since those are private fields and not settable outside of the constructor.
For example:
public class Map : Button
{
// Existing code exluded from this example
public static Map Parse(string input, int row, int col)
{
if (input == null) return null;
int typeVal;
if (!int.TryParse(input, out typeVal))
{
throw new ArgumentException("input must be a valid integer");
}
// Return a new map with row, col, and Type
return new Map(row, col) {Type = (MapType) typeVal};
}
}
Now that we can create a Map with the proper Type from a string (along with the row and col), we can create a couple of loops: one to loop through each row, and on each row we loop through each column, creating a Map and adding it to our panel:
// You might need to set some standard sizes for the Map controls
private int mapHeight = 50;
private int mapWidth = 300;
// Not sure where these are actually defined
private int rows;
private int cols;
private void openButton_Click(object sender, EventArgs e)
{
var ofg = new OpenFileDialog {Filter = "Game File(*.game)|*.game;"};
if (ofg.ShowDialog() == DialogResult.OK)
{
// Read all our lines into an array
var lines = File.ReadAllLines(ofg.FileName);
// And now do the opposite of how we created the text file:
// First, set the rows and cols based on the first line
// (some validation should be done here, but this works with "3,3")
var rowsCols = lines[0].Split(',');
int.TryParse(rowsCols[0], out rows);
int.TryParse(rowsCols[1], out cols);
// Next, create Maps from the rest of the lines and add them to our controls
// Note we should validate that lines.Length = rows * cols + 1
var line = 1; // This gets incremented below, so we read each line
for (int row = 0; row < rows; row ++)
{
for (int col = 0; col < cols; col++)
{
// Create a map from the line, row, and column using our new method
var map = Map.Parse(lines[line++], row, col);
// Set the layout by rows and columns (?)
map.Width = mapWidth;
map.Height = mapHeight;
map.Left = (col + 1) * mapWidth;
map.Top = (row + 1) * mapHeight;
// Hook up the Click event so our images load on click
map.Click += square_Click;
// Add the control to our panel
pnlGameBoard.Controls.Add(map);
}
}
}
}
I need to make a multi line textbox that changes the back ground for charaters after the line becomes longer than 80 characters. So that if someone types a sentence of 85 characters the last 5 characters will have a yellow background. I would like it to make this feature part of the style because currently we are tying to do this with logic in the code behind and it lags when someone types quickly.
current highlighting imlp
private void HighlightLines()
{
try
{
//// x is the distance in the row from the left side of the Textbox. e.g. Each character is one unit.
int x = 0;
//// y is the distance in the columns from the top of the Textbox. e.g. Each character is one unit.
int y = 0;
//// lines is the number of newline characters found in the Text.
int lines = 0;
//// Point 1 is the starting point of the range that needs to be highlighted.
TextPointer point1 = this.Document.ContentStart;
//// Point 2 is the end point of the range that needs to be highlighted.
TextPointer point2 = this.Document.ContentStart;
//// Range is the distance from Point 1 to Point 2 needed to apply the Yellow color to the area past 69 characters.
TextRange range;
//// Additional Ranges is the collection of all the ranges that need to be Yellow.
this.AdditionalRanges = new ObservableCollection<TextRange>();
//// Count the number of lines.
for (int i = 0; i < this.Text.Length; i++)
{
if (this.Text[i] == '\n')
{
lines++;
this.AdditionalRanges.Add(new TextRange(this.Document.ContentStart, this.Document.ContentEnd));
}
}
//// This map is used to differentiate which lines need to be colored. (True means the range is over 69 characters. False means the opposite).
bool[] map = new bool[lines];
//// Traverse the whole text.
for (int i = 0; i < this.Text.Length; i++)
{
var currentCharacter = this.Text[i];
var newLineCharacter = '\n';
if (currentCharacter == newLineCharacter)
{
var pointDifference = point1.GetOffsetToPosition(point2);
point1 = point1.GetPositionAtOffset(pointDifference);
x = 0;
y++;
}
else if (x > 69)
{
range = new TextRange(point1, point2);
this.AdditionalRanges[y] = range;
map[y] = true;
if (point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
{
point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
}
}
else if (point1.GetNextInsertionPosition(LogicalDirection.Forward) != null && point2.GetNextInsertionPosition(LogicalDirection.Forward) != null)
{
point1 = point1.GetNextInsertionPosition(LogicalDirection.Forward);
point2 = point2.GetNextInsertionPosition(LogicalDirection.Forward);
x++;
}
}
//// Make everything white.
foreach (var item in this.AdditionalRanges)
{
item.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.White);
}
//// Make the appropriate ranges Yellow.
for (int i = 0; i < this.AdditionalRanges.Count; i++)
{
if (map[i])
{
this.AdditionalRanges[i].ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
}
}
}
catch (Exception e)
{
// drop exception. this has only broke once and we dont exactly know why.
}
}
It's probably lagging because your HighlightLines() method is scanning the entire document every time, and I assume it gets called after every keypress. Ideally you want to only re-scan the portions of the text that have changed. Fortunately, the TextChanged event provides the exact offset of the changes.
The example code below was written to work with a RichTextBox but you should be able to adapt it. Also, it looks like your code was checking for 69 characters instead of 80, so this does the same:
RichTextBox txt;
...
bool suppressChanges = false;
private void Txt_TextChanged(object sender, TextChangedEventArgs e)
{
if (!suppressChanges)
{
// suppress changes because changing highlights will trigger the event again
suppressChanges = true;
foreach (var change in e.Changes)
{
var changeStart = txt.Document.ContentStart.GetPositionAtOffset(change.Offset);
TextRange changedRange;
if (change.AddedLength > 0)
changedRange = new TextRange(changeStart, changeStart.GetPositionAtOffset(change.AddedLength));
else
changedRange = new TextRange(changeStart, changeStart);
SetRangeColors(changedRange);
}
//unsuppress changes
suppressChanges = false;
}
}
void SetRangeColors(TextRange range)
{
// Scan one line at a time starting with the beginning of the range
TextPointer current = range.Start.GetLineStartPosition(0);
while (current != null && current.CompareTo(range.End) < 0)
{
// find the next line or the end of the document
var nextLine = current.GetLineStartPosition(1, out int lines);
TextPointer lineEnd;
if (lines > 0)
lineEnd = nextLine.GetNextInsertionPosition(LogicalDirection.Backward);
else
lineEnd = txt.Document.ContentEnd;
var lineRange = new TextRange(current, lineEnd);
// clear properties first or the offsets won't match the characters
lineRange.ClearAllProperties();
var lineText = lineRange.Text;
if (lineText.Length > 69)
{
var highlight = new TextRange(current.GetPositionAtOffset(70), lineEnd);
highlight.ApplyPropertyValue(TextElement.BackgroundProperty, Brushes.Yellow);
}
// advance to the next line
current = lineEnd.GetLineStartPosition(1);
}
}
I was asked to make a tic tac toe in windows form and i have to use 2D array
I am trying to store 1 in the array for each X and -1 for each 0
then i will add the values in each row ,column and diagonal and check if its 3 or -3
the problem is i don't know how to assign and i and j for each element in the array after i press on a corresponding button
private void storeInboard(int i, int j, object sender, EventArgs e)
{
{
if ((sender as Button).Text == "X")
board[i][j] = 1;
else if ((sender as Button).Text == "O")
board[i][j] = -1;
}
}
here i check for each column by giving its number
private bool checkCol(int col)
{
for (int i = 0; i < 3; i++)
{
rowSum += board[i][col];
if (colSum == 3 || colSum ==-3 )
return true;
else
colSum = 0;
}
return false;
}
checking for winner
private bool checkWinner()
{
return (checkCol(0) || checkCol(1) || checkCol(2) || checkDiag1() || checkDiag2() || checkRow(0) || checkRow(1) || checkRow(2));
}
here is the button click event >> its is assigned for all the buttons
private void button_click(object sender, EventArgs e)
{
if (turn)
{
(sender as Button).Text = "X";
}
else
(sender as Button).Text = "O";
turn = !turn;
turnCount++;
(sender as Button).Enabled = false;
if (checkWinner() && turnCount <=9)
MessageBox.Show("Winner !!!");
else
MessageBox.Show("Tie -.-");
so I just want to know how can i send an i and j for the event storeInboard for each button i click
thanks in advance
This question is not about arrays or some 2D stuff but binding information to winform controls.
You have several ways to do this.
1) In visual studio property panel fill buttons Tag property with an ordinal number one by one from left to right starting with zero. Left-upper button = 0, middle-upper = 1 and so on.
Then in button_click you can do something like:
var tag = int.Parse((string)((Button)sender).Tag);
int colIndex = tag % 3;
int rowIndex = tag / 3;
2) Maybe the buttons are placed in a Panel. Then you can iterate through the children of the parent panel in button_click and see if one equals to sender.
int c = 0;
foreach (var item in parentPanel.Controls)
{
if (item == sender)
break;
c++;
}
if (c<parentPanel.Controls.Count) // found
{
int colIndex = c % 3;
int rowIndex = c / 3;
}
// else -- ugh, something went wrong, maybe not only the buttons have this event
Much more solution is possible.
Below is my current code:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public int[] trialArray = new int[10];
public int trialCounter = -1;
private void button1_Click(object sender, EventArgs e)
{
bool button1Click = true;
if (button1Click == true)
{
ITIpanel.Visible = true;
for (int i = 0; i < trialArray.Length; i++) { trialArray[i] = -1; } // Set default value through array
int counter = 0;
Random rnd = new Random();
while (counter < 10 / 2)
{ // Red trials, fill half array
int index = rnd.Next(0, 10 - 1);
if (trialArray[index] == -1) { trialArray[index] = 1; ++counter; } //if unchanged value, change it
}
while (counter < 10)
{
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 2; ++counter; }
}
}
}
private void ITIpanel_Paint(object sender, PaintEventArgs e)
{
if (ITIpanel.Visible == true)
{
trialCounter += 1;
timer1.Enabled = true;
}
}
private void timer1_Tick(object sender, EventArgs e)
{
ITIpanel.Visible = false;
timer1.Enabled = false;
if (trialArray[trialCounter] == 1) { redstimPanel.Visible = true; }
else { bluestimPanel.Visible = true;}
if (trialCounter == 9) { Application.Exit(); }
}
public int counter = 0;
public event EventHandler Clicked5TimesEvent;
private void OnClicked5TimesEvent()
{ if (Clicked5TimesEvent != null) { Clicked5TimesEvent(this, EventArgs.Empty); } }
private void bluestimPanel_MouseDown(object sender, EventArgs e)
{
//FR requirement
counter++; if (counter % 5 == 0) { redstimPanel.Visible = false; ITIpanel.Visible = true; }
}
private void redstimPanel_MouseDown(object sender, EventArgs e)
{
//FR requirement
counter++; if (counter % 5 == 0) { redstimPanel.Visible = false; ITIpanel.Visible = true; }
}
}
}
As you can see, I am attempting to make a global array with 10 items. On the button click the 10 items are supposed to be altered such that half contain the value 1 and the other half contain the value 2.
Then, on the timer tick, depending on the value in the trialCounter, which determines the part of the array to be accessed, it should display either the redstimPanel or the bluestimPanel.
Therefore, if the 'trialCounter' is equal to 8, and 8 in the TrialArray is equal 1, the 'redstimPanel' should become Visible. Alternatively, if 8 in the 'TrialArray' is equal to 2, the 'bluestimPanel' should become Visible.
This, however, is not working as I would like it to. Thus, there are clearly some issues with my code. Do you all have any suggestions?
You never reset counter, or have the second loop (the one setting the 2s) be the full array.
There is also an error with the random number, rnd.Next(a,b) a - lower bound (inclusive), b - upper bound (exclusive). So it should be rnd.Next(0,10); so you have a chance of populating the last array position.
while (counter < 10 / 2) { // Red trials, fill half array
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 1; ++counter; } //if unchanged value, change it
}
//Counter on the first loop here is already 5 (it exited the previous loop)
//So allow it to get to 10, and populate the FULL array.
while (counter < 10) {
int index = rnd.Next(0, 10);
if (trialArray[index] == -1) { trialArray[index] = 2; ++counter; }
}
Allow me to give you some tips and some explanations regarding your code:
First of all, you probably wanted that local button1Click variable to know later on whether the button has been clicked or not. For that to work, you should place it outside that function, otherwise it's never going to be used, and will be true with every button click, something like this:
bool button1Click = false;
private void button1_Click(object sender, EventArgs e)
{
if (!button1Click)
{
When you have a condition, you want the code to decide, whether an expression is true or false you may omit the part "== true" because it doesn't add anything new.
You have two whiles. Your idea was to run the counter until 5, with the first piece of code, and then from 5 to 10 the second piece of code. Now let me try to explain what is actually going on. The counter will go on until 5 filling 1s at random indices. Then at 5, the expression in the while will become false and it breaks out from the loop. Since the second while has the very same expression, it simply avoids it and goes on. One of the many solutions would be to have an if in the loop like this:
while (counter < 10)
{
if (counter<5)
{
// fill red
}
else
{
// fill blue
}
}
The way you fill up the values in your array. Have you thought about what's going to happen when the same index will be generated several times? It means it'll overwrite the previous value while certain index will remain -1.