[360前端星計划]BlackJack(21點)(純JS,附總部學習筆記)


[360前端星計划]總部學習筆記(6/6)

[360前端星計划]詳情跳轉

游戲界面預覽

游戲界面

目錄

一.游戲介紹

1.起源

2.規則

3.技巧

二.游戲設計

1.整體UI構思

2.素材采集

3.游戲總規划

三.代碼實現

0.編碼規范以及優化記錄

1.HTML文檔結構

2.CSS布局與動畫

3.JavaScript功能模塊

四.游戲測試(以下所列皆可正常運行游戲)

1.PC端

2.移動端

五.番外篇

致謝

版權




一.游戲介紹

1.起源

    21點又名黑傑克(英文:Blackjack) ,起源於法國,已流傳到世界各地。21點,是一種使用撲克牌玩的賭博游戲。亦是唯一一種在賭埸中可以在概率中戰勝庄家的一種賭博游戲。

2.規則

  • 21點是一張牌面朝上(叫明牌),一張牌面朝下(叫暗牌);給自己發兩張牌,一張暗牌,一張明牌。
  • 玩家手中撲克點數的計算是:K、Q、J 和 10 牌都算作 10 點。
  • A 牌既可算作1 點也可算作11 點,由玩家自己決定。
  • 其余所有2 至9 牌均按其原面值計算。
  • 如果玩家前兩張牌是A 、10點牌,就擁有黑傑克(Blackjack);
  • 如果庄家沒有黑傑克,玩家就能贏得2倍的賭金(1賠2)。
  • 沒有黑傑克的玩家可以繼續拿牌,可以隨意要多少張。目的是盡量往21點靠,靠得越近越好。
  • 如果所有的牌加起來超過21點,玩家就輸了--叫爆掉(Bust),游戲隨之結束。
  • 如果玩家沒爆掉,又決定不再要牌了,這時庄家就把他的那張暗牌打開來。
  • 一般到17點或17點以上不再拿牌,但也有可能15到16點甚至12到13點就不再拿牌或者18到19點繼續拿牌。(本游戲采用1號電腦的邏輯為達到16點便不再拿牌)
  • 假如庄家爆掉了,那他就輸了.
  • 假如他沒爆掉,那么你就與他比點數大小,大為贏。一樣的點數為平手。

3.技巧

  • 障眼法
    • 這種方法主要適合於閑家,而且在眾多玩家參與的時候適用。
    • 一般在閑家明牌是10點的時候,如果牌底是3~7點,一般拿到這種點數特別難受,如果要牌,有50%的機率會暴,因為閑家暴點是100%輸,不要牌可能也會輸,我暫且把它稱為尷尬點數。這時候,閑家如果觀察到別的閑家點數都比較大,而且都不要牌的情況下,可以跟着不要。這樣一來,就會給庄家很大的壓力,以為每一位閑家手中的牌都是比較大的點數,如果庄家同樣拿着尷尬點數,這種情況只能逼着庄家要牌,很明了,無形中所用的這個障眼法是把風險轉移給了庄家,輸贏靠天定了,看哪個運氣好了,若庄家爆了,則閑家贏。
  • 姜太公釣魚法
    • 這種方法主要適合於閑家,而且在眾多玩家參與的時候適用。
    • 一般在閑家明牌是10點的時候,如果牌底是3~7點,一般拿到這種點數特別難受,如果要牌,有50%的機率會暴,因為閑家暴點是100%輸,不要牌可能也會輸,我暫且把它稱為尷尬點數。這時候,閑家如果觀察到別的閑家點數都比較大,而且都不要牌的情況下,可以跟着不要。這樣一來,就會給庄家很大的壓力,以為每一位閑家手中的牌都是比較大的點數,如果庄家同樣拿着尷尬點數,這種情況只能逼着庄家要牌,很明了,無形中所用的這個障眼法是把風險轉移給了庄家,輸贏靠天定了,看哪個運氣好了,若庄家爆了,則閑家贏。
  • 補救法
    • 這種方法庄家閑家都適用。
    • 所謂補救,就是拿到的是前面所提到的尷尬點數,輸地可能性極大,在明知道牌點數就比對手點數小的情況下,我們只能要牌,因為不要也是輸,而要牌還有一線希望是贏,所以我們能選擇的就是奔那一線希望,寄希望總比放棄希望好。

