prg-lang-2 / final / BlackJack / report / report.typ
report.typ
Raw
#import "../../../template.typ": *

#show: report.with()

#show: reportTop.with(
  title: "プログラミング言語Ⅱ 最終課題",
  submit-date: "2025/01/26",
)

#text([BlackJack], weight: "bold", font: sans, size: 1.7em)

= ゲームの説明

ブラックジャックはディーラーとプレイヤーが手札の合計値を競うカジノゲームの一つです。プレイヤーは最初に賭け金を決め、その後配られた手札を見ながら行動を選択していきます。手札の合計値が21に近いほど有利で、21を超えると「バースト」して負けとなります。

#box()

ゲームの基本的な流れは以下の通りです。

1. プレイヤーは賭け金(ベット)を決定
2. ディーラーとプレイヤーに2枚ずつカードが配られる(ディーラーの2枚目は伏せられる)
3. プレイヤーは自分の手札に応じて行動を選択
4. プレイヤーのターン終了後,ディーラーが手札を公開し、ルールに従って行動
5. 手札の合計値を比較して勝敗を決定

#box()

カードの価値は以下のように計算されます。

- `2`〜`10`: 数字通りの値
- `J`, `Q`, `K`: 10として扱う
- `A`: 1または11として扱う(有利な方を選択して良い)

#box()

本プログラムでは、以下の機能を実装しています。

- 複数のプレイヤーが同時にプレイ可能
- コンピュータプレイヤー(Bot)との対戦に対応
- Hit、Stand、Double Down、Split、Surrenderなど本格的なルールを実装
- 設定ファイル(`config.txt`)によるゲームパラメータの調整

= プログラムの実行方法

ゲームのプログラムは複数のファイルから構成されています。方法1ではこれらのファイルをまとめた圧縮ファイルをダウンロードして解凍しコンパイルをします。一方、方法2では結合されたソースコードを直接ダウンロードしてコンパイルして実行します。そのため方法2の場合はソースコードが見にくい場合があります。この場合、ファイル名で全文検索すると該当箇所にアクセスできます。

#pagebreak()

== 方法1

