Index

HOME > プログラムTOP > JavaScript



JSブラックジャック

 コンピュータと対戦するブラックジャックゲームのソースです。ディーラー方式の一般的なルールとは若干異なるところがあります。「ルールを見る」ボタンで確認してみてください。ゲームの難易度は結構難しいと思います。参考にされる方はもう少しやさしくした方がいいかもしれません。DOMやらクラスやら使っていますが、もう少しすっきりできそうな気がします。対象ブラウザはIE5以上、NN6以上、Firefox、Safariです。
 

[ INDEX ]
宣言部
初期化のための関数
イベント処理のための関数
ゲーム実行にかかわる関数
導入例

・トランプのアイコン(GIF) : サンプル / ダウンロード(ZIP)

sasaraan programming

Exposition

■宣言部

 クラスをふたつつくります。JavaScriptではすべてがオブジェクトなので、関数もクラスも区別はないということなのでしょう。functionキーワードで作成できるようです。プロパティはthisキーワードで登録できるということ。さらに、宣言がそのままコンストラクタとして作用するらしいです。メソッドも登録できますが今回は使用していません。
 @ Cardクラス ・・・ トランプの札52枚 + 裏面 + 配置場所表示用 = 54枚
 A Playerクラス ・・・ プレーヤーのステータス記憶用(CPU用とユーザー用)
/*** 宣言部 ***/

// トランプ(Cardオブジェクト)の配列
var nCard = 54;
var aCard = new Array(nCard);

// プレーヤー(Playerオブジェクト)の配列 :: [0]CPU, [1]ユーザー
var aPlayer = new Array(2);

// Card クラス
function Card() {
    this.image = null;    // Imageオブジェクト
    this.figure = 0;      // 数字(1-13)
    this.selected = false;  // 選択済みかどうか
}

// プレーヤークラス
function Player() {
    this.money = 10000;	  // 持ち金
    this.stand = true;		  // standコール
    this.score = 0;		    // 手札の点数
    this.count = 0;		    // 手札の枚数
    this.images = new Array(5);	// 表示イメージ
    this.indices = new Array(5);	// 手札のカードのインデックス
    this.textnode = null;	  // textnodeオブジェクト(表示文字列用)
    this.tdstatus = null;	  // tdオブジェクト(文字列表示用)
    this.tdimage = null;	  // tdオブジェクト(イメージ表示用)
} 

■初期化のための関数

 初期化はInit関数を呼び出して行います。この関数は以下の処理を行います。
 @ バージョンチェック
 A トランプ画像のプリロード(54イメージ分)
 B プレーヤーオブジェクトの作成(CPU用・ユーザー用)
 C ゲーム表示領域の作成(テーブルとボタン)
/*** 初期化のための処理 ***/

// プリロード
function PreLoad() {
    var strDir = "image/";
    var strExt = ".gif";
    var aFile = new Array(
        "S1","S2","S3","S4","S5","S6","S7","S8","S9","S10","S11","S12","S13",
        "H1","H2","H3","H4","H5","H6","H7","H8","H9","H10","H11","H12","H13",
        "D1","D2","D3","D4","D5","D6","D7","D8","D9","D10","D11","D12","D13",
        "C1","C2","C3","C4","C5","C6","C7","C8","C9","C10","C11","C12","C13",
        "Rev6"/* 裏面用 */,"XV"/* 配置場所用 */);
    var i;
    for(i = 0; i < nCard; i++) {
        aCard[i] = new Card();
        with (aCard[i]) {
            image = new Image();
            image.src = strDir + aFile[i] + strExt;
            index = i;
            figure =i % 13 + 1;
            selected = false;
        }
    }
} 

