ジャンケンゲームのサンプル

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関数側は必然的にゲームの内部動作にはタッチしない処理になります。
ファイルごとにできるだけ処理を独立させることで、バグが入り込む可能性が少なくなります。

ただジャンケンをするだけではゲーム性に乏しいので「勝利時に獲得できるコイン枚数のランダム性」と「コンピューター側のイカサマ機能」を搭載しています。

勝利時にもらえるコイン数が多いので、そのままだとコインは増える一方です。
そこで、コンピューター側が勝つ確率を上げることでバランスを取っています。

コンソールアプリケーションなので、画面はどうしても寂しくなります。
一般的なゲームのように音楽や画像などを用いてゲームらしく作るにはウィンドウアプリケーションの作り方を学ぶ必要があります。
その場合であってもゲームエンジン部分(裏で動作する部分)は、サンプルコードからほとんど変更せずに流用ができると思います。
(キー入力の受け取り処理あたりは変更が必要だと思います)