🍺 倒水解密
© Conmajia 2012
本文所有源代码和 VisualStudio2010(.NET Fx 2.0)工程打包在本文最后下载。
《倒水解密》是一款很不错的手机益智类游戏,游戏界面如下:
规则是这样的:
有 N 个容量不同的瓶子,指定「将 a 升水倒入容量为 b 的瓶子」。游戏要求通过装水、倒水,达成给定的目标。
该游戏虽然简单,但可玩性高,也可有不同的难度变化,并且能锻炼玩家的心算能力。《倒水解密》不失为一个很好的繁忙工作之余的休闲方式。
说到这里,大家应该能猜到我想干什么了。没错,山寨之。
下面是山寨游戏的屏幕录像:
虽然界面有点简陋,但至少实现了。(屏幕录像软件不能录入鼠标光标,实际上鼠标光标是动态的)。
游戏操作方式如下:
- 在瓶子上双击右键可以把瓶子灌满水
- 双击左键可以把瓶子里的水倒掉
- 将一个瓶子拖动到另一个瓶子上可以把水倒过去
下面是游戏的模型结构。
Bottle 类,表示瓶子。保存了瓶子的关键属性如容量、储量,以及基本方法如倒入、倒出。该类实现了 IVisible 可视化接口。这个接口很简单,只是提供了一个 Draw() 方法,用于重绘瓶子自身并返回图像。使用这种方式,可以方便的修改瓶子的外观样式而不用修改其他部分代码。例如可以简单的用矩形画瓶子,也可以像上面的手机游戏截图一样用非常的漂亮的贴图来做。
这里我用画图鼠绘了一个丑陋的瓶子。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class Bottle : Element, IVisible 2 { 3 const int SIZE_FACTOR = 10; 4 5 #region Variables 6 int content = 0; 7 int capacity = 0; 8 Size size = Size.Empty; 9 Rectangle bounds = Rectangle.Empty; 10 11 Bitmap canvas; 12 Graphics painter; 13 bool dirty = true; 14 15 #endregion 16 17 #region Properties 18 public int FreeSpace 19 { 20 get { return capacity - content; } 21 } 22 23 public int Content 24 { 25 get { return content; } 26 } 27 28 public int Capacity 29 { 30 get { return capacity; } 31 } 32 33 public bool IsEmpty 34 { 35 get { return content == 0; } 36 } 37 38 public bool IsFull 39 { 40 get { return content == capacity; } 41 } 42 #endregion 43 44 #region Initiators 45 public Bottle(int capacity) 46 : this(capacity, 0) 47 { 48 49 } 50 51 public Bottle(int capacity, int content) 52 { 53 if (capacity > 0) 54 { 55 this.capacity = capacity; 56 if (content > -1 && content <= capacity) 57 this.content = content; 58 59 size.Width = 30; 60 size.Height = SIZE_FACTOR * capacity; 61 bounds.Size = size; 62 canvas = new Bitmap(size.Width, size.Height); 63 painter = Graphics.FromImage(canvas); 64 } 65 } 66 #endregion 67 68 #region Methods 69 public void DropIn() 70 { 71 DropIn(capacity); 72 } 73 public void DropIn(int amount) 74 { 75 if (amount > 0) 76 { 77 content += amount; 78 if (content > capacity) 79 content = capacity; 80 81 dirty = true; 82 } 83 } 84 85 public void DropOut() 86 { 87 DropOut(capacity); 88 } 89 public void DropOut(int amount) 90 { 91 if (amount > 0 && amount < content) 92 { 93 content -= amount; 94 } 95 else 96 { 97 content = 0; 98 } 99 100 dirty = true; 101 } 102 #endregion 103 104 #region IVisible 105 public Rectangle Bounds 106 { 107 get { return bounds; } 108 } 109 110 public int X 111 { 112 get { return bounds.X; } 113 set { bounds.X = value; } 114 } 115 116 public int Y 117 { 118 get { return bounds.Y; } 119 set { bounds.Y = value; } 120 } 121 122 public Bitmap Draw() 123 { 124 if (dirty) 125 { 126 painter.Clear(Color.Transparent); 127 128 // simple look bottle 129 int contentHeight = (int)((float)bounds.Height * ((float)content / (float)capacity)); 130 131 if (contentHeight > 0) 132 { 133 using (Brush b = new LinearGradientBrush( 134 new Rectangle( 135 0, 136 bounds.Height - contentHeight - 1, 137 bounds.Width, 138 contentHeight 139 ), 140 Color.LightBlue, 141 Color.DarkBlue, 142 90)) 143 { 144 Rectangle contentRect = new Rectangle( 145 0, 146 bounds.Height - contentHeight, 147 bounds.Width, 148 contentHeight 149 ); 150 painter.FillRectangle(b, contentRect); 151 } 152 } 153 154 painter.DrawRectangle( 155 Pens.Silver, 156 0, 157 0, 158 bounds.Width - 1, 159 bounds.Height - 1 160 ); 161 162 string s = string.Format("{0}/{1}", content, capacity); 163 painter.DrawString( 164 s, 165 SystemFonts.DefaultFont, 166 Brushes.Black, 167 2, 168 1 169 ); 170 painter.DrawString( 171 s, 172 SystemFonts.DefaultFont, 173 Brushes.Black, 174 1, 175 2 176 ); 177 painter.DrawString( 178 s, 179 SystemFonts.DefaultFont, 180 Brushes.Black, 181 2, 182 3 183 ); 184 painter.DrawString( 185 s, 186 SystemFonts.DefaultFont, 187 Brushes.Black, 188 3, 189 2 190 ); 191 painter.DrawString( 192 s, 193 SystemFonts.DefaultFont, 194 Brushes.White, 195 2, 196 2 197 ); 198 199 dirty = false; 200 } 201 202 return canvas; 203 } 204 #endregion 205 206 #region Elemenet 207 public override Type Type 208 { 209 get { return typeof(Bottle); } 210 } 211 #endregion 212 213 }
World 类,表示瓶子所在世界。存储了所有的瓶子,用于和游戏交互。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class World 2 { 3 const int PADDING = 20; 4 5 #region Variables 6 List<Bottle> bottles = new List<Bottle>(); 7 8 Rectangle bounds = Rectangle.Empty; 9 #endregion 10 11 #region Properties 12 public List<Bottle> Bottles 13 { 14 get { return bottles; } 15 } 16 17 public Rectangle Bounds 18 { 19 get { return bounds; } 20 set 21 { 22 bounds = value; 23 arrangeBottles(); 24 } 25 } 26 #endregion 27 28 #region Initiators 29 public World() 30 { 31 32 } 33 public World(Rectangle bounds) 34 { 35 this.bounds = bounds; 36 } 37 #endregion 38 39 #region world methods 40 public Bottle CreateBottle(int capacity) 41 { 42 return CreateBottle(capacity, 0); 43 } 44 public Bottle CreateBottle(int capacity, int content) 45 { 46 Bottle b = new Bottle(capacity, content); 47 bottles.Add(b); 48 arrangeBottles(); 49 return b; 50 } 51 52 public void DestroyBottle() 53 { 54 bottles.Clear(); 55 } 56 public void DestroyBottle(Bottle b) 57 { 58 bottles.Remove(b); 59 } 60 public void DestroyBottle(int capacity) 61 { 62 List<Bottle> tmp = new List<Bottle>(); 63 foreach (Bottle b in bottles) 64 { 65 if (b.Capacity != capacity) 66 tmp.Add(b); 67 } 68 69 bottles.Clear(); 70 bottles.AddRange(tmp); 71 } 72 73 #endregion 74 75 #region paint helpers 76 Size getTotalSize() 77 { 78 Size sz = Size.Empty; 79 foreach (Bottle b in bottles) 80 { 81 sz.Width += PADDING + b.Bounds.Width; 82 if (sz.Height < b.Bounds.Height) 83 sz.Height = b.Bounds.Height; 84 } 85 86 return sz; 87 } 88 89 void arrangeBottles() 90 { 91 Size sz = getTotalSize(); 92 Point offset = new Point( 93 (bounds.Width - sz.Width) / 2, 94 (bounds.Height - sz.Height) / 2 + sz.Height 95 ); 96 97 foreach (Bottle b in bottles) 98 { 99 b.X = offset.X; 100 b.Y = offset.Y - b.Bounds.Height; 101 102 offset.X += PADDING + b.Bounds.Width; 103 } 104 } 105 #endregion 106 }
Game 类,游戏类。保存游戏世界,负责自动生成游戏和判定游戏胜利。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 public class Game 2 { 3 string name = string.Empty; 4 int bottleCount = 0; 5 bool initiated = false; 6 int targetBottle = -1; 7 int targetAmount = -1; 8 9 World world; 10 11 Random rand = new Random(); 12 13 public string Name 14 { 15 get { return name; } 16 } 17 18 public int Target 19 { 20 get { return targetBottle; } 21 } 22 23 public int Amount 24 { 25 get { return targetAmount; } 26 } 27 28 public World World 29 { 30 get { return world; } 31 } 32 33 public Game() 34 { 35 world = new World(); 36 } 37 38 /// <summary> 39 /// Difficaulty of game. 40 /// </summary> 41 /// <param name="difficaulty">Difficaulty from 1 to 3.</param> 42 public void AutoGenerate(int difficaulty) 43 { 44 if (difficaulty < 1) 45 return; 46 47 world.DestroyBottle(); 48 49 int bottleCount = rand.Next(3, 5); //3 + difficaulty); 50 targetBottle = rand.Next(0, bottleCount - 1); 51 52 int maxAmount = 10; 53 for (int i = 0; i < bottleCount; i++) 54 { 55 int cap = 0; 56 do 57 cap = rand.Next(3, maxAmount + difficaulty); 58 while (capacityInside(cap)); 59 world.CreateBottle(cap); 60 } 61 62 targetAmount = rand.Next(1, world.Bottles[targetBottle].Capacity); 63 64 initiated = true; 65 } 66 67 bool capacityInside(int cap) 68 { 69 foreach (Bottle b in world.Bottles) 70 if (b.Capacity == cap) 71 return true; 72 return false; 73 } 74 75 public bool CheckSuccess() 76 { 77 if (targetBottle > -1) 78 if (initiated && world.Bottles.Count > targetBottle) 79 return world.Bottles[targetBottle].Content == targetAmount; 80 return false; 81 } 82 }
游戏的计时、计步、界面操作等统统放在主窗体,这里不做赘述。
简单的实现,还有倒计时、高分榜等功能可以扩展,界面也可以做的更漂亮些,欢迎大家扩展。
源码及工程文件(VS2010)打包 :点击下载
© Conmajia 2012
if(jQuery('#no-reward').text() == 'true') jQuery('.bottom-reward').addClass('hidden');