ジャンケンゲームのサンプル
C言語編の最後に、コンソール画面で遊べるジャンケンゲームのサンプルコードを提示しておきます。
以下のソースコードをそのままコピー&ペーストでVisual Studio内のソースコードに張り付けて、ビルドするだけで実行できます。
(Visual Studioじゃなくても大丈夫なはずです)
ただしソースファイルを分割しているので、ソースコードの分割の方法に従ってソースファイルをふたつ、ヘッダファイルをひとつ作成して、それぞれ貼り付けてください。
//main.c
/*
ジャンケンゲーム
main.cでは主に画面表示に関わる処理を記述する
ゲーム本体の処理はgameEngine.cに記述する
*/
#include <stdio.h>
#include "gameEngine.h"
int main()
{
//画面表示の定義
const char* const SHAPENAME[] = { "グー ", "チョキ", "パー " };
const char* const SHAPESYMBOLYOU[] = { "ο", "<", "Ψ" };
const char* const SHAPESYMBOLCPU[] = { "ο", ">", "Ψ" };
const char* const EMOJIYOU[] = { "(;・ω・)", "(`・ω・)", "(´・ω・)" };
const char* const EMOJICPU[] = { "| ̄皿 ̄;|", "Σ×皿×ミ", "|◎皿◎|" };
const char* const EMOJIWINNER[] = { " ", "♪ ", " ♪" };
const char* const CALL[] = {
"じゃん、けん...", "ぽん!",
"あーいこーで...", "しょ!"
};
//掛け声の状態
typedef enum { CALL_NORMAL, CALL_DRAW = 2 } CallType;
int win = 0, lose = 0, draw = 0;
CallType state = CALL_NORMAL; //掛け声
InitGame();
//ゲームの設定を変更する場合
//InitParam(10, 4, 0.15f, 2.0f);
//イカサマ設定を変更する場合
//InitIkasama(0.1f, 0.1f);
printf(
"☆- - - - - - - - - - - -☆\n"
"☆- - - C言語による - - -☆\n"
"☆- - ジャンケンゲーム - -☆\n"
"☆ (・ω・) ☆ | ̄皿 ̄| ☆\n"
"☆- - - - スタート - - - -☆\n"
"\n");
while (1)
{
printf("残りコイン: %d, 遊戯回数: %d\n", CurrentCredit(), win + lose + draw);
printf("勝ち: %d回, 負け: %d回, あいこ: %d回\n\n", win, lose, draw);
printf("- - - - - - - - - - - -\n");
printf("%s\n", CALL[state]);
printf("[z,1]:グー, [x,2]:チョキ, [c,3]:パー, [q,0]:ゲーム終了\n");
ShapeType you, cpu;
you = GetKeyInput();
if (you <= QUIT) //終了
break;
if (you <= INVALID) //入力文字が不正
{
printf("有効なキーを入力しなおしてください。\n\n");
continue;
}
cpu = GetCpu(you);
ResultType result = Battle(you, cpu);
printf("\n%s\n", CALL[state + 1]);
printf(" YOU:%s VS CPU:%s\n", SHAPENAME[you], SHAPENAME[cpu]);
printf("%s%s%s%s%s\n\n",
EMOJIYOU[result], SHAPESYMBOLYOU[you],
EMOJIWINNER[result],
SHAPESYMBOLCPU[cpu], EMOJICPU[result]);
int prize = ChangeCredit(result);
switch (result)
{
case WIN:
printf("フィーバー!");
for (size_t i = 0; i < prize; i++)
printf("☆");
printf("\nヤッピー♪: %d枚獲得!\n", prize);
state = CALL_NORMAL;
++win;
break;
case LOSE:
printf("ズコー!\n\n");
state = CALL_NORMAL;
++lose;
break;
default:
printf("(あいこ)\n\n");
state = CALL_DRAW;
++draw;
break;
}
printf("\n");
if (CurrentCredit() <= 0)
{
printf("コインがなくなりました。\n");
break;
}
}
printf("\n- - - - - - - - - - - -\n");
printf("\nゲーム終了。\n");
printf("遊戯回数: %d\n", win + lose + draw);
printf("コイン: %d\n", CurrentCredit());
printf("勝ち: %d回, 負け: %d回, あいこ: %d回\n", win, lose, draw);
printf("Enterキーで終了...");
getchar();
}
//gameEngine.c
/*
ジャンケンゲーム
gameEngine.cではゲームの具体的な挙動を実装する
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "gameEngine.h"
//非公開関数のプロトタイプ宣言
static ShapeType ForceWin(ShapeType x);
static ResultType Judge(ShapeType you, ShapeType cpu);
static int Random01ByProb(double prob);
//ゲーム設定値
static struct {
int credit; //コイン枚数
int wonCredit; //勝利時にもらえるコイン最大数
float bonusProb; //勝利時にボーナスがもらえる確率
float bonusRate; //ボーナスの倍率
float forceWinProb; //強制的にCPUが勝つ確率(イカサマ)
float reBattleProb; //CPUが負けた場合に再試合する確率(イカサマ)
} param;
//初期化処理
//ゲーム開始前に一度だけ呼び出す
void InitGame()
{
static char isInit;
if (isInit > 0)
return;
isInit = 1;
srand((unsigned)time(NULL)); //乱数初期化
param.credit = 10;
param.wonCredit = 4;
param.bonusProb = 0.15f;
param.bonusRate = 2.0f;
param.forceWinProb = 0.1f;
param.reBattleProb = 0.1f;
}
//設定の調整
//任意でゲーム開始前に一度だけ呼び出す
//0未満を指定したパラメーターは変更しない
//credit: コイン初期枚数(1以上)
//wonCredit: 勝利時にもらえるコイン最大数(1以上)
//bonusProb: 勝利時にボーナスがもらえる確率(100分率)
//bonusRate: ボーナス時の倍率(1以上)
void InitParam(int credit, int wonCredit, float bonusProb, float bonusRate)
{
InitGame();
if (credit >= 1) param.credit = credit;
if (wonCredit >= 1) param.wonCredit = wonCredit;
if (bonusProb >= 0) param.bonusProb = bonusProb;
if (bonusRate >= 1) param.bonusRate = bonusRate;
}
//イカサマの設定
//任意でゲーム開始前に一度だけ呼び出す
//0未満を指定したパラメーターは変更しない
//forceWinProb: 強制的にCPUが勝つ確率(100分率)
//reBattleProb: CPUが負けた場合に再試合する確率(100分率)
void InitIkasama(float forceWinProb, float reBattleProb)
{
InitGame();
if (forceWinProb >= 0) param.forceWinProb = forceWinProb;
if (reBattleProb >= 0) param.reBattleProb = reBattleProb;
}
//現在のコイン枚数を返す
int CurrentCredit()
{
return param.credit;
}
//勝負結果を返す
ResultType Battle(ShapeType you, ShapeType cpu)
{
ResultType result = Judge(you, cpu);
//イカサマ機能が有効な場合は
//CPUが負けた場合に再試合
if (result == WIN && Random01ByProb(param.reBattleProb))
{
cpu = GetCpu(-1);
result = Judge(you, cpu);
}
return result;
}
//勝ち負け判定
ResultType Judge(ShapeType you, ShapeType cpu)
{
if (you == cpu)
return DRAW;
if (you == ROCK && cpu == SIZER)
return WIN;
if (you == SIZER && cpu == PAPER)
return WIN;
if (you == PAPER && cpu == ROCK)
return WIN;
return LOSE;
}
//コインの変動
//戻り値は獲得したコイン枚数
int ChangeCredit(ResultType result)
{
int get;
switch (result)
{
case WIN:
//0~param.wonCredit未満の値を取得
get = rand() % param.wonCredit;
//必ず1枚は獲得
get++;
//確率で獲得コインを倍増
if (Random01ByProb(param.bonusProb))
get = get * param.bonusRate;
param.credit += get;
//プレイに使用したコインを減らす
param.credit--;
return get;
case LOSE:
//プレイに使用したコインを減らす
param.credit--;
return -1;
default:
return 0;
}
}
//強制的に勝つ手を返す
static ShapeType ForceWin(ShapeType shape)
{
switch (shape)
{
case ROCK:
return PAPER;
case SIZER:
return ROCK;
default:
return SIZER;
}
}
//CPUの手をランダムで返す
//youがジャンケンの手以外の場合はイカサマ機能を強制的に無効
ShapeType GetCpu(ShapeType you)
{
if (you >= ROCK && Random01ByProb(param.forceWinProb))
return ForceWin(you);
return rand() % 3;
}
//キー入力を受け取る
ShapeType GetKeyInput()
{
int c = getchar();
if (c == '\n')
return INVALID;
while (getchar() != '\n');
switch (c)
{
case 'z':
case '1':
return ROCK;
case 'x':
case '2':
return SIZER;
case 'c':
case '3':
return PAPER;
case 'q':
case '0':
return QUIT;
default:
return INVALID;
}
}
//prob/1の確率で1を返す
static int Random01ByProb(double prob)
{
if (prob > (double)rand() / RAND_MAX)
return 1;
return 0;
}
//gameEngine.h
#pragma once
//ジャンケンの手
typedef enum { QUIT = -2, INVALID, ROCK, SIZER, PAPER } ShapeType;
//ジャンケンの結果
typedef enum { DRAW, WIN, LOSE } ResultType;
//プロトタイプ宣言
//初期化処理
//ゲーム開始前に一度だけ呼び出す
void InitGame();
//設定の調整
//任意でゲーム開始前に一度だけ呼び出す
//0未満を指定したパラメーターは変更しない
//credit: コイン初期枚数(1以上)
//wonCredit: 勝利時にもらえるコイン最大数(1以上)
//bonusProb: 勝利時にボーナスがもらえる確率(100分率)
//bonusRate: ボーナス時の倍率(1以上)
void InitParam(int credit, int wonCredit, float bonusProb, float bonusRate);
//イカサマの設定
//任意でゲーム開始前に一度だけ呼び出す
//0未満を指定したパラメーターは変更しない
//forceWinProb: 強制的にCPUが勝つ確率(100分率)
//reBattleProb: CPUが負けた場合に再試合する確率(100分率)
void InitIkasama(float forceWinProb, float reBattleProb);
//現在のコイン枚数を返す
int CurrentCredit();
//勝負結果を返す
ResultType Battle(ShapeType you, ShapeType cpu);
//コインの変動
//戻り値は獲得したコイン枚数
int ChangeCredit(ResultType result);
//CPUの手をランダムで返す
//youがジャンケンの手以外の場合はイカサマ機能を強制的に無効
ShapeType GetCpu(ShapeType you);
//キー入力を受け取る
ShapeType GetKeyInput();
「main.c」は画面の表示に関わる処理だけを記述します。
「gameEngine.c」は実際のゲーム処理や必要な関数群を定義したゲームエンジンです。
ファイルを分割することで、main関数側は必然的にゲームの内部動作にはタッチしない処理になります。
ファイルごとにできるだけ処理を独立させることで、バグが入り込む可能性が少なくなります。
ただジャンケンをするだけではゲーム性に乏しいので「勝利時に獲得できるコイン枚数のランダム性」と「コンピューター側のイカサマ機能」を搭載しています。
勝利時にもらえるコイン数が多いので、そのままだとコインは増える一方です。
そこで、コンピューター側が勝つ確率を上げることでバランスを取っています。
コンソールアプリケーションなので、画面はどうしても寂しくなります。
一般的なゲームのように音楽や画像などを用いてゲームらしく作るにはウィンドウアプリケーションの作り方を学ぶ必要があります。
その場合であってもゲームエンジン部分(裏で動作する部分)は、サンプルコードからほとんど変更せずに流用ができると思います。
(キー入力の受け取り処理あたりは変更が必要だと思います)