// テーブルの作成
// ... 引数 index :: 0=CPU用, 1=ユーザー用
// ... 戻り値 :: 作成したテーブルオブジェクト
function CreateTable(index) {
    // ゲーム領域の追加
    tBJ = document.createElement("table");
    var tbodyElem = document.createElement("tbody");
    var trElem1 = document.createElement("tr");
    var trElem2 = document.createElement("tr");
    aPlayer[index].tdstatus =  document.createElement("td");
    aPlayer[index].tdimage = document.createElement("td");

    // テーブルの設定
    with (tBJ) {
        style.width = "170px";
        style.fontSize = "9pt";
        style.border = "1px solid white";
        style.margin = "8px";
    }

    // ステータス表示領域の追加
    with (aPlayer[index].tdstatus) {
        style.color = "white";
        style.backgroundColor = "green";
        style.borderWidth = "0 0 1px 0";
        style.borderStyle = "solid";
        style.borderColor = "white";
        style.padding = "1pt 2pt";
    }

    // カード表示領域の追加
    with (aPlayer[index]) {
        tdimage.style.height = "40px";
        for (i=0; i<5; i++) {
            images[i] = new Image();
            images[i].src = aCard[53].image.src;
            tdimage.appendChild(aPlayer[index].images[i]);
        }
    }

    // 各オブジェクトの追加
    aPlayer[index].textnode = index == 0?
            document.createTextNode("ブラックジャックゲーム"):
            document.createTextNode("Playボタンを押してください");
    aPlayer[index].tdstatus.appendChild(aPlayer[index].textnode);
    trElem1.appendChild(aPlayer[index].tdstatus);
    trElem2.appendChild(aPlayer[index].tdimage);
    tbodyElem.appendChild(trElem1);
    tbodyElem.appendChild(trElem2);
    tBJ.appendChild(tbodyElem);

    return tBJ;
} 

// ボタンの追加
function AddButtons()
{
	var sTag = "<input type='button'";
	var sStyle = "style='width:43px; margin:1px;'>";
	document.write("<form style='width:182px'>");
	document.write(sTag, " value='Play' ", "onclick='Play_Click()' ", sStyle);
	document.write(sTag, " value='Hit' ", "onclick='Hit_Click()' ", sStyle);
	document.write(sTag, " value='Stand' ", "onclick='Stand_Click()' ", sStyle);
	document.write(sTag, " value='Help' ", "onclick='Help_Click()' ", sStyle);
	document.write("</form>");
}

// プレーヤーの登録
function RegistPlayer() 
{
    aPlayer[0] = new Player();    // CPU用
    aPlayer[1] = new Player();    // ユーザー用
}

// 初期化
// ... 引数oParent :: 親オブジェクト
function Init(oParent)
{
    if (!document.createElement) { 
        document.write("ゲームを起動することができませんでした。");
        return;
    }

    // トランプ画像のプリロード
    PreLoad();	

    // プレーヤーの登録
    RegistPlayer();

    // ゲーム領域の追加
    with (oParent) {
        style.backgroundColor="green";  // 親オブジェクトの背景色
        appendChild(CreateTable(0));    // CPU側テーブル
        appendChild(CreateTable(1));    // ユーザー側テーブル
    }

    // ボタンの追加
    AddButtons();
} 

■イベント処理のための関数

四つのボタンのクリックイベントに対応するコードを記述していきます。
 [Play]ボタン ・・・ ゲームを初期化し、カードを二枚づつ配ります。
 [Hit]ボタン ・・・ カードを一枚引きます。
 [Stand]ボタン ・・・ 一ゲームの終了を宣言。点数が計算され結果が表示されます。
 [Help]ボタン ・・・ ヘルプを表示します。
/*** イベント ***/

