├── color.h ├── color.cpp ├── particle_table.h ├── .gitignore ├── README.md ├── particle.h ├── particle.cpp ├── particle_table.cpp ├── LICENSE └── main.cpp /color.h: -------------------------------------------------------------------------------- 1 | class Color3 { 2 | public: 3 | Color3(double R, double G, double B); 4 | Color3(); 5 | double R; 6 | double G; 7 | double B; 8 | }; -------------------------------------------------------------------------------- /color.cpp: -------------------------------------------------------------------------------- 1 | #include "color.h" 2 | 3 | Color3::Color3(double R, double G, double B) { 4 | this->R = R; 5 | this->G = G; 6 | this->B = B; 7 | } 8 | 9 | Color3::Color3() = default; -------------------------------------------------------------------------------- /particle_table.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "particle.h" 4 | 5 | class ParticleTable { 6 | public: 7 | ParticleTable(); 8 | ParticleTable(int size); 9 | 10 | Particle get(uint x, uint y); 11 | void set(uint x, uint y, Particle value); 12 | 13 | void update(); 14 | void draw(); 15 | 16 | private: 17 | std::map, Particle> table; 18 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # VSCode 5 | .vscode 6 | 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sand++ 2 | Sand made in c++ in a week, I wanted to learn how to code in c++ 3 | 4 | ## Building 5 | You can simply build it with `g++ -std=gnu++17 main.cpp color.cpp particle.cpp particle_table.cpp -lglut -lGLU -lGL` 6 | 7 | You'll need OpenGL and glut tho 8 | 9 | ## Running 10 | It has two command line arguments: `-tps` and `-size`. `-tps` changes the speed at which the game will update the cells, in Ticks Per Second (TPS), you can use it like so: `-tps=50`. `-size` **SHOULD** change the size of the game, but for now, all it does is NOT WORK properly, you can use it like so: `-size=500`. 11 | -------------------------------------------------------------------------------- /particle.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "color.h" 3 | 4 | class Particle { 5 | public: 6 | Particle(uint x, uint y, Color3 color, std::string name, uint id, int density); 7 | Particle(); 8 | static Particle Air(uint x, uint y); 9 | static Particle Sand(uint x, uint y); 10 | static Particle Water(uint x, uint y); 11 | void update(); 12 | void draw(); 13 | bool fall(); 14 | bool liquidSpread(); 15 | bool floatUp(); 16 | uint x; 17 | uint y; 18 | Color3 color; 19 | std::string name; 20 | uint id; 21 | int density; 22 | }; -------------------------------------------------------------------------------- /particle.cpp: -------------------------------------------------------------------------------- 1 | #include "particle.h" 2 | 3 | Particle::Particle(uint x, uint y, Color3 color, std::string name, uint id, int density) { 4 | this->x = x; 5 | this->y = y; 6 | this->color = color; 7 | this->name = name; 8 | this->id = id; 9 | this->density = density; 10 | } 11 | 12 | Particle::Particle() = default; 13 | 14 | Particle Particle::Air(uint x, uint y) { 15 | return *new Particle(x,y,*new Color3(0.0, 0.0, 0.0),std::string("air"),0,0); 16 | } 17 | 18 | Particle Particle::Sand(uint x, uint y) { 19 | return *new Particle(x,y,*new Color3(1.0, 1.0, 0.0),std::string("sand"),1,2); 20 | } 21 | 22 | Particle Particle::Water(uint x, uint y) { 23 | return *new Particle(x,y,*new Color3(0.0, 0.0, 1.0),std::string("water"),2,1); 24 | } -------------------------------------------------------------------------------- /particle_table.cpp: -------------------------------------------------------------------------------- 1 | #include "particle_table.h" 2 | #include 3 | 4 | ParticleTable::ParticleTable() = default; 5 | 6 | ParticleTable::ParticleTable(int size) { 7 | 8 | for (uint x = 0; x != uint(size); x++) { 9 | for (uint y = 0; y != uint(size); y++) { 10 | table[std::pair(x,y)] = Particle::Air(x,y); 11 | } 12 | } 13 | } 14 | 15 | Particle ParticleTable::get(uint x, uint y){ 16 | return table[std::pair(x,y)]; 17 | } 18 | 19 | void ParticleTable::set(uint x, uint y, Particle value) { 20 | value.x = x; 21 | value.y = y; 22 | table[std::pair(x,y)] = value; 23 | value.draw(); 24 | } 25 | 26 | void ParticleTable::update() { 27 | for (auto& a : table) { 28 | auto v = a.second; 29 | if (v.id != 0) { 30 | v.update(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Lars von Wangenheim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "particle_table.h" 8 | using namespace std; 9 | 10 | const int MOUSE_LEFT = 0; 11 | const int MOUSE_RIGHT = 2; 12 | const int MOUSE_MIDDLE = 1; 13 | const int MOUSE_SCROLL_UP = 3; 14 | const int MOUSE_SCROLL_DOWN = 4; 15 | int window; 16 | 17 | int SIZE = 720; 18 | int PARTICLE_SIZE = 10; 19 | int PARTICLE_AMOUNT = SIZE/PARTICLE_SIZE; 20 | 21 | float TICK_SPEED = 48; 22 | double MS_P_TICK = 1000.0*(1.0/TICK_SPEED); 23 | 24 | GLubyte* buffer = new GLubyte[SIZE*SIZE*3]; 25 | 26 | int frame=0,timeNow,timeBase=0; 27 | int tickCounter=0,tickTimeNow,tickTimeBase=0; 28 | 29 | float fps; 30 | float tps; 31 | 32 | tuple click; 33 | 34 | ParticleTable * particles = new ParticleTable(PARTICLE_AMOUNT); 35 | 36 | void text(float x, float y, void* font, string text) 37 | { 38 | glColor3f(1.0f,1.0f,1.0f); 39 | glRasterPos2f(x, y); 40 | int len, i; 41 | len = (int)text.length(); 42 | for (i = 0; i < len; i++) { 43 | glutBitmapCharacter(font, text[i]); 44 | } 45 | } 46 | 47 | void makePixel(int x, int y, Color3 color) 48 | { 49 | int position = (x + y * SIZE) * 3; 50 | buffer[position] = color.R*255; 51 | buffer[position + 1] = color.G*255; 52 | buffer[position + 2] = color.B*255; 53 | } 54 | 55 | void drawParticle(uint x, uint y, Color3 color) { 56 | if (x < 0 || x > 71 || y < 0 || y > 71) {return;} 57 | uint x1 = x * PARTICLE_SIZE; 58 | uint y1 = y * PARTICLE_SIZE; 59 | for (uint x2 = x1; x2 < x1 + PARTICLE_SIZE; x2++) { 60 | for (uint y2 = y1; y2 < y1 + PARTICLE_SIZE; y2++) { 61 | makePixel(x2,y2,color); 62 | } 63 | } 64 | } 65 | 66 | void Particle::draw() { 67 | drawParticle(x, y, color); 68 | } 69 | 70 | 71 | 72 | bool Particle::fall() { 73 | if (y > 0) { 74 | Particle p; 75 | if ((p = particles->get(x,y-1)).density < density) { 76 | (*particles).set(x,y,p); 77 | (*particles).set(x,y-1,*this); 78 | return true; 79 | } 80 | else if (x < PARTICLE_AMOUNT-1 && (p = particles->get(x+1,y-1)).density < density) 81 | { 82 | (*particles).set(x,y,p); 83 | (*particles).set(x+1,y-1,*this); 84 | return true; 85 | } 86 | else if (x > 0 && (p = particles->get(x-1,y-1)).density < density) 87 | { 88 | (*particles).set(x,y,p); 89 | (*particles).set(x-1,y-1,*this); 90 | return true; 91 | } 92 | 93 | } 94 | return false; 95 | } 96 | 97 | bool Particle::liquidSpread() { 98 | int direction = (rand() % 5) - 2; 99 | if (x + direction > 0 && x + direction < PARTICLE_AMOUNT && particles->get(x+direction,y).id == 0) 100 | { 101 | this->x+=direction; 102 | (*particles).set(x,y,*this); 103 | (*particles).set(x-direction,y,Air(x-direction,y)); 104 | return true; 105 | } 106 | return false; 107 | } 108 | 109 | void Particle::update() { 110 | switch (this->id) 111 | { 112 | case 0: 113 | break; 114 | 115 | case 2: { 116 | if (!fall()) {liquidSpread();} 117 | break; 118 | } 119 | 120 | default: 121 | fall(); 122 | break; 123 | } 124 | 125 | } 126 | 127 | /*void ParticleTable::draw() { 128 | for (auto& a : table) { 129 | auto v = a.second; 130 | if (v.name != "air") { 131 | v.draw(); 132 | } 133 | } 134 | 135 | 136 | }*/ 137 | 138 | void draw(void) 139 | { 140 | glClear(GL_COLOR_BUFFER_BIT); 141 | 142 | glColor3d(1.0,1.0,1.0); 143 | 144 | //(*particles).draw(); 145 | glDrawPixels(SIZE,SIZE,GL_RGB, GL_UNSIGNED_BYTE, buffer); 146 | 147 | text(-1.0f, 0.95f, GLUT_BITMAP_HELVETICA_12,"FPS: " + to_string(int(round(fps)))); 148 | text(-1.0f, 0.9f,GLUT_BITMAP_HELVETICA_12, "TPS: " + to_string(int(round(tps))) + "/" + to_string(int(TICK_SPEED))); 149 | glRasterPos2f(-1, -1); 150 | 151 | glFlush(); 152 | } 153 | 154 | void tick() { 155 | tickTimeNow=glutGet(GLUT_ELAPSED_TIME); 156 | tps = 1000.0/(tickTimeNow-tickTimeBase); 157 | tickTimeBase = tickTimeNow; 158 | 159 | (*particles).update(); 160 | glutPostWindowRedisplay(window); 161 | } 162 | 163 | void idle() { 164 | frame++; 165 | timeNow=glutGet(GLUT_ELAPSED_TIME); 166 | 167 | if (timeNow - timeBase > MS_P_TICK) { 168 | fps = frame*1000.0/(timeNow-timeBase); 169 | timeBase = timeNow; 170 | frame = 0; 171 | 172 | tick(); 173 | } 174 | if (get<2>(click)) { 175 | uint x=get<0>(click), y=get<1>(click); 176 | (*particles).set(x, y, get<3>(click)(x, y)); 177 | } 178 | } 179 | 180 | void onMouse(int button, int state, int x, int y) { 181 | if (x > SIZE || x < 0 || y > SIZE || y < 0) {return;} 182 | uint x1 = round(x/PARTICLE_SIZE); 183 | float y1 = y/PARTICLE_SIZE; 184 | y1 = y1 / PARTICLE_AMOUNT - 0.5; 185 | y1 = round((-y1 + 0.5) * PARTICLE_AMOUNT) - 1; 186 | auto p = Particle::Air; 187 | switch (button) { 188 | case MOUSE_LEFT: { 189 | p = Particle::Sand; 190 | break; 191 | } 192 | case MOUSE_RIGHT: { 193 | p = Particle::Water; 194 | break; 195 | } 196 | default: { 197 | break; 198 | } 199 | } 200 | click = tuple(x1,y1,state==GLUT_DOWN,p); 201 | 202 | (*particles).set(x1, y1, p(x1,y1)); 203 | glutPostWindowRedisplay(window); 204 | } 205 | 206 | void onMotion(int x, int y) { 207 | if (get<2>(click)) { 208 | if (x > SIZE || x < 0 || y > SIZE || y < 0) {return;} 209 | uint x1 = round(x/PARTICLE_SIZE); 210 | float y1 = y/PARTICLE_SIZE; 211 | y1 = y1 / PARTICLE_AMOUNT - 0.5; 212 | y1 = round((-y1 + 0.5) * PARTICLE_AMOUNT) - 1; 213 | click = tuple(x1,y1,true,get<3>(click)); 214 | 215 | (*particles).set(x1, y1, get<3>(click)(x1,y1)); 216 | glutPostWindowRedisplay(window); 217 | 218 | } 219 | } 220 | 221 | void resize(int, int) { 222 | glutReshapeWindow(SIZE,SIZE); 223 | } 224 | 225 | int main(int argc, char *argv[]) 226 | { 227 | for (int i = 0; i < argc; ++i) { 228 | if (string(argv[i]).find("-tps=", 0) == 0) { 229 | TICK_SPEED = stof(string(argv[i]).erase(0,5)); 230 | MS_P_TICK = 1000.0*(1.0/TICK_SPEED); 231 | } 232 | else if (string(argv[i]).find("-size=", 0) == 0) 233 | { 234 | SIZE = stof(string(argv[i]).erase(0,6)); 235 | buffer = new GLubyte[SIZE*SIZE*3]; 236 | particles = new ParticleTable(PARTICLE_AMOUNT); 237 | } 238 | 239 | } 240 | 241 | glutInit(&argc, argv); 242 | 243 | glutInitDisplayMode(GLUT_SINGLE); 244 | glutInitWindowSize(SIZE, SIZE); 245 | glutInitWindowPosition(100, 100); 246 | window = glutCreateWindow("Sand++"); 247 | 248 | glutMouseFunc(onMouse); 249 | glutMotionFunc(onMotion); 250 | glutDisplayFunc(draw); 251 | glutIdleFunc(idle); 252 | //glutReshapeFunc(resize); 253 | 254 | //glutTimerFunc(MS_P_TICK, tick, 0); 255 | glClearColor(0.0f,0.0f,0.0f,1.0f); 256 | glutMainLoop(); 257 | return 0; 258 | } --------------------------------------------------------------------------------