© Conmajia 2012
《倒水解密》是一款很不错的手机益智类游戏,游戏界面如下:
规则是这样的:
有 N 个容量不同的瓶子,指定「将 a 升水倒入容量为 b 的瓶子」。游戏要求通过装水、倒水,达成给定的目标。
该游戏虽然简单,但可玩性高,也可有不同的难度变化,并且能锻炼玩家的心算能力。《倒水解密》不失为一个很好的繁忙工作之余的休闲方式。
说到这里,大家应该能猜到我想干什么了。没错,山寨之。
下面是山寨游戏的屏幕录像:
虽然界面有点简陋,但至少实现了。(屏幕录像软件不能录入鼠标光标,实际上鼠标光标是动态的)。
游戏操作方式如下:
- 在瓶子上双击右键可以把瓶子灌满水
- 双击左键可以把瓶子里的水倒掉
- 将一个瓶子拖动到另一个瓶子上可以把水倒过去
下面是游戏的模型结构。
Bottle 类,表示瓶子。保存了瓶子的关键属性如容量、储量,以及基本方法如倒入、倒出。该类实现了 IVisible 可视化接口。这个接口很简单,只是提供了一个 Draw() 方法,用于重绘瓶子自身并返回图像。使用这种方式,可以方便的修改瓶子的外观样式而不用修改其他部分代码。例如可以简单的用矩形画瓶子,也可以像上面的手机游戏截图一样用非常的漂亮的贴图来做。
这里我用画图鼠绘了一个丑陋的瓶子。
Bottle类源代码
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 类,表示瓶子所在世界。存储了所有的瓶子,用于和游戏交互。
World类源代码
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 类,游戏类。保存游戏世界,负责自动生成游戏和判定游戏胜利。
Game类源代码
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 }
游戏的计时、计步、界面操作等统统放在主窗体,这里不做赘述。
简单的实现,还有倒计时、高分榜等功能可以扩展,界面也可以做的更漂亮些,欢迎大家扩展。