1. `BlackJack.tar.gz`をダウンロード
2. 解凍する
  - 演習環境/WSL/Ubuntu等の場合
    #sourcecode[```sh
    tar -zxvf BlackJack.tar.gz
    cd BlackJack
    ```]
  - Windowsの場合\
    エクスプローラーでダウンロードしたファイルを右クリックし、解凍する
3. コンパイルを実行
  #sourcecode[```sh
  make BlackJack
  ./BlackJack
  ```]

== 方法2

1. `BlackJack.c`をダウンロード
2. コンパイルを実行
  #sourcecode[```sh
  make BlackJack
  ./BlackJack
  ```]

= 遊び方

== ゲーム開始時 <play-start>

1. プレイヤー名の入力(Enterキーで初期値を使用, 10文字まで)
2. 各プレイヤーのベット額を決定(100〜10000の範囲で指定, 設定から範囲は変更可能)
3. カードが配られ、ゲーム開始

== プレイヤーの行動選択 <play-select>

以下の行動から選択できます。括弧で囲まれたアルファベットを入力してください。状況に応じて選択可能なものだけが選択肢に表示されます。

- (H)it: カードを1枚引く
- (S)tand: 現在の手札で勝負
- (D)ouble Down: 賭け金を2倍にしてカードを1枚だけ引く
- S(p)lit: 同じランクのカード2枚を分割して別々の手札とする
- Su(r)render: 降りて賭け金の半分を回収

#pagebreak()

== ゲーム終了後  <play-end>

- 勝敗に応じてチップの精算が行われます
- ブラックジャックでの勝利は通常の1.5倍の配当が得られます(設定で変更可能)
- (N)ext gameで次のゲームを開始、(Q)uitでゲームを終了

== 設定ファイルの編集 <play-option>

`config.txt`にはゲームの設定情報が記述されています。ファイルが存在しない場合や設定値が不正な場合は起動時にデフォルト値で新規/上書き保存されます。

=== ゲームの設定

- `player_count`: プレイ可能なプレイヤー数(0 以上)
- `bot_count`: Bot の数(0 以上)
- `init_chips`: 初期チップ
- `min_bets`: 最低のベット数
- `max_bets`: 最高のベット数
- `bj_rate`: BJ 時の配当レート(`1.5` の場合 1.5 倍(3to2)の配当)

`player_count`と`bot_count`の合計は1 人以上でデフォルトで4人までですが、ターミナルの横幅を調整すると最大値は変動します。

=== Botの戦略設定

- `bot_double_down_min`: ダブルダウンする手札の最小値 (デフォルト: 9)
- `bot_double_down_max`: ダブルダウンする手札の最大値 (デフォルト: 11)
- `bot_double_down_rate`: ダブルダウンを選択する確率(%)(デフォルト: 75)
- `bot_hit_soft_rate`: 手札が 12 以下の時にヒットする確率(%)(デフォルト: 80)
- `bot_hit_middle_rate`: 手札が 13-16 の時にヒットする確率(%)(デフォルト: 40)
- `bot_hit_hard_rate`: 手札が 17-18 の時にヒットする確率(%)(デフォルト: 10)

= プログラムの内容の説明

ファイルごとに機能を分割し、各ファイルの役割を明確にしています。以下に各ファイルの説明を示します。方法2でダウンロードした場合は、ファイル名で全文検索すると該当箇所にアクセスできます。なお、関数に対するドキュメントコメントは`*.h`ファイルに記載しています。

== `util.c` / `util.h`

ゲーム内で使用するユーティリティ関数を定義しています。タイトルやボード情報を中央に表示するためにターミナルの横幅を取得する関数や、標準関数である`clock`を利用しリアルなゲーム進行にするための小数点以下の秒数に対応したスリープ関数などが含まれます。

#pagebreak()

== `config.c` / `config.h`

ゲームの設定情報を管理するための`Config`構造体と関数を定義しています。標準関数である`fopen`や`fscanf`を使用した設定ファイル(`config.txt`)からパラメータを読み込む関数や、`strcmp`を使用した設定情報を様々な型で取得するための関数が含まれます。設定項目のキーと値には文字列が使用され最大長はオブジェクト形式マクロで定義されています。また安全な変換処理のために`strtol`、文字列のコピーのために`strncpy`などの関数が使用されています。設定ファイルが存在しない場合、定数で定義されたデフォルト値を用いて新しいファイルを作成します。

== `card.c` / `card.h`

カードの情報を管理するための`Card`構造体と関数を定義しています。カードのスートとランクを表示する関数ではエスケープシーケンスを`printf`で利用してカラー表示されます。また構造体配列を使用したデッキの初期化や`rand`関数を用いたシャッフルなどの関数が含まれます。

== `player.c` / `player.h`

プレイヤーの情報を管理するための`Player`構造体と関数、ゲームの状態を表す列挙型を定義しています。文字列である`name`メンバを処理するための`strncpy`を利用したプレイヤーの初期化や手札の追加、合計値の計算などの関数が含まれます。また構造体ポインタを引数に利用したプレイヤーの行動処理に関する関数も定義されています。ここでは名前長やカード数、チップ数の最大値などの定数がマクロで定義されています。またBot用の名前が文字列の配列で定義されています。

== `game.c` / `game.h`

ゲームの進行を管理するための`Game`構造体と関数を定義しています。`Player`構造体のポインタをメンバに持ち、`malloc`や`snprintf`を利用したゲームの初期化やエスケープシーケンスを利用したタイトルやボードの表示、ベットや行動の処理などの関数が含まれます。これらの関数は`Game`構造体のポインタを引数に取り、ゲームの進行を管理します。

== `main.c`

プログラムのエントリーポイントとなる`main`関数が定義されています。ここではまず、`srand`や`time`関数を用いて乱数を初期化しタイトルを表示します。その後設定ファイルの読み込みやプレイヤーとゲームの初期化を行います。そして今までに定義した関数を呼び出し、@play-start から@play-end の通りにゲームの進行を行います。ユーザーの入力を受け付ける部分では無限ループと`fgets`関数やバッファの最大長を表すマクロ定数を使用し安全に入力を受け付けます。

#pagebreak()

= 工夫した点

== ユーザビリティの向上
- 視覚的な情報提示
  - エスケープシーケンスを利用したカラー表示によるカードスートの区別
  - プレイヤーの手札,チップ数,ベット額、各プレイヤーの状態の見やすい配置
- インタラクティブな操作性
  - リアルタイムの画面更新による臨場感のある進行
  - カード配布や行動実行時の適度な待機時間の設定
  - 状況に応じて選択可能な行動のみを表示することでミス入力を防止
- エラー処理とガイダンス
  - 無効な入力に対する適切なエラーメッセージと再入力の促し
  - ベット額の範囲外指定や所持チップ不足時の明確な警告

== 柔軟なゲーム設定
- `config.txt`によるゲーム各種パラメータとボット戦略のカスタマイズ(@play-option)
  - プレイヤー数やBot数、ベット額の最小値と最大値などゲームの設定
  - Botの戦略設定(ダブルダウン、ヒット確率など)

== 本格的なルール実装
- 本場カジノと同様の特殊ルールに対応
  - スプリット: 同じ数字のペアを2つの手札に分割
  - ダブルダウン: 賭け金を倍にしてカードを1枚だけ引く
  - サレンダー:手札が不利な時に降りて賭け金の半分を回収
- 高度な手札管理
  - エースの1/11の自動判定による最適な手札価値の計算
  - スプリット後の各手札の個別管理
  - ブラックジャック(Aと10のペア)の判定と特別配当

== リアリティーのあるボットAIの実装
- 名前とコンソールでの会話
  - ボットの名前と会話内容をランダムに設定
  - ボットの行動に応じた適切なメッセージの表示
- 戦略的な行動選択システム
  - 手札の合計値に基づく3段階の判断(12以下、13-16、17-18)
  - ディーラーの表カードを考慮したリスク判断
  - 確率要素を含む意思決定による戦略の多様化
  - リスクが高い状況での適切な降り判断
  - 所持チップに応じた賭け金の自動調整

#pagebreak()

= 既知の不具合、未実装の機能

- 本来のブラックジャックのルールではスピリットが複数回できるが、表示の問題のため本プログラムでは1回のみ対応

= 参考資料

#block(
  list([
    ブラックジャック|カジノゲームのルールと攻略ガイド (https://bright777.com/rules_blackjack)
  ],[
    【C言語】エスケープシーケンス チートシート (https://qiita.com/sudo00/items/2b2eec07d3099b5ad664)
  ],[
    乱数 - 苦しんで覚えるC言語 (https://9cguide.appspot.com/21-02.html)
  ])
)