自己动手实现一个《倒水解密》游戏

© Conmajia 2012

本文所有源代码和VisualStudio2010(.NET Fx 2.0)工程打包在本文最后下载。(别找我要源码,一概不理会)

《倒水解密》是一款很不错的手机益智类游戏,游戏界面如下:

规则是这样的:

有N个容量不同的瓶子,指定「将a升水倒入容量为b的瓶子」。游戏要求通过装水、倒水,达成给定的目标。

该游戏虽然简单,但可玩性高,也可有不同的难度变化,并且能锻炼玩家的心算能力。《倒水解密》不失为一个很好的繁忙工作之余的休闲方式。

说到这里,大家应该能猜到我想干什么了。没错,山寨之。

下面是山寨游戏的屏幕录像:

虽然界面有点简陋,但至少实现了。(屏幕录像软件不能录入鼠标光标,实际上鼠标光标是动态的)。

游戏操作方式如下:

  • 在瓶子上双击右键可以把瓶子灌满水
  • 双击左键可以把瓶子里的水倒掉
  • 将一个瓶子拖动到另一个瓶子上可以把水倒过去

下面是游戏的模型结构。

Bottle类,表示瓶子。保存了瓶子的关键属性如容量、储量,以及基本方法如倒入、倒出。该类实现了IVisible可视化接口。这个接口很简单,只是提供了一个Draw()方法,用于重绘瓶子自身并返回图像。使用这种方式,可以方便的修改瓶子的外观样式而不用修改其他部分代码。例如可以简单的用矩形画瓶子,也可以像上面的手机游戏截图一样用非常的漂亮的贴图来做。

这里我用画图鼠绘了一个丑陋的瓶子作为例子。

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
}


World类,表示瓶子所在世界。存储了所有的瓶子,用于和游戏交互。

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类,游戏类。保存游戏世界,负责自动生成游戏和判定游戏胜利。

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

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页