/* goblins - C# program skeleton by Chuck Liang, Hofstra University. This program is for academic use only. Your program needs to be compiled with csc observer.cs goblins.cs /r:boxworld.dll */ using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; /* the only methods you must change here are createplayers() and mainloop() */ public class goblins : System.Windows.Forms.Form { // 3d components - the y coordinate will be modeled by the // z coordinate in the boxworld. The boxworld y coordinate // is basically constant - since the playground is 2d. private System.ComponentModel.Container components = null; public static int XBOUND = 1024; // imaginary 3d dimensions public static int YBOUND = 768; // imaginary 3d dimensions public static int BOXHEIGHT = 600; // actual y coordinate in boxworld public static int Wwidth = 1070; // screen window dimensions public static int Wheight = 707; // some trial and error used :-) public static int LEVEL = 30; // height of playground grid public static Color Gridcolor = Color.Green; public static int Gridgap = 10; public static Color Wallcolor = Color.Yellow; public static double zoomratio = 0.5; public static double shortratio = 0.8; public static int tiltangle = 290; // initial view angle public int FDELAY = 40; // animation frame delay public static boxworld playground; private Bitmap dbuffer; // double buffer private Graphics displaydc; // for screen private Graphics bufferdc; // for double buffer private Bitmap sbg; // static background private Graphics sbgdc; public SolidBrush brush = new SolidBrush( Color.Black ); // bk color public Pen pen = new Pen( Color.Green ); public static bool stop = false; // global var to stop program //public static wavsound hahasnd; // .wav sound played at end of game ///////////////////////////////// players in the game ///////////// private human professor; private diamond treasure; /////////////////////////////////////////////////////////////////// // constructor public goblins() { // setup 3d boxworld - this is a completely modular component playground = new boxworld(XBOUND,BOXHEIGHT,YBOUND,zoomratio,tiltangle,shortratio); components = new System.ComponentModel.Container(); this.Size = new System.Drawing.Size(Wwidth,Wheight); this.Name = "goblinapp"; this.Text = "Professor versus the Goblins"; this.BackColor = Color.Black; this.Load += new System.EventHandler(init); this.KeyDown +=new KeyEventHandler(handlekeys); // delegate used } public void init(object o, System.EventArgs e) { // Graphics and Animation setup displaydc = this.CreateGraphics(); // device context for drawing dbuffer = new Bitmap(Wwidth,Wheight); // off-screen drawing buffer bufferdc = Graphics.FromImage(dbuffer); sbg = new Bitmap(Wwidth,Wheight); // static background buffer sbgdc = Graphics.FromImage(sbg); sbgdc.FillRectangle(brush,0,0,Wwidth,Wheight); // clear background // Draw static background grid; for(int i=0;i<=YBOUND;i+=Gridgap) playground.drawline(sbgdc,0,LEVEL,i,XBOUND-1,LEVEL,i,Gridcolor); for(int i=0;i<=XBOUND;i+=Gridgap) playground.drawline(sbgdc,i,LEVEL,0,i,LEVEL,YBOUND-1,Gridcolor); bufferdc.DrawImage(sbg,0,0,Wwidth,Wheight); // draw bkgrd to buffer createplayers(); // call to "user" function /* try{ wavsound.init(this); // static initialize directx sound adapter // initialize other sounds here hahasnd = new wavsound(@"Haha.wav"); } catch(Exception twe) {Console.WriteLine(twe);} */ } // end method init protected override void OnPaintBackground(PaintEventArgs pe) {} // don't autopaint (control animation manually) [STAThread] static void Main() { goblins theapp = new goblins(); Application.Run(theapp); } public int STEP = 4; // default speed increment // This function is called asynchronously when a key is pressed: private void handlekeys(object sender, KeyEventArgs ke) { ke.Handled = true; switch(ke.KeyCode) { case Keys.Left: professor.setdirection(-1*STEP,0); break; case Keys.Right: professor.setdirection(STEP,0); break; case Keys.Up: professor.setdirection(0,1*STEP); break; case Keys.Down: professor.setdirection(0,-1*STEP); break; case Keys.Space: professor.setdirection(0,0); break; // stop case Keys.PageUp: STEP++; break; // speedup/slowdown case Keys.PageDown: STEP--; break; // won't take effect immediately default: break; } // switch } protected override void OnPaint(PaintEventArgs paintEvent ) { mainloop(); } ///////////////////////////CHANGE CODE BELOW////////////////////////////////////// private void createplayers() { // creat all player objects *************************** professor = new human(XBOUND-50,50,bufferdc,"man15.gif",25); treasure = new diamond(XBOUND/2,YBOUND/2,bufferdc,"gem1.gif",12); // you should also put other initialization code here. } /* ------------mainloop controls how the game is played--------------- */ private void mainloop() { if (!stop) { professor.move(); // move should call draw automatically treasure.move(); // check if professor has caught the treasure if (professor.overlap(treasure)) { bufferdc.DrawString("The Professor Wins!", new Font("Serif",28), new SolidBrush(Color.Red), (XBOUND/2)-135,(YBOUND/2)-30); //hahasnd.play(); // play sound (need directx dlls) stop = true; } ///// The code above is not very object-oriented: it doesn't "let objects ///// decide for themselves." Can you make improvements to eliminate ///// the if(professor.overlap(treasure)) statement and instead rely on ///// purely dynamic dispatch? Do this modification last. ///// // Don't touch the following, and make sure all your "moves" are above displaydc.DrawImage(dbuffer,0,0); // displays one animation frame System.Threading.Thread.Sleep(FDELAY); // delay between frames (ms) bufferdc.DrawImage(sbg,0,0,Wwidth,Wheight); // reset background Invalidate(); // tail-recursive call to mainloop. } // if !stop } // mainloop // remember - any vars declared in mainloop are local - if you // want vars to maintain their values between "mainloop" calls, // you need to declare parameters or vars outside the method. // Clear commenting is required. /* ------------------------------------------------------------- */ } // end class goblins ///////////////////////////////*****////////////////////////////////////// /* --- You have to modify the code below by using inheritance and the observer pattern. ------------------------------------------------------------- */ public class human { private Image img; // animated gif private int xcord, ycord; // coordinates of center of gif private int dx, dy; // movement vector int radius; // size - also used to detect collision private Graphics dc; // graphical "device context" needed for rendering private static int XBOUND = goblins.XBOUND; // max x coordinate in 3d "playground" private static int YBOUND = goblins.YBOUND; private static int LEVEL = goblins.LEVEL; // fixed z-coordinate private static boxworld playground = goblins.playground; // For animation control of gifs // ... this took me a LONG time to figure out! private System.Drawing.Imaging.FrameDimension fdim; private int framecount; private int frame; // current frame // C# "properties": when you say object.X, you are calling the "get" code, // and when you say object.X = 2, you are calling the "set code". public int X { get { return xcord; } } public int Y { get { return ycord; } } public int R { get { return radius; } } public int DX { get { return dx; } set { dx = value; } // value is a keyword in C# } public int DY { get { return dy; } set { dy = value; } // value is whatever you're trying to assign to DY } public void setdirection(int newdx, int newdy) { dx = newdx; dy = newdy; } // compute distance between x1,y1 and x2,y2 public static double dist(int x1, int y1, int x2, int y2) { int dx = (x1-x2) * (x1-x2); int dy = (y1-y2) * (y1-y2); return Math.Sqrt(dx+dy); } // detects if human has collided with another object public virtual bool overlap(diamond h) { int x2, y2, r2; x2 = h.X; y2 = h.Y; r2 = h.R; return dist(xcord,ycord,x2,y2) <= (radius + r2 - 5); } public human(int x0, int y0,Graphics b0,string gif,int r0) // constructor { xcord = x0; ycord = y0; img = Image.FromFile(gif); // load animated gif file radius = r0; dc = b0; // device context in Windows GDI+ allows drawing dx = dy = 0; // initial movement vector (stopped) // set info for number of animation dimension and frames in gif: Guid[] fdlist = img.FrameDimensionsList; fdim = new System.Drawing.Imaging.FrameDimension(fdlist[0]); framecount = img.GetFrameCount(fdim); // assume only 1 dimension frame = 0; } // constructor public void draw() // you shouldn't have to touch this unless you { // want to use different gifs (as when prof explodes) img.SelectActiveFrame(fdim,frame/2); // change frames at 1/2 speed frame = (frame + 1) % (2*framecount); // advance to next frame int r2 = (int) playground.dscale(radius,ycord); playground.drawimage(dc,img,xcord-r2,LEVEL-r2,ycord,radius*2,radius*2); //playground.drawcircle(dc,xcord,LEVEL,ycord,radius,Color.Red); } /*** ---------------------------------------------------------- ***/ // My default move function will cause position of object to wrap around // the grid. You will have to override this method for the different // subclasses. For example, to make an object bounce off the edges instead // of wrapping around, reverse the sign of the dx value (dx *= -1) when // the object reaches the left/right edge, and reverse the dy value when // it reaches the top or bottom. // Also, note that the coordinate system is as you learned in math classes: // the lower left corner of the grid has coordinate(0,0) - the y coordinate // increases as you go upwards (or further away). public void move() { xcord = xcord + dx; // this line should always be part of move() if (xcord < radius/2) xcord = XBOUND-(radius/2); if (xcord > XBOUND-(radius/2)) xcord = radius/2; ycord = ycord + dy; // ... as is this line if (ycord < radius/2) ycord = YBOUND-(radius/2); if (ycord > YBOUND-(radius/2)) ycord = radius/2; draw(); } } // end class human /////////////////////////////////////////////////// ///////// diamond class: note the code different from human towards bottom public class diamond { private Image img; // animated gif private int xcord, ycord; // coordinates of center of gif private int dx, dy; // movement vector int radius; // size - also used to detect collision private Graphics dc; // private static int XBOUND = goblins.XBOUND; private static int YBOUND = goblins.YBOUND; private static int LEVEL = goblins.LEVEL; private static boxworld playground = goblins.playground; // For animation control of gifs // ... this took me a LONG time to figure out! private System.Drawing.Imaging.FrameDimension fdim; private int framecount; private int frame; // current frame // C# "properties": when you say object.X, you are calling the "get" code, // and when you say object.X = 2, you are calling the "set code". public int X { get { return xcord; } } public int Y { get { return ycord; } } public int R { get { return radius; } } public int DX { get { return dx; } set { dx = value; } // value is a keyword in C# } public int DY { get { return dy; } set { dy = value; } // value is whatever you're trying to assign to DY } public void setdirection(int newdx, int newdy) { dx = newdx; dy = newdy; } public diamond(int x0, int y0,Graphics b0,string gif,int r0) // constructor { xcord = x0; ycord = y0; img = Image.FromFile(gif); // load animated gif file radius = r0; dc = b0; // divice context in Windows GDI+ allows drawing dx = dy = 0; // initial movement vector (stopped) // set info for number of animation dimension and frames in gif: Guid[] fdlist = img.FrameDimensionsList; fdim = new System.Drawing.Imaging.FrameDimension(fdlist[0]); framecount = img.GetFrameCount(fdim); // assume only 1 dimension frame = 0; } // constructor public void draw() // you shouldn't have to touch this unless you { // want to use different gifs (as when prof explodes) img.SelectActiveFrame(fdim,frame/2); // change frames at 1/2 speed frame = (frame + 1) % (2*framecount); // advance to next frame int r2 = (int) playground.dscale(radius,ycord); playground.drawimage(dc,img,xcord-r2,LEVEL-r2,ycord,radius*2,radius*2); //playground.drawcircle(dc,xcord,LEVEL,ycord,radius,Color.Red); } /*** ---------------------------------------------------------- ***/ // the following code is the only real difference between human and diamond: // the diamond moves randomly. Naturally, you should make "move" a // virtual-override method in your inheritance scheme. private Random randnum = new Random(); // generates random nums public void move() // moves randomly, stays in bounds { int dx, dy; dx = randnum.Next(-6,6); // give random num between -6 and +5 dy = randnum.Next(-5,5); if ((xcord+dx > radius/2) && (xcord+dx < XBOUND-(radius/2))) xcord = xcord + dx; if ((ycord+dy > radius/2) && (ycord+dy < YBOUND-(radius/2))) ycord = ycord + dy; draw(); } } // end class diamond