前言 - 随机性
随机数生成 - https://zh.wikipedia.org/wiki/%E9%9A%8F%E6%9C%BA%E6%95%B0%E7%94%9F%E6%88%90
没啥事情, 写了个猜猜猜(猜数字)小游戏. 遇到猜的部分, 那肯定会用到随机函数. 我们为了多平台行为一致, 就统一
整合一个伪随机函数实现方案, 让随机更随机. 一开始看下接口设计 rand.h
1 #ifndef _RAND_H
2 #define _RAND_H
3
4 #include <stdint.h>
5 #include <assert.h>
6
7 //
8 // 传承(抄袭)不灭(创新) rand 库
9 // 大体思路
10 // { r | r[n+1] = (a*r[n] + c) mod m
11 // , n >= 0
12 // , m = 0xFFFFFFFFFFFF = 2 ^ 48
13 // , a = 0x0005DEECE66D = 0x5DEECE66D,
14 // , c = 0x000B = 0xB
15 // }
16 //
17 struct rand {
18 uint32_t x0, x1, x2;
19 uint32_t a0, a1, a2;
20 uint32_t c;
21 };
22
23 typedef struct rand rand_t[1];
24
25 //
26 // rand_init - 随机函数对象初始化种子
27 // r : 随机函数对象
28 // seed : 种子数
29 // return : void
30 //
31 extern void rand_init(rand_t r, int64_t seed);
32
33 //
34 // rand_rand - 获取一个随机值
35 // r : 随机函数对象
36 // return : 返回 [0, INT32_MAX] 随机数
37 //
38 extern int32_t rand_rand(rand_t r);
39
40 //
41 // r_rand - 得到 [0, INT32_MAX] 随机数
42 // r_ranb - 得到 [0, INT64_MAX] (int64 = big int32) 随机数
43 // r_rang - 得到 range [min, max] 随机数
44 //
45 extern int32_t r_rand(void);
46
47 extern int64_t r_ranb(void);
48
49 inline int32_t r_rang(int32_t min, int32_t max) {
50 assert(max >= min);
51 return r_rand() % (max - min + 1) + min;
52 }
53
54 #endif//_RAND_H
rand.c
1 #include "rand.h"
2
3 #define X0 (0x330E)
4 #define X1 (0xABCD)
5 #define X2 (0x1234)
6 #define A0 (0xE66D)
7 #define A1 (0xDEEC)
8 #define A2 (0x0005)
9 #define C (0x000B)
10
11 #define N (16)
12 #define MASK ((1 << N) - 1)
13 #define LOW(x) ((uint32_t)(x) & MASK)
14 #define HIGH(x) LOW((x) >> N)
15
16 //
17 // rand_init - 随机函数对象初始化种子
18 // r : 随机函数对象
19 // seed : 种子数
20 // return : void
21 //
22 inline void
23 rand_init(rand_t r, int64_t seed) {
24 r->x0 = X0; r->x1 = LOW(seed); r->x2 = HIGH(seed);
25 r->a0 = A0; r->a1 = A1; r->a2 = A2;
26 r->c = C;
27 }
28
29 #define CARRY(x, y) ((x + y) > MASK) // 基于 16 位判断二者和是否进位
30 #define ADDRQ(x, y, z) z = CARRY(x, y); x = LOW(x + y)
31 #define MUL(m, x, y, z) m = (x) * (y); z##0 = LOW(m); z##1 = HIGH(m)
32
33 inline void rand_next(rand_t r) {
34 uint32_t m, p0, p1, q0, q1, s0, s1, c0, c1;
35
36 MUL(m, r->a0, r->x0, p);
37 ADDRQ(p0, r->c, c0);
38 ADDRQ(p1, c0, c1);
39 MUL(m, r->a0, r->x1, q);
40 ADDRQ(p1, q0, c0);
41 MUL(m, r->a1, r->x0, s);
42
43 m = c0 + c1 + CARRY(p1, s0) + q1 + s1
44 + r->a0 * r->x2 + r->a1 * r->x1 + r->a2 * r->x0;
45 r->x2 = LOW(m);
46 r->x1 = LOW(p1 + s0);
47 r->x0 = LOW(p0);
48 }
49
50 //
51 // rand_rand - 获取一个随机值
52 // r : 随机函数对象
53 // return : 返回 [0, INT32_MAX] 随机数
54 //
55 inline int32_t
56 rand_rand(rand_t r) {
57 rand_next(r);
58 return (r->x2 << (N - 1)) + (r->x1 >> 1);
59 }
60
61 //
62 // 想过 - 朵朵浪花, 也不曾看见云彩飘过 :0
63 //
64 static rand_t r_r = { { X0, X1, X2 , A0, A1, A2 , C } };
65
66 // EXTERN_RUN(r_init) 启动初始化
67 extern inline void r_init(int64_t seed) {
68 rand_init(r_r, seed);
69 }
70
71 inline int32_t
72 r_rand(void) {
73 return rand_rand(r_r);
74 }
75
76 inline int64_t
77 r_ranb(void) {
78 uint64_t x = ((r_rand() << N) ^ r_rand()) & INT32_MAX;
79 uint64_t y = ((r_rand() << N) ^ r_rand()) & INT32_MAX;
80 return ((x << 2 * N) | y) & INT64_MAX;
81 }
算法层面网上资料很够(redis rand48 原理) . 我这里只聊聊工程实现方面, 可以看出来多线程并发发起 r_rand()
请求是不可控的(非线程安全, 你也不知道内部运行状态是啥). 但如果存在特殊业务需要可窥探随机序列时候,
rand_init 就可以运用上了, 来构建多线程情况下可控的随机函数对象.
对于 r_init 多数使用 time(NULL) 时间量初始化种子, 有没有人觉得不太可靠, 毕竟服务器时间也可以改. 有
没有想过让其更加混乱一点. 这里有个小想法, 提出来可供大家一块参略讨论讨论.
1 //
2 // EXTERN_RUN - 函数包装宏, 声明并立即使用
3 // frun : 需要执行的函数名称
4 // ... : 可变参数, 保留
5 //
6 #define EXTERN_RUN(frun, ...) \
7 do { \
8 extern void frun(); \
9 frun (__VA_ARGS__); \
10 } while(0)
11
12 // 此后 :) 随机与你无关 ~
13 EXTERN_RUN(rand_restrict);
17 // rand_nationalism - 民族主义, 国家是人民所共有, 各族平等, 团结一致
18 static void rand_nationalism(void) {
19 struct timespec s;
20 (void)timespec_get(&s, TIME_UTC);
21 EXTERN_RUN(r_init, s.tv_nsec + s.tv_sec);
22 for (int32_t i = BUFSIZ; i > 0 ; --i) {
23 (void)timespec_get(&s, TIME_UTC);
24 EXTERN_RUN(r_init, s.tv_nsec + i);
25 }
26 }
27
28 // rand_democracy - 民权主义, 政治是人民所共管, 选举权、罢免权、创制权和复决权
29 static void rand_democracy(void) {
30 int32_t x, y, z, w;
31 do {
32 x = r_rand();
33 y = r_rand();
34 z = r_rand();
35 w = r_rand();
36 } while (!(x > y && y > z && z > w && w > INT16_MAX));
37 }
38
39 // rand_livelihood - 民生主义, 利益是人民所共享, 让人像个人
40 static void rand_livelihood(void) {
41 for (int32_t i = r_rand(); i >= 0; --i)
42 r_rand();
43 }
44
45 // rand_restrict - 三权制衡在随机函数初中运用
46 void rand_restrict(void) {
47 thread_async(rand_nationalism);
48 thread_async(rand_democracy);
49 thread_async(rand_livelihood);
50 }
传送门 核心是引入多线程互相竞争制约, 让随机函数你我不知, 模糊不清, 捉摸不透. 这里只是瞎弄一个思路.
大家有更好的想法可以在评论区和我一块交流.
正文 - 猜猜猜
是不是要开始扯闲篇. 那就用上面随机库写个猜猜猜游戏吧 ~
main.c
#include "rand.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//
// EXTERN_RUN - 函数包装宏, 声明并立即使用
// frun : 需要执行的函数名称
// ... : 可变参数, 保留
//
#define EXTERN_RUN(frun, ...) \
do { \
extern void frun(); \
frun (__VA_ARGS__); \
} while(0)
//
// GUESS_THRESHOLD_INT - 猜数最多次数
// GUESS_TEMPER_INT - 遇到小脾气不再触发提示
// GUESS_MAX_INT - 最大值区间
//
#define GUESS_THRESHOLD_INT (8)
#define GUESS_TEMPER_INT (3)
#define GUESS_MAX_INT (8964)
// guess 猜数字游戏对象
struct guess {
int32_t max; // 数字区间上限
int32_t correct; // 正确数值
int32_t use; // 已经猜数次数
int32_t numbers[GUESS_THRESHOLD_INT]; // 用户猜数数组
int32_t now; // 用户当前输入值
};
// guess_run 猜数字游戏开始
void guess_run(struct guess * restrict g);
// guess_guess 猜数字开启猜猜猜
bool guess_guess(struct guess * restrict g);
// guess_end 猜数字游戏结束总结
void guess_end(struct guess * restrict g);
// threshold_input - 获取猜数字的范围
static int32_t threshold_input(int argc, char * argv[]) {
int32_t max = GUESS_MAX_INT;
if (argc > 1) {
int num = atoi(argv[1]);
if (num <= 0) {
printf("亲, 别👇瞎闹 [input = %s] 好不好 ~\n", argv[1]);
exit(EXIT_FAILURE);
}
max = num;
}
return max;
}
//
// 简单的猜猜猜数字游戏
//
int main(int argc, char * argv[]) {
// 获取猜数字范围
int max = threshold_input(argc, argv);
// 设置随机函数种子
struct timespec s;
(void)timespec_get(&s, TIME_UTC);
EXTERN_RUN(r_init, s.tv_nsec + s.tv_sec);
// 设置 guess number 对象
struct guess user = {
.max = max,
.correct = r_rand() % user.max + 1,
};
guess_run(&user);
// 游戏过程主逻辑
while (!guess_guess(&user))
;
guess_end(&user);
return 0;
}
// guess_run 猜数字游戏开始
void
guess_run(struct guess * restrict g) {
printf("------------------------******* 大侠,猜猜猜 *******------------------------\n");
printf("-> 大侠,如此难得舍得, 不如 🤱 猜猜猜\n");
printf("-> 1. 猜数范围 [1, %d]\n", g->max);
printf("-> 2. 拥有猜数机会 [%d] 次\n", GUESS_THRESHOLD_INT);
printf("-> 3. 触发小脾气 🦐 1/%d, 不再多给提示\n", GUESS_TEMPER_INT);
printf("-> ready Go\n");
}
// guess_check 猜数字游戏检查
static bool guess_check(struct guess * restrict g) {
// 猜中数字, 进入结束环节
if (g->now == g->correct) {
printf("-> 哇,棒棒棒\n");
return true;
}
// 免费次数用完
if (g->use >= GUESS_THRESHOLD_INT) {
printf("-> 坑多 🐾 不够,大侠就此别过 ~\n");
return true;
}
// 进入提示环节
int32_t luckly = r_rand() % GUESS_TEMPER_INT;
if (luckly == 0) {
printf("-> 小脾气,🧠 脑子是个好东西,可惜有些人没有 💵\n");
return false;
}
// 正常提示环节
if (g->now > g->correct) {
printf("-> [%d] 🤪 大了\n", g->now);
} else {
printf("-> [%d] 🤪 小了\n", g->now);
}
return false;
}
// guess_guess 猜数字开启猜猜猜
bool
guess_guess(struct guess * restrict g) {
int32_t num;
char buf[BUFSIZ];
for (;;) {
printf("-> 亲,输入 luckly 数字吧 🕗 : ");
if (fgets(buf, sizeof buf, stdin)) {
if (buf[0] == 'q' || buf[0] == 'Q') {
printf("-$ 聚是一团火,散是满天 💫 \n");
exit(EXIT_SUCCESS);
}
if ((num = atoi(buf)) > 0) {
break;
}
printf("-> 大侠外面的世界很精彩, 💨 快走吧,别乱叛逆 ⭕ 哦 --<-\n");
}
// 清除输入的缓冲指针
rewind(stdin);
}
// 数据记录
g->now = num;
g->numbers[g->use++] = num;
return guess_check(g);
}
// guess_end 猜数字游戏结束总结
void
guess_end(struct guess * restrict g) {
printf("<- 大侠喝杯茶, 🙏 稍后 ~\n");
printf("<- 猜范围 [1, %d]\n", g->max);
printf("<- luckly 是 [%d] 你是 %s\n", g->correct, g->now == g->correct ? "√ ":"❌");
printf("<- 猜测过程 [ %d", g->numbers[0]);
for (int i = 1; i < g->use; ++i)
printf(" -> %d ", g->numbers[i]);
printf("]\n");
if (g->use <= 1) {
printf("-$ 无数奇真供世眼, 一轮明月耀天心 ❤\n");
}
printf("<- 风🍃 从没有停止,大侠的故事⛵ 仍然在回响 ~\n");
printf("------------------------******* 猜数,猜猜猜 *******------------------------\n");
}
其中编译操作 Makefile
.PHONY : all clean
all : guess.exe
guess.exe : main.o rand.o
gcc -g -Wall -O2 -Wno-unused-result -o $@ $^
main.o : main.c
gcc -g -Wall -O2 -c -o $@ $<
rand.o : rand.c
gcc -g -Wall -O2 -c -o $@ $<
clean:
-rm -rf guess.exe main.o rand.o
-printf '\ec'
有兴趣朋友可以试试 ~ 哈哈哈 大侠请喝茶 ~
后记 - 去去去
细说都是错, 欢迎交流指正互相博一乐.
[黄梅戏] 谁料皇榜中状元 - https://www.kugou.com/song/#hash=C25E49A4B64E60E02895F60155942719&album_id=970764