C 語言初學教材 - 第四章 最後一次模擬

如果實際要開發遊戲,我們的模擬還是不夠的,因為我們還不知道使用擬隨機數設計遊戲,最後得到勝負結果的機率為何。我們希望開發出的是一個公平遊戲,所以每一次重複玩,最後得到勝負結果的機率應該都要是 1 / 2 ,當然這是就理論上來說。



我們繼續重複進行遊戲的模擬,程式碼如下
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define SIZE_A 1000 // 用為骰子表格
#define SIZE_B 100 // 用為模擬次數
#define ORIGIN 100 // 使用者籌碼的原始值

int main(void)
{
    int points1[SIZE_A]; // 第一組骰子表格
    int points2[SIZE_B]; // 第二組骰子表格
    int result[SIZE_A]; // 儲存玩家的籌碼變化
    int s[SIZE_B]; // 儲存每一次的模擬結果
    int temp; // 暫存變數
    int i, j; // 迴圈用變數
    int jeton; // 玩家的籌碼
    int n = 5; // 儲存籌碼值
    int counter1 = 0; // 累計 points1 贏的次數
    int counter2 = 0; // 累計 points2 贏的次數
    int counter3 = 0; // 累計平手的次數
    int second; // 取得現在的秒數
    
    srand(time(NULL));
    
    for (j = 0; j < SIZE_B; j++) {    
        jeton = ORIGIN;
        second = time(NULL);
    
        // 建立兩組骰子的亂數表
        for (i = 0; i < SIZE_A; i++) {
            temp = rand() % 6;
            if (temp == 0) {
                temp = 6;
            }
            points1[i] = temp;
          
            temp = rand() % 6;
            if (temp == 0) {
                temp = 6;
            }
            points2[i] = temp;
        }
        
        // 假設玩 SIZE 次,計算使用者手中籌碼的變化
        for (i = 0; i < SIZE_A; i++) {
            if (points1[i] == points2[i]) {
                result[i] = jeton;
            }
        
            if (points1[i] > points2[i]) {
                 jeton += n;
                 result[i] = jeton;
            }
        
            if (points1[i] < points2[i]) {
                 jeton -= n;
                 result[i] = jeton;
            }
        }
        
        // 印出 300 次的使用者現有籌碼
        for (i = 1; i < 301; i++) {
            printf("%3d ", result[i]);
        
            if (i % 15 == 0) {
                printf("\n");
            }
        }
        printf("\n");
        
        // 約暫停 1 秒
        while (second == time(NULL)) {
        }
        
        // 將第 300 次使用者的籌碼值存進陣列 s[] 中
        s[j] = result[i];
    }
    
    // 累計總共輸贏的次數
    for (j = 0; j < SIZE_B; j++) {
        if (s[j] == ORIGIN) {
            counter3++;
        }
        
        if (s[j] > ORIGIN) {
            counter1++;
        }
        
        if (s[j] < ORIGIN) {
            counter2++;
        }
        
        printf("%d ", s[j]);
    }
    printf("\n");
    
    printf("\n模擬結果,使用者贏了 %d 次, 電腦贏了 %d 次,平手 %d 次....\n", counter1, counter2, counter3);
    
    return 0;
}

/* 《程式語言教學誌》的範例程式
    http://pydoing.blogspot.com/
    檔名:dice7.c
    功能:簡單的擲骰子遊戲
    作者:張凱慶
    時間:西元2010年7月 */


我們在原本模擬 300 次遊戲的外層,多增加另一個 for 迴圈,用為記錄重複進行遊戲的結果,常數 SIZE_B 則是我們進行模擬的次數,目前設為 100 次。


第 72 行到第 74 行
// 約暫停 1 秒
while (second == time(NULL)) {
}


我們在 for 迴圈開始時,先用 second 取得函數 time() 回傳的目前秒數,這裡則是 for 迴圈結束的地方,這樣做的目的是會產生螢幕畫面約暫停一秒的效果,好讓我們可以看到每一次的模擬的情況。


來編譯執行看看吧!



嗯,使用者贏 51 次,電腦贏 48 次,平手一次,這樣其實看不出什麼。所以,我們再來模擬一百萬次看看吧!很簡單的,就把 SIZE_B 的值調整為 1000000 就可以囉!



使用者贏的次數與電腦贏的次數相當接近,於是我們可以做出一個結論,採用擬隨基數的設計方式執行這個遊戲,使用者與電腦贏的機都是 48.7% ,平手的機率則是 2.6% 。


問題與討論
  1. 我們所使用的約暫停 1 秒的機制,有沒有什麼缺點?
  2. 用自己的電腦模擬一百萬次,看看得到的結果如何?
  3. 想一想,有沒有方法可以調整輸贏的機率,例如給玩遊戲的使用者多點樂趣,將使用者贏的機率提高為 75% 。




3 則留言:

Unknown 提到...

請問第77行 s[j] = result[i]; 應該是 s[j] = result[300];這樣才是將第300次使用者的籌碼值存進陣列吧?

Unknown 提到...

仔細看63行
i最後的值會是300
又63行到75行沒有對i初始化 所以是可行的

Unknown 提到...

請問一下
在第12行的宣告中
將point2宣告大小為SIZE_B(100)
然而在31行其迴圈將重複執行的次數為SIZE_A(1000)
而此迴圈又對point2進行指派值的動作
這樣不是就超過point2的宣告大小嗎@@