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; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Runtime.Serialization; using Microsoft.Win32; namespace trackDesigner { public partial class trackDesigner : Form { /*-----*----- DATA MEMBERS -----*-----*/ const int MAX_SIZE = 30; const int MAX_TRAINS = 5; const int DELTA = 1; private Image imgTrack; private Image imgAll; private Graphics imgGraphics; private Graphics imgAllGraphics; private int shift_factor = 0; private trackStructure[] myTrack; private int num_segments; private turnout[] myTurnouts; private int tCount; private sensor[] mySensors; private int sCount; private train[] myTrains; private int trCount; private Point[] compass; private trackDocument tDoc; private simulatorStatus sStatus; private addTrain aTrain; private bool addingTrain = false; private Point trainPoint; private string trainColor; private int trainDir; private int trainSpeed; public trackDesigner() { InitializeComponent(); } /*-----*----- METHODS -----*-----*/ /******************************************************************* Method Name: shift_down Purpose: Shifts all TrackPts (Sensors, Turnouts) down by shift_factor. * Compensates for the menuStrip/toolStrip controls at top of form Input: None Output: None *******************************************************************/ void shift_down() { for (int i = 0; i < num_segments; i++) { myTrack[i].drawn = false; // return to undrawn state if (!myTrack[i].ptA.midpt) { myTrack[i].ptA.loc.Y += shift_factor; myTrack[i].ptA.midpt = true; } myTrack[i].ptMid.loc.Y += shift_factor; if (!myTrack[i].ptB.midpt) { myTrack[i].ptB.loc.Y += shift_factor; myTrack[i].ptB.midpt = true; } } for (int i = 0; i < sCount; i++) { mySensors[i].loc.Y += shift_factor; } for (int i = 0; i < tCount; i++) { myTurnouts[i].loc.Y += shift_factor; } } /******************************************************************* Method Name: shift_up Purpose: Shifts all TrackPts (Sensors, Turnouts) up by shift_factor. * Returns them to original location--makes sure multiple track * edits do not result in multiple shifts down Input: None Output: None *******************************************************************/ void shift_up() { for (int i = 0; i < num_segments; i++) { if (myTrack[i].ptA.midpt) { myTrack[i].ptA.loc.Y -= shift_factor; myTrack[i].ptA.midpt = false; } myTrack[i].ptMid.loc.Y -= shift_factor; if (myTrack[i].ptB.midpt) { myTrack[i].ptB.loc.Y -= shift_factor; myTrack[i].ptB.midpt = false; } } for (int i = 0; i < sCount; i++) { mySensors[i].loc.Y -= shift_factor; } for (int i = 0; i < tCount; i++) { myTurnouts[i].loc.Y -= shift_factor; } } /******************************************************************* Method Name: draw_track Purpose: Redraws the track only Input: None Output: None *******************************************************************/ private void draw_track() { imgTrack = new Bitmap(this.Width, this.Height); imgGraphics = Graphics.FromImage(imgTrack); for (int i = 0; i < num_segments; i++) { if (!myTrack[i].drawn) drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[i]); if (myTrack[i].ptA.num_connected > 2 && !myTrack[i].drawn) { // draw the segments in order and mark them int index = myTrack[i].ptA.val1; drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[index]); myTrack[index].drawn = true; index = myTrack[i].ptA.val2; SolidBrush b = new SolidBrush(this.BackColor); imgGraphics.FillRectangle(b, myTrack[i].ptA.loc.X - 25, myTrack[i].ptA.loc.Y - 25, 50, 50); drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[index]); myTrack[index].drawn = true; drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[i]); } if (myTrack[i].ptB.num_connected > 2 && !myTrack[i].drawn) { // draw the segments in order and mark them int index = myTrack[i].ptB.val1; drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[index]); myTrack[index].drawn = true; index = myTrack[i].ptB.val2; SolidBrush b = new SolidBrush(this.BackColor); imgGraphics.FillRectangle(b, myTrack[i].ptB.loc.X - 25, myTrack[i].ptB.loc.Y - 25, 50, 50); drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[index]); myTrack[index].drawn = true; drawCurve(imgGraphics, new Pen(Brushes.Black, 4), myTrack[i]); } myTrack[i].drawn = true; } for (int i = 0; i < num_segments; i++) { // set back to "undrawn" and draw the "points" myTrack[i].drawn = false; drawPT(imgGraphics, Brushes.Blue, myTrack[i].ptMid.loc); if(myTrack[i].ptA.num_connected>2) drawPT(imgGraphics, Brushes.Lime, myTrack[i].ptA.loc); else drawPT(imgGraphics, Brushes.Blue, myTrack[i].ptA.loc); if (myTrack[i].ptB.num_connected > 2) drawPT(imgGraphics, Brushes.Lime, myTrack[i].ptB.loc); else drawPT(imgGraphics, Brushes.Blue, myTrack[i].ptB.loc); } imgGraphics.DrawLine(Pens.Black, 0, 65, this.Width, 65); imgGraphics.DrawLine(Pens.Black, 0, 440, this.Width, 440); } /******************************************************************* Method Name: redo_track Purpose: Redraws the track and trains on the AllGraphics image Input: graphics and image objects on which the new track will be * drawn Output: changed graphics and image objects ("objects" can only have * data members changed. When you instantiate a new one, you must * use a reference parameter if you want the new one) *******************************************************************/ private void redo_Track(ref Graphics gr, ref Image im) { im = new Bitmap(imgTrack); gr = Graphics.FromImage(im); drawTrainOnImg(); return; } /******************************************************************* Method Name: drawTrainOnImg * NOTE: THIS LOGIC WAS DEVELOPED BY DR. PANKRATZ IN A C++ PROGRAM. * I TRANSFERRED CODE TO C# AND MADE NECESSARY MODIFICATIONS. Purpose: draws all train cars of each train on the track (even if some * cars are on top of one another) Input: None Output: None *******************************************************************/ void drawTrainOnImg() { int k; Color c; SolidBrush b; for (int i = 0; i < trCount; i++) { k = myTrains[i].firstcar; c = Color.FromName(myTrains[i].color); b = new SolidBrush(c); // FOR TRAIN WITH MORE THAN ONE CAR... //for (int j = 0; j < 4; j++) // for drawing 4 train cars //{ imgAllGraphics.FillEllipse(b, myTrains[i].loc[k].X - 5, myTrains[i].loc[k].Y - 5, 10, 10); k--; if (k < 0) k = 3; //} } } /******************************************************************* Method Name: drawCurve Purpose: Draws a segment of track from 3 points (TrackPt locations) Input: None Output: None *******************************************************************/ private void drawCurve(Graphics g, Pen p, trackStructure t) { Point[] temp = new Point[3]; temp[0] = t.ptA.loc; temp[1] = t.ptMid.loc; temp[2] = t.ptB.loc; g.DrawCurve(p, temp); } /******************************************************************* Method Name: drawPT Purpose: Draws a point at location p with brush b. Color of point is * specified by the brush Input: Brush b, Point p Output: None *******************************************************************/ private void drawPT(Graphics g, Brush b, Point p) { g.FillEllipse(b, p.X - 3, p.Y - 3, 6, 6); } /******************************************************************* Method Name: on_point Purpose: Determines whether Point p is on (or sufficiently close * enough) a sensor Input: Point p Output: integer for index of sensor that Point p is on. If p is not * "on" a sensor, but is close enough, p is changed to the location * of corresponding sensor and sent back as reference parameter *******************************************************************/ private int on_point(ref Point p) { int xdiff, ydiff; // find out if there's a corresponding sensor for (int i = 0; i < sCount; i++) { xdiff = Math.Abs(mySensors[i].loc.X - p.X); ydiff = Math.Abs(mySensors[i].loc.Y - p.Y); if (xdiff <= 5 && ydiff <= 5) { p = mySensors[i].loc; return i; } } return -1; } /******************************************************************* Method Name: move_train * NOTE: THIS LOGIC WAS DEVELOPED BY DR. PANKRATZ IN A C++ PROGRAM. * I TRANSFERRED CODE TO C# AND MADE NECESSARY MODIFICATIONS. Purpose: determines if train t can move along the track Input: train t, integer for last direction train moved, Point array * that serves as a compass (tells changes in x/y that correspond to * each direction), integer delta Output: boolean variable saying if train can be moved, integer heading * changed to new direction of train *******************************************************************/ private bool move_train( train t, Point[] compass) { int x = t.loc[0 /*t.firstcar*/].X; int y = t.loc[0/*t.firstcar*/].Y; int next = 0; // t.firstcar++; if (next > 3) next = 0; //compass is a circular queue with head = heading // valid directions are last_heading and one compass bearing in either direction Color pixel; Bitmap temp = new Bitmap(imgTrack); int xt, yt, n; n = t.direction; // check same direction xt = x; yt = y; xt += compass[n].X * DELTA; yt += compass[n].Y * DELTA; pixel = temp.GetPixel(xt, yt); if ((pixel.R == 0 && pixel.G == 0 && pixel.B == 255) || (pixel.R == 0 && pixel.G == 0 && pixel.B == 0 && pixel.A == 255) || (pixel.R == 0 && pixel.G == 255 && pixel.B == 0)) { if (pixel.B == 255) { // trigger sensor update for x/y coordinates } t.loc[next].X = xt; t.loc[next].Y = yt; t.direction = n; t.firstcar = next; return true; } n = t.direction + 1; // check counterclockwise if (n == 8) n = 0; xt = x; yt = y; xt += compass[n].X * DELTA; yt += compass[n].Y * DELTA; pixel = temp.GetPixel(xt, yt); if ((pixel.R == 0 && pixel.G == 0 && pixel.B == 255) || (pixel.R == 0 && pixel.G == 0 && pixel.B == 0 && pixel.A == 255) || (pixel.R == 0 && pixel.G == 255 && pixel.B == 0)) //color == trackcolor { if (pixel.B == 255) { // trigger sensor update for x/y coordinates } t.loc[next].X = xt; t.loc[next].Y = yt; t.direction = n; t.firstcar = next; return true; } n = t.direction - 1; //check clockwise if (n == -1) n = 7; xt = x; yt = y; xt += compass[n].X * DELTA; yt += compass[n].Y * DELTA; pixel = temp.GetPixel(xt, yt); if ((pixel.R == 0 && pixel.G == 0 && pixel.B == 255) || (pixel.R == 0 && pixel.G == 0 && pixel.B == 0 && pixel.A == 255) || (pixel.R == 0 && pixel.G == 255 && pixel.B == 0)) //color == trackcolor { if (pixel.B == 255) { // trigger sensor update for x/y coordinates } t.loc[next].X = xt; t.loc[next].Y = yt; t.direction = n; t.firstcar = next; return true; } return false; // can't go in any of those directions } /******************************************************************* Method Name: get_tDoc Purpose: uses properties of trackDocument object to assign data * members Input: None Output: None *******************************************************************/ private void get_tDoc() { myTrack = tDoc.track; num_segments = tDoc.segs; shift_factor = tDoc.sh_fac; mySensors = tDoc.sensor; sCount = tDoc.sensorCt; myTurnouts = tDoc.turnout; tCount = tDoc.turnCt; myTrains = tDoc.train; trCount = tDoc.trainCt; } /*-----*----- EVENTS -----*-----*/ private void btnEditTrack_Click(object sender, EventArgs e) { shift_up(); // shift it back up before going to edit form if (tDoc.EditTrack()) { tDoc.HasBeenModified = true; // mark dirty bit get_tDoc(); shift_down(); if (num_segments > 0) { // enable stuff if there is a valid track btnAddTrain.Enabled = true; btnStartStopSim.Enabled = true; } draw_track(); redo_Track(ref imgAllGraphics, ref imgAll); // on the screen! Invalidate(); Update(); } } private void trackDesigner_Load(object sender, EventArgs e) { // initialize all necessary data members tDoc = new trackDocument(); this.tDoc.NewDocument += new System.EventHandler(this.tDocument_NewDocument); this.tDoc.OpenDocument += new System.EventHandler(this.tDocument_OpenDocument); this.tDoc.UpdateCaption += new System.EventHandler(this.tDocument_UpdateCaption); sStatus = new simulatorStatus(); this.sStatus.UpdateTurnout += new System.EventHandler(this.sStatus_UpdateTurnout); mySensors = new sensor[3 * MAX_SIZE]; myTrains = new train[MAX_TRAINS]; myTurnouts = new turnout[MAX_SIZE]; myTrack = new trackStructure[MAX_SIZE]; compass = new Point[8]; // initialize the compass compass[0] = new Point(1, 0); compass[1] = new Point(1, -1); compass[2] = new Point(0, -1); compass[3] = new Point(-1, -1); compass[4] = new Point(-1, 0); compass[5] = new Point(-1, 1); compass[6] = new Point(0, 1); compass[7] = new Point(1, 1); imgTrack = new Bitmap(this.Width, this.Height); imgGraphics = Graphics.FromImage(imgTrack); imgAll = new Bitmap(this.Width, this.Height); imgAllGraphics = Graphics.FromImage(imgAll); } private void sStatus_UpdateTurnout(object sender, EventArgs e) { draw_track(); redo_Track(ref imgAllGraphics, ref imgAll); // on the screen! Invalidate(); Update(); } private void tDocument_NewDocument(object sender, EventArgs e) { // wipe out existing track on screen get_tDoc(); btnAddTrain.Enabled = false; btnStartStopSim.Enabled = false; draw_track(); redo_Track(ref imgAllGraphics, ref imgAll); // on the screen! Invalidate(); Update(); } private void tDocument_OpenDocument(object sender, EventArgs e) { get_tDoc(); if (num_segments > 0) { btnAddTrain.Enabled = true; btnStartStopSim.Enabled = true; } shift_down(); draw_track(); redo_Track(ref imgAllGraphics, ref imgAll); // on the screen! Invalidate(); Update(); } private void tDocument_UpdateCaption(object sender, EventArgs e) { if (tDoc.FileName.Length > 20) { int pos = tDoc.FileName.LastIndexOf('\\'); string x = tDoc.FileName.Substring(pos); this.Text = tDoc.FileName.Substring(0, 20 - x.Length - 3) + "..." + x; } else if (string.IsNullOrEmpty(tDoc.FileName)) this.Text = "Untitled"; else this.Text = tDoc.FileName; if (tDoc.HasBeenModified) this.Text += "*"; } private void myPaintHandler(object sender, PaintEventArgs e) { Graphics g = this.CreateGraphics(); g.DrawImage(imgAll, 0, 0); } private void onNewFile(object sender, EventArgs e) { tDoc.NewFile(false); } private void onOpenFile(object sender, EventArgs e) { tDoc.Open(); } private void onSaveFile(object sender, EventArgs e) { shift_up(); tDoc.Save(); shift_down(); } private void onSaveFileAs(object sender, EventArgs e) { shift_up(); tDoc.SaveAs(); shift_down(); } private void onExit(object sender, EventArgs e) { this.Close(); } private void trackDesigner_FormClosing(object sender, FormClosingEventArgs e) { // if it's been modified when the form is closed, ask if they want to save DialogResult res = DialogResult.No; if (tDoc.HasBeenModified) { res = MessageBox.Show("Would you like to save changes?", tDoc.FileName, MessageBoxButtons.YesNoCancel); } if (res != DialogResult.Cancel) { if (res == DialogResult.Yes) { tDoc.Save(); } } else e.Cancel = true; } private void btnAddTrain_Click(object sender, EventArgs e) { addingTrain = true; // don't allow them to add a second train without finishing adding the first // don't allow them to start simulator until train is initialized // don't allow them to edit the track until train is initialized btnAddTrain.Enabled = false; btnStartStopSim.Enabled = false; btnEditTrack.Enabled = false; MessageBox.Show("Click on a sensor to initialize your train's \n location. You will" + " not be allowed to \n initialize properties until you have clicked" + " \n a valid location"); aTrain = new addTrain(); this.aTrain.NewTrain += new System.EventHandler(this.aTrain_AddIt); aTrain.Show(); } private void aTrain_AddIt(object sender, EventArgs e) { trainColor = aTrain.color; trainDir = aTrain.direction; trainSpeed = aTrain.speed; aTrain.Close(); btnAddTrain.Enabled = true; btnStartStopSim.Enabled = true; btnEditTrack.Enabled = true; myTrains[trCount] = new train(trainPoint,trainDir,trainSpeed,'A', trainColor); trCount++; tDoc.trainCt++; redo_Track(ref imgAllGraphics, ref imgAll); Invalidate(); Update(); } private void trackDesigner_MouseClick(object sender, MouseEventArgs e) { trainPoint = new Point(e.X, e.Y); if (addingTrain && (on_point(ref trainPoint) != -1)) // valid location to add train { aTrain.enableControls(); aTrain.BringToFront(); addingTrain = false; } } private void timerMove_Tick(object sender, EventArgs e) { for (int i = 0; i < trCount; i++) { if (myTrains[i].ctr >= 10) { if (move_train( myTrains[i], compass)) { redo_Track(ref imgAllGraphics, ref imgAll); Invalidate(); Update(); myTrains[i].ctr = myTrains[i].speed; } // else train is stuck! } else if (myTrains[i].speed != 0) { myTrains[i].ctr++; } } } private void btnStartStopSim_Click(object sender, EventArgs e) { // enable/disable proper things if (timerMove.Enabled) { timerMove.Enabled = false; btnStartStopSim.Text = "Begin Simulation"; sStatus.Close(); btnEditTrack.Enabled = true; btnAddTrain.Enabled = true; fileToolStripMenuItem.Enabled = true; editTrackToolStripMenuItem.Enabled = true; startToolStripMenuItem.Enabled = true; pauseToolStripMenuItem.Enabled = false; } else { btnStartStopSim.Text = "Stop Simulation"; sStatus = new simulatorStatus(); this.sStatus.UpdateTurnout += new System.EventHandler(this.sStatus_UpdateTurnout); sStatus.track = myTrack; sStatus.segs = num_segments; sStatus.sensor = mySensors; sStatus.sensorCt = sCount; sStatus.train = myTrains; sStatus.trainCt = trCount; sStatus.turnout = myTurnouts; sStatus.turnCt = tCount; sStatus.Show(); btnEditTrack.Enabled = false; btnAddTrain.Enabled = false; fileToolStripMenuItem.Enabled = false; editTrackToolStripMenuItem.Enabled = false; startToolStripMenuItem.Enabled = false; pauseToolStripMenuItem.Enabled = true; timerMove.Enabled = true; } } } }