Pixi.js is a 2D webGL renderer with a seamless canvas fallback that enables it to work across all modern browsers both desktop and mobile. Helping Goodboy and hopefully you achieve the big fat goal of “Build once, play everywhere
Pixi.js是一個基於Javascript的2D繪圖引擎,主要是做網頁的2D遊戲,可兼容多種瀏覽器(所支援的瀏覽器版本請參見:What browsers are supported?),但即便是不支援WebGL的瀏覽器,PixiJS也可以正常運行,只是效能會變得差很多,因為沒辦法享受到GPU加速的優勢。
Cocos2d 是一個開源的2D遊戲框架,在各種不同的程式語言都有不同的實作。一開始在iOS裡支援度最高的遊戲引擎就是Cocos2d,因此有許多iOS的小遊戲都是使用Cocos2D開發的。後來也有了Java版本,可運行於Android平台。Cocos2d-X是基於Cocos2d for iPhone並使用C++語言實現的多平台版本,後來也有了JavaScript版本的Cocos2D。因此現在的cocos2D也可以支援許多不同的平台,但由於其IDE介面不像Unity般強大,所以普及性還是不如Unity。
家用遊戲機1983年7月15日在日本以「Family Computer」(FC)為名推出,1985年起在歐美以「Nintendo Entertainment System」(簡稱「NES」)為名發行。紅白機是當時最暢銷的遊戲機,全球累計銷量超過了6100萬台。紅白機出現對電子遊戲產生了十分深遠的影響,讓美國電子遊戲界從1983年的崩潰中恢復過來,1985年發售的超級瑪琍,奠定了FC的初步成功。
但此一商業模式由於機率無法透明,且有賭博的成份,因此歐美中各國紛紛以不同的法律來約束或是禁止,APPLE STORE與GOOGLE STORE也要求軟體供應商必須公布抽獎箱的抽出機率,但抽獎箱無保底機制還是讓許多玩家所詬病,因此有些遊戲有幾連抽(通常需30~50)必有SSR卡(Special Super Rare)的保底機制來吸引玩家抽卡,有些玩家因為無保底機制,花了幾百抽的錢但卻連一張SSR卡都沒有,怒刪遊戲、要求退費或是憤而提告的舉動也屢見不鮮
//取得第一條搜尋到的已知存在路徑
public getFirstExistPath():Path{
var searchedValue = [];//用以紀錄已搜尋過符號
//由最左上開始做判斷
for (var i =0;i<this.board.length;i++){
for (var j = 0; j<this.board[i].length;j++){
let value = this.board[i][j];
//判斷盤面上現在是有符號的(null代表沒有符號)
//並且這個符號之前還沒有被搜尋過
if(value!= null && searchedValue.indexOf(value) == -1){
searchedValue.push(value);
let positionsArr = this.getPositionByValue(value);//取得盤面上所有這個符號的位置
let permutationsArr = this.getPairNumPermutations(positionsArr.length);//取得可能存在的連線的點的排列組合
//getPairNumPermutations回傳的格式是[[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]],裡面數字為index
//嘗試每一個可能的排列組合
for(var k = 0;k<permutationsArr.length;k++){
let v = permutationsArr[k];
let path = new Path(positionsArr[v[0]], positionsArr[v[1]],this);
if(path.canLinkInLine()){
return path;
}
}
}
}
}
return null;
}[/code]
<code>getPositionByValue</code>這個函數主要是要取得盤面上所有這個符號的位置,方法內容如下:
public getPositionByValue(value:number):Array<point>{
let arr = new Array<point>();
for (var i =0;i<this.board.length;i++){
for (var j = 0; j<this.board[i].length;j++){
if (this.board[i][j] == value){
arr.push(new Point(i, j));
}
}
}
return arr;
}[/code]
<code>getPairNumPermutations</code>這個函數則是在列出,相同圖案任選2個所有可能的排列組合
如果傳入的是4,也就是C4取2=6,一共會有六種排序的可能性。
查一下維基百科,裡面有相關的解釋:<a href="https://zh.wikipedia.org/wiki/%E7%B5%84%E5%90%88" rel="noopener noreferrer" target="_blank">排列組合</a>
因為兩點間的路徑不會受到先後順序的影響而影響是否能連線,並且相同的點不可連線,這邊我一律讓第一個數字(index)小於第二個數字(index),因此用這個判斷式<code>i != j && i <= j</code>來排除重覆的組合。
用下面的函數,若是同樣的符號有4個,則輸入值為4,輸出值會是[[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]]
private pairNumPermutations = {};
/**
* 取得輸入的index中,2個2個一組的所有可能排列組合
* 回傳的格式是[[0,1],[0,2],[0,3],[1,2],[1,3],[2,3]]
*/
public getPairNumPermutations(num:number){
if(this.pairNumPermutations[num] != null){
return this.pairNumPermutations[num];
}
let data = [];
for(var i = 0; i <num;i++){
for(var j = 0; j <num;j++){
if(i != j && i <= j){
data.push([i,j]);
}
}
}
this.pairNumPermutations[num] = data;
return data;
}[/code]
<h3>可行路徑提示</h3>
上面做完了電腦自動搜尋路徑的功能,可以使用在兩個地方,第一個地方是,當盤面沒有任何路徑可以走時,要自動重整盤面。
這部份的程式碼如下:
//判斷還有沒有路走
if(board.gameRoundEnd()){
alert("恭喜完成遊戲!");
board = new Board();
vm.boardContent = board.board;
}else if(board.getFirstExistPath() == null){
vm.reloadTimes++;
board.rearrangeBoard();//重整盤面
}
而重整盤面的程式碼如下:
public rearrangeBoard(){
let values = this.getAllValueInBoard().sort((a, b) => (Math.random() > .5) ? 1 : 0);
for (var i =0;igetAllValueInBoard()所做的事情。
然後再隨機打亂陣列排序,再依序填入盤面上所有有圖案的格子內,就可以達到重整盤面但是不影響到空格的位置。
今日成果
連連看遊戲邏輯至此已大致完成囉!下一篇開始我們會實際開始製作一款實際上具有畫面、音效、特效等真正的網頁連連看遊戲
LIVE DEMO: 今日成果展示
完整程式碼可由此下載:ironman20181022
var app = angular.module('LianLianKan', []);
app.controller('myCtrl', function ($scope) {
$scope.select1 = new Point(-1, -1);
$scope.select2 = new Point(-1, -1);
$scope.selected = false;
let msgArra = [];
$scope.message = msgArra;
let board = new Board();
$scope.boardContent = board.board;
$scope.click = function (x: number, y: number) {
if ($scope.selected) {
$scope.select2 = new Point(x, y);
if (board.hasSameValue($scope.select1, $scope.select2)) {
if (! ($scope.select1.x == x && $scope.select1.y == y) ) {//確認所選的兩個點不一樣
let path = new Path($scope.select1, $scope.select2, board);
if(path.canLinkInLine()){
board.clearPoint($scope.select1);
board.clearPoint($scope.select2);
msgArra.push(path);
}
}
}
$scope.selected = false;
} else {
$scope.select1 = new Point(x, y);
$scope.selected = true;
}
};
});
判斷所選圖案是否相同
在上面的程式碼中,可以看到我們用board.hasSameValue($scope.select1, $scope.select2)來判斷所選的圖是是否相同。我們可以Board的類別增加public hasSameValue(point1: Point, point2: Point): boolean如下:
public hasSameValue(point1: Point, point2: Point): boolean {
return this.board[point1.x][point1.y] == this.board[point2.x][point2.y];
}
連線邏輯撰寫
新建一個類別Path內容如下
class Path {
public point1: Point;
public point2: Point;
readonly board: Board;
public path_Detail:Array;
constructor(point1: Point, point2: Point, board: Board) {
this.point1 = point1;
this.point2 = point2;
this.board = board;
}
public canLinkInLine(): boolean {
console.log(“board”,this.board);
//從上面消
//兩個點都往上找最遠能到達的距離
let point1UP = this.board.getNearByPointByDirection(this.point1, Direction.UP);
let point2UP = this.board.getNearByPointByDirection(this.point2, Direction.UP);
console.log(“point1UP”,point1UP,”point2UP”,point2UP);
//尋找這之中可能存在的路徑
{
let min = Math.max(point1UP.x,point2UP.x);
let max = Math.min(this.point1.x, this.point2.x);
for (var i = max;i>=min;i–){
if (!this.board.hasMiddleValue(new Point(i, this.point1.y), new Point(i, this.point2.y))){
this.path_Detail = [this.point1,new Point(i, this.point1.y),new Point(i, this.point2.y),this.point2];
console.log(“same up”);
return true;
}
}
}
//從下面消
let point1DOWN = this.board.getNearByPointByDirection(this.point1, Direction.DOWN);
let point2DOWN = this.board.getNearByPointByDirection(this.point2, Direction.DOWN);
console.log(“point1DOWN”,point1DOWN,”point2DOWN”,point2DOWN);
{
let max = Math.min(point1DOWN.x,point2DOWN.x);
let min = Math.max(this.point1.x, this.point2.x);
for (var i = min;i<=max;i++){
if (!this.board.hasMiddleValue(new Point(i, this.point1.y), new Point(i, this.point2.y))){
this.path_Detail = [this.point1,new Point(i, this.point1.y),new Point(i, this.point2.y),this.point2];
console.log("same down");
return true;
}
}
}
//從左邊消
let point1LEFT = this.board.getNearByPointByDirection(this.point1, Direction.LEFT);
let point2LEFT = this.board.getNearByPointByDirection(this.point2, Direction.LEFT);
console.log("point1LEFT",point1LEFT,"point2LEFT",point2LEFT);
{
let min = Math.max(point1LEFT.y,point2LEFT.y);
let max = Math.min(this.point1.y, this.point2.y);
for (var i = max;i>=min;i–) {
if (!this.board.hasMiddleValue(new Point(this.point1.x, i), new Point(this.point2.x, i))) {
this.path_Detail = [this.point1, new Point(this.point1.x, i), new Point(this.point2.x, i), this.point2];
console.log(“same left”);
return true;
}
}
}
//從右邊消
let point1RIGHT = this.board.getNearByPointByDirection(this.point1, Direction.RIGHT);
let point2RIGHT = this.board.getNearByPointByDirection(this.point2, Direction.RIGHT);
console.log(“point1RIGHT”,point1RIGHT,”point2RIGHT”,point2RIGHT);
{
let max = Math.min(point1RIGHT.y,point2RIGHT.y);
let min = Math.max(this.point1.y, this.point2.y);
for (var i = min;i<=max;i++) {
if (!this.board.hasMiddleValue(new Point(this.point1.x, i), new Point(this.point2.x, i))) {
this.path_Detail = [this.point1, new Point(this.point1.x, i), new Point(this.point2.x, i), this.point2];
console.log("same right");
return true;
}
}
}
//左右連消
if (this.point1.y != this.point2.y){
//先判斷那個點在左,那個點在右
let leftPoint = (this.point1.y < this.point2.y) ? this.point1:this.point2;
let rightPoint = (this.point1.y >= this.point2.y) ? this.point1:this.point2;
//取得右邊的點,直線往左最左的那個點
let leftPointRIGHT = this.board.getNearByPointByDirection(leftPoint, Direction.RIGHT);
let rightPointLEFT = this.board.getNearByPointByDirection(rightPoint, Direction.LEFT);
//參考前一篇文章的圖,右邊最左的點不可超過左邊的點,否則會造成誤判
leftPointRIGHT.y = (leftPointRIGHT.y < rightPoint.y) ? leftPointRIGHT.y : rightPoint.y;
rightPointLEFT.y = (rightPointLEFT.y > leftPoint.y) ? rightPointLEFT.y : leftPoint.y;
//用迴圈判斷在所有有可能的範圍中是否有可能存在的路徑
if (leftPointRIGHT.y != leftPoint.y && rightPointLEFT.y != rightPoint.y){
for (var i = rightPointLEFT.y; i <= leftPointRIGHT.y; i++) {
if (!this.board.hasMiddleValue(new Point(leftPoint.x, i), new Point(rightPoint.x, i))) {
this.path_Detail = [leftPoint, new Point(leftPoint.x, i), new Point(rightPoint.x, i), rightPoint];
console.log("same left to right");
return true;
}
}
}
}
//上下連消
if (this.point1.x != this.point2.x){
let upPoint = (this.point1.x < this.point2.x) ? this.point1:this.point2;
let downPoint = (this.point1.x >= this.point2.x) ? this.point1:this.point2;
let upPointDOWN = this.board.getNearByPointByDirection(upPoint, Direction.DOWN);
let downPointUP = this.board.getNearByPointByDirection(downPoint, Direction.UP);
upPointDOWN.x = (upPointDOWN.x < downPoint.x) ? upPointDOWN.x : downPoint.x;
downPointUP.x = (downPointUP.x > upPoint.x) ? downPointUP.x : upPoint.x;
if (upPointDOWN.x != upPoint.x && downPointUP.x != downPoint.x){
for (var i = downPointUP.x; i <= upPointDOWN.x; i++) {
if (!this.board.hasMiddleValue(new Point(i, upPoint.y), new Point(i, downPoint.y))) {
this.path_Detail = [upPoint, new Point(i, upPoint.y), new Point(i, downPoint.y), downPoint];
console.log("same top to down");
return true;
}
}
}
}
return false;
}
}[/code]
新增類別Board
class Board {
public board: Array>;
constructor() {
let content = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
//產生初始局面
let length = 10;
let data = content.concat(content).concat(content).concat(content).sort((a, b) => (Math.random() > .5) ? 1 : 0);
this.board = []
for (var i = 0;i= 0; i–) {
if (this.board[i][point.y] == null) {
nearByPoint.x = i;
} else {
break;
}
}
if (nearByPoint.x == 0) {
nearByPoint.x = -1;
}
break;
case Direction.DOWN:
//搜尋往下走最遠可到達的點
let maxLengthDOWN = this.board.length;
for (var i = point.x+1; i < maxLengthDOWN; i++) {
if (this.board[i][point.y] == null) {
nearByPoint.x = i;
} else {
break;
}
}
if (nearByPoint.x == maxLengthDOWN - 1) {
nearByPoint.x = maxLengthDOWN;
}
break;
case Direction.RIGHT:
//搜尋往右走最遠可到達的點
let maxLengthRIGHT = this.board[0].length;
for (var i = point.y+1; i < maxLengthRIGHT; i++) {
if (this.board[point.x][i] == null) {
nearByPoint.y = i;
} else {
break;
}
}
if (nearByPoint.y == maxLengthRIGHT - 1) {
nearByPoint.y = maxLengthRIGHT;
}
break;
case Direction.LEFT:
//搜尋往左走最遠可到達的點
for (var i = point.y-1; i >= 0; i–) {
if (this.board[point.x][i] == null) {
nearByPoint.y = i;
} else {
break;
}
}
if (nearByPoint.y == 0) {
nearByPoint.y = -1;
}
break;
}
return nearByPoint;
}
//搜尋在兩個點之中是否可以找到一直線可連接
public canFindPath(a: Point, b: Point, direction:string): boolean {
return this.hasMiddleValue(a ,b);
}
//偵測在兩個點中是否可用一條直線做連接
public hasMiddleValue(a: Point, b: Point): boolean {
let arr = [];
if (a.x == b.x) {
if (a.x == -1 || a.x == this.board.length) return false;
let max = Math.max(a.y, b.y);
let min = Math.min(a.y, b.y);
for (var i = min + 1; i < max; i++) {
if (this.board[a.x][i] != null) {
return true;
}
}
return false;
} else if (a.y == b.y) {
if (a.y == -1 || a.y == this.board[0].length) return false;
let max = Math.max(a.x, b.x);
let min = Math.min(a.x, b.x);
for (var i = min + 1; i < max; i++) {
if (this.board[i][a.y] != null) {
return true;
}
}
return false;
} else {
return true;
}
}
//判斷某兩個點的值是否相同
public hasSameValue(point1: Point, point2: Point): boolean {
return this.board[point1.x][point1.y] == this.board[point2.x][point2.y];
}
//將盤面上的圖消掉
public clearPoint(point: Point) {
this.board[point.x][point.y] = null;
point = null;
}
}
[/code]
今日成果:
Live Demo:今日成果展示
今天的專案可至此下載:ironman20181021
let potinC = getPathLeftPoint(pointA);
let pointD = getPathLeftPoint(pointB);
let min = Math.max(pointC.y,pointD.y);
let max = Math.min(pointA.y, pointB.y);
for (var i = max;i>=min;i--) {
if (!hasMiddleValue(new Point(pointA.x, i), new Point(pointB.x, i))) {
path = [pointA, new Point(pointA.x, i), new Point(pointB.x, i), pointB];
return "可消除";
}
}
深度優先搜尋法,是一種用來遍尋一個樹(tree)或圖(graph)的演算法。由樹的根(或圖的某一點當成 根)來開始探尋,先探尋邊(edge)上未搜尋的一節點(vertex or node),並儘可能深的搜索,直到該節點的所有邊上節點都已探尋;就回溯(backtracking)到前一個節點,重覆探尋未搜尋的節點,直到找到目 的節點或遍尋全部節點。