二.游戲設計

1.整體UI構思

  • 背景:我認為需要一個暗色的、非純色的背景。
  • 桌面:一張記憶中賭博賽事的標准綠色桌子,木質黃色的邊。
  • 按鈕:於是有了簡約風格的畫面,接下來我考慮將按鈕在桌面中。
  • logo:考慮到游戲叫 21點,所以 應用了簡單卻不失代表性的帶陰影的透明21圖標。
  • 候牌:做兩個等牌區的底框(透明的淡色框),等待發牌,並標注玩家x號。
  • 積分:考慮到籌碼問題,於是在等牌區的一側做一個不太復雜的標注,響應整體簡約的風格。
  • 標題:放在桌面的正中央,游戲主題的表示,BlackJack(21)足矣。
    • ⚠️不能貼頂,至少產生h1 { margin-top:20px;} 以保持美感。
    • 游戲界面的色彩搭配靈感部分來源於其他網絡撲克游戲
    • (游戲素材均來自網絡)

2.素材采集

游戲logo
操作手勢

發牌堆、暗牌
背景
一副撲克牌13*4

3.游戲總規划

  • 利用html+css將設計好的整體的布局+采集到的素材➡️應用到配置、搭建一個初始的頁面,后期再給予精確調整控制。
    • html基本文檔結構
    • css布局
      根據游戲的需求來編寫不同的css動畫js功能函數以貼近游戲規則、增強游戲體驗。
    • css動畫
      • 使用animation keyforms完成動畫:保證每次新局首發牌一人兩張,且1號電腦玩家的第二張牌為暗牌,后再發牌則進行一人一張動畫。
      • 配合js控制撲克牌發出前后的顯隱,特別是亮牌后1號電腦暗牌的顯示。
  • JavaScript各功能函數設計
    • 實現actions系列按鈕的功能
      • 新局 sendCard()
      • 要牌 sendCard()第二次開始
      • 亮牌 showSend()
      • 退出游戲 exit()
    • 初始化整副牌的數組,即洗牌,給52個數字重新分配撲克牌的值(花色,數字)
    • 本游戲設定只有一副牌,即在一個length為52的數組中抽取牌進行游戲,也就是說每次新局開始便初始化,保證其有52張牌的相應概率來進行游戲,屬於不放回游戲。
    • 每張牌的選取靠js配合for控制量i對每張牌的坐標進行計算以保證每次新局初始化時都可以在random的控制下以tmp結果隨機選取一張牌(剩余的牌堆中)。
    • 實現發牌后css動畫結束前 ,計算相應偏移位置並自動添加html內容,使得動畫結束之時,添加落牌剛好銜接,其中使用了for中的i控制外加settimeout實現單次計時。
  • 本游戲規則的邏輯設定聲明:
    • 每次開局一人兩張牌,后每次要牌均為單張發牌動畫。
    • 1號電腦的設定是發牌小於16時,則在2號玩家點擊要牌的同時加一張牌。若大於16,則不再要牌。2號玩家則自行判斷,任意要牌。
    • Ace牌的1或11,在亮牌時進行智能判斷:
      • 如果按11算不會爆牌則會按照11計算
      • 如果按照11算會爆牌則會按照1來計算
    • 黑傑克:新局初次發牌兩張為1+10
      • 若為黑傑克,且庄家沒有黑傑克時,則獲勝時籌碼加倍。
      • 庄家設定為一號電腦。
    • 如果雙方有一方拿牌爆掉,便判定另一方為獲勝方。
    • 如果都沒有在拿牌過程中爆掉,則正常比對雙方擁有的點數,小於21點且大的一方:獲勝。
    • Actions操作系列按鈕說明:(從左到右依次)
      • 手掌🖐️為新局
      • 食指☝️要加牌
      • 兩張牌交換為兩牌
      • 21點的logo為退出游戲
    • 本游戲 點擊發牌區的牌效果=點擊 新局+要牌按鈕

