I am learning to program, in particular starting with C# where I have been experimenting with dice games to become more familiar and to improve myself. I am now trying to create a basic game in windows forms, where two players roll 5 dice and keep record of their score.
Rules:
Begin with 5 dice
If a 1 or 4 appears, player scores no points and those dice are removed. Otherwise add all the dice to total
Continue with remaining dice until there a no dice left over
So far I have an Image array which stores all my dice images in my resources and a button which will roll the dice. What I need help with specifically is being able to remove the penalized dice (setting that particular one back to blank) and allowing the player to continue rolling their remaining dice until none are left.
At the moment I am unsure as to where I can further this and maybe I am biting off more than I can chew. I am loving coding and any help would be much appreciated.
Here's an image of the interface:
public partial class Form1 : Form
{
Image[] diceImages;
int[] dice;
int[] diceResults;
Random random;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
diceImages = new Image[7];
diceImages[0] = Properties.Resources.blank;
diceImages[1] = Properties.Resources.one;
diceImages[2] = Properties.Resources.two;
diceImages[3] = Properties.Resources.three;
diceImages[4] = Properties.Resources.four;
diceImages[5] = Properties.Resources.five;
diceImages[6] = Properties.Resources.six;
dice = new int[5] { 0, 0, 0, 0, 0 };
random = new Random();
diceResults = new int[6] { 0, 0, 0, 0, 0, 0 };
}
private void btn_rollDice_Click(object sender, EventArgs e)
{
RollDice();
GetResults();
ResetResults();
}
private void RollDice()
{
for (int i = 0; i < dice.Length; i++)
{
dice[i] = random.Next(1, 7);
switch (dice[i])
{
case 1:
diceResults[0]++;
break;
case 2:
diceResults[1]++;
break;
case 3:
diceResults[2]++;
break;
case 4:
diceResults[3]++;
break;
case 5:
diceResults[4]++;
break;
case 6:
diceResults[5]++;
break;
}
}
lbl_dice1.Image = diceImages[dice[0]];
lbl_dice2.Image = diceImages[dice[1]];
lbl_dice3.Image = diceImages[dice[2]];
lbl_dice4.Image = diceImages[dice[3]];
lbl_dice5.Image = diceImages[dice[4]];
}
private void GetResults()
{
bool oneRoll = false, fourRoll = false;
for (int i = 0; i < diceResults.Length; i++)
{
if (diceResults[i] == 1 && diceResults[i] == 4)
{
oneRoll = true;
fourRoll = true;
}
}
}
private void ResetResults()
{
}
}
The code you posted has at least a couple of oddities that don't seem to fit with your description:
The code simply increments an element in an array (diceResults) when a given die value is rolled (i.e. the element corresponds to the die value, not the die's order in the game). From your description, I would have expected the code to simply add the die value to a single sum variable.
In your GetResults() method, your code compares the individual element values in the diceResults to the values 2 and 5. In other words, for each possible die value, if that value comes up twice or five times, you set both flags. There are a number of reasons this is strange, but the biggest, most obvious one is that a single variable (i.e. the element diceResults[i]) can never have two different values at the same time. I.e. that array element will never be both 2 and 5, as the if statement requires.
Given those issues, I'm more inclined to focus on the original specification than to trust the code too much in terms of trying to understand what your intended behavior of the code actually is. :)
It seems that the basic question is how best to remove die from play. The suggestion (in the comments above) to use a list to track the dice is certainly a workable one. In that approach, one would iterate through the list to set each element, and if the roll for a given element ever comes up as 1 or 4, remove that element before moving on.
Having done that, one would just iterate through the list again to set the die value images, using the "blank" image for any die beyond the length of the list.
But there is a simpler way, and based on your statement "setting that particular one back to blank", which seems to imply that each blank die should appear in the same position in which it was rolled, it seems like that simpler way might be preferable to you.
Specifically, after rolling the dice, just scan through the dice array and reset any 1 and 4 values to 0, and use this 0 value as a special "sentinel" value to indicate that die is now blank.
Note that however you do this (with a list, or just setting values to 0), there is also the question of whether to show the user the actual 1 and 4 rolls, or to immediately set those rolls to a blank die. I'm going to assume the former, but it would be very easy to implement it the other way instead. (As always, the beginning of good code is a good specification…right now, your specification is a bit light on detail, and thus is vague and ambiguous).
Taking that approach, your code might look something more like this:
public partial class Form1 : Form
{
#region Declaration
Image[] diceImages;
Label[] labels;
int[] dice;
int diceTotal;
bool checkOnesAndFours;
Random random;
#endregion
#region Initialiazation;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Initializing an array this way eliminates the chance of having
// a typo in the array index for the assignment.
diceImages = new Image[]
{
Properties.Resources.blank,
Properties.Resources.one,
Properties.Resources.two,
Properties.Resources.three,
Properties.Resources.four,
Properties.Resources.five,
Properties.Resources.six
};
// Arrays are always initialized with the elements having their default
// values, so there's no need to specify `0` values for `int` arrays explicitly
dice = new int[5];
random = new Random();
diceTotal = 0;
// For the purposes of setting the dice images, it will be helpful
// to keep the control references in an array. This is both convenient
// and, again, helps guard against typographical errors
labels = new Label[]
{
lbl_dice1,
lbl_dice2,
lbl_dice3,
lbl_dice4,
lbl_dice5
};
}
#endregion
#region Private Methods
private void btn_rollDice_Click(object sender, EventArgs e)
{
RollDice();
}
private void RollDice()
{
bool rolledOneOrFour = false;
int rollTotal = 0;
for (int i = 0; i < dice.Length; i++)
{
if (checkOnesAndFours)
{
// First, clear any 1 or 4 from the previous roll
if (dice[i] == 1 || dice[i] == 4)
{
dice[i] = 0;
}
// Then, ignore any blank die
if (dice[i] == 0)
{
continue;
}
}
dice[i] = random.Next(1, 7);
if (dice[i] == 1 || dice[i] == 4)
{
rolledOneOrFour = true;
}
rollTotal += dice[i];
}
if (!rolledOneOrFour)
{
diceTotal += rollTotal;
}
checkOnesAndFours = true;
for (int i = 0; i < labels.Length; i++)
{
labels[i].Image = diceImages[dice[i]];
}
}
#endregion
}
NOTE: it was not entirely clear to me what you mean to do when a 1 or 4 comes up. Taking what you wrote literally, I understand it to mean that if any die shows a 1 or 4 on a roll, that none of the dice count for that roll. The above code is implemented with that understanding in mind.
It occurs to me that you might have instead meant that only the dice that show 1 or 4 are not counted for the roll, and that the other dice for that roll are still included. It would not be hard to change the above to accommodate that alternative specification.
NOTE: you'll also notice that I have made other changes to the code not technically required in order to address the immediate question. I added comments in the code itself to try to explain why I made those changes, and why I feel they make the code better.
Just for grins, here's a version that does use a list instead:
public partial class Form1 : Form
{
#region Declaration
Image[] diceImages;
Label[] labels;
List<int> dice;
int diceTotal;
bool checkOnesAndFours;
Random random;
#endregion
#region Initialiazation;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// Initializing an array this way eliminates the chance of having
// a typo in the array index for the assignment.
diceImages = new Image[]
{
Properties.Resources.blank,
Properties.Resources.one,
Properties.Resources.two,
Properties.Resources.three,
Properties.Resources.four,
Properties.Resources.five,
Properties.Resources.six
};
// Lists must be initialized explicitly with their initial values, as by default
// they are initially empty.
dice = new List<int>(Enumerable.Repeat(0, 5));
random = new Random();
diceTotal = 0;
// For the purposes of setting the dice images, it will be helpful
// to keep the control references in an array. This is both convenient
// and, again, helps guard against typographical errors
labels = new Label[]
{
lbl_dice1,
lbl_dice2,
lbl_dice3,
lbl_dice4,
lbl_dice5
};
}
#endregion
#region Private Methods
private void btn_rollDice_Click(object sender, EventArgs e)
{
RollDice();
}
private void RollDice()
{
bool rolledOneOrFour = false;
int rollTotal = 0;
for (int i = 0; i < dice.Count; i++)
{
// Clear any 1 or 4 from the previous roll
if (checkOnesAndFours && (dice[i] == 1 || dice[i] == 4))
{
// Remove this die from play
dice.RemoveAt(i);
// The next list element to examine is now at the current i value
// and the for loop is going to increment i when the continue
// is executed, so decrement i in anticipation of that
// so that the loop moves on to the correct next element
i--;
continue;
}
dice[i] = random.Next(1, 7);
if (dice[i] == 1 || dice[i] == 4)
{
rolledOneOrFour = true;
}
rollTotal += dice[i];
}
if (!rolledOneOrFour)
{
diceTotal += rollTotal;
}
checkOnesAndFours = true;
for (int i = 0; i < labels.Length; i++)
{
labels[i].Image = i < dice.Count ? diceImages[dice[i]] : diceImages[0];
}
}
#endregion
}
Finally, note that neither of the above addresses a couple of other issues with the code:
Initializing the dice labels at the beginning of a game.
Resetting the entire game once the game has ended (i.e. there are no more dice left).
I leave these two items as an exercise for the reader (of course, if you run into problems with those specific issues, you may always post another question on Stack Overflow asking about each specifically).
Related
Take a look at my first app, am I on the right track? I have a simple form, user enters value to be looked up in an Array. If found return value with index. I use VS 2019/C#.
private async void ButtonSingleGetValues_Click(object sender, RoutedEventArgs e)
{
//Tasks:
//Get Value from TextBox on Form
//Validate TextBox (TextBoxSingleArray.Text) is a number
//Check if Number exists in Array
//Number Exists, Return Number to Form TextBox to show it exists
//then return Index of Number to Form TextBox in Array
//Here is the single Array
int[] singleDimension = new int[5] { 1, 5, 10, 15, 20 };
//Grab value from user input textbox for single array
bool intSingleTryParse = int.TryParse(TextBoxSingleArray.Text, out int ValueSingle);
if (intSingleTryParse) //bool is true, continue
{
int ArrayValue = Array.Find(singleDimension, element => element == ValueSingle);
if (ArrayValue > 0)
{
TextBoxSingleArrayValue.Text = ArrayValue.ToString();
int ArrayPosition = Array.IndexOf(singleDimension, ValueSingle);
TextBoxSinglePosition.Text = ArrayPosition.ToString();
}
else
{
MessageDialog MsgNotFound = new MessageDialog("Did not find number in array!");
await MsgNotFound.ShowAsync();
}
}
else
{
MessageDialog MsgNope = new MessageDialog("You not a number!");
await MsgNope.ShowAsync();
}
}
}
}
My version would be:
private async void ButtonSingleGetValues_Click(object sender, RoutedEventArgs e)
{
//I wouldn't use the word "single" in an array of ints because single is a floating point number type
//use plurals for collections
//do not need to specify array size with initializer
int[] numbers = new int[] { 1, 5, 10, 15, 20 };
//name your result bool more simply, do not mention single
//name your text box more simply
//windows controls like textbox tend to be named with the textbox word at the end
bool success = int.TryParse(FindIntTextBox.Text, out int findInt);
//personally, I prefer "test bad input, then return if it's bad"
//instead of ending up with nested nested code blocks that run for screens at a time
//it's kinda anathema to the "never use return mid method" crowd though
if (!success)
{
await new MessageDialog("Not a valid integer in the ... box").ShowAsync();
return;
}
//local variables are namedLikeThis, private are typically _namedLikeThis and public are NamedLikeThis
//do not use Array.Find on simple ints etc: you know what you're finding
//array.Find would be used for looking up eg a person by name
//you already know the value to find and just want its index
int foundIndex = Array.IndexOf(numbers, findInt);
//If there is a way to structure a test so that there is
//either a resulting one line operation to perform or a ten line op, i
//tend to place the the shorter code in the if and the longer
//code in the else. This way the condition that applies to
//the else is likely still visible on screen whereas if the if is
//long the else can lose meaning without a scroll. I could
//also have used the "if bad return" here but I do subscribe
//to "avoid return mid method if possible" and we've already started our processing
if(foundIndex == -1)
await new MessageDialog("not a known number").ShowAsync();
else
{
//not sure why you'd bother unless the int parse tolerated eg spaces
FindIntTextBox.Text = findInt.ToString();
FoundIndexTextBox.Text = foundIndex.ToString();
}
}
}
I'd make the async await calls a single line await x.YAsync(z); rather than declaring a variable just for the task. We tend to only do that when we need to create a task, do some more work, then await the task
I'm making a little UWP app in C# that simulates drawing straws. The problem that I'm having is that I want the user to get to a point where they click a button, and the variable baseNumber (initially set to 2 -- the fewest amount of drawn straws to make any sense) is compared to a randomly generated number that is passed through from another button event. I have this part down.
The part that is giving me fits is that I want the next user to then click the same button, and I want the baseNumber incremented by 1, and have that new number compared to the losingStraw. If that user is declared safe, I want the number to increment again for the next user with their button click, until ultimately the numbers equal each other, a user draws the short straw, and another code path is taken.
I'm very new to coding, so what I want might not even be possible. Here's an example of what I've tried, below. I have also tried variants on a do / do while and a for loop.
If I could somehow stop something like a for loop at each pass and make it wait for user input (the button press), that would be ideal. I couldn't figure that out either though.
Any help or ideas you can offer would be greatly appreciated!
private void drawButton_Click(object sender, RoutedEventArgs e)
{
int baseNumber = 2;
int losingStraw = Convert.ToInt32(drawButton.Tag);
if (baseNumber < losingStraw)
{
instructions.Text = "You are safe!";
baseNumber = baseNumber++; // that doesn't work at all. I was hoping that baseNumber
}
else
{
instructions.Text = "You have drawn the losing straw";
}
}
You have to write baseNumber++; instead of baseNumber = baseNumber++; since ++ aka the unary operators helps increases integer value by one. Thus baseNumber++; means baseNumber = baseNumber + 1;
Edit
In your case the variable int baseNumber = 2; has been declare and initialized in the event itself thus every-time the event is fired the baseNumber will be initialized by 2 and you would not be able to see any changes. Thus you can do something like this
int baseNumber; // you can do 2 here as well
public YourFormName()
{
baseNumber = 2;
}
private void drawButton_Click(object sender, RoutedEventArgs e)
{
int losingStraw = Convert.ToInt32(drawButton.Tag);
if (baseNumber < losingStraw)
{
instructions.Text = "You are safe!";
baseNumber = baseNumber++; // that doesn't work at all. I was hoping that baseNumber
}
else
{
instructions.Text = "You have drawn the losing straw";
}
}
Just put the baseNumber outside the method, can be globally declared so that everytime you increment it, it will never to reset to 2 again when entered on butotn click :) -HAPPY CODING-
public class YourClass
{
int baseNumber = 2;
private void drawButton_Click(object sender, RoutedEventArgs e)
{
int losingStraw = Convert.ToInt32(drawButton.Tag);
if (baseNumber < losingStraw)
{
instructions.Text = "You are safe!";
baseNumber = baseNumber++; // that doesn't work at all. I was hoping that baseNumber
}
else
{
instructions.Text = "You have drawn the losing straw";
}
}
}
I was experimenting with lists in C#'s console application, specifically a randomized int list which had its number order randomized. In this experiment I wanted to go through the randomized values from the list when I pressed enter and when it had shown all the randomized values it would stop. And it worked just as I intended: http://i.imgur.com/bNOYrZp.png[^]
Random r = new Random();
int tempValue;
List<int> number = new List<int>();
number.Add(1);
number.Add(2);
number.Add(3);
number.Add(4);
number.Add(5);
number.Add(6);
number.Add(7);
number.Add(8);
number.Add(9);
number.Add(10);
for (int i = 0; i < 10; i++)
{
tempValue = r.Next(0, number.Count);
Console.WriteLine(number[tempValue]);
number.RemoveAt(tempValue);
Console.ReadLine();
}
Now how do I do a similar thing in C#'s Windows Form Application? Instead of pressing enter to go through the list, I press a button to go through the list, and the order of the values are displayed on a label every time I press this button.
I used a similar code, but it did not work as intended. Instead of going through the randomized values it kept making a new order of values which it kept doing every time I clicked the button. What I want it to do is to go through the randomized values and after it has showed all the 10 randomized values, without duplicates, it stops.
private void button1_Click(object sender, EventArgs e)
{
List<int> number = new List<int>();
Random r = new Random();
int tempValue;
number.Add(1);
number.Add(2);
number.Add(3);
number.Add(4);
number.Add(5);
number.Add(6);
number.Add(7);
number.Add(8);
number.Add(9);
number.Add(10);
for (int i = 0; i < 10; i++)
{
tempValue = r.Next(0, number.Count);
label1.Text = number[tempValue].ToString();
number.Remove(number[tempValue]);
}
}
Since you put the list creation and for loop in the click event, its working "as-coded" (obviously not what you intended).
Remember, the entire click handler runs every time you press the button. So you need to initialize the list elsewhere and then just iterate through it on click. Something like:
private Random rng = new Random();
private List<int> numbers = new List<int>();
private void Form_Loaded(...) //Set to Form's Loaded event
{
number.Add(1);
number.Add(2);
number.Add(3);
number.Add(4);
number.Add(5);
number.Add(6);
number.Add(7);
number.Add(8);
number.Add(9);
number.Add(10);
}
private void button1_click(...)
{
tempValue = rng.Next(0, number.Count);
label1.Text = number[tempValue].ToString();
number.Remove(number[tempValue]);
}
Note that this code will have a few issues once the list runs out, there is no way to re-initialize the list, etc. I leave those as an exercise to you.
Also note that I created one instance of Random and stored it at the class level. In general, you want to use one instance per class to avoid seeding issues (though recreating it in the button click would have technically worked, since you probably can't click the button fast enough).
I know there are MANY similiar questions, but I can't seem to get to the bottom of this.
In my program I execute a verification method which should compare two ascii HEX files with eachother (one is local, the other is read from a USB device). Some code:
private void buttonVerify_Click(object sender, EventArgs e)
{
onlyVerifying = true;
Thread t = new Thread(verifyProgram);
}
private void verifyProgram()
{
verifying = true;
externalFlashFile.Clear();
// After this method is finished, the returned data will end up in
// this.externalFlashFile since this listen to the usb's returned data
hexFile.readExternalFlashForVerify(usbDongle, autoEvent);
externalFlashFile.RemoveAt(0);
//externalFlashFile.RemoveAt(externalFlashFile.Count - 1);
hexFile.verifyProgram(externalFlashFile);
}
public void verifyProgram(List<string> externalProgram)
{
byte[] originalFile = null; // Will be modified later with given size
byte[] externalFile = new byte[4096];
int k = 0, errors = 0;
// Remove last line which contains USB command data
externalProgram.RemoveAt(externalProgram.Count - 1);
foreach (String currentLine in externalProgram)
{
for (int i = 0; i < 64; i += 2)
{
string currentDataByte = currentLine.Substring(i, 2);
externalFile[k] = Convert.ToByte(currentDataByte, 16);
k++;
}
progress += steps;
}
//... compare externalFile and originalFile
When executing the readExternalFlashForVerify the USB is responding with requested data. This data is parsed and calls an eventhandler:
public void usbDongle_OnDataParsed(object sender, EventArgs e)
{
if (verifying)
{
usbDongle.receivedBytesString.Trim();
externalFlashFile.Add(usbDongle.receivedBytesString.Substring(2, 32 * 2));
// Allow hexFile continue its thread processing
autoEvent.Set();
}
}
The first run is always completes correctly. The following executions, at the third or fourth iteration of the foreach, I get an extra element in externalProgram. This is not a global variable (argument in function call) and the function is not called anywhere else. This ofcourse throws an exception.
I tried adding .ToList() to externalProgram in the foreach but that didn't do any difference. How can my externalProgram be modified during this execution?
EDIT: I never found the cause of this, but replacing the foreach with a hard-coded for-loop solved the issue at hand. Not an optimal solution, but don't have much time on this.
// The list should never be larger than 128 items
for (int j = 0; j < 0x7f ; j++)
{
string currentLine = externalProgram[j];
// ...
Usually when you receive an exception with a message like that it is caused by multiple accesses from different threads to a list.
What I suggest you is to use a lock when you add and remove items from that list, so you're sure the indexes to that collection are not changing. You have to think what would happen if you try to remove the last element (of index 3, for example) of a collection when someone else removes a previous item (changing the lenght of the collection to 3...).
This example: Properly locking a List<T> in MultiThreaded Scenarios? describes better what I mean.
Probably this line is a problem:
externalProgram.RemoveAt(externalProgram.Count - 1);
If verifyProgram is called multiple times, it will remove more and more lines from externalProgram list passed by reference
I need a little help with a Random Number Guessing Game in visual studio. I got the brunt of the code down but I am having troubles with the Random number generator and getting the random number to port into the click events. As always, I don't really need code but some guidance and/or explanations as to what I am doing wrong and if there is a more effecient way to do things in the beginner phases of learning. Below is my code, the comments are the parts where I am having troubles. Thanks for any help as the help I've recieved to date as been phenominal.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace LAB6B
{
public partial class game : Form
{
public game()
{
InitializeComponent();
//Generate Random number between 1 and 100
//Not sure if there is a better way?
Random rand1 = new Random();
int num1 = rand1.Next(1,50);
int num2 = rand1.Next(1,50);
int answer = num1 + num2;
}
private void evaluate_Click(object sender, EventArgs e)
{
int count = 0;
int choice = Convert.ToInt32(guess);
if (guess.Text != string.Empty)
{
// set counter to keep track of how many tries
// should this be done by a loop or will it count without a loop?
count++;
//compare user input against random number
//Can’t import the random number for comparision
if (choice < answer)
{
Evaluate.Visible = false;
lblMessage.Visible = true;
lblMessage.Text = "Too Low!";
Clear.Visible = true;
BackColor = Color.LightSeaGreen;
}
else if (choice > answer)
{
Evaluate.Visible = false;
lblMessage.Visible = true;
lblMessage.Text = "Too High!";
Clear.Visible = true;
BackColor = Color.SlateBlue;
}
else
{
//Display correct message along with how many times it took to get it
MessageBox.Show(" Eso es CORRECTO! It took you {0} tries. ", count);
}
}
}
private void Clear_Click(object sender, EventArgs e)
{
guess.Text = "";
Evaluate.Visible = true;
lblMessage.Visible = false;
Clear.Visible = false;
BackColor = Color.PowderBlue;
}
}
}
As the rand1 and answer variables are defined within the constructor, you can only access them in the constructor. Defining answer on the class level will solve most of the problems, as you will be able to access it both from the constructor and the click handlers, like this:
private int answer;
private int count;
public game()
{
InitializeComponent();
//Generate Random number between 1 and 100
Random random= new Random();
// no need for num1 and num2, it's just as random
answer = random.Next(1,101);
}
I think you have an issue of scope. The "answer" variable is declared inside your constructor, so it will not be visible to the code inside evaluate_Click(…).
Looks like you need to declare answer as a class variable. When you declare a variable in a constructor, it's still local to that method and not available to other methods.
I do not really know what you want answered, but an obvious error is that you must define your count variable as a member variable in order to keep track of the number of tries. As it is now, the count will always be initialized as zero each time the user presses the button.
First of, you need to declare your variable answer in the page level so it can be used by other page level functions.
Do it like this
public partial class game : Form
{
int answer;
public game()
{
}
}
in your counter you can use a static counter or a page level variable also such as the variable answer
just reset the counter when the user have guessed correctly