Algorithm

Classical Cryptography – 古典密碼學

古典密碼學是密碼學中的一個類型,其加密方式大部分都是使用替換式加密或移項式加密。
由於現代已經很少使用,故稱為古典密碼學。
加密有助於防止遊戲被破解的風險,但任何形式的加密都只會增加破解的難易度,並不能保證被破解的風險為零。
其中古典密碼學更是只要取得密鑰就能夠輕易破解的程度。

本篇文章會粗略的介紹密碼學中的基本概念,並分享古典密碼學中的幾種加密演算法。

關鍵字快搜:古典密碼學凱撒密碼仿射密碼簡易替換密碼乘法逆元

Continue reading “Classical Cryptography – 古典密碼學”

Algorithm · Unity

A* Algorithm Line of Sight – 可視點優化

偶然和同好聊到 A* 時
得知了一種優化方法
可以得到更高效能的運算結果
 
先來看一張沒有用這種方法優化所算出來的路徑

 
可以看出雖然原來的方法所算出來的路徑的確正確
但卻又不能算是最佳路徑
原因在於一定要經過我們所定義出可行走的點
但如果將算出來的路徑再重製一次後會不會得到更好的結果呢?
 
可視點優化(Line of sight):
原理相當簡單,我們人在行走時如果遇到上面這種狀況
一定會以我們 “看” 到的最佳點前進
以上圖為例
我們在左下角的起點要向右上角的終點移動時
一定會直接以紅框為下一個目標點
因為我們可以 “看” 見紅框點
 
找到專案中的 AStar.cs
並新增 Function 如下

private static ArrayList LineOfSight( ArrayList path )
{
	ArrayList list = new ArrayList();
	list.Add( (Node)path[0] );

	Node startNode = (Node)path[0];
	Node nextNode;

	int checkIndex = 1;

	while( checkIndex < path.Count )
	{
		nextNode = ( (Node)path[checkIndex] );

		if( !Physics.Linecast( startNode.position, nextNode.position ) )
		{
			checkIndex++;
		}
		else
		{
			if( checkIndex == path.Count - 1 )
			{
				list.Add( (Node)path[checkIndex - 1] );
				list.Add( (Node)path[checkIndex] );
			}
			else
			{
				list.Add( (Node)path[checkIndex - 1] );
				startNode = (Node)path[checkIndex - 1];
				nextNode = (Node)path[checkIndex];
			}

			checkIndex++;
		}
	}

	return list;
}

 
接著找到原來的 CalculatePath 方法
並修改成

private static ArrayList CalculatePath(Node node)
{		
	ArrayList list = new ArrayList();
		
	while (node != null)
	{			
		list.Add( node );
		node = node.parent;			
	}
		
	list.Reverse();
	return LineOfSight( list );
}

 
這樣一來就可算出 Line of sight 優化過後的路徑

 
和沒有優化的路徑相比
是不是又更簡單明瞭
附上一張多障礙物模擬

Algorithm · Unity

A* Algorithm Eight Ways – 斜向方向優化

到目前為止
A* 的基本實作都已經結束了
但我們可以做一些簡單的優化來提高 A* 的運算結果
 
在上一篇中可以發現
雖然計算出來的路徑的確是最短路徑
但在移動方向上卻只限制於上下左右四個方向
 
考慮到優化演算的結果
我們在這邊加入斜向方向判定
 
首先一樣找到 NodeManager.cs 腳本
找到 GetNeighbours 方法

//Get the current node's neighbors
public void GetNeighbours( Node node, ArrayList neighbors )
{		
	Vector3 neighborPos = node.position;
	int row = GetNodeRow( neighborPos );
	int column = GetNodeColumn( neighborPos );
		
	//Bottom
	int leftNodeRow = row - 1;
	int leftNodeColumn = column;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Top
	leftNodeRow = row + 1;
	leftNodeColumn = column;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Right
	leftNodeRow = row;
	leftNodeColumn = column + 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Left
	leftNodeRow = row;
	leftNodeColumn = column - 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);			
}

在原本的方法中
我們只有上下左右四個方向
所以在這邊將斜向方向也一併加入運算

