#include #include #include #include #include #include // -------------------- RNG (splitmix64) -------------------- static uint64_t g_state = 0; static uint64_t splitmix64_next(void) { uint64_t z = (g_state += 0x9E3779B97F4A7C15ULL); z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9ULL; z = (z ^ (z >> 27)) * 0x94D049BB133111EBULL; return z ^ (z >> 31); } static uint32_t rng_u32(uint32_t n) { return (uint32_t)(splitmix64_next() % (uint64_t)n); } static void shuffle_int(int *a, int n) { for (int i = n - 1; i > 0; i--) { int j = (int)rng_u32((uint32_t)(i + 1)); int t = a[i]; a[i] = a[j]; a[j] = t; } } // -------------------- UTF-8 helpers -------------------- static int utf8_advance(const unsigned char *s, int i) { unsigned char c = s[i]; if (c < 0x80) return i + 1; if ((c & 0xE0) == 0xC0) return i + 2; if ((c & 0xF0) == 0xE0) return i + 3; if ((c & 0xF8) == 0xF0) return i + 4; return i + 1; } static int utf8_count_cp(const char *str) { const unsigned char *s = (const unsigned char *)str; int i = 0, cp = 0; while (s[i]) { i = utf8_advance(s, i); cp++; } return cp; } static int utf8_copy_first_n_cp(char *dst, size_t dst_cap, const char *src, int n_cp) { const unsigned char *s = (const unsigned char *)src; size_t di = 0; int i = 0, cp = 0; while (s[i] && cp < n_cp) { int ni = utf8_advance(s, i); for (int k = i; k < ni; k++) { if (di + 1 >= dst_cap) { dst[di] = '\0'; return cp; } dst[di++] = (char)s[k]; } i = ni; cp++; } dst[di] = '\0'; return cp; } static int utf8_find_last_jp_period_within(const char *s, int max_cp) { const unsigned char *p = (const unsigned char *)s; int i = 0, cp = 0; int last_after = -1; while (p[i] && cp < max_cp) { int ni = utf8_advance(p, i); if (ni - i == 3 && p[i] == 0xE3 && p[i + 1] == 0x80 && p[i + 2] == 0x82) { last_after = cp + 1; } i = ni; cp++; } return last_after; } // -------------------- Pools -------------------- static const char *KANA_POOL[] = { "あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ", "た", "ち", "つ", "て", "と", "な", "に", "ぬ", "ね", "の", "は", "ひ", "ふ", "へ", "ほ", "ま", "み", "む", "め", "も", "や", "ゆ", "よ", "ら", "り", "る", "れ", "ろ", "わ", "を", "ん"}; static const int KANA_POOL_N = (int)(sizeof(KANA_POOL) / sizeof(KANA_POOL[0])); static const char *WORD_POOL[] = { "机", "鉛筆", "ノート", "時計", "窓", "椅子", "本", "鞄", "紙", "電車", "学校", "友達", "公園", "道路", "空", "雲", "雨", "風", "星", "川", "海", "山", "花", "木", "鳥", "犬", "猫", "水", "茶", "米", "パン", "駅", "家", "部屋", "灯り", "音", "声", "写真", "地図", "鍵", "鏡", "箱", "皿", "塩", "砂", "庭", "橋", "道", "森", "畑", "店", "財布", "帽子", "靴", "傘", "電話", "切符", "薬", "手紙", "封筒"}; static const int WORD_POOL_N = (int)(sizeof(WORD_POOL) / sizeof(WORD_POOL[0])); static const char *NAME_POOL[] = { "佐藤陽菜", "鈴木颯太", "高橋美咲", "田中悠斗", "伊藤結衣", "渡辺大輝", "山本葵", "中村蓮", "小林航", "加藤咲", "吉田結菜", "山田海斗"}; static const int NAME_POOL_N = (int)(sizeof(NAME_POOL) / sizeof(NAME_POOL[0])); static const char *GRADE_POOL[] = {"中1", "中2", "中3", "高1", "高2", "高3"}; static const int GRADE_POOL_N = (int)(sizeof(GRADE_POOL) / sizeof(GRADE_POOL[0])); static const char *CLUB_POOL[] = {"科学部", "サッカー部", "吹奏楽部", "美術部", "陸上部", "バレー部"}; static const int CLUB_POOL_N = (int)(sizeof(CLUB_POOL) / sizeof(CLUB_POOL[0])); static const char *SUBJ_POOL[] = {"英語", "数学", "国語", "社会", "理科"}; static const int SUBJ_POOL_N = (int)(sizeof(SUBJ_POOL) / sizeof(SUBJ_POOL[0])); static const char *GOAL_POOL[] = {"定期テスト", "模試", "実力テスト", "入試"}; static const int GOAL_POOL_N = (int)(sizeof(GOAL_POOL) / sizeof(GOAL_POOL[0])); static const char *PARA_TPL[] = { "{NAME}は{GRADE}で、{CLUB}の活動と勉強を両立させている。最近は{SUBJ}が伸び悩み、授業ノートを見返しても要点がつかめなかった。そこで、紙に図を描きながら要点を三つにまとめ、最後に自分の言葉で短い説明文を作った。翌日、説明文だけを見て内容を再現できたので、自信が少し戻った。次の{GOAL}までに同じやり方で復習を続けるつもりだ。", "{NAME}は{GRADE}で、通学中はタブレットで単語を確認している。ただ、家に着くと通知が気になって集中が切れやすい。今日は端末を機内モードにして、{SUBJ}の要点だけを紙に書き写した。書き終えた後に内容を思い出して箇条書きにしたら、理解が深まった気がした。最後に間違えた所だけを小さな付せんに書き、机の横に貼っておいた。", "{NAME}は{GRADE}で、放課後は{CLUB}の練習が長い。帰宅後は疲れているので、短時間で復習する工夫が必要だった。{SUBJ}は間違えた問題だけをノートに貼り、なぜ誤答したかを一行で書く。翌日に見返すと、同じミスを避けやすくなると感じている。週末は解き直し用のページを作り、できるまで印を消さないことにした。", "{NAME}は{GRADE}で、{GOAL}が近づいて焦っている。解説を読むだけだと頭に残らないので、紙に問題文を写し、条件と求めるものに下線を引いた。次に、途中式を省かずに書き、最後に答えの意味を確認した。手を動かすほど落ち着いて考えられた。仕上げに、同じ形式の問題を一問だけ続けて解き、理解が本物か確かめた。", "{NAME}は{GRADE}で、{SUBJ}の長文が苦手だ。画面だとスクロールで位置感覚がずれてしまい、どこを読んでいるか迷うことがある。今日は印刷したプリントに線を引き、段落ごとに要点を横にメモした。読み終えた後、全体の流れが整理できた。次回は、段落の役割を一語で書き、先に地図を作ってから読むつもりだ。", "{NAME}は{GRADE}で、友だちと一緒に勉強する日が増えた。話し合いの前に、各自で教科書を読み、分かった点と疑問点を紙に書く。次に、疑問を順番に説明し合い、最後にまとめを一枚に整理した。人に説明すると理解が曖昧な所がすぐ見つかる。帰り道に、そのまとめを見ずに口で言い直すと、記憶に残りやすかった。", "{NAME}は{GRADE}で、{SUBJ}の暗記が多くて困っている。タブレットのカード学習は便利だが、覚えたつもりになりやすい。そこで、重要語句だけを紙に書き、空欄を作って自分でテストした。正解できなかった所は色を変えて翌日に復習する。さらに、似た言葉を並べて違いを一行で書くと、混同が減ってきた。", "{NAME}は{GRADE}で、部屋の机が散らかりがちだった。勉強を始める前に机の上を片づけ、紙のノートとペンだけを置くと集中しやすい。{SUBJ}の学習では、目標時間を決めてから解き、終わったらできた点を一つ書く。小さな達成感が次のやる気につながる。最後に、明日の最初にやる問題を一つ決めておくと、机に向かうのが楽になった。"}; static const int PARA_TPL_N = (int)(sizeof(PARA_TPL) / sizeof(PARA_TPL[0])); static const char *PARA_EXTRA[] = { "わからない語句はその場で調べず、印だけ付けて後でまとめて確認した。", "復習の最後に、今日できたことを一行で書くと気持ちが整った。", "短い休憩を挟むと、同じ内容でも読み直しが楽になった。", "大事な所だけを赤で囲み、細部はあとで見返せるように残した。", "次に同じ問題を見たときに迷わないよう、注意点を小さく書き足した。", "まとめた後に声に出して読むと、覚え方の抜けが見つかった。"}; static const int PARA_EXTRA_N = (int)(sizeof(PARA_EXTRA) / sizeof(PARA_EXTRA[0])); static int utf8_count_cp(const char *s); static const int PARA_TARGET = 200; static const int PARA_MIN = 195; static const int PARA_MAX = 205; static const char *PARA_MANDATORY = "最後に、要点を二行で書き残して区切った。"; static const char *PARA_FILL[] = { "気持ちが整った。", "迷いが減った。", "集中が戻った。", "要点がはっきりした。", "次も続けると決めた。", "手応えを感じた。", "理解が深まった。", "自信が少し戻った。"}; static const int PARA_FILL_N = (int)(sizeof(PARA_FILL) / sizeof(PARA_FILL[0])); static int ends_with_jp_period(const char *s) { size_t len = strlen(s); while (len > 0 && isspace((unsigned char)s[len - 1])) len--; if (len < 3) return 0; return ((unsigned char)s[len - 3] == 0xE3 && (unsigned char)s[len - 2] == 0x80 && (unsigned char)s[len - 1] == 0x82); } typedef struct { int kind; int idx; } addon_t; static addon_t choose_best_addon(int cur_cp, const int *used_extra, const int *used_fill) { addon_t best = {-1, -1}; int best_score = 1 << 30; for (int i = 0; i < PARA_EXTRA_N; i++) { if (used_extra && used_extra[i]) continue; int add = utf8_count_cp(PARA_EXTRA[i]); int new_cp = cur_cp + add; if (new_cp > PARA_MAX) continue; int score = abs(PARA_TARGET - new_cp); if (score < best_score || (score == best_score && best.kind != 0)) { best_score = score; best.kind = 0; best.idx = i; } } for (int i = 0; i < PARA_FILL_N; i++) { if (used_fill && used_fill[i]) continue; int add = utf8_count_cp(PARA_FILL[i]); int new_cp = cur_cp + add; if (new_cp > PARA_MAX) continue; int score = abs(PARA_TARGET - new_cp); if (score < best_score) { best_score = score; best.kind = 1; best.idx = i; } } return best; } static void append_str(char *dst, size_t cap, const char *src) { size_t dlen = strlen(dst); size_t slen = strlen(src); if (dlen + slen + 1 >= cap) { size_t room = (cap > dlen + 1) ? (cap - (dlen + 1)) : 0; if (room > 0) memcpy(dst + dlen, src, room); dst[cap - 1] = '\0'; return; } memcpy(dst + dlen, src, slen); dst[dlen + slen] = '\0'; } static void render_template(char *out, size_t cap, const char *tpl, const char *name, const char *grade, const char *club, const char *subj, const char *goal) { out[0] = '\0'; const char *p = tpl; while (*p) { if (p[0] == '{') { const char *q = strchr(p, '}'); if (q) { size_t keylen = (size_t)(q - (p + 1)); char key[32]; if (keylen >= sizeof(key)) keylen = sizeof(key) - 1; memcpy(key, p + 1, keylen); key[keylen] = '\0'; if (strcmp(key, "NAME") == 0) append_str(out, cap, name); else if (strcmp(key, "GRADE") == 0) append_str(out, cap, grade); else if (strcmp(key, "CLUB") == 0) append_str(out, cap, club); else if (strcmp(key, "SUBJ") == 0) append_str(out, cap, subj); else if (strcmp(key, "GOAL") == 0) append_str(out, cap, goal); else { append_str(out, cap, "{"); append_str(out, cap, key); append_str(out, cap, "}"); } p = q + 1; continue; } } char tmp[2] = {*p, '\0'}; append_str(out, cap, tmp); p++; } } static void gen_nonsense20(char *out, size_t cap) { out[0] = '\0'; for (int i = 0; i < 20; i++) { const char *k = KANA_POOL[rng_u32(KANA_POOL_N)]; append_str(out, cap, k); } } static void gen_words10(char *out, size_t cap) { out[0] = '\0'; int used[WORD_POOL_N]; for (int i = 0; i < WORD_POOL_N; i++) used[i] = 0; int picked = 0; while (picked < 10) { int idx = (int)rng_u32(WORD_POOL_N); if (used[idx]) continue; used[idx] = 1; if (picked > 0) append_str(out, cap, " "); append_str(out, cap, WORD_POOL[idx]); picked++; } } static void gen_paragraph(char *out, size_t cap) { const char *name = NAME_POOL[rng_u32(NAME_POOL_N)]; const char *grade = GRADE_POOL[rng_u32(GRADE_POOL_N)]; const char *club = CLUB_POOL[rng_u32(CLUB_POOL_N)]; const char *subj = SUBJ_POOL[rng_u32(SUBJ_POOL_N)]; const char *goal = GOAL_POOL[rng_u32(GOAL_POOL_N)]; char buf[4096]; render_template(buf, sizeof(buf), PARA_TPL[rng_u32(PARA_TPL_N)], name, grade, club, subj, goal); if (!ends_with_jp_period(buf)) append_str(buf, sizeof(buf), "。"); append_str(buf, sizeof(buf), PARA_MANDATORY); int used_extra[PARA_EXTRA_N]; for (int i = 0; i < PARA_EXTRA_N; i++) used_extra[i] = 0; int used_fill[PARA_FILL_N]; for (int i = 0; i < PARA_FILL_N; i++) used_fill[i] = 0; int cur = utf8_count_cp(buf); int guard = 0; while (cur < PARA_MIN && guard++ < 12) { addon_t a = choose_best_addon(cur, used_extra, used_fill); if (a.kind < 0) break; if (a.kind == 0) { append_str(buf, sizeof(buf), PARA_EXTRA[a.idx]); used_extra[a.idx] = 1; } else { append_str(buf, sizeof(buf), PARA_FILL[a.idx]); used_fill[a.idx] = 1; } cur = utf8_count_cp(buf); } int cp_total = utf8_count_cp(buf); int cut_cp = cp_total; if (cp_total > PARA_MAX) { int last_period_after = utf8_find_last_jp_period_within(buf, PARA_MAX); if (last_period_after >= 8) cut_cp = last_period_after; else cut_cp = PARA_MAX; } utf8_copy_first_n_cp(out, cap, buf, cut_cp); size_t len = strlen(out); while (len > 0 && isspace((unsigned char)out[len - 1])) { out[len - 1] = '\0'; len--; } } // -------------------- CLI -------------------- static void usage(const char *prog) { fprintf(stderr, "Usage: %s [-n sets] [-s seed] [-o out_prefix]\n" " -n : number of sets (default 6)\n" " -s : seed (default: time-based)\n" " -o : output file prefix (default: expA_set)\n" " Paragraph length: fixed 200±5 chars (195–205).\n" "\nExample:\n" " %s -n 6 -s 20260102 -o expA_set\n", prog, prog); } int main(int argc, char **argv) { int sets = 6; uint64_t seed = 0; const char *prefix = "expA_set"; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-n") == 0 && i + 1 < argc) { sets = atoi(argv[++i]); } else if (strcmp(argv[i], "-s") == 0 && i + 1 < argc) { seed = (uint64_t)strtoull(argv[++i], NULL, 10); } else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) { prefix = argv[++i]; } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { usage(argv[0]); return 0; } else { fprintf(stderr, "Unknown arg: %s\n", argv[i]); usage(argv[0]); return 2; } } if (sets <= 0) sets = 1; if (seed == 0) { seed = (uint64_t)time(NULL) ^ ((uint64_t)clock() << 32); } for (int si = 1; si <= sets; si++) { uint64_t set_seed = seed + (uint64_t)si * 1013904223ULL; g_state = set_seed; char nonsense[512], words[1024], para[4096]; gen_nonsense20(nonsense, sizeof(nonsense)); gen_words10(words, sizeof(words)); gen_paragraph(para, sizeof(para)); char filename[256]; snprintf(filename, sizeof(filename), "%s%02d.txt", prefix, si); FILE *fp = fopen(filename, "wb"); if (!fp) { fprintf(stderr, "Failed to write: %s\n", filename); return 1; } fprintf(fp, "# Stimulus Set %02d\n", si); fprintf(fp, "# seed=%llu\n", (unsigned long long)set_seed); fprintf(fp, "# paragraph_chars=195-205 (target 200)\n\n"); fprintf(fp, "[A] 意味の無い文字(20文字)\n%s\n\n", nonsense); fprintf(fp, "[B] 単語の羅列(10単語)\n%s\n\n", words); fprintf(fp, "[C] 文脈のある文章(約200字)\n%s\n", para); fclose(fp); } return 0; }