/*
* Xoshiro256++ 알고리즘을 사용한 로또 번호 생성기
* 1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성하고
* 각 세트 간의 중복 여부를 검사합니다.
*
* 아날로그 핀을 사용하여 무작위성을 높입니다.
* https://wokwi.com/projects/428584279970392065
*/
// Xoshiro256++ 난수 생성기 구현
class Xoshiro256PlusPlus
{
private:
uint64_t s[4];
// 64비트 회전 연산
static inline uint64_t rotl(const uint64_t x, int k)
{
return (x << k) | (x >> (64 - k));
}
public:
// 초기화 - 4개의 64비트 시드 값 필요
Xoshiro256PlusPlus(uint64_t seed1, uint64_t seed2, uint64_t seed3, uint64_t seed4)
{
s[0] = seed1;
s[1] = seed2;
s[2] = seed3;
s[3] = seed4;
}
// 시드 초기화
void setSeed(uint64_t seed1, uint64_t seed2, uint64_t seed3, uint64_t seed4)
{
s[0] = seed1;
s[1] = seed2;
s[2] = seed3;
s[3] = seed4;
}
// SplitMix64 알고리즘으로 시드 생성
// 단일 시드값으로부터 4개의 독립적인 시드 생성
void seedFromSingle(uint64_t seed)
{
for (int i = 0; i < 4; i++)
{
seed += 0x9e3779b97f4a7c15;
uint64_t z = seed;
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9;
z = (z ^ (z >> 27)) * 0x94d049bb133111eb;
s[i] = z ^ (z >> 31);
}
}
// 다음 난수 생성 (xoshiro256++ 알고리즘)
uint64_t next()
{
const uint64_t result = rotl(s[0] + s[3], 23) + s[0];
const uint64_t t = s[1] << 17;
s[2] ^= s[0];
s[3] ^= s[1];
s[1] ^= s[2];
s[0] ^= s[3];
s[2] ^= t;
s[3] = rotl(s[3], 45);
return result;
}
// 범위 내 난수 생성 (0 ~ bound-1)
uint32_t bounded(uint32_t bound)
{
// 범위 왜곡 방지를 위한 임계값 계산
uint32_t threshold = -bound % bound;
while (true)
{
uint32_t r = (uint32_t)(next() & 0xFFFFFFFF); // 64비트를 32비트로 축소
if (r >= threshold)
return r % bound;
}
}
};
// 로또 번호 세트 (6개 숫자)를 표현하는 클래스
class LottoSet
{
public:
byte numbers[6];
void sort()
{
// 버블 정렬로 번호 정렬
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5 - i; j++)
{
if (numbers[j] > numbers[j + 1])
{
byte temp = numbers[j];
numbers[j] = numbers[j + 1];
numbers[j + 1] = temp;
}
}
}
}
// 두 로또 세트가 동일한지 비교
bool equals(const LottoSet &other) const
{
for (int i = 0; i < 6; i++)
{
if (numbers[i] != other.numbers[i])
{
return false;
}
}
return true;
}
// 출력용 문자열 변환
void toString(char *buffer)
{
sprintf(buffer, "%02d %02d %02d %02d %02d %02d",
numbers[0], numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]);
}
};
// 로또 세트 저장소
const int MAX_SETS = 100;
LottoSet lottoSets[MAX_SETS];
int setsGenerated = 0;
// Xoshiro256++ 난수 생성기 인스턴스
Xoshiro256PlusPlus rng(0, 0, 0, 0);
// 첫 번째 실행인지 확인하는 플래그
bool firstRun = true;
void setup()
{
Serial.begin(115200);
while (!Serial)
{
}
Serial.println(F("Xoshiro256++ 알고리즘을 이용한 로또 번호 생성기"));
Serial.println(F("1~45 사이의 숫자에서 중복 없이 6개의 번호를 100세트 생성합니다."));
Serial.println();
// 아날로그 핀 및 타이머를 이용한 시드 생성
initializeRNG();
// 로또 번호 100세트 생성
generateLottoSets();
// 중복 검사 수행
checkDuplicates();
}
void loop()
{
// 루프에서는 추가 작업 없음
if (firstRun)
{
firstRun = false;
Serial.println(F("완료! 다시 실행하려면 리셋 버튼을 누르세요."));
}
}
// RNG 초기화 - 아날로그 핀과 타이머를 활용한 시드 생성
void initializeRNG()
{
// 시스템 시작 이후 경과한 마이크로초
uint64_t microTime = micros();
// 아날로그 핀에서 노이즈 값 읽기
uint64_t analogNoise1 = (uint64_t)analogRead(A0) << 48 | (uint64_t)analogRead(A1) << 32 |
(uint64_t)analogRead(A2) << 16 | analogRead(A3);
uint64_t analogNoise2 = (uint64_t)analogRead(A4) << 48 | (uint64_t)analogRead(A5) << 32 |
(uint64_t)analogRead(A0) << 16 | analogRead(A1);
// 추가 노이즈 수집 (마이크로초 값도 계속 변화)
delay(1);
uint64_t analogNoise3 = (uint64_t)analogRead(A2) << 48 | (uint64_t)analogRead(A3) << 32 |
(uint64_t)analogRead(A4) << 16 | analogRead(A5);
uint64_t analogNoise4 = (uint64_t)analogRead(A0) << 48 | (uint64_t)analogRead(A3) << 32 |
(uint64_t)micros() << 16 | (analogRead(A2) * analogRead(A4));
// 초기 시드 설정
rng.setSeed(
analogNoise1 ^ microTime,
analogNoise2 ^ (microTime << 11),
analogNoise3 ^ (microTime >> 13),
analogNoise4 ^ (microTime * 741));
// 초기 상태 혼합을 위해 몇 번 건너뛰기
for (int i = 0; i < 20; i++)
{
rng.next();
}
Serial.println(F("난수 생성기 초기화 완료"));
}
// 로또 번호 세트 생성
void generateLottoSets()
{
Serial.println(F("로또 번호 생성 중..."));
for (int set = 0; set < MAX_SETS; set++)
{
generateLottoSet(&lottoSets[set]);
setsGenerated++;
// 10개 세트마다 진행 상황 출력
if ((set + 1) % 10 == 0 || set == 0)
{
Serial.print(F("생성된 세트: "));
Serial.println(set + 1);
}
}
Serial.println(F("로또 번호 생성 완료!"));
Serial.println();
}
// 단일 로또 세트 생성 (중복 숫자 없이)
void generateLottoSet(LottoSet *set)
{
bool used[46] = {false}; // 1-45 범위의 숫자 사용 여부 (0은 사용하지 않음)
for (int i = 0; i < 6; i++)
{
int num;
do
{
// 1-45 범위의 난수 생성
num = rng.bounded(45) + 1;
} while (used[num]); // 이미 사용된 숫자라면 다시 생성
set->numbers[i] = num;
used[num] = true;
}
// 오름차순 정렬
set->sort();
}
// 모든 생성된 세트 간의 중복 검사
void checkDuplicates()
{
Serial.println(F("생성된 모든 로또 세트:"));
// 모든 세트 출력
char buffer[50];
for (int i = 0; i < setsGenerated; i++)
{
lottoSets[i].toString(buffer);
Serial.print(i + 1);
Serial.print(F(": "));
Serial.println(buffer);
}
Serial.println();
Serial.println(F("세트 간 중복 검사 중..."));
bool foundDuplicate = false;
int totalComparisons = 0;
// 모든 가능한 세트 쌍 비교
for (int i = 0; i < setsGenerated - 1; i++)
{
for (int j = i + 1; j < setsGenerated; j++)
{
totalComparisons++;
if (lottoSets[i].equals(lottoSets[j]))
{
foundDuplicate = true;
Serial.print(F("중복 발견! 세트 #"));
Serial.print(i + 1);
Serial.print(F(" 와 세트 #"));
Serial.println(j + 1);
char buffer1[50], buffer2[50];
lottoSets[i].toString(buffer1);
lottoSets[j].toString(buffer2);
Serial.print(F(" 세트 #"));
Serial.print(i + 1);
Serial.print(F(": "));
Serial.println(buffer1);
Serial.print(F(" 세트 #"));
Serial.print(j + 1);
Serial.print(F(": "));
Serial.println(buffer2);
}
}
// 진행 상황 표시 (10% 단위)
if ((i + 1) % (setsGenerated / 10) == 0)
{
int percent = ((i + 1) * 100) / (setsGenerated - 1);
Serial.print(F("검사 진행: "));
Serial.print(percent);
Serial.println(F("%"));
}
}
Serial.println();
Serial.print(F("총 비교 횟수: "));
Serial.println(totalComparisons);
if (foundDuplicate)
{
Serial.println(F("중복된 세트가 발견되었습니다!"));
}
else
{
Serial.println(F("중복된 세트가 없습니다! 모든 100개 세트는 고유합니다."));
}
// 통계 검사 추가 - 세 가지 체크 로직
checkStatistics();
}
// 생성된 로또 번호의 통계 검사 (세 가지 체크 로직)
void checkStatistics()
{
Serial.println();
Serial.println(F("===== 로또 번호 통계 검사 ====="));
// 1. 체크 로직 1: 각 숫자의 출현 빈도
Serial.println(F("1. 각 숫자의 출현 빈도:"));
int numberFrequency[46] = {0}; // 1-45의 숫자 빈도
for (int i = 0; i < setsGenerated; i++)
{
for (int j = 0; j < 6; j++)
{
numberFrequency[lottoSets[i].numbers[j]]++;
}
}
// 출현 빈도 출력
for (int i = 1; i <= 45; i++)
{
Serial.print(F("숫자 "));
Serial.print(i);
Serial.print(F(": "));
Serial.print(numberFrequency[i]);
Serial.print(F("회"));
if (i % 5 == 0)
{
Serial.println();
}
else
{
Serial.print(F("\t"));
}
}
Serial.println();
// 2. 체크 로직 2: 홀수/짝수 분포
Serial.println(F("2. 홀수/짝수 분포:"));
int oddEvenDistribution[7] = {0}; // 홀수 0개~6개 분포
for (int i = 0; i < setsGenerated; i++)
{
int oddCount = 0;
for (int j = 0; j < 6; j++)
{
if (lottoSets[i].numbers[j] % 2 == 1)
{ // 홀수 카운트
oddCount++;
}
}
oddEvenDistribution[oddCount]++;
}
Serial.println(F("홀수 개수별 세트 수:"));
for (int i = 0; i <= 6; i++)
{
Serial.print(F("홀수 "));
Serial.print(i);
Serial.print(F("개 (짝수 "));
Serial.print(6 - i);
Serial.print(F("개): "));
Serial.print(oddEvenDistribution[i]);
Serial.println(F("세트"));
}
// 3. 체크 로직 3: 총합 분포
Serial.println(F("3. 번호 총합 분포:"));
int sumDistribution[300] = {0}; // 가능한 총합 범위
int minSum = 255, maxSum = 0;
for (int i = 0; i < setsGenerated; i++)
{
int sum = 0;
for (int j = 0; j < 6; j++)
{
sum += lottoSets[i].numbers[j];
}
sumDistribution[sum]++;
if (sum < minSum)
minSum = sum;
if (sum > maxSum)
maxSum = sum;
}
Serial.print(F("최소 총합: "));
Serial.println(minSum);
Serial.print(F("최대 총합: "));
Serial.println(maxSum);
Serial.println(F("총합 분포:"));
for (int i = minSum; i <= maxSum; i++)
{
if (sumDistribution[i] > 0)
{
Serial.print(F("총합 "));
Serial.print(i);
Serial.print(F(": "));
Serial.print(sumDistribution[i]);
Serial.println(F("세트"));
}
}
}
Loading
ssd1306
ssd1306