Skip to content

Commit 14f4258

Browse files
committed
Простые и не только правила жизни
1 parent 1e152ed commit 14f4258

8 files changed

+166
-22
lines changed

README.md

+19-6
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,28 @@
3636
## Основные функции
3737

3838
- **3D визуализация**: Игра отображается в трехмерном пространстве, где клетки представлены на плоскости.
39-
- **Интерактивное управление**:
40-
- **Навигация камеры**: Используйте клавиши WSAD для перемещения камеры, мышь для вращения (средняя кнопка мыши).
39+
- **Реализация на GPU**: Для ускорения симуляции используется GPU через OpenGL Compute Shaders.
40+
- - **Интерактивное управление**:
41+
- **Навигация камеры**: Используйте клавиши WSAD для перемещения камеры.
4142
- **Изменение состояния клеток**: Левая кнопка мыши позволяет переключать состояние клеток (живые/мертвые).
4243
- **Цветная клеточная сетка**: Клетки могут иметь разные цвета в зависимости от их состояния и типа.
4344
- **Управление симуляцией**:
4445
- **Запуск/остановка**: Пробел для управления симуляцией.
4546
- **Пошаговое выполнение**: Стрелки вправо для перехода к следующему поколению.
4647
- **Рандомизация сетки**: Клавиша 'R' для случайного заполнения поля.
47-
48+
- **Управление правилами**: Реализована возможность динамически менять правила игры:
49+
- **Рождение**: Количество живых соседей, необходимое для рождения новой клетки.
50+
- **Выживание**: Минимальное и максимальное количество живых соседей, при котором клетка выживает.
51+
- **Перенаселение**: Количество живых соседей, при котором клетка умирает от перенаселения.
52+
- **Интерфейс пользователя**: Используется ImGui для создания пользовательского интерфейса:
53+
- Управление симуляцией (старт, стоп, следующий шаг).
54+
- Выбор и размещение паттернов (Glider, Blinker и т.д.).
55+
- Настройка правил игры через комбобоксы.
56+
- **Сетка и визуализация**:
57+
- Возможность переключаться между отображением и скрытием сетки.
58+
- Поддержка тороидального мира или ограниченного поля.
59+
- **Сохранение и загрузка**: Механизмы для сохранения и загрузки состояния игры.
60+
- **Камера**: Реализована навигация по 3D пространству с возможностью перемещения камеры.
4861

4962
## Установка
5063

@@ -72,13 +85,13 @@ git submodule update --recursive --remote
7285
```
7386

7487
4. **Сборка**:
75-
- Откройте проект в Visual Studio.
88+
- Откройте проект в Visual Studio 2022 или выше.
7689
- Настройте для разработки на Windows с поддержкой OpenGL.
7790
- Соберите решение.
7891

7992
5. **Сборка через гитхаб**
8093
- Внесите изменение в проект, подготовте релиз.
81-
- Сделайте гит тег.
94+
- Сделайте `git tag`.
8295
```bash
8396
git tag -a v1.0.0 -m "Версия 1.0.0"
8497
git push origin v1.0.0
@@ -172,7 +185,7 @@ git push origin v1.0.0
172185

173186
### Будущие улучшения
174187

175-
- [ ] Реализация различных наборов правил для "Жизни".
188+
- [x] Реализация различных наборов правил для "Жизни".
176189
- [ ] Управления изменения размера сетки.
177190
- [x] Оптимизация рендеринга для лучшей производительности на больших сетках или более сложных узорах.
178191

game/GPUAutomaton.cpp

