Head First C# (蜂巢模拟系统)
这个系统模拟一个蜂巢,蜜蜂进行采集花粉并酿蜜,该模拟系统运用到的技术有 LINQ、 GDI、 序列化、打印 和 委托等C#常用技术,利用GDI绘图简单实现了动画效果如下图所示:
打印预览效果如下图所示,本链接提供了本系统的源代码,还有之前Head First3个游戏的源代码,下面贴一些核心代码,如果比较熟悉该部分内容则可略过
namespace 蜂巢模拟系统 { public partial class Form1 : Form { World world;//花园 private Random random = new Random(); private DateTime start = DateTime.Now; private DateTime end; private int framesRun = 0;//游戏已经运行的时间 private HiveForm hiveForm;//蜂窝窗体 private FieldForm fieldForm;//花园窗体 Renderer renderer;//图像绘制类 public Form1() { //初始化并显示窗体 InitializeComponent(); world = new World(new Bee.BeeMessage(SendMessage)); timer1.Interval = 50; timer1.Tick+=new EventHandler(RunFrame); timer1.Enabled = false; UpdateStats(new TimeSpan()); hiveForm = new HiveForm(); fieldForm = new FieldForm(); CreateRender(); MoveChildForms(); hiveForm.Show(this); fieldForm.Show(this); } private void MoveChildForms() { hiveForm.Location = new Point(Location.X+Width+10,Location.Y); fieldForm.Location = new Point(Location.X, Location.Y + Math.Max(Height, hiveForm.Height) + 10); } /// <summary> /// 更新窗体数据 /// </summary> private void UpdateStats(TimeSpan frameDuration) { Bees.Text = world.Bees.Count.ToString(); Flowers.Text = world.Flowers.Count.ToString(); HoneyInHive.Text = string.Format("{0:f3}",world.Hive.Honey); double nectar = 0; foreach (Flower flower in world.Flowers) nectar += flower.Nectar; NectarInFlowers.Text = string.Format("{0:f3}",nectar); FrameRun.Text = framesRun.ToString(); double milliSeconds = frameDuration.TotalMilliseconds; if (milliSeconds != 0.0) FrameRate.Text = string.Format("{0:f0}({1:f1}ms)", 1000 / milliSeconds, milliSeconds); else FrameRate.Text = "N/A"; } /// <summary> /// 运行 /// </summary> public void RunFrame(object sender, EventArgs e) { framesRun++; world.Go(random); end = DateTime.Now; TimeSpan frameDuration = end - start; start = end; UpdateStats(frameDuration); hiveForm.Invalidate(); fieldForm.Invalidate(); } /// <summary> /// 系统运行按钮触发 /// </summary> private void startSimulation_Click(object sender, EventArgs e) { if (timer1.Enabled) { toolStrip1.Items[0].Text = "Resume simulation"; timer1.Stop(); } else { toolStrip1.Items[0].Text = "Pause simulation"; timer1.Start(); } } /// <summary> /// 系统重置按钮触发 /// </summary> private void reset_Click(object sender, EventArgs e) { world = new World(new Bee.BeeMessage(SendMessage)); CreateRender(); framesRun = 0; if (!timer1.Enabled) toolStrip1.Items[0].Text = "Start simulation"; } private void CreateRender() { renderer = new Renderer(world,hiveForm,fieldForm); hiveForm.renderer = renderer; fieldForm.renderer = renderer; } /// <summary> /// 显示蜜蜂信息 /// </summary> private void SendMessage(int ID, string Message) { statusStrip1.Items[0].Text = "Bee #" + ID + ":" + Message; //LINQ var beeGroups = from bee in world.Bees group bee by bee.CurrentState into beeGroup orderby beeGroup.Key select beeGroup; listBox1.Items.Clear(); foreach (var group in beeGroups) { string s; if (group.Count() == 1) s = ""; else s = "s"; listBox1.Items.Add(group.Key.ToString() + ": " + group.Count() + " bee" + s); if (group.Key == BeeState.Idle && group.Count() == world.Bees.Count() && framesRun > 0) { listBox1.Items.Add("Simulation ended:all bees are idle"); toolStrip1.Items[0].Text = "Simulation ended"; timer1.Enabled = false; } } } private void Form1_Move(object sender, EventArgs e) { MoveChildForms(); } /// <summary> /// 打开已保存的数据(反序列化) /// </summary> private void 打开OToolStripButton_Click(object sender, EventArgs e) { World currentWord = world; int currentFramesRun = framesRun; bool enabled = timer1.Enabled; if (enabled) timer1.Stop(); OpenFileDialog open = new OpenFileDialog(); open.Filter = "Simulator File(*.bees)|*.bees"; open.CheckFileExists = true; open.CheckPathExists = true; open.Title = "Choose a file with a simulation to load"; if (open.ShowDialog() == DialogResult.OK) { try { BinaryFormatter bf = new BinaryFormatter(); using (Stream input = File.OpenRead(open.FileName)) { world = (World)bf.Deserialize(input); framesRun = (int)bf.Deserialize(input); } } catch (Exception ex) { MessageBox.Show("Unable to read the simulator file\r\n"+ex.Message, "Bee Simulator Error",MessageBoxButtons.OK,MessageBoxIcon.Error); world = currentWord; framesRun = currentFramesRun; } } world.Hive.MessageSender = new Bee.BeeMessage(SendMessage); foreach (Bee bee in world.Bees) bee.MessageSender = new Bee.BeeMessage(SendMessage); if (enabled) timer1.Start(); CreateRender(); } private void timer2_Tick(object sender, EventArgs e) { renderer.AnimateBees(); } /// <summary> /// 打印按钮触发 /// </summary> private void 打印PToolStripButton_Click(object sender, EventArgs e) { bool stoppedTimer = false; if (timer1.Enabled) { timer1.Stop(); stoppedTimer = true; } PrintPreviewDialog preview = new PrintPreviewDialog(); PrintDocument document = new PrintDocument(); preview.Document = document; document.PrintPage += new PrintPageEventHandler(document_PrintPage); preview.ShowDialog(this); if (stoppedTimer) timer1.Start(); } /// <summary> /// 打印表格 /// </summary> private int PrintTableRow(Graphics printGraphics, int tableX, int tableWidth, int firstColumnX, int secondColumnX, int tableY, string firstColumn, string secondColumn) { Font arial12 = new Font("Arial",12); Size stringSize = Size.Ceiling(printGraphics.MeasureString(firstColumn, arial12)); tableY += 2; printGraphics.DrawString(firstColumn, arial12, Brushes.Black, firstColumnX, tableY); printGraphics.DrawString(secondColumn, arial12, Brushes.Black, secondColumnX, tableY); tableY += (int)stringSize.Height + 2; printGraphics.DrawLine(Pens.Black, tableX, tableY, tableX + tableWidth, tableY); arial12.Dispose(); return tableY; } /// <summary> /// 打印页面绘制 GDI /// </summary> private void document_PrintPage(object sender, PrintPageEventArgs e) { Graphics g = e.Graphics; Size stringSize; using (Font arial24bold = new Font("Arial", 24, FontStyle.Bold)) { stringSize = Size.Ceiling(g.MeasureString("Bee Simulator", arial24bold)); g.FillEllipse(Brushes.Gray, new Rectangle(e.MarginBounds.X + 2, e.MarginBounds.Y + 2, stringSize.Width + 30, stringSize.Height + 30)); g.FillEllipse(Brushes.Black,new Rectangle (e.MarginBounds.X,e.MarginBounds.Y, stringSize.Width + 30, stringSize.Height + 30) ); g.DrawString("Bee Simulator", arial24bold, Brushes.Gray, e.MarginBounds.X + 17, e.MarginBounds.Y + 17); g.DrawString("Bee Simulator", arial24bold, Brushes.White, e.MarginBounds.X + 15, e.MarginBounds.Y + 15); } int tableX = e.MarginBounds.X + (int)stringSize.Width + 50; int tableWidth = e.MarginBounds.X + e.MarginBounds.Width - tableX - 20; int firstColumnX = tableX + 2; int secondColumnX = tableX + (tableWidth / 2) + 5; int tableY = e.MarginBounds.Y; tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Bees", Bees.Text); tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Flowers", Flowers.Text); tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Honey in Hive", HoneyInHive.Text); tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Nectar in Flowers", NectarInFlowers.Text); tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Frames Run",FrameRun.Text); tableY = PrintTableRow(g, tableX, tableWidth, firstColumnX, secondColumnX, tableY, "Frame Rate", FrameRate.Text); g.DrawRectangle(Pens.Black,tableX,e.MarginBounds.Y,tableWidth,tableY-e.MarginBounds.Y); g.DrawLine(Pens.Black, secondColumnX, e.MarginBounds.Y, secondColumnX, tableY); using(Pen blackPen=new Pen (Brushes.Black)) using(Bitmap hiveBitmap=new Bitmap (hiveForm.ClientSize.Width,hiveForm.ClientSize.Height)) using (Bitmap fieldBitmap = new Bitmap(fieldForm.ClientSize.Width, fieldForm.ClientSize.Height)) { using (Graphics hiveGraphics = Graphics.FromImage(hiveBitmap)) { renderer.PaintHive(hiveGraphics); } int hiveWidth = hiveForm.ClientSize.Width+50; float ratio = (float)hiveBitmap.Height / (float)hiveBitmap.Width; int hiveHeight = (int)(hiveWidth * ratio); int hiveX = e.MarginBounds.X + (e.MarginBounds.Width - hiveWidth) / 2; int hiveY = tableY + 40; g.DrawImage(hiveBitmap, hiveX, hiveY, hiveWidth, hiveHeight); g.DrawRectangle(blackPen,hiveX,hiveY,hiveWidth,hiveHeight); using (Graphics fieldGraphics = Graphics.FromImage(fieldBitmap)) { renderer.PaintField(fieldGraphics); } int fieldWidth = e.MarginBounds.Width; ratio = (float)fieldBitmap.Height / (float)fieldBitmap.Width; int fieldHeight = (int)(fieldWidth * ratio); int fieldX = e.MarginBounds.X; int fieldY = hiveY +hiveHeight+ 50; g.DrawImage(fieldBitmap, fieldX, fieldY, fieldWidth, fieldHeight); g.DrawRectangle(blackPen, fieldX, fieldY, fieldWidth, fieldHeight); } } /// <summary> /// 保存按钮触发(序列化) /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void 保存SToolStripButton_Click(object sender, EventArgs e) { bool enabled = timer1.Enabled; if (enabled) timer1.Stop(); SaveFileDialog saveDialog = new SaveFileDialog(); saveDialog.Filter = "Simulator File(*.bees)|*.bees"; saveDialog.CheckFileExists = true; saveDialog.Title = "Choose a file to save the current simulation"; if (saveDialog.ShowDialog() == DialogResult.OK) { try { BinaryFormatter bf = new BinaryFormatter(); using (Stream output = File.OpenWrite(saveDialog.FileName)) { bf.Serialize(output, world); bf.Serialize(output, framesRun); } } catch (Exception ex) { MessageBox.Show("Unable to save the simulator file\r\n"+ex.Message, "Bee Simulator Error",MessageBoxButtons.OK,MessageBoxIcon.Error); } } if (enabled) timer1.Start(); } } [Serializable] public class Flower { private const int LifeSpanMin = 1500;//花的最短寿命 private const int LifeSpanMax = 3000;//花的最长寿命 private const double InitialNectar = 1.5;//最初花粉 private const double MaxNectar = 5.0;//一朵花能装多少花粉 private const double NectarAddedPerTurn = 0.01;//花逐渐变老时每次增加多少花粉 private const double NectarGatheredPerTurn = 0.03;//一个周期内收集多少花粉 public Point Location { get; private set; }//花的位置 public int Age { get; private set; }//年龄 public bool Alive { get; private set; }//是否存活 public double Nectar { get; private set; }//花粉 public double NectarHarvested { get; set; }//已被采集的花粉 private int lifeSpan;//生命 public Flower(Point location, Random random) { Location = location; Age = 0; Alive = true; Nectar = InitialNectar; NectarHarvested = 0; lifeSpan = random.Next(LifeSpanMin,LifeSpanMax+1); } public double HarvestNectar() { //采集花粉,蜜蜂采集花粉减少花粉存量增加花粉已采量 if (NectarGatheredPerTurn > Nectar) return 0; else { Nectar -= NectarGatheredPerTurn; NectarHarvested += NectarGatheredPerTurn; return NectarGatheredPerTurn; } } public void Go() { //运行一帧,花增大年龄并且增加自身花粉存量 Age++; if (Age > lifeSpan) Alive = false; else { Nectar += NectarAddedPerTurn; if (Nectar > MaxNectar) Nectar = MaxNectar; } } } public enum BeeState { Idle,//什么也没做 FlyingToFlower,//正飞向花 GatheringNectar,//收集花粉 ReturningToHive,//飞回蜂巢 MakingHoney,//酿蜜 Retired//退休 } [Serializable] public class Bee { private const double HoneyConsumed = 0.05;//每次消耗的蜂蜜 private const int MoveRate = 3;//移动速率 private const double MinimunFlowerNectar = 1.5;//采蜜花粉最低存量 private const int CareerSpan = 1000;//生命 public int Age { get; private set; }//年龄 public bool InsideHive { get; private set; }//是否在蜂巢 public double NectarCollected { get; private set; }//采集的花粉 public BeeState CurrentState { get; private set; } private Point location; public Point Location { get { return location; } } private int ID; private Flower destinationFlower; private World world; private Hive hive; public delegate void BeeMessage(int ID, string Message); [NonSerialized] public BeeMessage MessageSender; private bool MoveTowardsLocation(Point destination) { if (destination != null) { if (Math.Abs(destination.X - location.X) <= MoveRate && Math.Abs(destination.Y - location.Y) <= MoveRate) return true; if (destination.X > location.X) location.X += MoveRate; else if(destination.X<location.X) location.X -= MoveRate; if (destination.Y > location.Y) location.Y += MoveRate; else if(destination.Y < location.Y) location.Y -= MoveRate; } return false; } public Bee(int ID, Point InitialLocation,World world,Hive hive) { this.ID = ID; Age = 0; location = InitialLocation; InsideHive = true; CurrentState = BeeState.Idle; destinationFlower = null; NectarCollected = 0; this.world = world; this.hive = hive; } public void Go(Random random) { Age++; BeeState oldState = CurrentState; #region BeeAction switch (CurrentState) { case BeeState.Idle: if (Age > CareerSpan) { CurrentState = BeeState.Retired; } else if (world.Flowers.Count > 0 && hive.ConsumeHoney(HoneyConsumed)) { Flower flower = world.Flowers[random.Next(world.Flowers.Count)]; if (flower.Nectar >= MinimunFlowerNectar && flower.Alive) { destinationFlower = flower; CurrentState = BeeState.FlyingToFlower; } } break; case BeeState.FlyingToFlower: if (!world.Flowers.Contains(destinationFlower)) CurrentState = BeeState.ReturningToHive; else if (InsideHive) { if (MoveTowardsLocation(hive.GetLocation("Exit"))) { InsideHive = false; location = hive.GetLocation("Entrance"); } } else if (MoveTowardsLocation(destinationFlower.Location)) CurrentState = BeeState.GatheringNectar; break; case BeeState.GatheringNectar: double nectar = destinationFlower.HarvestNectar(); if (nectar > 0) NectarCollected += nectar; else CurrentState = BeeState.ReturningToHive; break; case BeeState.ReturningToHive: if (!InsideHive) { if (MoveTowardsLocation(hive.GetLocation("Entrance"))) { InsideHive = true; location = hive.GetLocation("Exit"); } } else if (MoveTowardsLocation(hive.GetLocation("HoneyFactory"))) CurrentState = BeeState.MakingHoney; break; case BeeState.MakingHoney: if (NectarCollected < 0.5) { NectarCollected = 0; CurrentState = BeeState.Idle; } else if (hive.AddHoney(0.5)) NectarCollected -= 0.5; else NectarCollected = 0; break; case BeeState.Retired: break; } #endregion if (oldState != CurrentState && MessageSender != null) MessageSender(ID,CurrentState.ToString()); } } [Serializable] public class Hive { private const int InitialBees = 10;//初始蜜蜂数量 private const double InitialHoney = 3.2;//初始蜂蜜值 private const double MaximumHoney = 15;//最大蜂蜜值 private const double NectarHoneyRatio = 0.25;//花粉转换成蜂蜜的比率 private const double MinimumHoneyForCreatingBees = 4.0;//新增蜜蜂最少需要的蜂蜜存量 private const int MaximumBees = 12;//最大蜜蜂数量 [NonSerialized] public Bee.BeeMessage MessageSender; private Dictionary<string, Point> locations; private int beeCount = 0; private World world; public double Honey { get; private set; } private void InitializeLocation() { locations = new Dictionary<string, Point>(); locations.Add("Entrance",new Point (600,100)); locations.Add("Nursery", new Point(95, 174)); locations.Add("HoneyFactory", new Point(157, 68)); locations.Add("Exit", new Point(194, 213)); } public Point GetLocation(string location) { if (locations.Keys.Contains(location)) return locations[location]; else throw new ArgumentException("Unknown location:" + location); } public Hive(World world,Bee.BeeMessage MessageSender) { this.MessageSender = MessageSender; Honey = InitialHoney; InitializeLocation(); this.world = world; Random random = new Random(); for (int i = 0; i < InitialBees; i++) AddBee(random); } public bool AddHoney(double nectar) { //增加蜂蜜 double honeyToAdd = nectar * NectarHoneyRatio; if (honeyToAdd + Honey > MaximumHoney) return false; Honey += honeyToAdd; return true; } public bool ConsumeHoney(double amount) { //消耗蜂蜜 if (amount > Honey) return false; else { Honey -= amount; return true; } } private void AddBee(Random random) { //增加蜜蜂 beeCount++; int r1 = random.Next(100) - 50; int r2 = random.Next(100) - 50; Point startPoint = new Point(locations["Nursery"].X + r1, locations["Nursery"].Y + r2); Bee newBee = new Bee(beeCount,startPoint,world,this); newBee.MessageSender += this.MessageSender; world.Bees.Add(newBee); } public void Go(Random random) { if (world.Bees.Count<MaximumBees && Honey > MinimumHoneyForCreatingBees && random.Next(10)==1) AddBee(random); } } [Serializable] public class World { private const double NectarHarvestedPerNewFlower = 30.0;//累积采集多少花粉会增加花朵 private const int FieldMinX = 15; private const int FieldMinY = 177; private const int FieldMaxX = 690; private const int FieldMaxY = 290; public Hive Hive; public List<Bee> Bees; public List<Flower> Flowers; public World(Bee.BeeMessage messageSender) { Bees = new List<Bee>(); Flowers=new List<Flower>(); Random random=new Random (); Hive = new Hive(this,messageSender); for(int i=0;i<10;i++) AddFlower(random); } public void Go(Random random) { Hive.Go(random); for (int i = Bees.Count - 1; i >= 0; i--) { Bee bee = Bees[i]; bee.Go(random); if (bee.CurrentState == BeeState.Retired) Bees.Remove(bee); } double totalNectarHarvested = 0; for (int i = Flowers.Count - 1; i >= 0; i--) { Flower flower = Flowers[i]; flower.Go(); totalNectarHarvested += flower.NectarHarvested; if (!flower.Alive) Flowers.Remove(flower); if (totalNectarHarvested > NectarHarvestedPerNewFlower) { foreach (Flower flo in Flowers) { flo.NectarHarvested = 0; } AddFlower(random); } } } private void AddFlower(Random random) { Point location = new Point(random.Next(FieldMinX,FieldMaxX), random.Next(FieldMinY,FieldMaxY)); Flower newFlower = new Flower(location,random); Flowers.Add(newFlower); } } public class Renderer { private World world; private HiveForm hiveForm; private FieldForm fieldForm; public Renderer(World world, HiveForm hiveForm, FieldForm fieldForm) { this.world = world; this.hiveForm = hiveForm; this.fieldForm = fieldForm; InitialIizeImage(); } public static Bitmap ResizeImage(Bitmap picture, int width, int height) { Bitmap resizedPicture = new Bitmap(width, height); using (Graphics graphics = Graphics.FromImage(resizedPicture)) { graphics.DrawImage(picture,0,0,width,height); } return resizedPicture; } Bitmap HiveInside; Bitmap HiveOutSide; Bitmap Flower; Bitmap[] BeeAnimationSmall; Bitmap[] BeeAnimationLarge; private int Cell = 0; private int Frame = 0; private void InitialIizeImage() { HiveOutSide = ResizeImage(Properties.Resources.Hive__outside_,85,100); Flower = ResizeImage(Properties.Resources.Flower,75,75); HiveInside = ResizeImage(Properties.Resources.Hive__inside_,hiveForm.ClientRectangle.Width,hiveForm.ClientRectangle.Height); BeeAnimationLarge = new Bitmap[4]; BeeAnimationLarge[0] = ResizeImage(Properties.Resources.Bee_animation_1, 40, 40); BeeAnimationLarge[1] = ResizeImage(Properties.Resources.Bee_animation_2, 40, 40); BeeAnimationLarge[2] = ResizeImage(Properties.Resources.Bee_animation_3, 40, 40); BeeAnimationLarge[3] = ResizeImage(Properties.Resources.Bee_animation_4, 40, 40); BeeAnimationSmall = new Bitmap[4]; BeeAnimationSmall[0] = ResizeImage(Properties.Resources.Bee_animation_1, 20, 20); BeeAnimationSmall[1] = ResizeImage(Properties.Resources.Bee_animation_2, 20, 20); BeeAnimationSmall[2] = ResizeImage(Properties.Resources.Bee_animation_3, 20, 20); BeeAnimationSmall[3] = ResizeImage(Properties.Resources.Bee_animation_4, 20, 20); } public void PaintHive(Graphics g) { g.FillRectangle(Brushes.SkyBlue,hiveForm.ClientRectangle); g.DrawImageUnscaled(HiveInside,0,0); foreach (Bee bee in world.Bees) { if (bee.InsideHive) g.DrawImageUnscaled(BeeAnimationLarge[Cell], bee.Location.X, bee.Location.Y); } } public void PaintField(Graphics g) { using (Pen brownPen = new Pen(Color.Brown, 6.0F)) { g.FillRectangle(Brushes.SkyBlue,0,0,fieldForm.ClientSize.Width,fieldForm.ClientSize.Height); g.FillEllipse(Brushes.Yellow, new RectangleF(50, 15, 70, 70)); g.FillRectangle(Brushes.Green,0,fieldForm.ClientSize.Height/2,fieldForm.ClientSize.Width,fieldForm.ClientSize.Height/2); g.DrawLine(brownPen, new Point(643, 0), new Point(643, 30)); g.DrawImageUnscaled(HiveOutSide,600,20); foreach (Flower flower in world.Flowers) { g.DrawImageUnscaled(Flower,flower.Location.X,flower.Location.Y); } foreach (Bee bee in world.Bees) { if (!bee.InsideHive) g.DrawImageUnscaled(BeeAnimationSmall[Cell],bee.Location.X,bee.Location.Y); } } } public void AnimateBees() { Frame++; if (Frame > 6) Frame = 0; switch (Frame) { case 0: Cell = 0; break; case 1: Cell = 1; break; case 2: Cell = 2; break; case 3: Cell = 3; break; case 4: Cell = 2; break; case 5: Cell = 1; break; default: Cell = 0; break; } hiveForm.Invalidate(); fieldForm.Invalidate(); } } }