0011. 運命数(C言語)

戻る

【解説】
 カバラ数秘術という簡単な占いがある。誕生日を年(西暦)・月・日で表し、それぞれの数字を足し合わせる。合計した数字が10以上であれば、再びすべての桁の数字を足し合わせる。これを1桁の数字になるまで繰り返し、得られた数字を運命数とする。ただし、計算途中で11、22、33、44のようにぞろ目の数字になった場合は、それを運命数とする。
例:2015年12月23日→2+0+1+5+1+2+2+3=16→1+6=7

unmeisuu.cのソースコード】


/*
 * 生年月日から運命数を求めるプログラム
 * unmeisuu.c
 * 
 * 1582年10月15日以降(グレゴリオ暦の範囲内)、9999年12月31日以前で動作する。
 */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h> /* strlen(), strcmp(), strcpy(), strcat() */
#include <ctype.h>  /* isdigit() */

#define BUF_SIZE 256





/*
 * 自作のbool型の定義
 */
typedef enum {
    false, /* 0 */
    true   /* 1 */
} bool;





/*
 * 構造体dateの定義
 */
struct date {
    int year;
    int month;
    int day;
};





/*
 * ユーザー定義関数の関数原型宣言
 */
void print_tile(void);
void input_birthday(struct date *birthdatep);
bool is_valid_number(char buf[]);
int get_days_of_the_month(int year,int month);
bool is_leap_year(int year);
void sel_outputto(void);
void output(void);
void output_result(void);
void foutput_result(char filename[], char oldfilename[]);
void gen_filename(char filename[]);
int unmeisuu(int birthdate_value);
bool is_zorome(int n);
bool retry(void);





/*
 * グローバル変数
 */
char outputto,
     filename[BUF_SIZE]    = {1}, /* filenameとoldfilenameの初期値を      */
     oldfilename[BUF_SIZE] = {0}; /* 異なる物に設定する。                 */
                                  /* (ファイルへの追記の判断に使用する。) */
int birthdate_value;
struct date birthdate;





/*
 * メイン関数
 */
int main(void)
{
    while (1) {
        
        /* 表題・説明の表示 */
        print_tile();
        
        /* 生年月日の入力 */
        input_birthday(&birthdate);
        
        /* 出力先の選択 */
        sel_outputto();
        
        /* 出力 */
        output();
        
        /* 操作を続けるかどうかの選択 */
        if (!retry())
            break;
    }
    return EXIT_SUCCESS;
}





/*
 * 表題・説明を表示する関数
 */
void print_tile(void)
{
    printf("【運命数を計算・表示するプログラム】\n");
    printf("生年月日を入力して、運命数を計算します。\n\n");
}



/*
 * 生年月日を入力する関数
 * is_valid_number()関数とget_days_of_the_month()関数を使用する。
 */
void input_birthday(struct date *birthdatep)
{
    char buf[BUF_SIZE], zz;
    
    while (1) {
        
        printf("生年月日を入力してください。\n");
        
        /* 年の入力 */
        while (1) {
            printf("年は?(西暦で) ");
            fgets(buf, sizeof buf, stdin);
            sscanf(buf, "%d%c", &birthdatep->year, &zz);
            buf[strlen(buf) - 1] = '\0';
            if (!is_valid_number(buf)) {
                printf("半角整数値を入力してください。\n\n");
                continue;
            } else if (birthdatep->year < 1582) {
                printf("1582年以降の年を入力してください。\n\n");
                continue;
            } else if (birthdatep->year > 9999) {
                printf("9999年以前の年を入力してください。\n\n");
                continue;
            } else
                break;
        }
        
        while (1) {
            printf("月は? ");
            fgets(buf, sizeof buf, stdin);
            sscanf(buf, "%d%c", &birthdatep->month, &zz);
            buf[strlen(buf) - 1] = '\0';
            if (!is_valid_number(buf)) {
                printf("半角整数値を入力してください。\n\n");
                continue;
            } else if ((birthdatep->month < 1) || (12 < birthdatep->month)) {
                printf("1から12の値を入力してください。\n\n");
                continue;
            } else
                break;
        }
        
        while (1) {
            printf("日は? ");
            fgets(buf, sizeof buf, stdin);
            sscanf(buf, "%d%c", &birthdatep->day, &zz);
            buf[strlen(buf) - 1] = '\0';
            if (!is_valid_number(buf)) {
                printf("半角整数値を入力してください。\n\n");
                continue;
            } else if ((birthdatep->day < 1) ||
             (get_days_of_the_month(birthdatep->year, birthdatep->month)
             < birthdatep->day)) {
                printf("%d年%d月は、1日から%d日までです。\n",
                 birthdatep->year, birthdatep->month,
                 get_days_of_the_month(birthdatep->year, birthdatep->month));
                printf("1から%dまでの値を入力してください。\n\n",
                 get_days_of_the_month(birthdatep->year, birthdatep->month));
                continue;
            } else
                break;
        }
        
        birthdate_value
         = 10000*birthdatep->year + 100*birthdatep->month + birthdatep->day;
        
        /* 入力結果がグレゴリオ暦の範囲外となった場合の処理 */
        if (birthdate_value < 15821015) {
            printf("%d年%d月%d日はグレゴリオ暦の範囲外です。\n",
             birthdatep->year, birthdatep->month, birthdatep->day);
            printf("1582年10月15日以降の日付を入力してください。\n\n");
            continue;
        } else
            break;
    }
}



