prg-lang-2 / final / BlackJack / config.c
config.c
Raw
#include <string.h>
#include <limits.h>

#include "config.h"

const ConfigItem DEFAULT_CONFIG[] = {
    {"player_count", "1"},          // プレイヤー数
    {"bot_count", "3"},             // ボットの数
    {"init_chips", "10000"},        // 初期チップ数
    {"min_bets", "100"},            // 最小ベット額
    {"max_bets", "10000"},          // 最大ベット額
    {"bj_rate", "1.5"},             // ブラックジャック時の配当倍率
    {"bot_double_down_min", "9"},   // ボットがダブルダウンする最小値
    {"bot_double_down_max", "11"},  // ボットがダブルダウンする最大値
    {"bot_double_down_rate", "75"}, // ボットがダブルダウンを選択する確率
    {"bot_hit_soft_rate", "80"},    // ボットが12以下でヒットする確率
    {"bot_hit_middle_rate", "40"},  // ボットが13-16でヒットする確率
    {"bot_hit_hard_rate", "10"},    // ボットが17-18でヒットする確率
};
const int DEFAULT_CONFIG_COUNT = 12;

int _createConfig(const char *filename)
{
    FILE *file = fopen(filename, "w");
    if (!file)
    {
        return 0;
    }
    for (int i = 0; i < DEFAULT_CONFIG_COUNT; i++)
    {
        fprintf(file, "%s=%s\n", DEFAULT_CONFIG[i].key, DEFAULT_CONFIG[i].value);
    }
    fclose(file);
    return 1;
}

char *_trimLine(char *line)
{
    char *end;
    // 行頭の空白文字をスキップ
    while (*line == ' ' || *line == '\t' || *line == '\n' || *line == '\r')
    {
        line++;
    }
    if (*line == '\0')
    {
        return line;
    }
    // 行末の空白文字を削除
    end = line + strlen(line) - 1;
    while (end > line && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r'))
    {
        *end = '\0';
        end--;
    }
    return line;
}

Config *loadConfig(const char *filename)
{
    FILE *file = fopen(filename, "r");
    if (!file)
    {
        // ファイルが存在しない場合、デフォルト設定で作成
        if (!_createConfig(filename))
        {
            fprintf(stderr, "Error: Failed to create config file '%s'\n", filename);
            return NULL;
        }
        file = fopen(filename, "r");
        if (!file)
        {
            fprintf(stderr, "Error: Failed to load config file '%s'\n", filename);
            return NULL;
        }
    }
    Config *config = (Config *)malloc(sizeof(Config));
    if (!config)
    {
        fprintf(stderr, "Error: Failed to allocate memory for config\n");
        return NULL;
    }
    config->items = NULL;
    config->count = 0;

    // 設定ファイルを1行ずつ読み込み
    char line[CONFIG_MAX_KEY_LENGTH + CONFIG_MAX_VALUE_LENGTH + 2];
    while (fgets(line, sizeof(line), file))
    {
        char *trimmedLine = _trimLine(line);
        if (*trimmedLine == '\0')
        {
            continue;
        }
        // key=value形式をパース
        char *equalsSign = strchr(trimmedLine, '=');
        if (!equalsSign)
        {
            continue;
        }
        *equalsSign = '\0';
        char *key = _trimLine(trimmedLine);
        char *value = _trimLine(equalsSign + 1);

        // 設定項目を追加
        ConfigItem *newItems = (ConfigItem *)realloc(config->items, (config->count + 1) * sizeof(ConfigItem));
        if (!newItems)
        {
            fprintf(stderr, "Error: Failed to allocate memory for config items\n");
            free(config->items);
            free(config);
            return NULL;
        }
        config->items = newItems;
        strncpy(config->items[config->count].key, key, CONFIG_MAX_KEY_LENGTH);
        strncpy(config->items[config->count].value, value, CONFIG_MAX_VALUE_LENGTH);
        config->count++;
    }
    fclose(file);

    // 必要な設定項目が全て存在するか確認
    int missingKey = 0;
    for (int i = 0; i < DEFAULT_CONFIG_COUNT; i++)
    {
        if (getConfigValue(config, DEFAULT_CONFIG[i].key) == NULL)
        {
            missingKey = 1;
            break;
        }
    }

    // 不足している項目がある場合は設定ファイルを作り直し
    if (missingKey)
    {
        fprintf(stderr, "Error: Missing required keys in config file '%s'\n", filename);
        free(config->items);
        free(config);
        if (!_createConfig(filename))
        {
            fprintf(stderr, "Error: Failed to create config file '%s'\n", filename);
            return NULL;
        }
        return loadConfig(filename);
    }
    return config;
}

const char *getConfigValue(const Config *config, const char *key)
{
    for (int i = 0; i < config->count; i++)
    {
        if (strcmp(config->items[i].key, key) == 0)
        {
            return config->items[i].value;
        }
    }
    return NULL;
}

int getConfigValueInt(const Config *config, const char *key, int *target)
{
    char *e;
    const char *value = getConfigValue(config, key);
    if (value == NULL)
        return 1;
    const long t = strtol(value, &e, 10);
    if (*e != '\0')
        return 1;
    // intの範囲外の値はエラー
    if (t > INT_MAX || t < INT_MIN)
        return 1;
    *target = (int)t;
    return 0;
}

int getConfigValueLong(const Config *config, const char *key, long *target)
{
    char *e;
    const char *value = getConfigValue(config, key);
    if (value == NULL)
        return 1;
    const long t = strtol(value, &e, 10);
    if (*e != '\0')
        return 1;
    *target = t;
    return 0;
}

int getConfigValueDouble(const Config *config, const char *key, double *target)
{
    char *e;
    const char *value = getConfigValue(config, key);
    if (value == NULL)
        return 1;
    const double t = strtod(value, &e);
    if (*e != '\0')
        return 1;
    *target = t;
    return 0;
}