三.代碼實現

0.編碼規范以及優化記錄

HTML:
	標准的html結構,meta標簽等
	思考布局方式,合理的結構
	避免使用行內樣式
	事件盡量采用事件綁定
	頁面樣式盡量處理的精致一些(優先級以功能為主,這些次之)
	本次作品,時間和質量的比重,質量的權重高,所以要優先提升質量

CSS:
	1. 頁面單獨引用xxxx.css
	2. 功能樣式可以分類(就算沒必要分頁、也可以按照功能寫在一起),如:
		2.1 公共樣式:包括字體、h1~h6 p div等會用到的一些樣式
		2.2 布局樣式:就是布局相關的css寫在一起,主要處理布局、結構
		2.3 功能樣式:各個子功能塊樣式,如:桌面、操作圖標,基本思想是按功能分塊
		2.4 編碼相關:盡可能少使用id;最好使用class,且其命名有功能性
		(用於綁定事件)
		樣式性(用戶寫樣式),命名語義話(能表達出你這個樣式是干什么用的,
		盡量避免寫flash1、flash2...)(已經改成sendCardTo)
	動畫相關樣式可以單獨寫在一個文件里,進行引用
	手機上不居中可能也是這個原因,另外手機上可以設置meta的viewport.
	按鈕問題:可以給其父元素一個position:relative;
	然后操作元素整體使用position:absolute;bottom:xx;進行定位
	如果手機上要求訪問效果和PC相同,可以考慮樣式做兩套(根據時間情況看吧)
		
Js:
	 格式得當。
	 盡量簡化代碼。
	 簡單封裝一下dom操作:獲取dom、addClass、removeClass、事件綁定等。
	 合理的注釋,每個方法都得有注釋。
	 函數命名按照功能命名。

1.HTML文檔結構

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<title></title>
	<link rel="stylesheet" href="">
</head>
<body>
	<header></header>
	<section>
		<div></div>
		…… …… …… ……
		<div></div>
	</section>
	<footer></footer>
	<script></script>
</body>
</html>
  • 本結構由html5中語義化標簽構成,使得文檔易讀、結構清晰。
  • 細節則根據題意和規則設計來完善

2.CSS布局與動畫

  • 動畫
    • 設定路徑實現簡單的翻轉發牌animation動畫
/*繪制透明的綠色桌面以及木質桌面邊緣*/
@keyframes sendCardTo1 
{
	0% { right:-450px;top: 30px; transform: rotate(240deg); }
	100% { right: 0; transform: rotate(0); }
}
@keyframes sendCardTo2 
{
	0% { right:-220px; top:30px; transform: rotate(240deg); }
	100% { right: 0; top:0; transform: rotate(0); }
}
@-moz-keyframes sendCardTo1 /* Firefox */
{sendCardTo1
    0% { right:-450px;top: 30px; transform: rotate(240deg); }
    100% { right: 0; transform: rotate(0); }
}
@-moz-keyframes sendCardTo2 /* Firefox */
{
    0% { right:-220px; top:30px; transform: rotate(240deg); }
    100% { right: 0; top:0; transform: rotate(0); }
}
@-webkit-keyframes sendCardTo1 /* Safari 和 Chrome */
{
    0% { right:-450px;top: 30px; transform: rotate(240deg); }
    100% { right: 0; transform: rotate(0); }
}
@-webkit-keyframes sendCardTo2 /* Safari 和 Chrome */
{
    0% { right:-220px; top:30px; transform: rotate(240deg); }
    100% { right: 0; top:0; transform: rotate(0); }
}
@-o-keyframes sendCardTo1 /* Opera */
{
    0% { right:-450px;top: 30px; transform: rotate(240deg); }
    100% { right: 0; transform: rotate(0); }
}
@-o-keyframes sendCardTo2 /* Opera */
{
    0% { right:-220px; top:30px; transform: rotate(240deg); }
    100% { right: 0; top:0; transform: rotate(0); }
}
  • 布局
    • 繪制桌面主體以及發牌區牌及牌堆
    • 控制各個部件定位,樣式
    • 調整至可以正常在移動端(手機端)進行游戲