+66-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,67 @@ void GPUAutomaton::CreateComputeShader() {
6060
next[pos.y * gridSize.x + pos.x] = nextState;
6161
}
6262
)";
63-
shaderManager.loadComputeShader("computeShader", computeShaderSource);
63+
const char* computedRulesShaderSource = R"(
64+
#version 430 core
65+
66+
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
67+
68+
layout(std430, binding = 0) buffer CurrentCells {
69+
int current[];
70+
};
71+
72+
layout(std430, binding = 1) buffer NextCells {
73+
int next[];
74+
};
75+
76+
uniform ivec2 gridSize;
77+
uniform bool isToroidal;
78+
uniform int birth; // Êîëè÷åñòâî ñîñåäåé äëÿ ðîæäåíèÿ
79+
uniform int survivalMin; // Ìèíèìàëüíîå êîëè÷åñòâî ñîñåäåé äëÿ âûæèâàíèÿ
80+
uniform int survivalMax; // Ìàêñèìàëüíîå êîëè÷åñòâî ñîñåäåé äëÿ âûæèâàíèÿ
81+
uniform int overpopulation; // Êîëè÷åñòâî ñîñåäåé äëÿ ñìåðòè îò ïåðåíàñåëåíèÿ
82+
83+
int countLiveNeighbors(ivec2 pos) {
84+
int count = 0;
85+
for(int dy = -1; dy <= 1; ++dy) {
86+
for(int dx = -1; dx <= 1; ++dx) {
87+
if(dx == 0 && dy == 0) continue;
88+
ivec2 neighbor = pos + ivec2(dx, dy);
89+
if (isToroidal) {
90+
neighbor = (neighbor + gridSize) % gridSize;
91+
} else {
92+
if (neighbor.x < 0 || neighbor.x >= gridSize.x || neighbor.y < 0 || neighbor.y >= gridSize.y) continue;
93+
}
94+
count += current[neighbor.y * gridSize.x + neighbor.x] > 0 ? 1 : 0;
95+
}
96+
}
97+
return count;
98+
}
99+
100+
void main() {
101+
ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
102+
if (pos.x >= gridSize.x || pos.y >= gridSize.y) return;
103+
104+
int currentState = current[pos.y * gridSize.x + pos.x];
105+
int neighbors = countLiveNeighbors(pos);
106+
107+
int nextState = 0;
108+
if (currentState == 1) { // Æèâàÿ êëåòêà
109+
// Âûæèâàíèå: êîë-âî ñîñåäåé îò survivalMin äî survivalMax âêëþ÷èòåëüíî
110+
if (neighbors >= survivalMin && neighbors <= survivalMax) {
111+
nextState = 1;
112+
} else {
113+
nextState = 0; // Ñìåðòü îò îäèíî÷åñòâà èëè ïåðåíàñåëåíèÿ
114+
}
115+
} else { // Ìåðòâàÿ êëåòêà
116+
if (neighbors == birth) {
117+
nextState = 1; // Ðîæäåíèå
118+
}
119+
}
120+
next[pos.y * gridSize.x + pos.x] = nextState;
121+
}
122+
)";
123+
shaderManager.loadComputeShader("computeShader", computedRulesShaderSource);
64124
shaderManager.linkComputeProgram("computeProgram", "computeShader");
65125
computeProgram = shaderManager.getProgram("computeProgram");
66126

@@ -79,6 +139,11 @@ void GPUAutomaton::Update() {
79139
glUniform2i(glGetUniformLocation(computeProgram, "gridSize"), gridWidth, gridHeight);
80140
glUniform1i(glGetUniformLocation(computeProgram, "isToroidal"), isToroidal); // Ïåðåäà÷à isToroidal
81141

142+
glUniform1i(glGetUniformLocation(computeProgram, "birth"), birth);
143+
glUniform1i(glGetUniformLocation(computeProgram, "survivalMin"), survivalMin);
144+
glUniform1i(glGetUniformLocation(computeProgram, "survivalMax"), survivalMax);
145+
glUniform1i(glGetUniformLocation(computeProgram, "overpopulation"), overpopulation);
146+
82147
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, cellsBuffer[bufferIndex]);
83148
glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, cellsBuffer[(bufferIndex + 1) % 2]);
84149

