咸鱼游戏涂色游戏的逻辑分析与目标块执行步骤推演

本文介绍了一个实现N维涂色游戏的项目,通过代码展示了如何寻找最短的涂色路径。作者探讨了游戏的逻辑,包括不可解目标的情况,并提供了一种基于逆向思维的解决算法。此外,还提到了将游戏与直播结合的想法,但由于技术限制未能实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

咸鱼游戏中有许多小游戏,我在玩小游戏的时候发现了一个涂色小游戏。

下方地址是我的代码地址,可以查看完整代码,并进行测试。

GitHub - Basicconstruction/Painter: 涂色游戏的简单实现和算法解决

类似于下面的涂色,为了完成游戏,我们需要使用右侧和下侧的刷子吧方块刷到目标色。

下图是我实现的涂色游戏的逻辑,这两个图展示的分别是初始图,和目的图。

我们需要使用若干的右侧刷子和若干的下侧刷子,吧初始图刷成目标图。

 

很显然,在玩的时候一些简单的多用眼睛观察,然后思考一下,就可以刷成,但是对于复杂的就比较难以完成。这个玩法比较有意思,于是在我们的网路应用编程的课大作业时,我把这个玩法推荐给了我们班的一个小组。

玩这个游戏的时候,我不禁思考,如何使用代码来计算出最短的刷子路径。我想着开发一个wcf的程序,放在抖音直播里面,让特定的直播观看者尝试或者让他和机器人来比较速度。

结果,抖音的Api不太容易获得。但是,我想这可以以其他形式来开直播,尽管我现在放弃了这个想法,这需要不少的精力,但是扑街的可能性还是比较大。

但是,在前天,我实现了n维的涂色游戏的游戏类,可以创建1*1,2*2,3*3,4*4,。。。20*20,等等的涂色游戏。当然如果你的显示屏够大也可以创建更多。

昨天我试着推演他的规律,尝试通过逆推解决,昨天思路没有打开,今天打开了思路,并解决了问题。

首先: 不是所有的目标都是可解的,对于2*2的,对角线就是不可解的,更高维度的也有不可解的。

在我的算法中,不可解的目标是,在寻找上一步的步骤时,找不到时判断是否已经解决,如果没有解决就是不可解的。

这个解决是思路的转变,是从Uncertain 到 Any的转变。

我先贴一下代码: 

枚举类: 

public enum Direction
    {
        Row,
        Col,
    }
public enum PaintType
    {
        One,
        Zero,
        Any,
    }

 数据结构和解决算法:

using System;
using System.Collections.Generic;
using System.Linq;

namespace Reverse
{
    public class Cube
    {
        private readonly PaintType[,] _miss;
        private readonly int _axis;

        public Cube(int axis)
        {
            _axis = axis;
            _miss = new PaintType[axis, axis];
            
        }

        public Cube(int axis, PaintType[,] miss)
        {
            _axis = axis;
            _miss = miss;
        }

        public void Init()
        {
            for (var i = 0; i < _axis; i++)
            {
                for (var j = 0; j < _axis; j++)
                {
                    _miss[i, j] = PaintType.Zero;
                }
            }
        }

        public static List<Result> Sort(List<Result> results)
        {
            var tmp = new List<Result>();
            var res = new List<Result>();
            if (results.Count < 1)
            {
                return null;
            }
            var direction = results.First().Direction;
            foreach (var ele in results)
            {
                if (ele.Direction == direction)
                {
                    tmp.Add(ele);
                }
                else
                {
                    res.AddRange(PartSort(tmp));
                    tmp.Clear();
                    direction = ele.Direction;
                    tmp.Add(ele);
                }
            }

            if (tmp.Count > 0)
            {
                res.AddRange(PartSort(tmp));
            }

            return res;
        }

        private static List<Result> PartSort(List<Result> results)
        {
            results.Sort((r1, r2) => r1.Num.CompareTo(r2.Num));
            return results;
        }

        public void RandomMix(int count)
        {
            var random = new Random((int)DateTimeOffset.Now.Ticks);
            var i = random.Next(_axis);
            var j = random.Next(_axis); 
            _miss[i, j] = PaintType.One;
            Console.WriteLine($"Item {i} {j}");
            var start = new List<(int, int)> { (i,j) };
            while (count > 0)
            {
                var list = new List<(int,int)>();
                var top = (i - 1, j);
                var bottom = (i + 1, j);
                var left = (i, j - 1);
                var right = (i, j + 1);
                if (Valid(top)&&NotResult(top))
                {
                    list.Add(top);
                }

                if (Valid(bottom)&&NotResult(bottom))
                {
                    list.Add(bottom);
                }

                if (Valid(left)&&NotResult(left))
                {
                    list.Add(left);
                }

                if (Valid(right)&&NotResult(right))
                {
                    list.Add(right);
                }

                if (list.Count != 0)
                {
                    var tuple = list[random.Next(list.Count)];
                    _miss[tuple.Item1, tuple.Item2] = PaintType.One;
                    count--;
                    i = tuple.Item1;
                    j = tuple.Item2;
                    start.Add((tuple.Item1,tuple.Item2));
                    Console.WriteLine($"Item {tuple.Item1} {tuple.Item2}");
                }
                else
                {
                    Console.WriteLine("Back");
                    var tuple = start.Last();
                    start.Remove(tuple);
                    i = tuple.Item1;
                    j = tuple.Item2;
                }
            }
        }

