Conways Game Of Life Rendering - c#

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;
}
}

Related

Wondering why my code demands a wait timer, since everything should run serial, major nonsensical change without it

My goal is to create 100 dots and have them take random paths, it's for a simple evolution algorythem. However, it needs a wait timer to make the pathing unique to each dot, and from my testing and viewing of the code I see no reason for that to happen. I could just leave it like it is, but I like to understand the code I write. Because there isn't any specific error or anything, I unfortunately do have to give quite a bit of code (around 200 lines with spaces and comments), I hate doing it, but it really is only the necessary files.
They should be commented pretty well, so I hope that helps at least, sorry for the inconvinience
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Workflow.ComponentModel;
class Genetics
{
//Just a bunch of references to classes
//----------------------------------------
Dot.Class dot = new Dot.Class();
FormSetup setup = new FormSetup();
Brain.Class brain = new Brain.Class();
Watch watch = new Watch();
Population.Class Pop = new Population.Class();
Dot.Class[] Dots;
//----------------------------------------
//The main function just starts a timer, and sets up the form in FormSetup and runs the application
private static void Main(string[] args)
{
Genetics gen = new Genetics();
gen.watch.watch();
FormSetup.Start();
}
//Makes the dots before anything visual occurs
public void Form1_Load(object sender, EventArgs e)
{
Dots = Pop.Birth();
}
//Paint sets up the goal and draws and redraws the dots by following their location
//This does not fire before the birthing process is done
public void Paint(object sender, PaintEventArgs e)
{
Goal.Class goal = new Goal.Class();
Graphics G = e.Graphics;
Pen pen = new Pen(Color.Black);
//------------------------------------
//Goal
G.DrawRectangle(pen, goal.MakeGoal());
//Since this should fire as many times as tick, which fires every 50th milisecond, it should follow the dots and drawing spheres from their locations pretty alright
//Goes through the list from the birth process (Dots) and initiates the Move() method for each of them, which sets their path coordinates randomly.
// This path SHOULD be unique to every single on of them, but without a wait timer, they aren't.
for (int i = 0; i < Dots.Length; i++)
{
Dots[i].Move();
G.DrawEllipse(pen, Dots[i].Location.X, Dots[i].Location.Y, 10, 10);
// With that testing loop, I saw how that without the sleep function, every dot had the same path, however with it, each dot's path is unique.
/* for (int b = 0; b < Dots[i].Directions.Length; b++)
{
Console.WriteLine("I am dot number: " + i + " And for step number: " + b + " my coordinate is: " + Dots[i].Directions[b]);
} */
}
}
//This just asks the form to be redrawn every 50th milisecond. This event does not fire before the Birthing process is complete.
public void Tick(object sender, EventArgs e)
{
FormSetup.form1.Invalidate();
FormSetup.form1.Update();
}
}
The code above is the main code, the most likely cause for the problem is the for loop in the paint method
Below is the brain code, which is even weirder for me, more is explained in the comments, but it shouldn't really be impacted by the wait timer at all, but alas, it is.
using System;
using System.Windows;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Workflow.ComponentModel;
namespace Brain
{
//The Brain program makes a big array of directions, and then randomizes a whole path of them
public class Class
{
// Sets up the variables
Point[] Directions;
Random rand = new Random();
//The GetDirections methods sets the size of the point array, then runs the randomize method, then returns the directions
public Point[] GetDirections(int size)
{
Directions = new Point[size];
Randomize();
return Directions;
}
// Simply fills the entire Directions point array with randonmized points
//This will output the same path over and over without the sleep function, however that doesn't make too much sense for me, since this is only called a bit AFTER the sleep function, since this method is only called after load, which stores all the dots
void Randomize()
{
for (int i = 0; i < Directions.Length; i++)
{
Directions[i] = new Point(rand.Next(-5, 5), rand.Next(-5, 5));
}
}
}
}
Then there is the population code which creates a lot of dots, this is the code that actually births the collection of dots, and also contains the sleep function
using System;
using System.Windows;
using System.Windows.Forms;
using System.ComponentModel;
using System.Drawing;
using System.Workflow.ComponentModel;
namespace Population
{
public class Class
{
//Makes a variable for the dots array
Dot.Class[] dots;
//Birth is only called once just before the form is loaded
public Dot.Class[] Birth()
{
//Sets the size of the dots array
dots = new Dot.Class[100];
//Fills out the dots array with unique Dot classes
for (int i = 0; i < dots.Length; i++)
{
dots[i] = new Dot.Class();
//This little thing that the code requires for it to semi-work, and I have no idea why...
System.Threading.Thread.Sleep(50);
}
return dots;
}
}
}
So, as you have probarbly noticed by now, the point was for each dot to have a unique coordinate path, but for some reason it won't do this without a sleep function between each iterration of the for loop, doesn't do anything if it is outside of it.
I am seriously lost here, since I have no idea what my code is doing here, while I am new to forms, it doesn't seem to be a forms problem, since it's the pure coordinates that are the issue, not how they appear on the screen.
I would be very thankful to anyone who wants to even try and help.
Added the Dot code:
public class Class
{
//Creates relevant variables for the dots
public Point Location = new Point(1370/2, 650/2);
Point Velocity = new Point(0, 0);
Point accelleration = new Point(0, 0);
bool IsDead = false;
int DistanceToGoal = 0;
public Point[] Directions;
//Some more variables, these are just used for some cheap programming tricks
bool StartupComplete = false;
int steps = 0;
//Class references
public Brain.Class brain = new Brain.Class();
Goal.Class goal = new Goal.Class();
//This is called every 50th milisecond after the load of the form. Before this Method is called, the dot won't have any coordinates
public void Move()
{
//A one-off if statement that fills the Directions array with the randomized points from the brain
if (!StartupComplete)
{
Directions = brain.GetDirections(500);
StartupComplete = true;
}
//Will only move if not marked as death
if (!IsDead)
{
//Makes sure that we aren't going further into the array than allowed to. We do not use a for loop here, because it is called every 50th milisecond
if (steps < Directions.Length)
{
//Just fills the accelleration variable with the randomized directions relevant to the current step, and then goes to the next step for the next itteration
accelleration.X = Directions[steps].X;
accelleration.Y = Directions[steps].Y;
steps++;
}
//If there are no more directions, the dot is marked as death
else
{
IsDead = true;
}
//Sets and limits the velocity
Velocity.Y += accelleration.Y;
if (Velocity.Y > 10)
Velocity.Y = 10;
if (Velocity.Y < -10)
Velocity.Y = -10;
Velocity.X += accelleration.X;
if (Velocity.X > 10)
Velocity.X = 10;
if (Velocity.X < -10)
Velocity.X = -10;
//Moves an amount equal to the velocity, then checks if the dot is out of bounds
Location.X += Velocity.X;
Location.Y += Velocity.Y;
CheckDead();
}
}
//Just checks if the dot is just about halfway outside the form size
void CheckDead()
{
if (Location.X > 1374 || Location.X < -0.4)
IsDead = true;
if (Location.Y > 654 || Location.Y < -0.4)
IsDead = true;
}
I can only guess, but it looks like an issue with Random() since it's using the clock to generate random numbers. Also it may explain why Thread.Sleep(50) helps you with random paths.
Basically, in a tight loop you get the same value lots of times. I bet that's what happens here. You should keep a single Random instance and keep using Next on the same instance.
Try to change your randomizer to use a code below:
private static readonly Random random = new Random();
private static readonly object syncLock = new object();
public static int RandomNumber(int min, int max)
{
lock(syncLock) { // synchronize
return random.Next(min, max);
}
}
I hope it helps 😊

Zoom/unzoom and points decimation

Situation:
I have one Chart and three ChartArea that are aligned in view, zoom, cursor through the AxisViewChanged method that act in this way:
if (e.Axis == ax1)
{
ax2.ScaleView.Size = ax1.ScaleView.Size;
ax2.ScaleView.Position = ax1.ScaleView.Position;
ax3.ScaleView.Size = ax1.ScaleView.Size;
ax3.ScaleView.Position = ax1.ScaleView.Position;
min = (int)ax1.ScaleView.ViewMinimum;
max = (int)ax1.ScaleView.ViewMaximum;
}
if (e.Axis == ax2)
{
....
And it works very well in both cases: when I zoom in/out or scroll.
Problem:
The problem is that my graph source is made by a lot of points, in the worst case we talk about 3'600'000 samples. With this amount of samples, when I move around points with cursor and try to show a tooltip with the values, the interaction quality collapse and becomes unusables (even having set Fast Line).
So I tried to implement a simple decimation algorithm to reducethe number of showed points:
void draw_graph(int start, int end)
{
double fract_value = 0;
int int_value = 0;
int num_saples_selected = end - start;
if(num_saples_selected <= MAX_GRAPH_NUM_SAMPLES)
fill_graphs_point(0, end, 1);
else
{
fract_value = ((double)num_saples_selected) / ((double)MAX_GRAPH_NUM_SAMPLES);
int_value = (int)fract_value;
if (fract_value > int_value)
int_value++;
fill_graphs_point(0, end, int_value);
}
}
void fill_graphs_point(int start, int end, int step)
{
int i = 0;
for (i = start; i < end; i=i+step)
{
dlChart.Series[SERIES_VOLTAGE].Points.AddXY(timestamps_array[i], voltage_array[i]);
dlChart.Series[SERIES_CURRENT].Points.AddXY(timestamps_array[i], current_array[i]);
dlChart.Series[SERIES_ENERGY].Points.AddXY(timestamps_array[i], energy_array[i]);
// I will use this to came back to real position of the initial array
decimation_positions.Add(i);
}
}
Assuminig I had a good idea with this method to reduce the points number, I do not know where to put the call to the function "draw_graph". If I put it in the AxisViewChanged method it will call my method also when I scroll (horizontally) my graph and this is not what I want. I want to call my method only on zoom and unzoom event.
Expected behavior: in the first view (without any zoom) the graph has to show an "idea" of the trend of the graph. Then for every selection/(un)zoom I want to call my function to check if the points number of selected portion will fit in my window size that is MAX_GRAPH_NUM_SAMPLES(=10000).
Hope someone can help me. Whatever kind of suggestion will be appreciated. Thanks in advance.
I forgot to say that strangely the problem appeared when I zoomed in more than one time. At some point also the PC fan started. I resolved disabling the library zoom and implement my self a sort of zoom (a little bit more simple). The solution is in this method and the method I write in the question:
private void dlChart_SelectionRangeChange(object sender, CursorEventArgs e)
{
double startSelection = e.NewSelectionStart;
double endSelection = e.NewSelectionEnd;
// Allow the selection to disappear
reset_selection();
try
{
// before convert point from "showed point" to "real point" I check the limits
if (
(startSelection >= 0) && (startSelection <= decimation_positions.Count()) &&
(endSelection >= 0) && (endSelection <= decimation_positions.Count()) &&
(endSelection != startSelection)
)
{
// convert
startSelection = decimation_positions[(int)startSelection];
endSelection = decimation_positions[(int)endSelection];
// check for reverse zoom (from right to left)
if (startSelection > endSelection)
{
double tmp = startSelection;
startSelection = endSelection;
endSelection = tmp;
}
// clean the series
this.reset_series();
// draw the selected range
draw_graph((int)startSelection, (int)endSelection);
}
}catch(ArgumentOutOfRangeException ex)
{
// todo
}
//System.Console.WriteLine("SelSTART = "+e.NewSelectionStart+" SelEND = "+e.NewSelectionEnd);
}

MonoGame - calling the same function from different classes result in different behavior

I'm working a game using XNA/MonoGame, and I'm stuck with a bug.
I have this function Expload, that show a very simple animation using a sprite sheet.
public void Exploade(Rectangle explosionRect)
{
ex = new ExpolsionAnimation(content, "Explsion4", explosionRect,
_frameWaiteTime:50,
_frameWidth:90 , _frameHeight: 45,
_rows: 4 , _culloms: 3,
_rotation: 0
);
playAnimation = true;
}
I call this function from 2 classes:
A class that function was made in, called Arrow
A different class, that call currentArrow.Explode (and work fine) called OnCollsionEvents
here is how i call the function in the different classes:
in class Arrow (which giving me the problem/bug:
public override void Update(GameTime gt)
{
if (!MonsterHit)
{
this.location.Y -= movmentSpeed;
}
//when the arrow touch the top of the window, it need to expload, but for some reason when i start the animation here, it just doesn't work.
if (ReachedTheTop)
this.Exploade(new Rectangle(location.X - 10, 1, 30, 30));
//here i start updating the animation
if (playAnimation)
ex.Update(gt);
base.Update(gt);
}
in class OnCollsionEvents
public void Update(SpaceShip _spaceShip, Monsters _monsters)
{
List<Arrow> gameArrows = player.Gun.ArrowList;
if (MonsterCount > 0)
{
foreach (var monster in monsters.MonstersList)
{
if (monster.ReachedTheButtom)
{
MonsterHitTheFloor = true;
}
if (monster.CollsionWith(player))
{
MonsterHitTheSpaceship = true;
}
foreach (var currentArrow in gameArrows)
{
if (currentArrow.CollsionWith(monster))
{
currentArrow.MonsterHit = true;
currentArrow.Exploade(new Rectangle(monster.Location.X, monster.Location.Y, monster.AreaRect.Width, monster.AreaRect.Height));
monster.GotHit = true;
}
}
}
}
here is where Arrow.Update is called (in class Gun)
public override void Update(GameTime gt)
{
if (arrowsNotEmpty)
{
for (int i = 0; i < ArrowList.Count; i++)
{
ArrowList[i].Update(gt);
//because of the bug, i cant even remove an arrow afther it reach the top of the window
//because the animation is not Ended.
if ((ArrowList[i].IsOverTheTop || ArrowList[i].MonsterHit) && ArrowList[i].AnimationEnded)
ArrowList.RemoveAt(i);
}
}
}
now the problem i'm having is like this.
in the animation class i have a integer that get the milliseconds pass, so i can create a sort of a timer to run the animation with.
when i call the function Exploade, and start updating the animation in OnCollsionEvents class, the integer value is 64 and start the animation correctly.
when i call the Exploade from Arrow and start updating, the integer value is 16, which doesn't start the animation.
what i don't understand is, how its possible to call the same function, from different class, and update the function in the same class it was created, and get different results.
what did i miss?
or where should i try looking?
what is a good way to test this sort of stuff?
I found my problem.
when the arrow got to the top of the screen, it kept making explostions.
so i just add another if statment, to check if there isnt explonstion animation allrdy runing.

Picking objects in OpenGl ,using C#

i'm working with opengl using C#(using TaoFrame work ) ,the thing is that i want to draw
2 objects on screen (rectangle for example ),the main goal of my Question is, how to press the mouse button so i can select one object to make it move .
Basically i just need to know how to select one object that's all :
Info about my project :
i'm using taoframe work libraries , the opengl view port is just a small panel in a main control window ,it's not full screen .
this function is the main opengl draw function :
private void draw()
{
//Gl.glInitNames(); is this function invoked here ?
Gl.glMatrixMode(Gl.GL_PROJECTION);
GC.Collect();
simpleOpenGlControl1_Resize(new object(), new EventArgs());
Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glLoadIdentity(); // Reset The Current Modelview M
//Gl.glPushName(table);
//Gl.glPushName(room);
Gl.glPushMatrix();
Gl.glLoadName(table);
Gl.glTranslatef(-2, 0,-13);
Gl.glRects(0, 0, 2, 2);
Gl.glLoadName(obj1); // obj1 is a const int =1
Gl.glTranslatef(-5, 0, -13);
Gl.glRects(0, 0, 2, 2);
Gl.glPopMatrix();
simpleOpenGlControl1.Draw();
}
this function is the mouse handler :
private void simpleOpenGlControl1_MouseClick(object sender, MouseEventArgs e)
{
//this code here is to supposed to select object from select Function
if (e.Button == MouseButtons.Left)
{
uint selected = Select(e.X, e.Y);
if (selected == obj1)
label1.Text = "object 1";
else if (selected == obj2)
label1.Text = " object 2";
else label1.Text = "nothing";
}
}
The Actual select Vodo is being done here :
public uint Select(int x, int y)
{
int[] viewport = new int[4]; //Current Viewport
uint[] selectBuf = new uint[512]; // will hold the id's of selected objects
//1) Get Current Viewport
Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);
// 2 , selection buffer and selection mode
Gl.glSelectBuffer(512, selectBuf); // Assign a selection buffer
Gl.glRenderMode(Gl.GL_SELECT); // switch to selection mode
Gl.glInitNames(); // Init Name Stack
// go ahead and put a name in
Gl.glPushName(obj1);
Gl.glPushName(obj2);
// 4 matrix mode and initialize
Gl.glMatrixMode(Gl.GL_PROJECTION); // Change Matrix Mode to projection
Gl.glPushMatrix(); // Don't disturb other things
Gl.glLoadIdentity(); // Reset the axis
// 6 - create pick matrix around current point
// Main function! Thiss make a virtual clipping region with
// center x, y and width and height 1 each w.r.t the current viewport.
// Note the "viewport[3]-y" parameter instead of y!
Glu.gluPickMatrix(
(double)x, (double)(viewport[3] - y),
//Specify the center of a picking region in window coordinates.
4.0, 5.0, // delx, dely
viewport // current viewport
);
Gl.glOrtho(0.0f, (double)Size.Width, 0.0f, (double)Size.Height, -0.5f, 2.5f);
//Call our Render function to load object with names.
// Note that since our Render mode is GL_SELECT nothing will be drawn
// on the screen due to this function call!
simpleOpenGlControl1.Draw();
Gl.glPopMatrix(); // Don't disturb other things
//Switch back to default mode!
//This time the "glRenderMode" function returns the no.
// of objects that were found to be drawn in the clipping region made by glPickMatrix.
int hits = Gl.glRenderMode(Gl.GL_RENDER);
System.Console.WriteLine("Select, number of hits:" + hits.ToString());
return ProcessHits(hits, selectBuf);
}
This function ProcessHits so i can get the name of object
private static uint ProcessHits(int hits, uint[] buffer)
{
uint i, j;
uint names;
uint[] ptr;
uint result = 666;
Console.WriteLine("ProcessHits hits = {0}", hits);
ptr = buffer;
for (i = 0; i < hits; i++)
{ // For Each Hit
names = ptr[i];
Console.WriteLine(" number of names for hit = {0}", names);
i++;
Console.WriteLine(" z1 is {0}", (float)ptr[i] / 0x7fffffff);
i++;
Console.WriteLine(" z2 is {0}", (float)ptr[i] / 0x7fffffff);
i++;
Console.Write(" the name is ");
for (j = 0; j < names; j++)
{ // For Each Name
Console.Write("{0} ", ptr[i]);
result = ptr[i]; // if there are multiple selections, this is an ERROR, but at least for the time being, return something
i++;
}
Console.Write("\n");
}
Console.Write("\n");
return result;
}
1-The Result value in ProcessHits function is always 1 which prefers to object1 so if i press in the space i've got object 1 even though there aint nothing to select, so am i missing some code ? .
2-the hits value in the select function is always 2? i don't know why ?
It is simple, you are calling glOrtho() after gluPickMatrix(), effectively erasing the pick matrix and drawing the full frame instead. That is why your control is always picked.
Oh yeah, don't use selection mode, it's deprecated and it's a terrible piece of work anyway. Use picking by ray using gluUnProject() or use color picking instead.

Cannot get the counters to move (game in C# with PictureBox).

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.

Categories