//Get the current node's neighbors
public void GetNeighbours( Node node, ArrayList neighbors )
{		
	Vector3 neighborPos = node.position;
	int row = GetNodeRow( neighborPos );
	int column = GetNodeColumn( neighborPos );
		
	//Bottom
	int leftNodeRow = row - 1;
	int leftNodeColumn = column;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Top
	leftNodeRow = row + 1;
	leftNodeColumn = column;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Right
	leftNodeRow = row;
	leftNodeColumn = column + 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Left
	leftNodeRow = row;
	leftNodeColumn = column - 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);

	//Bottom Right
	leftNodeRow = row - 1;
	leftNodeColumn = column + 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Bottom Left
	leftNodeRow = row - 1;
	leftNodeColumn = column - 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Top Right
	leftNodeRow = row + 1;
	leftNodeColumn = column + 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);
	//Top Left
	leftNodeRow = row + 1;
	leftNodeColumn = column - 1;
	AssignNeighbour(leftNodeRow, leftNodeColumn, neighbors);			
}

 
再次執行 Unity
結果如下

 
加入斜向移動後果然移動距離又更短了
最後附上多個障礙物的模擬

Algorithm · Unity

A* Algorithm Obstacle Detection – 障礙物判定

在上一篇中我們已經完成了 A* 尋路的功能了
但是缺少了障礙物判定的方法
如果有認真看每一篇教學的讀者會發現
在一開始定義 Node 時,有一個 isObstacle 參數用來判斷該 Node 是不是障礙物
在這一篇我們就是要將它實現出來
 
找到 NodeManager.cs 腳本
找到其中的 CreateNode 方法

void CreateNodes()
{
	nodes = new Node[numOfColumns, numOfRows];
		
	for (int col = 0; col < numOfColumns; col++)
	{    
		for (int row = 0; row < numOfRows; row++)
		{
			Vector3 cellPos = GetNodePosition(col, row);
			Node node = new Node(cellPos);
			nodes[col, row] = node;
		}            
	}        
}

將它改寫成

void CreateNodes()
{
	nodes = new Node[numOfColumns, numOfRows];
		
	for (int col = 0; col < numOfColumns; col++)
	{	
		for (int row = 0; row < numOfRows; row++)
		{
			Vector3 cellPos = GetNodePosition(col, row);
			Node node = new Node(cellPos);
			nodes[col, row] = node;

			//Obstacle Update
			cellPos -= new Vector3( 0, 100, 0 );
			RaycastHit hit;
			Ray ray = new Ray( cellPos, new Vector3( 0, 1, 0 ) );
				
			if( Physics.Raycast( ray , out hit, 1000 ) )
			{
				if( hit.transform.tag == "Obstacle" )
					nodes[col, row].MarkAsObstacle();
			}				
		}			
	}		
}

 
接著在場景中加入任一包含 Collider 的物件
並將其 Tag 設定為 Obstacle

 
執行 Unity
得到以下成果

Algorithm · Unity

A* Algorithm Achievement – 演算法實作成果

終於到了最後一步
可以看到辛苦理解後的實現成果了
 
新增一個 C# 腳本命名為 TestAStar.cs

using UnityEngine;
using System.Collections;

public class TestAStar : MonoBehaviour
{
    //The start and end gameobject
    public GameObject startCube, endCube;
    
    private Transform startTransform, endTransform;

    private Node startNode;
    private Node endNode;

    public ArrayList pathArray;
    private float elapsedTime = 0.0f;

    //Interval time between pathfinding
    public float intervalTime = 1.0f;
    
    void Start ()
    {
        startTransform = startCube.transform;
        endTransform = endCube.transform;

        pathArray = new ArrayList();
        FindPath();
    }
    
    void Update ()
    {        
        elapsedTime += Time.deltaTime;
        
        if (elapsedTime >= intervalTime)
        {
            elapsedTime = 0.0f;
            FindPath();
        }        
    }
    
    void FindPath()
    {
        //Get start node with position
        startNode = new Node( NodeManager.instance.GetNodeCenter( startTransform.position ) );
        //Get end node with position
        endNode = new Node( NodeManager.instance.GetNodeCenter( endTransform.position ) );

        pathArray = AStar.FindPath(startNode, endNode);
    }

    //Display the a-star path finding line
    void OnDrawGizmos()
    {
        if (pathArray == null)
            return;

        if (pathArray.Count > 0)
        {            
            for( int cnt = 0; cnt < pathArray.Count; cnt++ )
            {                
                if( cnt <= pathArray.Count - 2 )
                    Debug.DrawLine( ( (Node)pathArray[cnt] ).position, ( (Node)pathArray[cnt + 1] ).position, Color.green);                
            }
        }
    }    
}

 
這個腳本主要是用來呼叫前面幾篇寫好的方法
直接來看怎麼使用 TestAStar.cs
 
在場景中新增一空物件取名為 AStar
並新增兩個 Cube 分別取名為 Start 以及 End 當作起始點與終點
並將兩個 Cube 分別拉近 AStar 欄位中
執行開始則會看到實作結果

 
到此 A* 演算法實作已經完成了

Algorithm · Unity

A* Algorithm Implement – 虛擬程式碼實例化

A* 虛擬程式碼實例化