        private bool NotResult((int, int) tuple)
        {
            return _miss[tuple.Item1, tuple.Item2] != PaintType.One;
        }

        private bool Valid((int,int) tuple)
        {
            return Valid(tuple.Item1) && Valid(tuple.Item2);
        }

        private bool Valid(int index)
        {
            return index >= 0 && index < _axis;
        }
        public class Result
        {
            public Direction Direction
            {
                get;
                set;
            }

            public int Num
            {
                get;
                set;
            }

            public Result()
            {
            }

            public Result(Direction direction, int num)
            {
                Direction = direction;
                Num = num;
            }

            public override string ToString()
            {
                return $"{Direction} {Num}";
            }
        }

        private bool Success()
        {
            for (var i = 0; i < _axis; i++)
            {
                for (var j = 0; j < _axis; j++)
                {
                    if (_miss[i, j] != PaintType.Any) return false;
                }
            }

            return true;
        }

        private Result FindReverse()
        {
            // 查找行 one
            for (var i = 0; i < _axis; i++)//行
            {
                var row = true;// 需要保持true,有一个false就可省略
                var notAllAny = false;
                for (var j = 0; j < _axis; j++)// 列
                {
                    if (!row)
                    {
                        break;
                    }

                    row = _miss[i, j] != PaintType.Zero;// one or any
                    var tmp = _miss[i, j] != PaintType.Any;
                    if (tmp)
                    {
                        notAllAny = true;
                    }
                    
                }

                //Console.WriteLine($"row {row} notAllAny {notAllAny}");
                if (row&&notAllAny)
                {
                    return new Result(Direction.Row,i);
                }
            }
            // 查找列 zero
            for (var j = 0; j < _axis; j++)// 列
            {
                var col = true;
                var notAllAny = false;
                for (var i = 0; i < _axis; i++)// 行
                {
                    if (!col)
                    {
                        break;
                    }

                    col = _miss[i,j] != PaintType.One;
                    var tmp = _miss[i, j] != PaintType.Any;
                    if (tmp)
                    {
                        notAllAny = true;
                    }
                }

                //Console.WriteLine($"col {col} notAllAny {notAllAny}");
                if (col&&notAllAny)
                {
                    return new Result(Direction.Col, j);
                }
            }

            return null;
        }

        private void Reverse(Result result)
        {
            Reverse(result.Direction,result.Num);
        }

        private void Reverse(Direction direction, int num)
        {
            if (direction == Direction.Row)
            {
                for (var i = 0; i < _axis; i++)// 列
                {
                    _miss[num,i] = PaintType.Any;
                }
            }
            else
            {
                for (var i = 0; i < _axis; i++)// 行
                {
                    _miss[i,num] = PaintType.Any;
                }
            }
        }

        public void Print()
        {
            for (var i = 0; i < _axis; i++)
            {
                for (var j = 0; j < _axis; j++)
                {
                    switch (_miss[i, j])
                    {
                        case PaintType.Any:
                            Console.Write("A ");
                            break;
                        case PaintType.One:
                            Console.Write("1 ");
                            break;
                        case PaintType.Zero:
                            Console.Write("0 ");
                            break;

                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
                Console.WriteLine();
            }
        }

        public static List<Result> PureSolve(Cube cube)
        {
            if(cube==null) return null;
            var result = new List<Result>();
            var res = cube.FindReverse();
            while (res != null)
            {
                result.Add(res);
                cube.Reverse(res);
                //cube.Print();
                res = cube.FindReverse();
            }
            if (cube.Success())
            {
                Console.WriteLine("算法求解成功!!");
            }
            else
            {
                Console.WriteLine("算法求解失败!!初始状态");
                cube.Print();
            }

            if (result.Count <= 1)
            {
                return null;
            }
            while (result.Count>0&&result.Last().Direction==Direction.Col)
            {
                result.Remove(result.Last());
            }

            result.Reverse();
            result = Sort(result);
            return result;
        }
        public static void Solve(Cube cube)
        {
            if(cube==null) return;
            var res = cube.FindReverse();
            while (res != null)
            {
                Console.WriteLine("找到可倒退的一行或一列(行: 一行不全是Any,且全部不是Zero; 列" +
                                  "一行不全是Any,且全部不是One)。");
                cube.Reverse(res);
                Console.WriteLine($"上一步的操作是 {res.Direction} {res.Num}.上一步是: ");
                //cube.Print();
                res = cube.FindReverse();
            }

            if (cube.Success())
            {
                Console.WriteLine("算法求解成功!!");
            }
            else
            {
                Console.WriteLine("算法求解失败!!初始状态");
                cube.Print();
            }
            Console.WriteLine("算法完成!");
        }
    }
}