game/GPUAutomaton.h

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ class GPUAutomaton {
1818
void SetGridState(const std::vector<int>& inState);
1919
void SetToroidal(bool toroidal);
2020

21+
int birth = 3; // Ïî óìîë÷àíèþ 3 ñîñåäà äëÿ ðîæäåíèÿ
22+
int survivalMin = 2; // Ïî óìîë÷àíèþ ìèíèìóì 2 ñîñåäà äëÿ âûæèâàíèÿ
23+
int survivalMax = 3; // Ïî óìîë÷àíèþ ìàêñèìóì 3 ñîñåäà äëÿ âûæèâàíèÿ
24+
int overpopulation = 4; // Ïî óìîë÷àíèþ 4 èëè áîëüøå äëÿ ñìåðòè îò ïåðåíàñåëåíèÿ
25+
void SetBirth(int b) { birth = b; }
26+
void SetSurvivalMin(int smin) { survivalMin = smin; }
27+
void SetSurvivalMax(int smax) { survivalMax = smax; }
28+
void SetOverpopulation(int o) { overpopulation = o; }
29+
2130
private:
2231
void CreateComputeShader();
2332
void SetupBuffers();
@@ -26,6 +35,7 @@ class GPUAutomaton {
2635
GLuint computeProgram;
2736
GLuint cellsBuffer[2];
2837
GLint bufferIndex;
38+
2939
int gridWidth, gridHeight;
3040
bool isToroidal;
3141
};

game/GameController.h

+2
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,7 @@ class GameController {
135135
void SetCellInstanceProvider(ICellInstanceProvider* provider) {
136136
gameOfLife.SetCellProvider(provider); // Ïåðåäàåì ïðîâàéäåðà â GameOfLife
137137
}
138+
139+
GPUAutomaton& getGPUAutomaton() { return gameOfLife.getGPUAutomaton(); }
138140
};
139141
#endif // GAMECONTROLLER_H_

game/GameOfLife.cpp

+15-14
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,22 @@ void GameOfLife::nextGenerationGPU() {
115115
}
116116
else if (currentState) {
117117
// Åñëè êëåòêà óìèðàåò, äåëàåì å¸ òåìíî-ñåðîãî öâåòà
118-
cell.setColor(Vector3d(0.33f, 0.33f, 0.33f));
119-
}
120-
else {
121-
Vector3d oldColor = cell.getColor();
122-
float rc = oldColor.X();
123-
float gc = oldColor.Y();
124-
float bc = oldColor.Z();
125-
rc -= 0.01f;
126-
gc -= 0.01f;
127-
bc -= 0.01f;
128-
if (rc < 0) rc = 0.0f;
129-
if (gc < 0) gc = 0.0f;
130-
if (bc < 0) bc = 0.0f;
131-
cell.setColor(Vector3d(rc, bc, gc));
118+
//cell.setColor(Vector3d(0.3f, 0.3f, 0.3f));
119+
cell.setColor(Vector3d(0.05f, 0.05f, 0.06f));
132120
}
121+
//else {
122+
// Vector3d oldColor = cell.getColor();
123+
// float rc = oldColor.X();
124+
// float gc = oldColor.Y();
125+
// float bc = oldColor.Z();
126+
// rc -= 0.01f;
127+
// gc -= 0.01f;
128+
// bc -= 0.01f;
129+
// if (rc < 0) rc = 0.0f;
130+
// if (gc < 0) gc = 0.0f;
131+
// if (bc < 0) bc = 0.0f;
132+
// cell.setColor(Vector3d(rc, bc, gc));
133+
//}
133134

134135
SetCellColor(x, y, cell.getColor());
135136

game/GameOfLife.h

+2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class GameOfLife {
2323
void SetCellProvider(const ICellInstanceProvider* provider);
2424

2525
void SetCellColor(int x, int y, const Vector3d& color);
26+
27+
GPUAutomaton& getGPUAutomaton() { return gpuAutomaton; }
2628
private:
2729
Grid& grid;
2830
Grid nextGrid; // Äîáàâëÿåì nextGrid äëÿ äâîéíîãî áóôåðà

rendering/UIController.cpp

+49-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
UIController::UIController(GameController* gc) : gameController(gc), showExitDialog(false) {
44
aboutText = LoadTextFromResource(GetModuleHandle(NULL), IDR_ABOUT_TEXT);
5+
gpuAutomaton = &gc->getGPUAutomaton();
56
}
67

78
void UIController::InitializeUI() {
@@ -36,8 +37,19 @@ void UIController::DrawUI() {
3637
ImGui::NewFrame();
3738
//DrawMenuBar();
3839
// ------------------------------------ главное меню игры --------------------------------
40+
bool windowWasHovered = false;
41+
ImGui::SetNextWindowSize(ImVec2(0.0f, 650.0f), ImGuiCond_Always);
3942
ImGui::Begin("Управление игрой", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
40-
ImGui::SetWindowPos(ImVec2(6, 6), ImGuiCond_Once);
43+
// Проверяем, был ли окно под курсором мыши в прошлый раз
44+
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_RootWindow)) {
45+
windowWasHovered = true;
46+
}
47+
else if (windowWasHovered) {
48+
// Если мышь только что покинула область окна, снимаем фокус
49+
ImGui::SetWindowFocus(NULL); // NULL означает дефокусировку всех окон
50+
windowWasHovered = false;
51+
}
52+
ImGui::SetWindowPos(ImVec2(2, 2), ImGuiCond_Once);
4153

4254
// Находим максимальную ширину текста всех кнопок
4355
ImVec2 maxSize(0, 0);
@@ -110,6 +122,42 @@ void UIController::DrawUI() {
110122
}
111123
}
112124
ImGui::Separator();
125+
// В UI
126+
if (gpuAutomaton) {
127+
ImGui::Text("Правила игры:");
128+
ImGui::Separator();
129+
ImGui::Text("Рождение");
130+
{
131+
static const char* birthOptions[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
132+
ImGui::Combo("##birth", &gpuAutomaton->birth, birthOptions, IM_ARRAYSIZE(birthOptions));
133+
}
134+
ImGui::Separator();
135+
ImGui::Text("Выживание (мин)");
136+
{
137+
static const char* survivalMinOptions[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
138+
ImGui::Combo("##survivalMin", &gpuAutomaton->survivalMin, survivalMinOptions, IM_ARRAYSIZE(survivalMinOptions));
139+
}
140+
ImGui::Separator();
141+
ImGui::Text("Выживание (макс)");
142+
{
143+
static const char* survivalMaxOptions[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8" };
144+
ImGui::Combo("##survivalMax", &gpuAutomaton->survivalMax, survivalMaxOptions, IM_ARRAYSIZE(survivalMaxOptions));
145+
}
146+
ImGui::Separator();
147+
ImGui::Text("Перенаселение");
148+
{
149+
static int selectedOverPopulation = 3;
150+
static const char* overpopulationOptions[] = { "1", "2", "3", "4", "5", "6", "7", "8" };
151+
if (ImGui::Combo( "##overpopulation", &selectedOverPopulation, overpopulationOptions, IM_ARRAYSIZE(overpopulationOptions)) ) {
152+
gpuAutomaton->overpopulation = selectedOverPopulation + 1;
153+
}
154+
155+
}
156+
}
157+
else {
158+
ImGui::Text("GPUAutomaton не инициализирован!");
159+
}
160+
ImGui::Separator();
113161
static char saveFilename[128] = "state.txt";
114162
ImGui::Text("Имя файла");
115163
// Поле ввода с фиксированной шириной

rendering/UIController.h

+3
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@
1414
class UIController {
1515
private:
1616
GameController* gameController;
17+
GPUAutomaton* gpuAutomaton;
1718

1819
bool showExitDialog;
1920
std::string aboutText;
2021

2122
public:
2223
UIController(GameController* gc);
24+
void SetGPUAutomaton(GPUAutomaton* ga) { gpuAutomaton = ga; } // Äîáàâëÿåì ñåòòåð
25+
2326
void InitializeUI();
2427
std::string LoadTextFromResource(HINSTANCE hInstance, int resourceId);
2528
void DrawUI();

0 commit comments

Comments
 (0)