看到這步的人可能還是會覺得一頭霧水
怎麼前面已經三篇了可是都還沒有看到 A* 的實作成果
但這篇看完後會突然發現概念都連接起來了 ( 應該吧…..

首先希望各位回想一下最一開始的 A* 演算法虛擬碼
這篇將會以那個虛擬碼來處理 A* 演算法運算
A* Algorithm Introduction – 演算法簡介

新增一個C#腳本命名為AStar.cs

using UnityEngine;    
using System.Collections;    
    
public class AStar    
{        
    public static NodeSort closedList, openList;    
        
    private static float HeuristicEstimateCost(Node curNode, Node goalNode)    
    {    
        Vector3 vectorCost = goalNode.position - curNode.position;    
            
        return vectorCost.magnitude;        
    }    
        
    public static ArrayList FindPath( Node start, Node goal )  
    {         
        openList = new NodeSort();  
        openList.Push(start);  
        start.G_Cost = 0.0f;  
        start.H_Cost = HeuristicEstimateCost(start, goal);  
          
        closedList = new NodeSort();  
        Node node = null;  
          
        while (openList.Length != 0)  
        {     
            node = openList.First();  
              
            //Push the current node to the closed list  
            closedList.Push(node);  
            //and remove it from openList  
            openList.Remove(node);  
              
            //Check if the current node is the goal node  
            if ( node.position == goal.position )  
            {  
                return CalculatePath(node);  
            }  
              
            //Create an ArrayList to store the neighboring nodes  
            ArrayList neighbours = new ArrayList();  
            NodeManager.instance.GetNeighbours(node, neighbours);  
              
            Node neighbourNode;  
              
            for ( int i = 0; i < neighbours.Count; i++ )  
            {                 
                neighbourNode = (Node)neighbours[i];  
                  
                if ( !closedList.Contains( neighbourNode ) )  
                {                     
                    float cost;  
                    float totalCost;  
                      
                    float neighbourNodeEstCost;  
                      
                    if ( !openList.Contains( neighbourNode ) )  
                    {                         
                        //G  
                        cost = HeuristicEstimateCost( node, neighbourNode );  
                        totalCost = node.G_Cost + cost;  
                          
                        //H  
                        neighbourNodeEstCost = HeuristicEstimateCost( neighbourNode, goal );  
                          
                        neighbourNode.G_Cost = totalCost;  
                        neighbourNode.parent = node;  
                        neighbourNode.H_Cost = neighbourNodeEstCost;  
                          
                        openList.Push(neighbourNode);  
                          
                    }  
                    else  
                    {     
                        cost = HeuristicEstimateCost( node, neighbourNode );  
                        totalCost = node.G_Cost + cost;  
                          
                        if( neighbourNode.G_Cost > totalCost )  
                        {  
                              
                            neighbourNode.G_Cost = totalCost;  
                            neighbourNode.parent = node;                              
                        }                         
                    }                     
                }                 
            }             
        }  
          
        if ( node.position != goal.position )  
        {             
            Debug.LogError("Goal Not Found");  
            return null;              
        }  
          
        return CalculatePath(node);       
    }  
        
    private static ArrayList CalculatePath(Node node)    
    {            
        ArrayList list = new ArrayList();    
            
        while (node != null)    
        {                
            list.Add( node );    
            node = node.parent;                
        }    
            
        list.Reverse();    
        return list;            
    }    
}    

6
一開始先分別定義 openList 以及 closeList

8~13 HeuristicEstimateCost
用來計算兩個節點間的移動距離

15~96 FindPath
這段是 A* 演算法實作中最複雜的一部分
可以將虛擬碼與實作互相對照

在演算初期先將 openList 及 closeList 初始化
並將 StartNode 的 G_Cost 及 H_Cost 也初始化
( 小提醒:G_Cost為實際移動距離、H_Cost為到終點的預估距離 )

接著將 StartNode 加入 openList 裡面
開始進行 A* 演算法中的核心計算
接下來都是將虛擬碼轉換為實際code的結果

需要注意的是取得 neighbors 用到了上一篇中的 GetNeighbours 方法

下一步則是判斷 neighbors 是否在 openList 裡
如果沒有再 openList 裡代表該節點沒有被運算過
所以需要將其 cost 算出來後加入 openList 裡

如果 neighbors 已經在 openList 裡
代表該節點已經運算過
但若這次運算的 cost 小於之前計算過的結果
則將它更新

如此一來,當程式運行到找到終點時
則會呼叫 CalculatePath 方法

98~110 CalculatePath
獲得終點 Node 後
即可逐一的從終點 Node 回推回起點 Node
最後將該陣列反轉後
就可以取得 A* 移動路徑