/*
 * 文字列が正しい形式の数値かどうかを判定する関数
 */
bool is_valid_number(char buf[])
{
    int i = 0;
    
    /* 最初は符号(省略可) */
    if (buf[i] == '+' || buf[i] == '-')
        i++;
    /* 次に数字(省略不可) */
    if (!isdigit(buf[i]))
        return false;
    i++;
    /* 次に数字列(省略可) */
    while (isdigit(buf[i]))
        i++;
    
    return buf[i] == '\0';
}



/*
 * year年month月に含まれる日数を返す関数
 * is_leap_year()関数を使用する。
 */
int get_days_of_the_month(int year,int month)
{
    int days;
    
    if (is_leap_year(year) && (month == 2))         /* うるう年の2月           */
        days = 29;
    else if ((!is_leap_year(year)) && (month == 2)) /* うるう年ではない2月     */
        days = 28;
    else if ((month == 4) || (month == 6) ||
     (month == 9) || (month == 11))                 /* 4月、6月、9月、11月     */
        days = 30;
    else if ((month == 1) || (month == 3) ||
     (month == 5) || (month == 7) ||
     (month == 8) || (month == 10) ||
     (month == 12))                                 /* 1、3、5、7、8、10、12月 */
        days = 31;
    else {                                          /* 上記以外                */
        printf("月の日数の計算ができません。処理を終了します。\n");
        exit(EXIT_FAILURE);
    }
    return days;
}



/*
 * うるう年の判定をする関数
 */
bool is_leap_year(int year)
{
    /* 400で割り切れればうるう年                        */
    /* 400で割り切れず、100で割り切れればうるう年でない */
    /* 100で割り切れず、4で割り切れればうるう年         */
    /* 4で割り切れなければうるう年でない                */
    
    return ((year % 400 == 0) || ((year % 100) && (year % 4 == 0)));
}



/*
 * 出力先(outputto)を選択する関数
 */
void sel_outputto(void)
{
    char buf[BUF_SIZE], zz;
    
    while (1) {
        printf("結果の出力先を1、2、3から選んでください。\n");
        printf("1. 画面だけに出力する。\n");
        printf("2. ファイルだけに出力する。\n");
        printf("3. 画面とファイルの両方に出力する。\n");
        
        fgets(buf, sizeof buf, stdin);
        sscanf(buf, "%c%c", &outputto, &zz);
        
        if (((outputto != '1') && (outputto != '2') && (outputto != '3'))
         || (zz != '\n')) {
            printf("無効な入力です。\n");
            printf("1、2、3のいずれかを入力してください。\n\n");
            continue;
        } else
            break;
    }
}



/*
 * 出力する関数
 * output_result()関数とfoutputresult()関数を使用する。
 */
void output(void)
{
    switch (outputto) {
    case '1':
        output_result();
        break;
    case '2':
        foutput_result(filename, oldfilename);
        break;
    case '3':
        output_result();
        foutput_result(filename, oldfilename);
        break;
    default:
        break;
    }
}



/*
 * 結果を画面に出力する関数
 * unmeisuu()関数を使用する。
 */
void output_result(void)
{
    printf("%d年%d月%d日生まれの人の運命数は%dです。\n",
     birthdate.year, birthdate.month, birthdate.day, unmeisuu(birthdate_value));
}



/*
 * 結果をファイルに出力する関数
 * gen_filename()関数、unmeisuu()関数を使用する。
 */
void foutput_result(char filename[], char oldfilename[])
{
    FILE *fp;
    
    /* ifによる分岐は、2行目以降を同一ファイルに追記出力させるため                      */
    if (strcmp(filename, oldfilename) == 0) { /* ファイル名が同じなら(2行目以降なら)    */
        fp = fopen(oldfilename, "a");         /* oldfilenameを開く。                    */
        if (fp == NULL) {
            printf("ファイルを開けません。ファイル出力を中断します。\n");
            return;
        }
    } else {                                  /* ファイル名が違うなら(初めての出力なら) */
        gen_filename(filename);               /* 新規にファイル名を生成して             */
        fp = fopen(filename, "a");            /* そのファイル(filename)を開いて         */
        strcpy(oldfilename, filename);        /* oldfilenameを更新する。                */
        if (fp == NULL) {
            printf("ファイルを開けません。ファイル出力を中断します。\n");
            return;
        }
    }
    fprintf(fp, "%d年%d月%d日生まれの人の運命数は%dです。\n",
     birthdate.year, birthdate.month, birthdate.day, unmeisuu(birthdate_value));
    
    fclose(fp);
}