// [Play]ボタンのクリックイベント
function Play_Click()
{
    var i, j;

    if (!aPlayer[1].stand) {
        alert("まだゲームが終わっていません。\nHitボタンかStandボタンを押してください。");
        return;
    }

    // プレーヤーの初期化
    var bMinus = (aPlayer[0].money < 0 || aPlayer[1].money < 0);  // 破産しているか
    for (i=0; i<2; i++)  {
        with (aPlayer[i]) {
            for (j=0; j<5; j++) {
                if (indices[j] >= 0) aCard[indices[j]].selected = false;
                images[j].src = aCard[53].image.src;
                indices[j] = -1;
            }
            stand = false;
            score = 0;
            count = 0;
            if (bMinus) money = 10000;  // 破産の場合は初期値に戻す
        }
    }
	
    // シャッフル
    Shuffle();

    // プレーヤーに二枚づつ配る
    for (i=0; i<2; i++) {
        for (j=0; j<2; j++) DrawCard(i);
    }

    ShowStatus();    // ステータス文字列の表示
}

// [Hit]ボタンのクリックイベント
function Hit_Click()
{
    if (aPlayer[1].stand) {
        alert("ゲームは終了しています。\nPlayボタンを押してください。");
        return;
    }

    // ユーザー側
    if (aPlayer[1].count < 5) DrawCard(1);	
    else alert("引けるカードは5枚までです。");

    // CPU側
    if (!aPlayer[0].stand) DrawCard(0);

    ShowStatus();    // ステータス文字列の表示
}

// [Stand]ボタンのクリックイベント
function Stand_Click()
{
    if (aPlayer[1].stand) {
        alert("ゲームは終了しています。\nPlayボタンを押してください。");
        return;
    }

    // ユーザーのstand宣言
    aPlayer[1].stand = true;
    while (!aPlayer[0].stand) {
    DrawCard(0);
    }

    // CPUの一枚目を表にする
    aPlayer[0].images[0].src = aCard[aPlayer[0].indices[0]].image.src;

    ShowStatus();    // ステータス文字列の表示
} 

// [Help]ボタンのクリックイベント
function Help_Click()
{
    window.open("bjhelp.html", "HelpWnd", "");
} 

■ゲーム実行にかかわる関数

ゲームの心臓部です。すべて、ボタンのクリックイベントから呼び出されます。
 ・Shuffle ・・・ カードをシャッフルします。
 ・DrawCard ・・・ カードを一枚引いて現在の手札の点数を算出し生ます。
 ・ShowStatus ・・・ ゲームの終了を判定し、ステータス文字列を表示します。
/*** ゲームの実行関数 ***/

// シャッフル
function Shuffle() {
    var i, j;
    for (i=0; i<52; i++) {
        j = Math.floor(Math.random() * 100) % 52;
        var oCard = aCard[i];
        aCard[i] = aCard[j];
        aCard[j] = oCard;
    }
}

// 一枚引く
// ... 引数 :: プレーヤー番号
function DrawCard(nPlayer) {
    // カードを選択
    var nIndex;
    while (1) {
        nIndex = Math.floor(Math.random() * 100) % 52;
        if (aCard[nIndex].selected == false) {
            aCard[nIndex].selected = true;
            break;
        }
    }

    // カードを配る
    with (aPlayer[nPlayer]) {
        if (nPlayer == 0 && count == 0) 
            images[count].src = aCard[52].image.src;  // CPUの一枚目を裏に
        else
            images[count].src = aCard[nIndex].image.src;
        indices[count] = nIndex;
        count++;
     }

    // 点数の計算
    var bAce = false;    // エースがあるか
    var i;
    aPlayer[nPlayer].score = 0;
    for (i=0; i<5; i++) {
        var nCard = aPlayer[nPlayer].indices[i];
        if (nCard != -1) {
            if (aCard[nCard].figure >= 10) aPlayer[nPlayer].score += 10;
            else aPlayer[nPlayer].score += aCard[nCard].figure;
            if (aCard[nCard].figure == 1) bAce = true;
        }
    }

    // エースがあるときは再計算
    if (bAce && aPlayer[nPlayer].score+10 <= 21) 
        aPlayer[nPlayer].score += 10;

    // CPUのときはstandの判断
    if (nPlayer == 0) {
        var nNum;
        with (aPlayer[0]) {
            if (count == 5 || score >= 18) {  /* 手札5枚か18以上でstand */
                stand = true;
            } else if (score == 17) {        /* 17→9/10の確率でstand */
                nNum = Math.random();
                if (nNum > 0.1) stand = true;
            } else if (score == 16) {        /* 16→1/5の確率でstand */
                nNum = Math.random();
                if (nNum < 0.2) stand = true;
            } else if (score == 15) {        /* 15→1/20の確率でstand */
                nNum = Math.random();
                if (nNum < 0.05) stand = true;
            }
        }
    }
}