.circle {
    background: rgba(7,121,5,0.7);
    border-radius: 50%;
    position: absolute;
    top: -460px;
    left: 145px;
    width: 900px;
    height: 900px;
    border: 30px solid rgba(85,72,4,0.9);
}
/*發牌區動畫的出發點以及牌*/
.send {
    position: absolute;
    top: 531px;
    right: 50px;
    height: 85px;
    width: 60px;
    -ms-transform: rotate(35deg); /* IE 9 */
    -webkit-transform: rotate(35deg); /* Safari and Chrome */
    -o-transform: rotate(35deg); /* Opera */
    -moz-transform: rotate(35deg); /* Firefox */
    transform: rotate(35deg);
    background-color: white;
    z-index: 2;
    border-top: 3px solid white;
    border-right: 2px solid white;
}

3.JavaScript功能模塊

  • sendCard():
    • 利用num來實現第一次發牌為一人兩張,第二次開始皆為一張。
    • 使用setTimeout函數來實現一次性的定時操作:使得在動畫結束后的已設定的時刻,依次調用realSend()函數以銜接發牌動作。
    • 實現了防治未開始游戲便點擊亮牌的錯誤邏輯:新游戲開始調用sendCard(id)則begain++,若未執行此處,則要牌和亮牌均無法執行。
//此為核心代碼,重復部分已省去,詳情見源代碼。
var num = 1;
    if (count[1].count == 0) {
        num = 2;
        //AI
        for (var i = 0; i < num; ++i) {
            setTimeout(function() {realSend(1)}, i * 100);
            setTimeout(function() {
                var n = document.getElementById('p_1')
                .getElementsByClassName('send-card1');
                if (n.length > 0)
                    n[0].classList.remove('send-card1');
            }, i * 100 + 800);
        }
    }
  • showCard():
    • 實現了防止未開始游戲便點擊亮牌的錯誤邏輯
    • 實現了亮牌后的一系列函數動作:
    • getMax()、alert(count[id].sum)、lose()(已分別注釋)
    • 以及根據結果判斷輸贏並彈出提示。
//此為核心代碼,重復部分已省去,詳情見源代碼。
var begain = 0;//防止沒開始游戲直接點擊要牌和亮牌的按鈕

