11 static constexpr int SIZE = 16;
12 static constexpr float PI = 3.14159265f;
14 const std::array<std::array<bool, SIZE>, SIZE>& GetGrid()
const {
18 bool GetCell(
int r,
int c)
const {
19 return InBounds(r, c) ? grid_[r][c] :
false;
22 void SetCell(
int r,
int c,
bool val) {
23 if (InBounds(r, c)) grid_[r][c] = val;
27 if (grid_[15][15] ==
false) {
35 bool AddGrainNearExisting() {
36 std::vector<std::pair<int, int>> candidates;
37 for (
int row = 0; row < SIZE; ++row) {
38 for (
int col = 0; col < SIZE; ++col) {
39 if (grid_[row][col]) {
40 for (
int dr = -1; dr <= 1; ++dr) {
41 for (
int dc = -1; dc <= 1; ++dc) {
42 if (dr == 0 && dc == 0)
continue;
45 if (InBounds(nr, nc) && !grid_[nr][nc]) {
46 candidates.emplace_back(nr, nc);
54 if (candidates.empty())
return false;
55 std::uniform_int_distribution<int> dist(0, candidates.size() - 1);
56 auto [r, c] = candidates[dist(rng_)];
61 void StepOnce(
float gravity_deg) {
63 gravity_deg += 225.0f;
64 if (gravity_deg >= 360.0f) gravity_deg -= 360.0f;
67 float angle_rad = gravity_deg * PI / 180.0f;
68 float gx = std::cos(angle_rad);
69 float gy = std::sin(angle_rad);
72 static const std::vector<std::pair<int, int>> directions = {
73 {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1},
77 std::uniform_real_distribution<float> noise_dist(-30.0f, 30.0f);
81 std::vector<std::tuple<int, int, int, int>> moves;
84 std::array<std::array<bool, SIZE>, SIZE> occupied_next = {};
87 std::vector<std::pair<int, int>> traversal_order;
88 for (
int r = SIZE - 1; r >= 0; --r) {
89 int center = SIZE / 2;
90 traversal_order.emplace_back(r, center);
91 for (
int offset = 1; offset < SIZE; ++offset) {
92 int left = center - offset;
93 int right = center + offset;
94 if (left >= 0) traversal_order.emplace_back(r, left);
95 if (right < SIZE) traversal_order.emplace_back(r, right);
100 for (
auto [r, c] : traversal_order) {
101 if (!grid_[r][c])
continue;
103 float noise_deg = noise_dist(rng_);
104 float cos_threshold = std::cos((55.0f + noise_deg) * PI / 180.0f);
106 std::pair<int, int> best_move = {0, 0};
107 float best_dot = -2.0f;
110 for (
auto [dr, dc] : directions) {
113 if (!InBounds(nr, nc) || grid_[nr][nc])
116 float vx =
static_cast<float>(dc);
117 float vy =
static_cast<float>(dr);
118 float len = std::sqrt(vx * vx + vy * vy);
119 if (len == 0.0f)
continue;
125 float dot = vx * gx + vy * gy;
128 if (dot > cos_threshold && dot > best_dot) {
130 best_move = {dr, dc};
135 if (best_dot > -1.0f) {
136 int nr = r + best_move.first;
137 int nc = c + best_move.second;
139 if (!occupied_next[nr][nc]) {
140 occupied_next[nr][nc] =
true;
141 moves.emplace_back(r, c, nr, nc);
147 for (
auto [r, c, nr, nc] : moves) {
149 grid_[nr][nc] =
true;
157 for (
auto& row : grid_) {
164 for (
const auto& row : grid_) {
173 if (angle < 90 || angle > 270) {
174 if (up->grid_[0][0] && !down->grid_[15][15]) {
175 down->grid_[15][15] =
true;
176 up->grid_[0][0] =
false;
180 if (!up->grid_[0][0] && down->grid_[15][15]) {
181 up->grid_[0][0] =
true;
182 down->grid_[15][15] =
false;
190 std::cout <<
"[SandGrid::UnitTest] Starting sand grid test...\n";
196 bool added = test.AddNewSand();
197 std::cout << std::format(
"[Test] AddNewSand → {}\n",
198 added ?
"✅ Success" :
"❌ Failed");
200 int before = test.Count();
202 int after = test.Count();
203 std::cout << std::format(
"[Test] StepOnce → Particle count: {} → {}\n",
210 up.SetCell(0, 0,
true);
211 bool moved = SandGrid::MoveSand(&up, &down, 45.0f);
212 std::cout << std::format(
"[Test] MoveSand(→down) → {}\n",
213 moved ?
"✅ Success" :
"❌ Failed");
217 std::cout << std::format(
"[Test] Clear → Count after clear: {}\n",
221 std::cout <<
"[Perf] Running StepOnce 100 times...\n";
222 for (
int i = 0; i < 50; ++i) {
223 test.AddGrainNearExisting();
226 std::vector<float> times;
228 for (
int i = 0; i < 100; ++i) {
229 auto start = std::chrono::high_resolution_clock::now();
231 auto end = std::chrono::high_resolution_clock::now();
232 float ms = std::chrono::duration<float, std::micro>(end - start).count();
236 auto [min_it, max_it] = std::minmax_element(times.begin(), times.end());
238 std::accumulate(times.begin(), times.end(), 0.0f) / times.size();
240 std::cout << std::format(
241 "[Perf] StepOnce timing (µs): min = {:>6.2f}, max = {:>6.2f}, avg = "
243 *min_it, *max_it, avg);
245 std::cout <<
"[SandGrid::UnitTest] ✅ Test complete.\n";
249 bool InBounds(
int r,
int c)
const {
250 return r >= 0 && r < SIZE && c >= 0 && c < SIZE;
253 std::array<std::array<bool, SIZE>, SIZE> grid_ = {};
255 std::mt19937 rng_{std::random_device{}()};