// ステータスの表示
function ShowStatus() {
    var i;
    var nScore;        // 点数
    var sResult;        // 表示文字列
    var nWin, nLoose;  //勝った方のインデックス、負けた方のインデックス

    // ゲーム終了 :: 0:継続、1:終了、2:破産終了
    var nOver = (aPlayer[0].stand && aPlayer[1].stand)? 1: 0;
    if (nOver) {
        if (aPlayer[0].score > 21) {
            if (aPlayer[1].score <= 21) nWin = 1;
            else nWin = -1;
        } else {
            if (aPlayer[1].score > 21) nWin = 0;
            else if (aPlayer[0].score > aPlayer[1].score) nWin = 0;
            else if (aPlayer[0].score < aPlayer[1].score) nWin = 1;
            else nWin = -1;
        }
        if (nWin != -1) {
            nLoose = nWin==0? 1: 0;
            if (aPlayer[nWin].score == 21) {    /* ブラックジャックは二倍 */
                if (aPlayer[nWin].count == 2) {
                    aPlayer[nWin].money += 2000;
                    aPlayer[nLoose].money -= 2000;
                } else {            /* 21のときは1.5倍 */
                    aPlayer[nWin].money += 1500;
                    aPlayer[nLoose].money -= 1500;
                }
            } else {                /* 通常は1000点 */
                aPlayer[nWin].money += 1000;
                aPlayer[nLoose].money -= 1000;
            }
            // 破産終了のチェック
            if (aPlayer[nWin].money < 0 || aPlayer[nLoose].money < 0) nOver = 2; 
        }
    }

    // ステータス文字列
    for (i=0; i<2; i++) {
        with (aPlayer[i]) {
            // CPUの一枚目は"??"を表示
            nScore = (i==0 && nOver==0)? "??": aPlayer[i].score;
            if (nOver == 2) {	            /* 破産終了時 */
                sResult = money < 0? "  >>> 破産 <<<": "  <<< 優勝 >>>";
            } else if (nOver == 1) {        /* 一回分の終了 */
                if (nWin == -1) sResult = "  = 引き分け =";
                else if (nWin == i) sResult = "  +++ 勝ち +++";
                else sResult = "  --- 負け ---";
            } else if (i == 1 && count == 2 && score == 21) {    /* ブラックジャック成立時 */
                sResult = "  BlackJack !";
            } else {                    /* ゲーム継続時 */
                sResult = "  $" + aPlayer[i].money;
            }
            // ステータス文字列の削除と追加
            tdstatus.removeChild(textnode);
            textnode = i == 0?
                    document.createTextNode("CPU : " + nScore + "点" + sResult):
                    document.createTextNode("YOU : " + nScore + "点" + sResult);
            tdstatus.appendChild(textnode);
        }
    }
} 

■導入例

駆け引きを重視するなら、掛け金を設定できるようにした方がおもしろいと思います。難易度は、DrawCard関数内のCPUがStandする確率の箇所を調整するといいでしょう。一般には、ディーラーは16以下で必ずHitしなければならず、また、17以上で必ずStandしなければならず、その代わり21を超えても、プレーヤーが21を超えたら勝ちとなるようです。
*** 使用例 ***

<head>
    ...
    <script type="text/javascript" src="*****.js"></script>
    ...
</head>
<body id="bj">
    <script type="text/javascript"><!--
        Init(document.getElementById("bj"));
    --></script>
</body>

www.sasaraan.net

(c) morijoh