本文所有源代码和VisualStudio2010(.NET Fx 2.0)工程打包在本文最后下载。(别找我要源码,一概不理会)
《倒水解密》是一款很不错的手机益智类游戏,游戏界面如下:
规则是这样的:
有N个容量不同的瓶子,指定「将a升水倒入容量为b的瓶子」。游戏要求通过装水、倒水,达成给定的目标。
该游戏虽然简单,但可玩性高,也可有不同的难度变化,并且能锻炼玩家的心算能力。《倒水解密》不失为一个很好的繁忙工作之余的休闲方式。
说到这里,大家应该能猜到我想干什么了。没错,山寨之。
下面是山寨游戏的屏幕录像:
虽然界面有点简陋,但至少实现了。(屏幕录像软件不能录入鼠标光标,实际上鼠标光标是动态的)。
游戏操作方式如下:
- 在瓶子上双击右键可以把瓶子灌满水
- 双击左键可以把瓶子里的水倒掉
- 将一个瓶子拖动到另一个瓶子上可以把水倒过去
下面是游戏的模型结构。
Bottle类,表示瓶子。保存了瓶子的关键属性如容量、储量,以及基本方法如倒入、倒出。该类实现了IVisible可视化接口。这个接口很简单,只是提供了一个Draw()方法,用于重绘瓶子自身并返回图像。使用这种方式,可以方便的修改瓶子的外观样式而不用修改其他部分代码。例如可以简单的用矩形画瓶子,也可以像上面的手机游戏截图一样用非常的漂亮的贴图来做。
1、这里我用画图鼠绘了一个丑陋的瓶子作为例子。
// By Conmajia<conmajia@gmail.com> on September 6th., 2012
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
namespace DropWaterGame
{
public class Bottle : Element, IVisible
{
const int SIZE_FACTOR = 10;
#region Variables
int content = 0;
int capacity = 0;
Size size = Size.Empty;
Rectangle bounds = Rectangle.Empty;
Bitmap canvas;
Graphics painter;
bool dirty = true;
#endregion
#region Properties
public int FreeSpace
{
get { return capacity - content; }
}
public int Content
{
get { return content; }
}
public int Capacity
{
get { return capacity; }
}
public bool IsEmpty
{
get { return content == 0; }
}
public bool IsFull
{
get { return content == capacity; }
}
#endregion
#region Initiators
public Bottle(int capacity)
: this(capacity, 0)
{
}
public Bottle(int capacity, int content)
{
if (capacity > 0)
{
this.capacity = capacity;
if (content > -1 && content <= capacity)
this.content = content;
size.Width = 30;
size.Height = SIZE_FACTOR * capacity;
bounds.Size = size;
canvas = new Bitmap(size.Width, size.Height);
painter = Graphics.FromImage(canvas);
}
}
#endregion
#region Methods
public void DropIn()
{
DropIn(capacity);
}
public void DropIn(int amount)
{
if (amount > 0)
{
content += amount;
if (content > capacity)
content = capacity;
dirty = true;
}
}
public void DropOut()
{
DropOut(capacity);
}
public void DropOut(int amount)
{
if (amount > 0 && amount < content)
{
content -= amount;
}
else
{
content = 0;
}
dirty = true;
}
#endregion
#region IVisible
public Rectangle Bounds
{
get { return bounds; }
}
public int X
{
get { return bounds.X; }
set { bounds.X = value; }
}
public int Y
{
get { return bounds.Y; }
set { bounds.Y = value; }
}
public Bitmap Draw()
{
if (dirty)
{
painter.Clear(Color.Transparent);
// simple look bottle
int contentHeight = (int)((float)bounds.Height * ((float)content / (float)capacity));
if (contentHeight > 0)
{
using (Brush b = new LinearGradientBrush(
new Rectangle(
0,
bounds.Height - contentHeight - 1,
bounds.Width,
contentHeight
),
Color.LightBlue,
Color.DarkBlue,
90))
{
Rectangle contentRect = new Rectangle(
0,
bounds.Height - contentHeight,
bounds.Width,
contentHeight
);
painter.FillRectangle(b, contentRect);
}
}
painter.DrawRectangle(
Pens.Silver,
0,
0,
bounds.Width - 1,
bounds.Height - 1
);
string s = string.Format("{0}/{1}", content, capacity);
painter.DrawString(
s,
SystemFonts.DefaultFont,
Brushes.Black,
2,
1
);
painter.DrawString(
s,
SystemFonts.DefaultFont,
Brushes.Black,
1,
2
);
painter.DrawString(
s,
SystemFonts.DefaultFont,
Brushes.Black,
2,
3
);
painter.DrawString(
s,
SystemFonts.DefaultFont,
Brushes.Black,
3,
2
);
painter.DrawString(
s,
SystemFonts.DefaultFont,
Brushes.White,
2,
2
);
dirty = false;
}
return canvas;
}
#endregion
#region Elemenet
public override Type Type
{
get { return typeof(Bottle); }
}
#endregion
}
}
(1)我们可以看到Bottle类的构造函数是Bottle(int capacity)又调用了另一构造函数Bottle(int capacity, int content).
这样的表达方式要是在C++中会编译报错的:
1>c:\users\chenyj\desktop\temp\temp\tempdlg.cpp(14) : error C2059: 语法错误 : “this”
(2)比较复杂的是Draw方法。他做的事情有:
(2、1)画出水里占有的方形图。
画的方法是通过using里创建的线性画刷。
(2、2)分别写出文字。文字是分为5个点来写的(为什么要这样呢?至今未知)。
2、
World类,表示瓶子所在世界。存储了所有的瓶子,用于和游戏交互。
// By Conmajia<conmajia@gmail.com> on September 6th., 2012
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace DropWaterGame
{
public class World
{
const int PADDING = 20;
#region Variables
List<Bottle> bottles = new List<Bottle>();
Rectangle bounds = Rectangle.Empty;
#endregion
#region Properties
public List<Bottle> Bottles
{
get { return bottles; }
}
public Rectangle Bounds
{
get { return bounds; }
set
{
bounds = value;
arrangeBottles();
}
}
#endregion
#region Initiators
public World()
{
}
public World(Rectangle bounds)
{
this.bounds = bounds;
}
#endregion
#region world methods
public Bottle CreateBottle(int capacity)
{
return CreateBottle(capacity, 0);
}
public Bottle CreateBottle(int capacity, int content)
{
Bottle b = new Bottle(capacity, content);
bottles.Add(b);
arrangeBottles();
return b;
}
public void DestroyBottle()
{
bottles.Clear();
}
public void DestroyBottle(Bottle b)
{
bottles.Remove(b);
}
public void DestroyBottle(int capacity)
{
List<Bottle> tmp = new List<Bottle>();
foreach (Bottle b in bottles)
{
if (b.Capacity != capacity)
tmp.Add(b);
}
bottles.Clear();
bottles.AddRange(tmp);
}
#endregion
#region paint helpers
Size getTotalSize()
{
Size sz = Size.Empty;
foreach (Bottle b in bottles)
{
sz.Width += PADDING + b.Bounds.Width;
if (sz.Height < b.Bounds.Height)
sz.Height = b.Bounds.Height;
}
return sz;
}
void arrangeBottles()
{
Size sz = getTotalSize();
Point offset = new Point(
(bounds.Width - sz.Width) / 2,
(bounds.Height - sz.Height) / 2 + sz.Height
);
foreach (Bottle b in bottles)
{
b.X = offset.X;
b.Y = offset.Y - b.Bounds.Height;
offset.X += PADDING + b.Bounds.Width;
}
}
#endregion
}
}
Game类,游戏类。保存游戏世界,负责自动生成游戏和判定游戏胜利。
// By Conmajia<conmajia@gmail.com> on September 6th., 2012
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace DropWaterGame
{
public class Game
{
string name = string.Empty;
int bottleCount = 0;
bool initiated = false;
int targetBottle = -1;
int targetAmount = -1;
World world;
Random rand = new Random();
public string Name
{
get { return name; }
}
public int Target
{
get { return targetBottle; }
}
public int Amount
{
get { return targetAmount; }
}
public World World
{
get { return world; }
}
public Game()
{
world = new World();
}
/// <summary>
/// Difficaulty of game.
/// </summary>
/// <param name="difficaulty">Difficaulty from 1 to 3.</param>
public void AutoGenerate(int difficaulty)
{
if (difficaulty < 1)
return;
world.DestroyBottle();
int bottleCount = rand.Next(3, 5); //3 + difficaulty);
targetBottle = rand.Next(0, bottleCount - 1);
int maxAmount = 10;
for (int i = 0; i < bottleCount; i++)
{
int cap = 0;
do
{
cap = rand.Next(3, maxAmount + difficaulty);
} while (capacityInside(cap));
world.CreateBottle(cap);
}
targetAmount = rand.Next(1, world.Bottles[targetBottle].Capacity);
initiated = true;
}
bool capacityInside(int cap)
{
foreach (Bottle b in world.Bottles)
{
if (b.Capacity == cap)
return true;
}
return false;
}
public bool CheckSuccess()
{
if (targetBottle > -1)
{
if (initiated && world.Bottles.Count > targetBottle)
{
return world.Bottles[targetBottle].Content == targetAmount;
}
}
return false;
}
}
}
游戏的计时、计步、界面操作等统统放在主窗体,这里不做赘述。
简单的实现,还有倒计时、高分榜等功能可以扩展,界面也可以做的更漂亮些,欢迎大家扩展。
源码及工程文件(VS2010)打包 :点击下载
© Conmajia 2012