 调用示例:

测试用例1,4维游戏,随机通过4邻域填充算法进行填充6个色块(提高有解的概率),

获得解决方案列表,输出结果

            const int testNum = 1;
            var i = 0;
            while (i<testNum)
            {
                var cube = new Cube(4);
                cube.Init();
                cube.RandomMix(6);
                cube.Print();
                var ps = Cube.PureSolve(cube);
                if (ps != null)
                {
                    foreach (var result in ps)
                    {
                        Console.WriteLine(result);
                    }
                }
                i++;
            }

输出结果: 

下面再进行简单解释: 

对于一个待解目标,我们持续后推,直到所有的行和列都是Any。 推出所有的行和列都是Any说明有解,否则说明无解,无解的明显规律是,发现对于所有行和列,除了都是Any的,改行一定包含 Zero,列一定包含One。

如下面图示:  

(One代表目标色,在右侧,Zero代表初始色,在下方,Any代表任意)

下方是 Solve的方法:

public static List<Result> PureSolve(Cube cube)
        {
            if(cube==null) return null;
            var result = new List<Result>();
            var res = cube.FindReverse();
            while (res != null)
            {
                result.Add(res);
                cube.Reverse(res);
                //cube.Print();
                res = cube.FindReverse();
            }
            if (cube.Success())
            {
                Console.WriteLine("算法求解成功!!");
            }
            else
            {
                Console.WriteLine("算法求解失败!!初始状态");
                cube.Print();
            }

            if (result.Count <= 1)
            {
                return null;
            }
            while (result.Count>0&&result.Last().Direction==Direction.Col)
            {
                result.Remove(result.Last());
            }

            result.Reverse();
            result = Sort(result);
            return result;
        }

首先先判断,是否需要Solve,然后持续的进行倒推。直到不能进行倒推,然后判断是否解决。

最后是休整工作。

主要的流程是: 删除最终的几个 0(Col) 操作,如果有的话,这几个0操作的目的是把,包含0的一列转为全部为Any。 对于初始为全0的初始状态是不需要的。

然后进行部分排序。排序规则类似于把

A3 A2 A1 B4 A3 B5 B2 A0 A2

排序为

A1 A2 A3 B4 A3 B2 B5 A0 A2.

对于相邻的同属于行或列的几个操作进行按照序号从小到大排序,这样方便进行验证和观察。

下面是关键的寻找逆向操作的算法:

 private Result FindReverse()
        {
            // 查找行 one
            for (var i = 0; i < _axis; i++)//行
            {
                var row = true;// 需要保持true,有一个false就可省略
                var notAllAny = false;
                for (var j = 0; j < _axis; j++)// 列
                {
                    if (!row)
                    {
                        break;
                    }

                    row = _miss[i, j] != PaintType.Zero;// one or any
                    var tmp = _miss[i, j] != PaintType.Any;
                    if (tmp)
                    {
                        notAllAny = true;
                    }
                    
                }

                //Console.WriteLine($"row {row} notAllAny {notAllAny}");
                if (row&&notAllAny)
                {
                    return new Result(Direction.Row,i);
                }
            }
            // 查找列 zero
            for (var j = 0; j < _axis; j++)// 列
            {
                var col = true;
                var notAllAny = false;
                for (var i = 0; i < _axis; i++)// 行
                {
                    if (!col)
                    {
                        break;
                    }

                    col = _miss[i,j] != PaintType.One;
                    var tmp = _miss[i, j] != PaintType.Any;
                    if (tmp)
                    {
                        notAllAny = true;
                    }
                }

                //Console.WriteLine($"col {col} notAllAny {notAllAny}");
                if (col&&notAllAny)
                {
                    return new Result(Direction.Col, j);
                }
            }

            return null;
        }

思路就是查找那一步可能是 上一步操作,需要满足,这一行只能包含 One或Any,但是也不能全是Any,因为全是Any的话就没有必要进行这一步操作。

这一列只能包含Zero或Any,但是也不能全是Any,理由同上。

解决方案的Painter2项目是可创建n*n涂色游戏的项目,可以算出的解决思路进行验证,不过需要修改代码,来适应更多的需求。

Reverse就是解决一个随机的涂色游戏的项目,不过对于特定的涂色目标,你需要手动编码,然后计算解决思路。

 

示例:

6维11个随机色块:

 

解决失败(无法逆推)

8维16个随机色块

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫叶kx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值