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

2023-11-21 06:50

本文主要是介绍咸鱼游戏涂色游戏的逻辑分析与目标块执行步骤推演,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

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

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(){// 查找行 onefor (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 anyvar 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);}}// 查找列 zerofor (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(){// 查找行 onefor (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 anyvar 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);}}// 查找列 zerofor (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个随机色块

 

 

这篇关于咸鱼游戏涂色游戏的逻辑分析与目标块执行步骤推演的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/400589

相关文章

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

更改docker默认数据目录的方法步骤

《更改docker默认数据目录的方法步骤》本文主要介绍了更改docker默认数据目录的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1.查看docker是否存在并停止该服务2.挂载镜像并安装rsync便于备份3.取消挂载备份和迁

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、

mysql重置root密码的完整步骤(适用于5.7和8.0)

《mysql重置root密码的完整步骤(适用于5.7和8.0)》:本文主要介绍mysql重置root密码的完整步骤,文中描述了如何停止MySQL服务、以管理员身份打开命令行、替换配置文件路径、修改... 目录第一步:先停止mysql服务,一定要停止!方式一:通过命令行关闭mysql服务方式二:通过服务项关闭

Servlet中配置和使用过滤器的步骤记录

《Servlet中配置和使用过滤器的步骤记录》:本文主要介绍在Servlet中配置和使用过滤器的方法,包括创建过滤器类、配置过滤器以及在Web应用中使用过滤器等步骤,文中通过代码介绍的非常详细,需... 目录创建过滤器类配置过滤器使用过滤器总结在Servlet中配置和使用过滤器主要包括创建过滤器类、配置过滤

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

Spring Boot 中整合 MyBatis-Plus详细步骤(最新推荐)

《SpringBoot中整合MyBatis-Plus详细步骤(最新推荐)》本文详细介绍了如何在SpringBoot项目中整合MyBatis-Plus,包括整合步骤、基本CRUD操作、分页查询、批... 目录一、整合步骤1. 创建 Spring Boot 项目2. 配置项目依赖3. 配置数据源4. 创建实体类

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结