用Horizon搭建可擴展的Javascript移動應(yīng)用后端方案
譯文【51CTO.com快譯】
簡介
Horizon是一個著名的跨平臺可擴展的后端框架,適用于構(gòu)建跨平臺基于JavaScript的移動應(yīng)用程序,尤其是那些需要實時功能的應(yīng)用。這個框架是由來自RethinkDB產(chǎn)品的程序員開發(fā)的,因此使用RethinkDB作為默認數(shù)據(jù)庫。如果你還不熟悉RethinkDB,那么你只需知識它是一個開放源碼的支持實時功能的數(shù)據(jù)庫(https://www.rethinkdb.com)。
Horizon框架公開一組客戶端API來允許你與底層數(shù)據(jù)庫進行交互。這意味著,你不必編寫任何后端代碼。你要做的就是,搭建一個新的服務(wù)器,運行它,Horizon將會自動管理其他內(nèi)容。借助于Horizon,你可以輕松地實現(xiàn)實時連接的客戶端和服務(wù)器之間的數(shù)據(jù)同步。
如果你想要了解更多關(guān)于Horizon的消息,請查閱其 faq頁面(http://horizon.io/faq/)。
在本教程中,你要使用Icon和Horizon來協(xié)同開發(fā)一個Tic-Tac-Toe井字游戲。因此,閱讀本文的前提是假定你已經(jīng)了解Icon和Horizon,所以我不打算解釋程序中Icon相關(guān)的特定代碼。當然,如果你想要一點有關(guān)Icon的背景知識的話,我建議你去查閱這個網(wǎng)址http://ionicframework.com/getting-started/。如果你想繼續(xù)閱讀本文內(nèi)容,那么請你先下載文章的示例工程源碼(https://github.com/anchetaWern/ionic-horizon-tictactoe)。
下圖給出的是本文示例應(yīng)用程序最終的結(jié)果快照。
安裝Horizon
RethinkDB用作Horizon的數(shù)據(jù)庫。因此,在安裝Horizon之前你需要先安裝RethinkDB。有關(guān)安裝RethinkDB的具體信息,你可以從網(wǎng)址https://www.rethinkdb.com/docs/install/處找到答案。
一旦安裝了RethinkDB,你就可以在終端程序中執(zhí)行以下命令通過npm工具來安裝Horizon:
npm install -g horizon
Horizon服務(wù)器開發(fā)
Horizon服務(wù)器用作應(yīng)用程序的后端。每當應(yīng)用程序執(zhí)行代碼時,它要與數(shù)據(jù)庫進行通信。
您可以通過在您的終端執(zhí)行以下命令來創(chuàng)建一個新的Horizon服務(wù)器:
hz init tictactoe-server
這個命令將創(chuàng)建RethinkDB數(shù)據(jù)庫并提供Horizon所使用的文件。
一旦創(chuàng)建了服務(wù)器,您可以通過執(zhí)行以下命令運行它:
hz serve --dev
在上面的命令中,指定-dev作為一個選項。這意味著,你想要運行一個開發(fā)服務(wù)器。在開發(fā)服務(wù)器中會設(shè)置以下選項:
--secure no:這意味著websocket和文件不會通過加密連接提供服務(wù)。
--permissions no:禁用權(quán)限約束。這意味著,任何客戶端都可以在數(shù)據(jù)庫中執(zhí)行任何他們想執(zhí)行的操作。Horizon的權(quán)限系統(tǒng)基于白名單。這意味著,默認情況下,所有用戶都沒有權(quán)限來做任何事情。你必須顯式地指定允許哪些操作。
--auto-create-collection yes:在首次使用時自動創(chuàng)建一個集合。在Horizon中,集合相當于關(guān)系數(shù)據(jù)庫中的表。此選項設(shè)置為true意味著,每次客戶端使用一個新的集合,它都會被自動創(chuàng)建。
--auto-create-index yes:在首次使用中自動創(chuàng)建索引。
--start-rethinkdb yes:在當前目錄中自動啟動RethinkDB的一個新實例。
--allow-unauthenticated yes:允許未經(jīng)身份驗證的用戶來執(zhí)行數(shù)據(jù)庫操作。
--allow-anonymous yes:允許匿名用戶執(zhí)行數(shù)據(jù)庫操作。
--serve-static ./dist:啟用靜態(tài)文件服務(wù)。如果你想要在瀏覽器中測試與Horizon API的交互時,這是很有用的。Horizon服務(wù)器默認運行在端口8181,所以你可以通過訪問地址http://localhost:8181來訪問服務(wù)器。
【注意】--dev選項永遠不要用于生產(chǎn)環(huán)境下,因為它會打開大量的易于被攻擊者能夠利用的漏洞。
構(gòu)建Ionic應(yīng)用程序
現(xiàn)在,我們已經(jīng)作好了充分準備。接下來,我們著手創(chuàng)建一個Ionic程序框架,命令如下:
ionic start tictactoe blank
安裝Chance.js
接下來,您需要安裝chance.js,這是一個JavaScript實用程序庫,用于生成隨機數(shù)據(jù)。在本應(yīng)用程序中,我們使用它來為玩家生成一個唯一的ID。你可以通過bower工具并使用下面的命令來安裝chance.js:
bower install chance
創(chuàng)建index.html
現(xiàn)在,打開文件www/index.html,并把其內(nèi)容修改為如下:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
- <title></title>
- <link href="lib/ionic/css/ionic.css" rel="stylesheet">
- <link href="css/style.css" rel="stylesheet">
- <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
- <link href="css/ionic.app.css" rel="stylesheet">
- -->
- <!-- chance.js -->
- <script src="lib/chance/dist/chance.min.js"></script>
- <!-- ionic/angularjs js -->
- <script src="lib/ionic/js/ionic.bundle.js"></script>
- <!-- cordova script (this will be a 404 during development) -->
- <script src="cordova.js"></script>
- <!-- horizon script -->
- <script src="http://127.0.0.1:8181/horizon/horizon.js"></script>
- <!-- your app's js -->
- <script src="js/app.js"></script>
- <!--main app logic -->
- <script src="js/controllers/HomeController.js"></script>
- </head>
- <body ng-app="starter">
- <ion-nav-view></ion-nav-view>
- </body>
- </html>
上面的代碼大部分來自于Icon空白向?qū)0迳傻臉影宕a。現(xiàn)在,我們來添加對chance.js腳本的引用:
- <script src="lib/chance/dist/chance.min.js"></script>
Horizon服務(wù)器將自動提供Horizon腳本服務(wù),代碼如下:
- <script src="http://127.0.0.1:8181/horizon/horizon.js"></script>
【注意】如果你以后想部署這些內(nèi)容的話,你必須修改URL。
接下來,主應(yīng)用程序邏輯位于下面這個腳本文件中:
- <script src="js/controllers/HomeController.js"></script>
編寫主程序app.js
文件app.js是運行初始化應(yīng)用程序代碼的地方。下面,需要打開文件www/js/app.js并把如下內(nèi)容添加到run函數(shù)的下面:
- .config(function($stateProvider, $urlRouterProvider) {
- $stateProvider
- .state('home', {
- cache: false,
- url: '/home',
- templateUrl: 'templates/home.html'
- });
- // if none of the above states are matched, use this as the fallback
- $urlRouterProvider.otherwise('/home');
- });
這將為默認的應(yīng)用程序頁設(shè)置一個路由。此路由將指定頁面所使用的模板和可以訪問它的URL。
開發(fā)控制器程序HomeController.Js
現(xiàn)在,我們在路徑www/js/controllers下創(chuàng)建一個控制器文件HomeController.js,并修改其代碼如下:
- (function(){
- angular.module('starter')
- .controller('HomeController', ['$scope', HomeController]);
- function HomeController($scope){
- var me = this;
- $scope.has_joined = false;
- $scope.ready = false;
- const horizon = Horizon({host: 'localhost:8181'});
- horizon.onReady(function(){
- $scope.$apply(function(){
- $scope.ready = true;
- });
- });
- horizon.connect();
- $scope.join = function(username, room){
- me.room = horizon('tictactoe');
- var id = chance.integer({min: 10000, max: 999999});
- me.id = id;
- $scope.player = username;
- $scope.player_score = 0;
- me.room.findAll({room: room, type: 'user'}).fetch().subscribe(function(row){
- var user_count = row.length;
- if(user_count == 2){
- alert('Sorry, room is already full.');
- }else{
- me.piece = 'X';
- if(user_count == 1){
- me.piece = 'O';
- }
- me.room.store({
- id: id,
- room: room,
- type: 'user',
- name: username,
- piece: me.piece
- });
- $scope.has_joined = true;
- me.room.findAll({room: room, type: 'user'}).watch().subscribe(
- function(users){
- users.forEach(function(user){
- if(user.id != me.id){
- $scope.$apply(function(){
- $scope.opponent = user.name;
- $scope.opponent_piece = user.piece;
- $scope.opponent_score = 0;
- });
- }
- });
- },
- function(err){
- console.log(err);
- }
- );
- me.room.findAll({room: room, type: 'move'}).watch().subscribe(
- function(moves){
- moves.forEach(function(item){
- var block = document.getElementById(item.block);
- block.innerHTML = item.piece;
- block.className = "col done";
- });
- me.updateScores();
- },
- function(err){
- console.log(err);
- }
- );
- }
- });
- }
- $scope.placePiece = function(id){
- var block = document.getElementById(id);
- if(!angular.element(block).hasClass('done')){
- me.room.store({
- type: 'move',
- room: me.room_name,
- block: id,
- piece: me.piece
- });
- }
- };
- me.updateScores = function(){
- const possible_combinations = [
- [1, 4, 7],
- [2, 5, 8],
- [3, 2, 1],
- [4, 5, 6],
- [3, 6, 9],
- [7, 8, 9],
- [1, 5, 9],
- [3, 5, 7]
- ];
- var scores = {'X': 0, 'O': 0};
- possible_combinations.forEach(function(row, row_index){
- var pieces = {'X' : 0, 'O': 0};
- row.forEach(function(id, item_index){
- var block = document.getElementById(id);
- if(angular.element(block).hasClass('done')){
- var piece = block.innerHTML;
- pieces[piece] += 1;
- }
- });
- if(pieces['X'] == 3){
- scores['X'] += 1;
- }else if(pieces['O'] == 3){
- scores['O'] += 1;
- }
- });
- $scope.$apply(function(){
- $scope.player_score = scores[me.piece];
- $scope.opponent_score = scores[$scope.opponent_piece];
- });
- }
- }
- })();
現(xiàn)在,分析一下上面代碼。首先,設(shè)置默認狀態(tài)。其中,has_joined變量用于是否玩家已經(jīng)進入某個房間。其次,ready變量用于確定是否用戶已經(jīng)連接到Horizon服務(wù)器。當這個變量值為false時,還不能向用戶顯示應(yīng)用程序的界面。
- $scope.has_joined = false;
- $scope.ready = false;
連接到服務(wù)器的代碼如下:
- const horizon = Horizon({host: 'localhost:8181'});
- horizon.onReady(function(){
- $scope.$apply(function(){
- $scope.ready = true;
- });
- });
- horizon.connect(); //connect to the server
如我前面所提到的,Horizon服務(wù)器默認使用的是8181端口。這正是我們?yōu)槭裁词褂胠ocal:8181作為端口的原因。如果你連接到一個遠程服務(wù)器,這應(yīng)該對應(yīng)于分配給服務(wù)器的IP地址或者域名。當用戶連接到服務(wù)器時,onReady事件將會觸發(fā)。正是在此時,我們把ready設(shè)置為true,這樣就可以向用戶顯示程序的UI部分了。
- horizon.onReady(function(){
- $scope.$apply(function(){
- $scope.ready = true;
- });
- });
進入房間
每當用戶點擊Join按鈕時,將執(zhí)行join函數(shù):
- $scope.join = function(username, room){
- ...
- };
在此函數(shù)內(nèi)部,連接到一個稱為tictactoe的集合。
【注意】因為我們處于開發(fā)模式下;所以,如果集合不存在的話,系統(tǒng)將自動為你創(chuàng)建。
- me.room = horizon('tictactoe');
接下來,生成一個ID,并把它設(shè)置為當前用戶的ID:
- var id = chance.integer({min: 10000, max: 999999});
- me.id = id;
接下來,設(shè)置玩家用戶名和默認的玩家得分。
- $scope.player = username;
- $scope.player_score = 0;
【注意】這兩個變量都被綁定到模板中;所以,你可以隨時顯示與更新它們。
接下來,進行文檔查詢,查詢條件是:room屬性為當前房間且type屬性為user。千萬不要把這種查詢與subscribe函數(shù)弄混,在這里我們并不監(jiān)聽數(shù)據(jù)變化的。而且,這里還要使用fetch函數(shù);這意味著,只有在用戶進入一個房間時才執(zhí)行該操作。相關(guān)代碼如下:
- me.room.findAll({room: room, type: 'user'}).fetch().subscribe(function(row){
- ...
- });
一旦結(jié)果返回,即檢查用戶個數(shù)。當然,井字游戲只能由兩個玩家玩,所以,如果用戶想加入一個已經(jīng)有兩名玩家的房間的話,系統(tǒng)會向他們發(fā)出警報。
- var user_count = row.length;
- if(user_count == 2){
- alert('Sorry, room is already full.');
- }else{
- ...
- }
上面代碼中的else語句將繼續(xù)處理接受用戶的邏輯,即根據(jù)當前用戶數(shù)確定將被分配給用戶的卡片。第一個加入該房間的人得到"X"卡片,而第二個人得到"O"卡片。
- me.piece = 'X';
- if(user_count == 1){
- me.piece = 'O';
- }
一旦你選定了卡片,就把新用戶存儲到集合中,并把has_joined開關(guān)值取反,從而顯示井字棋盤。
- me.room.store({
- id: id,
- room: room,
- type: 'user',
- name: username,
- piece: me.piece
- });
- $scope.has_joined = true;
接下來,偵聽集合中的變化。這次,不是通過fetch方式,而是使用watch方式。具體地說,每當添加新文檔或更新(或刪除)匹配查詢的現(xiàn)有文檔時,都要執(zhí)行回調(diào)函數(shù)?;卣{(diào)函數(shù)執(zhí)行時,循環(huán)遍歷所有的結(jié)果并設(shè)置對手的詳細信息——如果該文檔的用戶ID與當前用戶的ID不匹配的話。本程序中正是通過這種方式來向當前用戶顯示他們的對手是誰。
- me.room.findAll({room: room, type: 'user'}).watch().subscribe(
- function(users){
- users.forEach(function(user){
- if(user.id != me.id){
- $scope.$apply(function(){
- $scope.opponent = user.name;
- $scope.opponent_piece = user.piece;
- $scope.opponent_score = 0;
- });
- }
- });
- },
- function(err){
- console.log(err);
- }
- );
接下來要訂閱move事件,該事件每當玩家把他們的卡片放到棋盤上從而這導致文檔變化時就觸發(fā)一次。如果發(fā)生這種情況,則遍歷所有的移動動作并將文本添加到相應(yīng)的格子。從現(xiàn)在開始,代碼中將使用“block”一詞來表示棋盤上的每一個格子。
添加的文本對應(yīng)于每個用戶所使用的卡片;此外,代碼中還將類名替換為“col done”。其中,col相應(yīng)于Ionic編程中網(wǎng)格實現(xiàn)類,而done是用于表示一個特定塊上已經(jīng)已經(jīng)有一個卡片的類。如果用戶還能將卡片放在格子上,我們就使用這種辦法來檢查。在更新棋盤用戶界面后,通過調(diào)用updateScores函數(shù)(將在以后添加這個函數(shù))來更新玩家的成績。
- me.room.findAll({room: room, type: 'move'}).watch().subscribe(
- function(moves){
- moves.forEach(function(item){
- var block = document.getElementById(item.block);
- block.innerHTML = item.piece;
- block.className = "col done";
- });
- me.updateScores();
- },
- function(err){
- console.log(err);
- }
- );
放置卡片
每當用戶點擊棋盤上的任何一格時都要調(diào)用placePiece函數(shù),同時要提供對應(yīng)格子的ID值作為此函數(shù)的參數(shù)。這允許我們隨心所欲地操縱游戲格子。在本程序中,使用此函數(shù)來檢查某個游戲格子是否屬于done類型。如果沒有done標志,則創(chuàng)建一個新的移動,并顯示當前房間、格子ID值及對應(yīng)的卡片。
- $scope.placePiece = function(id){
- var block = document.getElementById(id);
- if(!angular.element(block).hasClass('done')){
- me.room.store({
- type: 'move',
- room: me.room_name,
- block: id,
- piece: me.piece
- });
- }
- };
更新玩家得分
為了更新玩家得分,需要構(gòu)建一個包含可能的獲勝組合的數(shù)組,如下所示:
- const possible_combinations = [
- [1, 4, 7],
- [2, 5, 8],
- [3, 2, 1],
- [4, 5, 6],
- [3, 6, 9],
- [7, 8, 9],
- [1, 5, 9],
- [3, 5, 7]
- ];
在這段代碼中,[1, 4, 7]對應(yīng)于第一行,[1, 2, 3]對應(yīng)于第一列。只要相應(yīng)的數(shù)字存在,順序是無關(guān)緊要的。下面的圖形有助于你更直觀地了解這一點。
接下來,需要初始化每個單獨卡片的得分并循環(huán)遍歷每個可能的組合。對于每一次循環(huán)遍歷,初始化已經(jīng)放到棋盤上的卡片總數(shù)。然后針對每一種可能的組合進行循環(huán)遍歷。使用id來檢查是否相應(yīng)的格子上已經(jīng)放了卡片。如果已經(jīng)有了卡片,則取得實際卡片并把卡片總數(shù)加1。在循環(huán)結(jié)束后,檢查是否卡片總數(shù)等于3。如果卡片總數(shù)等于3,則增加該卡片得分數(shù),直到遍歷完所有可能的組合。一旦完成,更新當前玩家和對手的得分值。
- var scores = {'X': 0, 'O': 0};
- possible_combinations.forEach(function(row, row_index){
- var pieces = {'X' : 0, 'O': 0};
- row.forEach(function(id, item_index){
- var block = document.getElementById(id);
- if(angular.element(block).hasClass('done')){ //check if there's already a piece
- var piece = block.innerHTML;
- pieces[piece] += 1;
- }
- });
- if(pieces['X'] == 3){
- scores['X'] += 1;
- }else if(pieces['O'] == 3){
- scores['O'] += 1;
- }
- });
- //update current player and opponent score
- $scope.$apply(function(){
- $scope.player_score = scores[me.piece];
- $scope.opponent_score = scores[$scope.opponent_piece];
- });
創(chuàng)建主模板文件
現(xiàn)在,在目錄www/templates下創(chuàng)建一個模板文件home.html,并添加如下代碼:
- <ion-view title="Home" ng-controller="HomeController as home_ctrl" ng-init="connect()">
- <header class="bar bar-header bar-stable">
- <h1 class="title">Ionic Horizon Tic Tac Toe</h1>
- </header>
- <ion-content class="has-header" ng-show="home_ctrl.ready">
- <div id="join" class="padding" ng-hide="home_ctrl.has_joined">
- <div class="list">
- <label class="item item-input">
- <input type="text" ng-model="home_ctrl.room" placeholder="Room Name">
- </label>
- <label class="item item-input">
- <input type="text" ng-model="home_ctrl.username" placeholder="User Name">
- </label>
- </div>
- <button class="button button-positive button-block" ng-click="join(home_ctrl.username, home_ctrl.room)">
- join
- </button>
- </div>
- <div id="game" ng-show="home_ctrl.has_joined">
- <div id="board">
- <div class="row">
- <div class="col" ng-click="placePiece(1)" id="1"></div>
- <div class="col" ng-click="placePiece(2)" id="2"></div>
- <div class="col" ng-click="placePiece(3)" id="3"></div>
- </div>
- <div class="row">
- <div class="col" ng-click="placePiece(4)" id="4"></div>
- <div class="col" ng-click="placePiece(5)" id="5"></div>
- <div class="col" ng-click="placePiece(6)" id="6"></div>
- </div>
- <div class="row">
- <div class="col" ng-click="placePiece(7)" id="7"></div>
- <div class="col" ng-click="placePiece(8)" id="8"></div>
- <div class="col" ng-click="placePiece(9)" id="9"></div>
- </div>
- </div>
- <div id="scores">
- <div class="row">
- <div class="col col-50 player">
- <div class="player-name" ng-bind="player"></div>
- <div class="player-score" ng-bind="player_score"></div>
- </div>
- <div class="col col-50 player">
- <div class="player-name" ng-bind="opponent"></div>
- <div class="player-score" ng-bind="opponent_score"></div>
- </div>
- </div>
- </div>
- </div>
- </ion-content>
- </ion-view>
現(xiàn)在,我們來分析一下上面的代碼。首先,創(chuàng)建了一個總的包裝器,在用戶連接到Horizon服務(wù)器前這部分內(nèi)容是不顯示的:
- <ion-content class="has-header" ng-show="home_ctrl.ready">
- ...
- </ion-content>
加入游戲房間的表單代碼如下:
- <div id="join" class="padding" ng-hide="home_ctrl.has_joined">
- <div class="list">
- <label class="item item-input">
- <input type="text" ng-model="home_ctrl.room" placeholder="Room Name">
- </label>
- <label class="item item-input">
- <input type="text" ng-model="home_ctrl.username" placeholder="User Name">
- </label>
- </div>
- <button class="button button-positive button-block" ng-click="join(home_ctrl.username, home_ctrl.room)">
- join
- </button>
- </div>
井字棋棋盤的設(shè)計相關(guān)代碼如下:
- <div id="board">
- <div class="row">
- <div class="col" ng-click="placePiece(1)" id="1"></div>
- <div class="col" ng-click="placePiece(2)" id="2"></div>
- <div class="col" ng-click="placePiece(3)" id="3"></div>
- </div>
- <div class="row">
- <div class="col" ng-click="placePiece(4)" id="4"></div>
- <div class="col" ng-click="placePiece(5)" id="5"></div>
- <div class="col" ng-click="placePiece(6)" id="6"></div>
- </div>
- <div class="row">
- <div class="col" ng-click="placePiece(7)" id="7"></div>
- <div class="col" ng-click="placePiece(8)" id="8"></div>
- <div class="col" ng-click="placePiece(9)" id="9"></div>
- </div>
- </div>
玩家得分部分對應(yīng)的代碼如下:
- <div id="scores">
- <div class="row">
- <div class="col col-50 player">
- <div class="player-name" ng-bind="player"></div>
- <div class="player-score" ng-bind="player_score"></div>
- </div>
- <div class="col col-50 player">
- <div class="player-name" ng-bind="opponent"></div>
- <div class="player-score" ng-bind="opponent_score"></div>
- </div>
- </div>
- </div>
編寫樣式文件
下面給出客戶端應(yīng)用程序的樣式定義:
- #board .col {
- text-align: center;
- height: 100px;
- line-height: 100px;
- font-size: 30px;
- padding: 0;
- }
- #board .col:nth-child(2) {
- border-right: 1px solid;
- border-left: 1px solid;
- }
- #board .row:nth-child(2) .col {
- border-top: 1px solid;
- border-bottom: 1px solid;
- }
- .player {
- font-weight: bold;
- text-align: center;
- }
- .player-name {
- font-size: 18px;
- }
- .player-score {
- margin-top: 15px;
- font-size: 30px;
- }
- #scores {
- margin-top: 30px;
- }
運行應(yīng)用程序
現(xiàn)在,你可以通過執(zhí)行應(yīng)用程序根目錄下的如下命令在你的瀏覽器中測試應(yīng)用程序:
- ionic serve
這樣啟動的服務(wù)器端將服務(wù)于本地項目并在你的默認瀏覽器中打開一個新的選項卡。
如果你想要和朋友一起測試的話,你可以使用Ngrok把Horizon服務(wù)器發(fā)布到互聯(lián)網(wǎng)上,命令如下:
- ngrok http 8181
這個命令將生成一個URL,當你連接到Horizon服務(wù)器時可以用作主機地址:
- const horizon = Horizon({host: 'xxxx.ngrok.io'});
除此之外,還要在index.html文件中改變到horizon.js文件的引用:
- <script src="http://xxxx.ngrok.io/horizon/horizon.js"></script>
若要創(chuàng)建程序的移動版本,需要在你的項目中添加一個平臺(例如,安卓系統(tǒng))。這假定你已經(jīng)在自己的計算機上安裝了Android SDK。
- ionic platform add android
接下來,我們生成.apk文件,命令如下:
- ionic build android
到此,你可以把這個.apk文件發(fā)送給你的朋友來一起把玩這個游戲。當然,你也可以自己玩這個游戲,這全是你自己的事了。
小結(jié)
在本教程中,你開發(fā)的僅僅是一個再簡單不過的應(yīng)用程序;因此,還有很多方面加以適當改進的話,效果會更好。下面列舉的是供你參考改進的幾個內(nèi)容,把這些當成你的技能作業(yè)好了。
再開發(fā)一個4×4或5×5的版本:目前你開發(fā)出的3×3版本幾乎總是會導致僵局,尤其是如果兩名玩井字游戲的玩家都是專家級的話。
得分邏輯:你不得不通過大量的循環(huán)來取得玩家得分。也許你可以想出更好的方案來實現(xiàn)。
美化游戲風格:當前游戲的風格十分平實,其實它模擬的是適用于在紙上玩的井字游戲。
添加動畫:當用戶加入一個房間時,你可以嘗試為棋盤添加“滑落”動畫效果;或者當玩家把卡片放到棋盤上時實現(xiàn)“彈出”動畫效果。您可以使用animate.css文件來實現(xiàn)這類動畫。
添加SNS登錄支持:在這么簡單的一個應(yīng)用中添加SNS功能恐怕要求有點過了,但如果你想要了解如何在Horizon框架中實現(xiàn)身份驗證工作原理,這倒是一個相當不錯的鍛煉機會。使用Horizon認證,你可以讓用戶登錄其Facebook、Twitter或Github帳戶。
添加再玩一次功能:游戲完畢后你可以嘗試添加一個“Play Again”按鈕。按下這個按鈕時,系統(tǒng)將清除排行榜和得分,以便玩家可以再玩一次。
添加實時排行榜功能:添加比賽排行榜來顯示誰贏了比賽。
【51CTO譯稿,合作站點轉(zhuǎn)載請注明原文譯者和出處為51CTO.com】