筆者我很喜歡打橋牌,但目前為止還未找到一個覺得玩起來順手的橋牌APP,因此乾脆自己開出橋牌遊戲的api,再找工作室寫APP的夥伴來做畫面。
這遊戲看似很複雜,但其實應用到的技術不外乎是資料庫的CRUD而已,難度並不高,對Laravel初學者來講,可以拿來當作對資料庫操作的練習。
由於橋牌的遊戲邏輯較為複雜,落落長的程式碼其實不外乎是在判斷這局是否輪到這個玩家出牌、他出的牌是不是合法的等等,我在這邊就不一一解釋每一行程式碼在幹麻了,只分享這次學到的新寫法。
完整程式碼:點我
$rules = $request->validate([
'name' => ['exists:cards'],
'trump' => ['required', 'integer', 'min:100', 'max:500'],
'line' => ['required', 'integer', 'max:7']
]);
若照上面的寫法,一旦其中一個欄位驗證不符合規則,Laravel就會自動回傳它預設的錯誤訊息。
如果想要客製化自己的錯誤訊息,可以寫成這樣:
$rules = [
'name' => ['exists:cards'],
'trump' => ['required', 'integer', 'min:100', 'max:500'],
'line' => ['required', 'integer', 'max:7']
];
$validator = validator::make($request->all(), $rules);
if ($validator->fails()) { //若驗證失敗,則回傳以下錯誤訊息
return $this->sendError(
"Player's name not found. Trump may not be greater than 500, or line may not be greater than 7",
3, 400);
}
Eloquent是Laravel的ORM(物件關聯對映),它讓我們可以透過程式語言(PHP)去操作資料庫,妥善運用它可以讓資料庫的操作更加方便、程式碼更簡潔。
$data['card'] = Card::where('name', $request->name)->orderBy('color', 'ASC')->orderBy('card', 'ASC')->get()
這一段是讓玩家呼叫看牌api來看自己的手牌時,能讓牌的順序按照花色,再按照點數從小到大排序。
我這邊有踩過一個小小的坑:如果我的'color'和'card'這兩個欄位儲存的資料型態是integer的話,讓他由小至大排序就會是這樣:{2, 3, 4, 5, ....., 11, 12, 13};但如果是string的話,排序則會變成這樣:{12, 13, 14, 2, 3, 4, 5, ...},1開頭的十位數字會被排在各位數字之前。這是因為若要排序string型態的資料,是依照字元的ascii code來做排序的,因此字串'11'會比字串'2'還要小。要處理這個問題的話,DB的欄位就必須是integer。
應前端要求,我把所有關於遊戲的資訊都放在card這支API,response內容其中一個欄位是遊戲房間的玩家資料,原本長這樣:
"room": [
{
"id": 1,
"name": "lulu",
"goal": 7,
"trick": 0,
},
{
"id": 2,
"name": "阿寶",
"goal": 7,
"trick": 0,
}
],
當時前端的夥伴要求我在room裡再加一個欄位'me'來分辨哪位是client端的玩家名稱:若是該位玩家,value為true,否則為false。
我第一時間想到最簡單粗暴的方法:就是先把 $data['room'] 轉成array,取出玩家名稱後,比對其和client端送來的名字是否相同,在他底下加入'me'這個欄位後,再把剛才比對結果放進去。原本寫法如下:
$data['room'] = Player::all()->toArray();
$ $data['room']['me'] = $data['room']['name'] == $request->name;
後來才知道,原來上述步驟可以這樣寫:
$data['room'] = Player::all()->toArray();
$data['room'] = array_map(function ($player) use ($request) { //use 後面括號放的是需要在這個closure中用到的參數
$player['me'] = $player['name'] == $request->name;
return $player;
}, $data['room']);
array_map()放的第一個參數是closure,也就是一個anonymous的function;第二個參數是你主要要對他動手腳的變數。
closure括號裡對應到的參數就是array_map()的第二個參數($player就是$data['room'])。
增加上面這段後,'room'就會變成這樣:
"room": [
{
"id": 1,
"name": "lulu",
"goal": 7,
"trick": 0,
"me": false
},
{
"id": 2,
"name": "阿寶",
"goal": 7,
"trick": 0,
"me": true
}
],
APP做好後,某一次測試發現一個很奇怪的問題:
Compare::latest()->first()
拿到的竟然不是資料表中所看到的最後一筆資料,而是倒數第二筆。
後來才發現,原來Laravel ORM的latest()是將資料表中的資料按照"created_at",由最新到最舊排序,也就是說,latest()預設是以寫入時間來作為排序依據的。
由於兩個玩家出牌時間可能很接近,進到後端儲存出牌資料時所紀錄下的時間有可能是一樣的。
假設若最後三筆資料的寫入時間是相同的,那麼將資料撈出來的時候,這三筆資料的順序會是隨機的。
如果想讓資料依照id大小排序,那麼要在latest()中加入參數:
Compare::latest('id')->first();
這樣才能確保資料是按照id的順序來查找的。
在遠端migrate的時候爆出以下訊息:
boa1417@instance0205:~/spoiler$ php artisan migrate
Illuminate\Database\QueryException : could not find driver (SQL: select * from information_schema.tables where table_schema = spoiler and table_name = migrations and table_type = 'BASE TABLE')
at /home/boa1417/spoiler/vendor/laravel/framework/src/Illuminate/Database/Connection.php:669
665| // If an exception occurs when attempting to run a query, we'll format the error
666| // message to include the bindings with SQL, which will make this exception a
667| // lot more helpful to the developer instead of just the database's errors.
668| catch (Exception $e) {
> 669| throw new QueryException(
670| $query, $this->prepareBindings($bindings), $e
671| );
672| }
673|
Exception trace:
1 PDOException::("could not find driver")
/home/boa1417/spoiler/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
2 PDO::__construct()
/home/boa1417/spoiler/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70
Please use the argument -v to see more details.
可能原因:少了讓php和mysql連街的PDO
Solution:
sudo apt-get install php版本號-mysql