
游戲界面預覽

目錄
一.游戲介紹
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.素材采集





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實現單次計時。
- 實現actions系列按鈕的功能
- 本游戲規則的邏輯設定聲明:
- 每次開局一人兩張牌,后每次要牌均為單張發牌動畫。
- 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
五.番外篇
異常:若遇到任何問題導致無法正常瀏覽源代碼
- 請您及時與我聯系
- 手機號:15594980508
- 郵箱:451323138@qq.com
- 您也可以訪問我的個人小站進行測試下載:
- 同時這份產品文檔也將置頂在我的技術博客之中:
致謝:
感謝北京奇虎360可以給我這次展示自己的機會,無論結果如何。
版權:
本游戲僅為奇虎360公司前端星計划編寫,任何人未征得本人同意
之前請勿使用本代碼作商業用途。