/*
 * 現在日付時刻からファイル名を生成する関数
 */
void gen_filename(char filename[])
{
    struct tm *t;
    time_t tt;
    char snumrand[5];
    
    time(&tt);
    t = localtime(&tt);
    
    srand((unsigned int)time(NULL));
    sprintf(filename, "unmeisuu%04d%02d%02d_%02d%02d%02d_",
     1900+(t->tm_year), 1+(t->tm_mon), t->tm_mday,
     t->tm_hour, t->tm_min, t->tm_sec);
    sprintf(snumrand, "%04d", rand() % 10000);
    filename = strcat(filename, snumrand);
    filename = strcat(filename, ".txt");
}



/*
 * 運命数を計算して返す関数
 * is_zorome()関数を使用する。
 */
int unmeisuu(int birthdate_value)
{
    int sum = 0;
    
    while (birthdate_value > 0) {
        sum += birthdate_value % 10;
        birthdate_value /= 10;
    }
    if (is_zorome(sum))
        return sum;
    if (sum > 9)
        sum = unmeisuu(sum);
    return sum;
}



/*
 * ぞろ目かどうかを判定する関数
 */
bool is_zorome(int n)
{
    int m;
    
    /* 1桁の場合、ぞろ目ではないので偽を返す */
    if (!(n/10))
        return false;
    
    m = n % 10;           /* 1の位を記憶                         */
    while (1) {           /* 繰り返し                            */
        n /= 10;          /* 10で割って1未満を切り捨て           */
        if (!n)           /* nが0になったら                      */
            break;        /* 繰り返しをやめる                    */
        if (m != n % 10)  /* 1の位とある位を比較して違っていたら */
            return false; /* 偽を返す                            */
    }
    
    return true;          /* 0になるまですべての位が同じなら真を返す */
}



/*
 * 操作を続けるかどうかを選択する関数
 * true: 繰り返す、false: 終了する。
 */
bool retry(void)
{
    bool ret;
    char buf[BUF_SIZE], ch, zz;
    
    while (1) {
        printf("\n続けますか?(y/n) ");
        
        fgets(buf, sizeof buf, stdin);
        sscanf(buf, "%c%c", &ch, &zz);
        
        if ((ch == 'y' || ch == 'Y') && zz == '\n') {
            printf("\n+++++\n\n");
            ret = true;
            break;
        } else if ((ch == 'n' || ch == 'N') && zz == '\n') {
            ret = false;
            break;
        }
        printf("無効な入力です。\n");
        printf("yまたはnを入力してください。\n");
    }
    
    return ret;
}


unmeisuuの実行時画面表示】赤字はキーボードからの入力を表す。

C:\Users\skonishi\Documents>unmeisuu
【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1957
月は? 7
日は? 29
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1948
月は? 12
日は? 6
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1954
月は? 9
日は? 21
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1999
月は? 8
日は? 29
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1991
月は? 1
日は? 1
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1999
月は? 3
日は? 20
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) y

+++++

【運命数を計算・表示するプログラム】
生年月日を入力して、運命数を計算します。

生年月日を入力してください。
年は?(西暦で) 1999
月は? 9
日は? 25
結果の出力先を1、2、3から選んでください。
1. 画面だけに出力する。
2. ファイルだけに出力する。
3. 画面とファイルの両方に出力する。
2

続けますか?(y/n) n


【出力されたファイルunmeisuu20240318_214710_6467.txtの内容】 ←現在日付時刻から取得されたファイル名となっている。

1957年7月29日生まれの人の運命数は4です。          ←岸田文雄総理
1948年12月6日生まれの人の運命数は4です。          ←菅義偉前総理
1954年9月21日生まれの人の運命数は4です。          ←安倍晋三元総理
1999年8月29日生まれの人の運命数は11です。         ←運命数が11となる例
1991年1月1日生まれの人の運命数は22です。          ←運命数が22となる例
1999年3月20日生まれの人の運命数は33です。         ←運命数が33となる例
1999年9月25日生まれの人の運命数は44です。         ←運命数が44となる例


【参考サイト】
・基礎プログラミング演習I ~100本ノック上級編、No.87 運命数
 (http://www.cc.kyoto-su.ac.jp/~mmina/bp1/hundredKnocksAdvanced.html)。
・ゾロ目判断(https://www.harukitchen.com/programming/src/zoroharu.c)

戻る