I'm trying to learn C# (so go easy on me!) and as part of a coursework I have been asked to create a game called strikeout.
The game rules are very simple. It is simply a case of knocking one counter into another, with the aim of leaving one counter left on the screen.
I am having trouble simply moving one counter into another. I have created a number of picture boxes using a loop (to make up my board), and created an event handler using the loop. How can I use the single even handler to move all of my counters?
{
(...)
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
int x = j * (75);
int y = i * (75);
pictureBoxCounter[j, i] = new PictureBox();
pictureBoxCounter[j, i].Location = new System.Drawing.Point(x, y);
pictureBoxCounter[j, i].Size = new System.Drawing.Size(75, 75);
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
panelGame.Controls.Add(pictureBoxCounter[j, i]);
this.pictureBoxCounter[j, i].Click += new System.EventHandler(this.pictureBoxCounter_Click);
}
}
} // end of some function
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
//I need some code here but nothing seems to work :(
}
I've spent way to long on this problem. Even asked my tutor to help. Instead of helping he managed to break most of my code. So after fixing the problems he caused I am now back with a compiling program!
(When running the program you will need to enter player info to enable the start game button.)
If I can help with any other information don't hesitate to ask!
This programming problem appears intended to teach you about separation of concerns. If it isn't, it should be. (It is now!)
You have two problems, not one. (Actually, you have more than two, but you have two big ones.) One problem is: How do I create a set of objects representing the counters in this game, and what rules do those objects follow? The other problem is: How do I represent the counters on the screen?
If your solution to the first problem is to create a bunch of PictureBoxes, you're going down a rabbit hole that it's going to be tough to get back out of. You should solve the first problem first, and then the second problem.
Here's a rough sketch (very rough, because I don't know the rules of this game) of an object model that addresses the first problem:
public class Board
{
public const int Height = 8;
public const int Width = 8;
private Counters[Height][] Counters { get; set; }
public Counter GetCounter(int row, int col)
{
return Counters[row][col];
}
public void Initialize() { }
public void ExecuteMove(Counter c) { }
}
public class Counter
{
public int Row { get; set; }
public int Column { get; set; }
}
So, a Counter object knows where it is (its Row and Column). A Board object knows about all of the Counter objects, it knows how to find a Counter given its row and column, and it knows how to execute a move when a counter gets clicked on. (I don't know anything about the rules of the game, but they're going to live in the ExecuteMove method.
You can now trivially write a method that prints the board to the console:
public void PrintBoard(Board b)
{
for (int col = 0; col < board.Width; col ++)
{
for (int row = 0, row < board.Height; row++)
{
Counter c = board.GetCounter[row][col];
Console.Write(c == null ? " " : "*");
}
Console.WriteLine();
}
}
and a method to input a move:
public Counter InputMove(Board b)
{
string s;
Console.Write("Row: ");
s = Console.ReadLine();
int row = Convert.ToInt32(s);
if (s == "") return null;
Console.Write("Column: ");
s = Console.ReadLine();
if (s == "") return null;
int column = Convert.ToInt32(s);
return b.GetCounter(row, column);
}
...and now you have everything you need in order to code and test the ExecuteMove method. Get that working. I'll wait.
You done? Good. There are a couple of problems that you probably ran into that I haven't addressed. For instance, you probably discovered that when you move a Counter, you have to update both the board's array and the Counter itself. And you also probably discovered that you have to come up with some way of keeping track of what happens when a Counter is destroyed. Aren't you glad you weren't screwing around with mouse clicks and UI elements too?
Now for part two. Let's make a UI that knows how to talk to the Board. In your form, loop through the counters in the board, create a PictureBox for each (with its position based on the Row and Column properties), and add it to a Dictionary<PictureBox, Counter> called, say, Counters. You'll also want a Dictionary<Counter, PictureBox> called, say, PictureBoxes. These maps give you the ability to find a Counter given its PictureBox, and vice versa.
Attach this event handler to each PictureBox:
private void PictureBox_Click(object sender, EventArgs e)
{
PictureBox p = (PictureBox) sender;
Counter c = Counters[p];
Board.ExecuteMove(c);
}
(This, by the way, is the answer to your original question.)
Well, that's all well and good: you can click on a PictureBox, find its Counter, and execute the move. But how do you update the visual state of the game in the UI? That depends, a lot, on what the actual rules of the game are.
For instance, if clicking on a counter makes it disappear, you might add a Visible property to the Counter class and have the Board class's ExecuteMove update it. If, when a Counter becomes invisible, the other Counters in its row move left or right, the ExecuteMove method will have to update their Row and Column, and do something with the now-invisible Counter that was at that position.
But you already worked all of this out back when you tested all of the game logic, right? All you need to implement in your UI is an equivalent method to the one you built for printing it to the console. Only this one iterates through all of the Counters in the board, and updates their PictureBox based on their state.
There are a lot of subtleties that I'm glossing over here, mostly because a) I don't fully understand your problem and b) I'm not actually trying to do your homework for you.
You'll need to find the clicked picture box back in the Click event handler. You can use the sender argument for that. Trivially:
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
}
But you'll need to implement your game logic as well, which will require you know the box's location on the game board. You could use the PictureBox.Tag property for that. It could store a Point for example:
...
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
pictureBoxCounter[j, i].Tag = new Point(j, i);
panelGame.Controls.Add(pictureBoxCounter[j, i]);
...
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
Point pos = (Point)box.Tag;
int row = pos.X;
int col = pos.Y;
//etc...
}
But you probably want to use a helper class that stores more info than just the row and column number.
Related
so here is the idea i have a couple of game objects in a UI listed below each other so the idea is as follows
GameObject1
GameObject2
GameObject3
GameObject4
GameObject5
so i want to disable GameObject4 but when doing so GameObject5 will stay in the position its at i want it to automatically move up in to GameObject4's position
like so
GameObject1
GameObject2
GameObject3
GameObject5
and not
GameObject1
GameObject2
GameObject3
GameObject5
anyone have an Idea on how to do this?
Try creating an array of those objects (I think you mean UI elements) in an array, store their initial positions onStart in another array then loop to pop them from a stack.
Try fiddling with this to suit your needs:
public GameObject[] buttons;
float[] buttonPos;
private void Start()
{
buttonPos = new float[buttons.Length];
for (int i = 0; i < buttons.Length; i++)
{
buttonPos[i] = buttons[i].transform.position.y;
print(buttonPos[i]);
}
}
private void Update()
{
if (Input.GetKeyDown("space"))
{
DestroyButton(1);
}
}
void DestroyButton(int i)
{
Destroy(buttons[i]);
Stack(i);
}
void Stack(int i)
{
for ( int j= i; j < buttons.Length; j++)
{
if(j != buttons.Length-1)
buttons[j + 1].transform.position = new Vector3(buttons[j + 1].transform.position.x, buttonPos[j], buttons[j + 1].transform.position.z);
}
}
Your question is pretty unclear and incomplete; even the (missing) line-breaks are confusing...
Since it's a UI and your question contains spaces and not-spaces, I guess your gameObjects are in a VerticalLayoutGroup? Far fetched, if they are, shame on you for not stating that in your question.
If you want the elements to be disabled without the lower elements to "move up"
If I understood your question correctly, the solution would be to put your content as a child of an other one like so:
EDIT:
as #comphonia correctly suggests, it is preferable to have this menu made programmatically, with every button being a child of it's never disabled parent game object, and putting them into a List or array, such that you can disable them or reenable them individually
First time question here.
I'm trying to build a game board like one you would see playing chess. I've made two scripts, one that spawns the board and stores each instantiated tile in a 2d array. And another script that sets the row and column location and onMouseOver prints that location to the console. However this is always print Row = 0 and Col = 0.
I've come to the conclusion that the 2d array is just setting a clone in memory and therefor the stored values I set via calling the function setRC(); are not being being found as the instantiated objects the the Unity scene don't share the memory location.
Anyone have an idea as to how I can fix this?
public class Map : MonoBehaviour {
public static int Row = 7;
public static int Col = 7;
private GameObject[,]boardPiece = new GameObject[Row,Col];
public GameObject prefab;
public GameObject prefab2;
void Start () {
for (int i = 0; i < Row; i++) {
for (int j = 0; j < Col; j++)
{
if(i%2 ==1 && j%2 ==1 || i%2 ==0 && j%2 ==0)
{
boardPiece[i,j] = (GameObject)Instantiate(prefab, new Vector3(i*4.0f,0,j*4.0f),Quaternion.identity);
}else{
boardPiece[i,j] = (GameObject)Instantiate(prefab2, new Vector3(i*4.0f,0,j*4.0f),Quaternion.identity);
}
boardPiece[i,j].GetComponent<BoardPiece>().setRC(i,j);
}
}
}
}
public class BoardPiece : MonoBehaviour {
private int rowPlace;
private int colPlace;
// Use this for initialization
void Start () {
rowPlace = 0;
colPlace = 0;
}
// Update is called once per frame
void Update () {
}
void OnMouseOver(){
string message = "Row: " + rowPlace + " Col: " + colPlace;
print (message);
}
public void setRC(int R, int C)
{
rowPlace = R;
colPlace = C;
print ("set " + rowPlace + "," + colPlace);
}
}
Without a complete code example (difficult or even impractical to provide for Unity3d projects I know, but still...) it is difficult to know for sure what the problem is. However…
While the Start() method is often used in a fashion similar to a constructor, it's important to understand that it's not in fact one. I don't think it's called yet by the time your code is calling the setRC() method on your new object.
This means that the flow of execution is that you first set the row and column values as desired, and then later the BoardPiece.Start() method is called by the Unity3d framework, setting the row and column values back to 0.
Since the rowPlace and colPlace fields are already set to 0 by default when the object is created, I think the best fix is to just remove those two lines from the Start() method altogether. They aren't needed, and I believe they are responsible for the problem you are having.
I've been messing around trying to create an analog style scoring system for my game that I am building with Unity3D and I've noticed a huge lag when I call AnalogScoreProcessing() from the updateScore() function:
/******************************** Update Score ********************************/
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
AnalogScoreProcessing(oldScore, newScore);
}
// Display GUI score
oldScore = newScore;
guiText.text = "" + oldScore;
}
/*************************** Analog Scoring System ****************************/
void AnalogScoreProcessing(int oldScore, int newScore){
for(int i = oldScore; i <= newScore; i++){
print (i);
}
}
Do I have to create a new thread or a co-routine to complete the looping task while the remaining parts of updateScore() are carried out? I've never done any threading but I have used co-routines. I'm just not sure what I should be using here.
The easiest solution is to use coroutines here.
You basically want the players to see the effect of counting up right?
So say you want to display a count up to the user being +1 to the user ever few frames or so so the count up will be perceivable.
Try...
I would do it like this
int current =0;
int target = 0;
float percentateOfTarget = 0;
float step = 0.1f;
IEnumerable AnalogScoreProcessing()
{
while(current <= target)
{
current = (int)Math.Ceiling(Mathf.Lerp(current, target, percentateOfTarget));
return yield new WaitForSeconds(step);
percentageOfTarget += step;
// Display GUI score
guiText.text = "" + current;
}
}
public void updateScore (int newScore)
{
// Send the old score/new score to AnalogScoreProcessing()
if (oldScore != newScore)
{
StopCoroutine("AnalogScoreProcessing");
current = oldScore;
target = newScore;
StartCoroutine("AnalogScoreProcessing");
}
}
Please be aware this code is pulled directly out of my head so youll probably have to tweek some things here and there but should give you something close to what you desire.
You could/should even scale the GUIText up while the coroutine running for an even more dramatic effect. Let me know how it goes.
As I side note coroutines are not separate threads in unity they are ran on unity's main thread.
Well i dont understandt the Logic behind the function, but cant you just print the score once it changes?
if (oldScore != newScore)
{
//AnalogScoreProcessing(oldScore, newScore);
Debug.log(newScore);
}
Also you have to set the GUI calls inside the
OnGUI()
function inside your file.
If there is a large difference between oldScore and newScore then the print function in AnalogScoreProcessing will be run many times.
You might want to look at using a tween to change the displayed value, as you cannot display more than one value per frame anyway. Or if you're printing to the console then... why are you doing that?
edit: have moved quick and dirty solution a more appropriate question/answer
I've finished a game of life implementation but I'm running into a issue when rendering the grid after applying the game rules. I have a game loop that looks like this:
while (gameIsRunning)
{
//Needed for accessing UIControls from the background
//thread.
if (InvokeRequired)
{
//Process the array.
MainBoard.Cells = engine.ApplyGameRules(MainBoard.Cells, MainBoard.Size.Height, MainBoard.Size.Width, BOARD_DIMENSIONS);
//Check if there is a state such as
//all states being dead, or all states being
//alive.
//Update the grid with the updated cells.
this.Invoke(new MethodInvoker(delegate
{
timeCounter++;
lblTimeState.Text = timeCounter.ToString();
pictureBox1.Invalidate();
pictureBox1.Update();
Thread.Sleep(100);
}));
}
}
and a draw function that looks like this:
for (int x = 0; x < MainBoard.Size.Height; x++)
{
for (int y = 0; y < MainBoard.Size.Width; y++)
{
Cell individualCell = MainBoard.Cells[y, x];
if (individualCell.IsAlive() == false)
{
e.Graphics.FillRectangle(Brushes.Red, MainBoard.Cells[y, x].Bounds);
}
//White indicates that cells are alive
else if (individualCell.IsAlive() == true)
{
e.Graphics.FillRectangle(Brushes.White, MainBoard.Cells[y, x].Bounds);
}
else if (individualCell.IsInfected() == true)
{
e.Graphics.FillRectangle(Brushes.Green, MainBoard.Cells[y, x].Bounds);
}
//Draws the grid background itself.
e.Graphics.DrawRectangle(Pens.Black, MainBoard.Cells[y, x].Bounds);
}
}
The problem that I'm running into is that I'm applying all of the game rules to every cell in the grid and then drawing that grid and then applying all the rules again so I never get the life form blobs that I should be seeing. Should the game rules be applied on a cell by cell basis so that its something along the lines of: Apply game rule to cell, draw grid, apply game rule to another cell, draw grid...?
It looks like the current intent of the program is correct.
What you should be doing is (pseudocode):
Board oldBoard = new Board(start cell definitions);
while(not finished) {
Board newBoard = calculate(oldBoard);
display(newBoard);
oldBoard = newBoard();
}
If you're not seeing the forms you expect, then either your display code is wrong, or your rule code is wrong.
In the pseudocode I'm throwing away the previous generation's board once it's no longer needed, and making a new board for each generation. calculate() contains a new Board() statement.
Of course if it's expensive to make a new board you could re-use one instead, and just flip back and forth between a "current" and "other" board. Just bear in mind that each time you write to a board, its new state must be 100% a function of the previous generation's state, and in no way affected by its own starting state. i.e. you must write to every cell.
An alternative method would be for each cell to hold two values. So instead of two boards with one value per cell, you have one board with each cell containing a "current" and "previous" value.
Board board = new Board(initial state);
while(not finished) {
board.calculate(); // fills "current" cells based on "previous" cells.
display(board);
board.tick(); // "current" becomes "previous".
// "previous" becomes current, but is "dirty" until calculated.
}
There are lots of ways you could do it. One way is:
public class Cell {
private boolean[] state = new boolean[2];
private int generation = 0;
public void setCurrentState(boolean state) {
state[generation] = state;
}
public void getCurrentState() {
return state[generation];
}
public void getLastState() {
return state[ (generation + 1) % 2 ];
}
public void tick() {
generation = (generation + 1) % 2;
}
}
This post corresponds to your answer on my previous post...
Before I upload a simple project for you, let me try something else: I noticed that when I swap the Points Series with a ColorGrid series the same thing happens, BUT, when I then (with the ColorGrid Series) use "MyColorGrid.YValues[gridPosition] = val" instead of MyColorGrid.Add(X, Y, Z) then it works. Is there a way I can use the Points Series in the same way, ie, allocate all the points the first time, and then just use XValues[idx] = x, and YValues[idx] = y to update the points? The problem seems to happen when I use the Add method, together with the Clear method. When I just update the values with XValues and YValues etc. the problem seems to be solved! The thing is, I can not get it to work on a Points Series...it was easy with the ColorGrid Series:
for (int r = 0; r < 128; r++)
{
for (int d = 0; d < 128; d++)
{
MyColorGrid.YValues[d * 128 + r] = some_value;
}
}
MyColorGrid.BeginUpdate();
MyColorGrid.EndUpdate();
Question 1: How do I achieve the same for the Points Series?
Question 2: If I succeed, how do I clear/delete points, without again having to "Add(x, y)" them afterwards?
Question 3: Is this the best way to use BeginUpdate/EndUpdate? Whats the difference? In general, what are the differences between all the available update methods, and how do I choose the correct one?
A few examples:
MyColorGrid.RefreshSeries
MyColorGrid.Repaint
MyTChart.Refresh
MyTChart.AutoRepaint
Regards
JD
Question 1: How do I achieve the same for the Points Series?
I suggest you use a similar code as next that works in correct way when you update the points.
Steema.TeeChart.Styles.Points points1;
Steema.TeeChart.TChart tChart1;
Random rnd;
public Form1()
{
InitializeComponent();
tChart1 = new Steema.TeeChart.TChart();
this.Controls.Add(tChart1);
tChart1.Aspect.View3D = false;
tChart1.Height = 400;
tChart1.Width = 500;
tChart1.Dock = DockStyle.Bottom;
points1 = new Steema.TeeChart.Styles.Points(tChart1.Chart);
rnd = new Random();
InitializeChart();
}
private void InitializeChart()
{
for (int i = 0; i < 128; i++)
{
points1.Add(i, rnd.Next(100));
}
tChart1.Refresh();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 128; i++)
{
points1.XValues[i] = i+1;
points1.YValues[i] = rnd.Next(100);
}
points1.BeginUpdate();
points1.EndUpdate();
}
Question 2: If I succeed, how do I clear/delete points, without again having to "Add(x, y)" them afterwards?
I suggest you use the method SetNull() to make null the point as you don't want. You can do the same as next line of code:
points1.SetNull(3);
Question 3: Is this the best way to use BeginUpdate/EndUpdate? Whats the difference? In general, what are the differences between all the available update methods, and how do I choose the correct one? A few examples:
About BeginUpdate/EndUpdate:
The BeginUpdate method recalculates the function just one time, when finished adding points and the EndUpdate method is necessary used with .BeginUpdate to recalculate the function just once when finished adding points.
Therefore, you must use the both methods when you decide use BeginUpdate to update your series.
About other methods:
The differences between methods are explained by its definition that is found in help documentation and you can see in next lines:
Series.RefreshSeries: The RefreshSeries method notifies all dependent Series to recalculate their points again. Each Series has a DataSource property. When DataSource is a valid Series or DataSet component, Series get all point values from the DataSource and adds them as Series points. The RefreshSeries method forces the Series to Clear and get all points again from the DataSource component. The Refreshing process traverses the Series tree recursively
Series.Repaint: This Series method forces the whole Parent Chart to Repaint. You don't normally call Repaint directly. It can be used within derived TChartSeries components when changing their properties internally .
TChart.Refresh: Forces the control to invalidate its client area and immediately redraw itself and any child controls.
TChart.AutoRepaint: Use AutoRepaint false to disable Chart repainting whilst (for example) adding a large number of points to a Chart Series. This avoids repainting of the Chart whilst the points are added. AutoRepaint may be re-enabled, followed by a manual Repaint command when all points are added.
I hope will helps. If you have any questions please let me know.
Thanks,