function showCard() {
    if (begain == 0) {} else {
        var hidden = document.getElementById('p_1')
        .getElementsByClassName('card-hidden')[0];
        if (typeof(hidden) != "undefined")
            hidden.result();
        if (over)
            return;
        count[1].sum = getMax(1);
        count[2].sum = getMax(2);
		 … … … …
		if(){alert("… …"); }else{alert("… …"); };
}
  • realSend()
    • 通過getPorker()、getPorkerPos()獲得初始撲克牌位置真實發牌.
    • 並進行相關的dom操作html以完成真實發牌動作。
    • 如果一開始兩張牌為1和10則為黑傑克。
    • 如果2號玩家擁有黑傑克而庄家沒有則籌碼翻倍。
    • 根據count[id].sum > 21與否對lose()進行傳遞id以獲得相應提示。
if (id == 1 && count[1].count == 1) {
        /* 庄家第二章暗牌 通過屬性k, v來實現明牌和計算 */
        newNode.className = "send-card" + id + " card-hidden";
        newNode.setAttribute('k', pos);
        newNode.result = function() {
            this.className = "card";
            this.style.backgroundPosition = 
            this.getAttribute('k');
        }
    } else {
        newNode.className = "send-card" + id + " card";
        newNode.style.backgroundPosition = pos;
    }
    if (count[id].count == 1)
        newNode.setAttribute('v', card.value);
    node.appendChild(newNode);

    ++count[id].count;
    /* 對A單獨處理 */
    if (card.value != 1) {
        if (card.value > 10)
            count[id].sum += 10;
        else
            count[id].sum += card.value;
    } else ++count[id].A;

    var ai = count[1];
    var pl = count[2];
    if (pl.A == 1 && pl.count == 2 && pl.sum == 10)
    //如果一開始兩張牌為1和10則為黑傑克。
        blackjack = 1;
    if (blackjack == 
    1 && ai.A != 1 && ai.count != 2 && ai.sum != 10)
        scale = 2;   
        //如果2號玩家擁有黑傑克而庄家沒有則籌碼翻倍。

    /* 先判定是否已經結束 否則調用機器人函數 */
    if (count[id].sum > 21)
        lose(id);
    else if (count[id].sum + count[id].A > 21)
        lose(id);
    else if (id != 1 && count[2].count > 2)
        AI();
  • getPorker()

    • 抽牌函數。
    • 每次從開局時定義好的52個數的數組中不放回(porker.pop();)抽牌。
    • 返回tmp關聯數組供getPorkerPos調用。
  • getPorkerPos()

    • 接受傳入的tmp參數以獲取背景坐標
function getPorkerPos(tmp) {
    // console.log(tmp.value);
    return porkerPos[tmp.suit.toString()
     + tmp.value.toString()];
}
  • getMax()

    • 在sendCard中調用,為的是實現每次傳入id值算總和時智能判定將A算作1或者11,獲得最大的優勢。
    • 如果把A算作11大於21,則將其算為1,否則,算11。
    • 隨后返回sum。
  • lose()

  • 接受由realCard()傳入的id參數,以銜接彈窗提示判斷輸贏。

  • 根據不同情況,編寫不同的獲勝提示,以二號玩家為操作玩家的角度提示。

  • init()

    • 初始化游戲,將候牌區的清空並替換成玩家編號提示。
    • 重置blackjack,scale,over值。
  • AI()

    • 函數功能:當sendCard()觸發AI執行邏輯:
    • 若1號電腦拿牌總和小於16或者A牌經過getMax()智能處理后依舊小於16時,執行realSend(1)以繼續要牌,這是一個智能判斷,增加了游戲的可玩性。
    • 同時配合動畫刪除發牌區中的待發牌,然后一張隨機抽取的牌顯示在相應的位置。
function AI() {
    if (noneed)
        return;
    if (count[1].sum + count[1].A < 16 || getMax(1) < 16) {
        realSend(1);
        setTimeout(function() {
            var n = document.getElementById('p_1')
            .getElementsByClassName('send-card1');
            if (n.length > 0)
                n[0].classList.remove('send-card1');
        }, 800);
    } else
        noneed = 1;
}
  • DOM操作實現Actions按鈕及發牌堆首張牌的點擊功能函數的事件綁定:
// 給[btnName]按鈕 添加fn()功能並發牌功能
function addFn(btnName,fn) {
var btnName = document.getElementById("btnName");
    btnName.onclick = function() {
        if (begain == 0) {} else {
            fn();
        }
    }
}

四.游戲測試(以下所列皆可正常運行游戲)

1.PC端

  • Mac os x
    • chrome
    • firefox
    • safari
    • IE11
    • Edge
  • Windows 10
    • chrome
    • firefox
    • safari
    • IE11
    • Edge

2.移動端

  • Android:5.1
    • 自帶瀏覽器
  • ios:9.3
    • Safari

五.番外篇

異常:若遇到任何問題導致無法正常瀏覽源代碼

致謝:

感謝北京奇虎360可以給我這次展示自己的機會,無論結果如何。

版權:

本游戲僅為奇虎360公司前端星計划編寫,任何人未征得本人同意
之前請勿使用本代碼作商業用途。

[360前端星計划]總部學習筆記(6/6)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM