jjjzw 发表于 2023-9-22 15:50

python 星穹铁道引航罗盘解算

本帖最后由 jjjzw 于 2023-9-22 15:51 编辑

## 介绍

引航罗盘是游戏《星穹铁道》中的一个解密小游戏,由外圈、中圈、内圈三层同心圆组成,每个圆环分别具有:

+ 指针一枚
+ **初始角度**
+ **单次旋转角度**,每亮一个点单次旋转多60度,共60、120、180、240四档
+ **旋转方向**,分为顺时针、逆时针

罗盘还具有三个**联动规则**,每个规则代表按一次,会有哪一层或哪两层圈同时转动(**例如**:规则1:内圈和外圈会同时转动;规则2:内圈和中圈会同时转动;规则3:外圈会单独转动)

**最终目标**:三个圈的指针全部指向左侧

罗盘示意图:

!(https://z1.ax1x.com/2023/09/22/pPoJvut.png)

## 解题

设坐标系以罗盘中心为原点,假设罗盘按照规则1转动了x次,规则2转动了y次,规则3转动了z次后,外圈恰好第i次到达左侧目标角度,中圈恰好第j次到达目标,内圈恰好第k次到达目标

以角度为等式,可以列出一个3方程的6元1次方程组,显然是解不出来的:

外圈:`a1*x+a2*y+a3*z=b1*i`

中圈:`a4*x+a5*y+a6*z=b2*j`

内圈:`a7*x+a8*y+a9*z=b3*k`

对于计算机来说,可以用三重循环来模拟所有的i, j, k的情况(限定遍历的次数),将6元变成3元,从而能够使用克拉默法则进行求解


## 代码实现

无numpy,原生python实现

```python
import copy

class Game:
    def __init__(self) -> None:
      self.direction = [-1, 1]# -1逆时针,1顺时针
      self.rotate = # 共4档旋转
      self.init = # 初始状态共6档
      self.disk = [
            {
                "name": "内圈",
                "direction": self.direction,
                "rotate": self.rotate,
                "init": self.init,
                "angle": 0
            },
            {
                "name": "中圈",
                "direction": self.direction,
                "rotate": self.rotate,
                "init": self.init,
                "angle": 0
            },
            {
                "name": "外圈",
                "direction": self.direction,
                "rotate": self.rotate,
                "init": self.init,
                "angle": 0
            },
      ]
      self.coordination = [
            [],# 两圈联动索引,写为列表格式,内圈0,中圈1,外圈2,无联动设置另一位为-1
            [],
            []
      ]
      print("==输入初始罗盘信息:==")
      print("==内圈:==")
      self.disk["direction"] = self.direction
      self.disk["rotate"] = self.rotate
      self.disk["init"] = self.init
      print("==中圈:==")
      self.disk["direction"] = self.direction
      self.disk["rotate"] = self.rotate
      self.disk["init"] = self.init
      print("==外圈:==")
      self.disk["direction"] = self.direction
      self.disk["rotate"] = self.rotate
      self.disk["init"] = self.init
      print("==联动规则:==")
      print("== 内/中/外 分别为 0/1/2 ; *单个旋转另一位写-1* ==")
      self.coordination =
      self.coordination =
      self.coordination =
      print("=====================================================================")
      print(f'==内圈 -> 初始:{self.disk["init"]} -> 旋转方向:{self.get_direction(self.disk["direction"])} -> 步长:{self.disk["rotate"]}')
      print(f'==中圈 -> 初始:{self.disk["init"]} -> 旋转方向:{self.get_direction(self.disk["direction"])} -> 步长:{self.disk["rotate"]}')
      print(f'==外圈 -> 初始:{self.disk["init"]} -> 旋转方向:{self.get_direction(self.disk["direction"])} -> 步长:{self.disk["rotate"]}')
      print(f'==联动规则 -> 1.{self.get_rules(self.coordination)} -> 2.{self.get_rules(self.coordination)} -> 3.{self.get_rules(self.coordination)}')
      print("=====================================================================")
   
    @staticmethod
    def get_direction(num: int) -> str:
      return "逆时针" if num == -1 else "顺时针"
   
    @staticmethod
    def get_rules(rule: list) -> str:
      def get_str(num: int) -> str:
            if num == 0:
                return "内圈"
            elif num == 1:
                return "中圈"
            else:
                return "外圈"
      return f'{get_str(rule)}<+>{get_str(rule)}'

    # 答案计算主程序
    def cal(self) -> bool:
      # 最终目标:三圈状态均为180
      # x, y, z 三个变量,分别对应三个联动规则所需次数

      # 内圈旋转次数:exist(self.coordination, 0) * x + exist(self.coordination, 0) * y + exist(self.coordination, 0) * z
      # 中圈旋转次数:exist(self.coordination, 1) * x + exist(self.coordination, 1) * y + exist(self.coordination, 1) * z
      # 外圈旋转次数:exist(self.coordination, 2) * x + exist(self.coordination, 2) * y + exist(self.coordination, 2) * z
      
      # 内圈最终方程:
      # exist(self.coordination, 0) * x + exist(self.coordination, 0) * y + exist(self.coordination, 0) * z = count(self.disk["init"], self.disk["direction"], self.disk["rotate"])
      # 中圈最终方程:
      # exist(self.coordination, 1) * x + exist(self.coordination, 1) * y + exist(self.coordination, 1) * z = count(self.disk["init"], self.disk["direction"], self.disk["rotate"])
      # 外圈最终方程:
      # exist(self.coordination, 2) * x + exist(self.coordination, 2) * y + exist(self.coordination, 2) * z = count(self.disk["init"], self.disk["direction"], self.disk["rotate"])
      
      # 用克拉默法则计算:
      # 左侧系数矩阵
      raw = [, 0), self.exist(self.coordination, 0), self.exist(self.coordination, 0)],
             , 1), self.exist(self.coordination, 1), self.exist(self.coordination, 1)],
             , 2), self.exist(self.coordination, 2), self.exist(self.coordination, 2)]]
      # 右侧系数矩阵
      rep = ["init"], self.disk["direction"], self.disk["rotate"]),
               self.count(self.disk["init"], self.disk["direction"], self.disk["rotate"]),
               self.count(self.disk["init"], self.disk["direction"], self.disk["rotate"])]
      # 原行列式值
      D = self.calculate(raw)
      # 可能不止一次到达目的地,通过遍历的方式寻找答案
      # 这是到达180度后继续旋转再次到达180度所需的次数
      chg_1 = self.count(180 + self.disk["rotate"], -1, self.disk["rotate"]) + 1
      chg_2 = self.count(180 + self.disk["rotate"], -1, self.disk["rotate"]) + 1
      chg_3 = self.count(180 + self.disk["rotate"], -1, self.disk["rotate"]) + 1
      # 遍历得到新的系数矩阵
      num = 5# 遍历层数
      for i in range(num):
            for j in range(num):
                for k in range(num):
                  rep_temp = rep.copy()
                  rep_temp = rep + chg_1 * i
                  rep_temp = rep + chg_2 * j
                  rep_temp = rep + chg_3 * k
                  x = self.calculate(raw, rep_temp, 0) / D# 替换后的行列式值 / 原行列式值
                  y = self.calculate(raw, rep_temp, 1) / D
                  z = self.calculate(raw, rep_temp, 2) / D
                  if int(x) == x and int(y) == y and int(z) == z and x >= 0 and y >= 0 and z >= 0:
                        print("==答案为:")
                        print(f"规则1按动{int(x)}次")
                        print(f"规则2按动{int(y)}次")
                        print(f"规则3按动{int(z)}次")
                        return True# 注释这一句查看更多答案
      return False

    @staticmethod
    def exist(index: list, num: int) -> int:
      return 1 if num in index else 0
   
    # 计算到达180所需的次数
    @staticmethod
    def count(angle: int, direction: int, rotate: int) -> float:
      b = 0
      while True:
            if angle % 180 == 0 and angle % 360 != 0:
                return b
            else:
                angle = angle - direction * rotate
                b += 1
   
    # 行列式计算,输入矩阵matrix如:
    # [,
    #,
    #]
    # 输入系数矩阵如:
    #
    # index为替换x/y/z系数索引
    @staticmethod
    def calculate(matrix_temp: list, replace: list = [], index: int = -1) -> int:
      matrix = copy.deepcopy(matrix_temp)
      if index != -1:
            matrix, matrix, matrix = replace, replace, replace
      return \
      matrix * matrix * matrix + \
      matrix * matrix * matrix + \
      matrix * matrix * matrix - \
      matrix * matrix * matrix - \
      matrix * matrix * matrix - \
      matrix * matrix * matrix


game = Game()
game.cal()
```



## 运行结果

!(https://z1.ax1x.com/2023/09/22/pPoJXjI.md.png)

规则2按动一次即可



## 仓库

https://github.com/Icingworld/CompassCal

jjjzw 发表于 2023-9-22 23:44

FDE9 发表于 2023-9-22 21:45
之前用winform也写了一个,大半时间在处理ui

有UI看起来清楚多了

jjjzw 发表于 2023-9-22 16:09

雨墨 发表于 2023-9-22 16:07
点赞!不过这玩意不是瞎按几下就好了吗

目的不是解密{:301_1001:}目的是锻炼代码水平

FDE9 发表于 2023-9-22 21:45

https://picshack.net/ib/7FdJwhxR9t.png
之前用winform也写了一个,大半时间在处理ui

雨墨 发表于 2023-9-22 16:07

点赞!不过这玩意不是瞎按几下就好了吗{:1_926:}

exchange110 发表于 2023-9-22 20:02

牛啊,感谢分享

艾莉希雅 发表于 2023-9-23 02:46

等一个人写个html版本的丢github.io好吧

78zhanghao87 发表于 2023-9-23 08:51

有点意思

yaphoo 发表于 2023-9-23 09:19

牛人,宇宙都能解密了

温柔小明 发表于 2023-9-23 10:25

原来,你也玩():$qqq
页: [1] 2
查看完整版本: python 星穹铁道引航罗盘解算