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

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

相关文章

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

vite搭建vue3项目的搭建步骤

《vite搭建vue3项目的搭建步骤》本文主要介绍了vite搭建vue3项目的搭建步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1.确保Nodejs环境2.使用vite-cli工具3.进入项目安装依赖1.确保Nodejs环境

Nginx搭建前端本地预览环境的完整步骤教学

《Nginx搭建前端本地预览环境的完整步骤教学》这篇文章主要为大家详细介绍了Nginx搭建前端本地预览环境的完整步骤教学,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录项目目录结构核心配置文件:nginx.conf脚本化操作:nginx.shnpm 脚本集成总结:对前端的意义很多

一文详解Python如何开发游戏

《一文详解Python如何开发游戏》Python是一种非常流行的编程语言,也可以用来开发游戏模组,:本文主要介绍Python如何开发游戏的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录一、python简介二、Python 开发 2D 游戏的优劣势优势缺点三、Python 开发 3D

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

Redis中的有序集合zset从使用到原理分析

《Redis中的有序集合zset从使用到原理分析》Redis有序集合(zset)是字符串与分值的有序映射,通过跳跃表和哈希表结合实现高效有序性管理,适用于排行榜、延迟队列等场景,其时间复杂度低,内存占... 目录开篇:排行榜背后的秘密一、zset的基本使用1.1 常用命令1.2 Java客户端示例二、zse

Redis中的AOF原理及分析

《Redis中的AOF原理及分析》Redis的AOF通过记录所有写操作命令实现持久化,支持always/everysec/no三种同步策略,重写机制优化文件体积,与RDB结合可平衡数据安全与恢复效率... 目录开篇:从日记本到AOF一、AOF的基本执行流程1. 命令执行与记录2. AOF重写机制二、AOF的

java中ssh2执行多条命令的四种方法

《java中ssh2执行多条命令的四种方法》本文主要介绍了java中ssh2执行多条命令的四种方法,包括分号分隔、管道分隔、EOF块、脚本调用,可确保环境配置生效,提升操作效率,具有一定的参考价值,感... 目录1 使用分号隔开2 使用管道符号隔开3 使用写EOF的方式4 使用脚本的方式大家平时有没有遇到自

使用EasyPoi快速导出Word文档功能的实现步骤

《使用EasyPoi快速导出Word文档功能的实现步骤》EasyPoi是一个基于ApachePOI的开源Java工具库,旨在简化Excel和Word文档的操作,本文将详细介绍如何使用EasyPoi快速... 目录一、准备工作1、引入依赖二、准备好一个word模版文件三、编写导出方法的工具类四、在Export