五子棋是規則簡單明了的政策型遊戲,先形成五子連線者獲勝。
本課程習作采用兩人線上對弈的方式進行比賽,拿着手機在上下班路上玩特别合适。
整個過程在衆觸低代碼應用平台進行,使用表達式描述遊戲邏輯(高度簡化版JS)。
本課程重點學習websocket實時消息的發送與接收處理。
兩人線上下棋示範
先動手玩一玩:https://gobang.zc-app.cn因為是線上遊戲,需要登入,可以用手機和郵箱分别注冊,用電腦和手機自己跟自己玩。
URL後面加
/z
就進入開發模式:https://gobang.zc-app.cn/z
詳盡的的教學請移步哔哩哔哩視訊:https://www.bilibili.com/video/BV1QX4y1A7FW
棋盤結構
$v.棋盤 = array(14, array(14, ""))
用嵌套資料元件使用14 * 14的二維數組渲染而成,即圖中畫黑線的格子。
方陣結構
$v.方陣 = array(15, array(15, ""))
用嵌套資料元件使用15 * 15的二維數組渲染而成,即圖中黑線交叉的點,滑鼠hover在元件上時高亮的圓圈。
棋手狀态
undefined、邀請中、對方出棋、己方出棋
預設就是沒有值,表示還沒開始下棋或者結束了(勝負已分)。對方出棋的時候己方不能落子。
賬号登入
既然是線上玩的,就要求登入。可以用手機或郵箱注冊。
登入後馬上打開連接配接,有玩家上線了,有玩家邀請了,對方落子了都是通過此連接配接socket即時通知的。
打開連接配接socket:onLogin
$socket.open($c.exp, { channels: ["比賽"], onOnline: true, onOffline: true, allowMultiLogin: true })
第一個參數$c.exp是個對象,可以包含onConnect, onData, onReconnect, onError,前兩個是必須的。
第二個參數是option選項,channels數組裡放你需關注的頻道,onOnline表示有人上線時是否要通知到你,onOffline則是有人下線是要否通知,allowMultiLogin表示同一個賬号能否能在多個地方登入而不強制前面登入的賬号下線。
連上後:onConnect
$socket.onlines(["比賽"])
$v.onlines = $r.比賽.filter('$x !== $c.me._id')
$v.onlines.forEach('$user.get($x)')
render()
連上後查詢一下關注"比賽"頻道的線上玩家,排除自己後放到$v.onlines清單後依次擷取使用者資訊。
消息格式
下面有多個on開頭的表達式都是隻收到socket消息。它們都有共同的格式:
type
是消息類型,
x
是消息體,
from
是消息發送人。消息接受人
to
和發送時間
d
在此案例中未使用到。
有人上線了:onOnline
$v.onlines.push(x)
$v.onlines = $v.onlines.unique()
$user.get(x)
把上線的人放入上面的$v.onlines中,并去重。
有人斷線了:onOffline
$v.對手 === x ? alert("對方斷線了") : ""
$v.onlines.splice($v.onlines.indexOf(x), 1)
斷線了就把他/她從$v.onlines移除。如果剛好是正在跟你對弈的棋手則抛出一個警告通知。
收到資料後:onData
stopIf($c.me._id == from)
$c.exp[type].exc()
render()
先要排除是自己發出的資料,因為socket是廣播消息的,自己也能收到。
然後再根據消息類型執行對應的表達式,可能的類型有:on被邀、on拒邀、on受邀、on落子。
當其他人登入時,【對手】右邊的問号圓圈就會閃爍,點選它會彈出線上玩家清單,從中選擇一個可發出對弈邀請。
發出對弈邀請
$socket.send($x, "on被邀", "邀請")
$v.狀态 = "邀請中"
info("邀請已發出,請等待對方接受邀請")
收到消息:on被邀
stopIf($v.狀态, '$socket.send(from, "on拒邀", "對方正在下棋")')
$user.get(from)
$v.對手 = from
$v.pop = "選棋子"
如果自己正在下棋就直接發出"on拒邀"消息,拒絕邀請。
擷取對方使用者資訊,彈出模态視窗提示接受要是拒絕邀請。
收到消息:on拒邀
$v.狀态 = undefined
warn(x || "對方拒絕你的邀請")
把前面的”邀請中“的狀态置空,彈出對方發來的拒邀消息
選子
$v.己方 = "白" // "黑"
$c.exp.受邀.exc()
接受邀請:受邀
$socket.send($v.對手, "on受邀", $v.己方)
$v.方陣 = array(15, array(15, ""))
$v.pop = undefined
$v.對方 = ($v.己方 === "黑" ? "白" : "黑")
$v.狀态 = "己方出棋"
info("請出棋")
給對方發送“on被邀“消息,捎上自己選的子。
清空方陣,準備出棋。
收到消息:on被邀
$v.方陣 = array(15, array(15, ""))
$v.對手 = from
$v.對方 = x
$v.己方 = (x === "黑" ? "白" : "黑")
$v.狀态 = "對方出棋"
info("對方已接受邀請,請等待對方先出棋")
from
是對手使用者ID,
x
是對方選的子,自己就隻能選另一種子了。
落子
stopIf($v.狀态 !== "己方出棋" || $v.方陣[$parent.$index][$index] || $v.連續棋子.length > 4)
$v.落子點 = [$parent.$index, $index]
$socket.send($v.對手, "on落子", $v.落子點)
$("." + $v.己方 + "子聲音").play()
$v.方陣[$parent.$index][$index] = $v.己方
$v.檢查方向.forEach($c.exp.落_是否勝出)
$v.狀态 = "對方出棋"
如果不是己方出棋的狀态,或者落子位置不在方陣内,或者已經組成4個以上連續棋子都不可落子。
發出"on落子"消息,捎上剛才的落子點坐标軸。
播放落子聲音,并把己方棋子放在方陣的落子點上,并通過動态類名發出光暈。
$v.落子點[0] === $parent.$index && $v.落子點[1] === $index ? "光暈" : ""
檢查剛才的落子能否勝出。
檢查勝出(形成五子連線)
要判斷勝負隻需落子時從落子點 [y, x] 以四種連線的正反方向分别檢視,累計4個以上連續同色棋子為聲。
$v.檢查方向
[
[
[-1, 0],
[1, 0]
],
[
[0, -1],
[0, 1]
],
[
[1, -1],
[-1, 1]
],
[
[-1, -1],
[1, 1]
]
]
-1表示往後檢查,0表示不動,1表示往前檢查。比如[-1, 0]是是X軸上往負值方向檢查,即正西方向;[1, -1]表示先往X軸正方向檢查再往Y軸負方向檢查,即東北方向。
即
落子是否勝出
$v.連續棋子 = [$v.落子點]
$l.方向 = $x[0]
$l.非連續 = false
$v.循環4次.forEach($c.exp.落_相鄰同色)
$l.方向 = $x[1]
$l.非連續 = false
$v.循環4次.forEach($c.exp.落_相鄰同色)
stopIf($v.連續棋子.length > 4, 'info(($v.狀态 === "己方出棋" ? $v.己方 : $v.對方) + "子赢了"); $v.狀态 = undefined;')
先把目前落子位置作為第一個連續棋子,先往
$v.檢查方向
提供的一對方向的第一個方向試探移動4次(即循環4遍)看是否有相鄰同色子,再往另一個方向也試探4次。
如果試探得到的
$v.連續棋子
大于4個,那目前落子方勝出。
檢查與落子相鄰的同色子
$l.y = $v.落子點[0] + $l.方向[0] * $x
$l.x = $v.落子點[1] + $l.方向[1] * $x
!$l.非連續 && $v.方陣[$l.y][$l.x] === ($v.狀态 === "己方出棋" ? $v.己方 : $v.對方) ? $v.連續棋子.push([$l.y, $l.x]) : $l.非連續 = true
一個試探方向包括X軸方向和Y軸方向,有-1、0、1三種移法,分别移動一下坐标,檢查新坐标在方陣中的棋子,如果坐标上有子,并且現在是己方出棋而且這個子正好是己方顔色,那這個子就是連續棋子的一部分。其它情況都不能算連續同色子,比如坐标上沒有子,或者是對方的子,再或者是以前就已經非連續了,這次就沒必要繼續檢查了。
勝出的連續5個棋子也要發出光暈。前面新落的子已經通過動态類名發出光暈,現在要找出連續棋子的其它棋子。
$v.連續棋子.length > 4 && $v.連續棋子.find('$x[0] === $ext.$parent.$index && $x[1] === $ext.$index') ? "光暈" : ""