C ++ w / OpenGL (+17)
इसलिए मैंने 3-Isohedral उत्तल पेंटागन ग्रिड की कोशिश की। मेरे लिए काम करता है;) जीवन के नियमों का मानक खेल लागू होता है, सिवाय इसके कि ग्रिड अनंत नहीं है - छवि के बाहर सीमा कोशिकाएं हैं। शुरू में 30% कोशिकाएँ जीवित होती हैं।
इस तरह से ग्रिड दिखता है:
लाइव संस्करण:
नीली कोशिकाएं जीवित हैं, सफेद मृत हैं। लाल कोशिकाएं सिर्फ मर गईं, हरे रंग का जन्म हुआ। ध्यान दें कि छवि में कलाकृतियाँ gif संपीड़न का परिणाम हैं, SO को 10MB gifs पसंद नहीं है :(।
अभी भी जीवन: (+2)
दोलन टी = 2, टी = 3, टी = 12: (+9)
थरथरानवाला टी = 6, टी = 7: (+6)
कई और अलग-अलग ऑसिलेटर हैं ... लेकिन ऐसा लगता है कि ग्रिड एक जहाज के लिए नियमित रूप से पर्याप्त नहीं है ...
यह कुछ भी नहीं है (कोई बिंदु नहीं), लेकिन मुझे यह पसंद है:
कोड एक गड़बड़ है :) कुछ प्राचीन फिक्स्ड ओपनजीएल का उपयोग करता है। अन्यथा जिफ निर्यात के लिए GLEW, GLFW, GLM और ImageMagick का उपयोग किया जाता है।
/**
* Tile pattern generation is inspired by the code
* on http://www.jaapsch.net/tilings/
* It saved me a lot of thinkink (and debugging) - thank you, sir!
*/
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <FTGL/ftgl.h> //debug only
#include <ImageMagick-6/Magick++.h> //gif export
#include "glm/glm.hpp"
#include <iostream>
#include <array>
#include <vector>
#include <set>
#include <algorithm>
#include <unistd.h>
typedef glm::vec2 Point;
typedef glm::vec3 Color;
struct Tile {
enum State {ALIVE=0, DEAD, BORN, DIED, SIZE};
static const int VERTICES = 5;
static constexpr float SCALE = 0.13f;
static constexpr std::array<std::array<int, 7>, 18> DESC
{{
{{1, 0,0, 0,0,0, 0}},
{{0, 1,2, 0,2,1, 0}},
{{2, 2,3, 0,2,3, 1}},
{{1, 0,4, 0,0,1, 0}},
{{0, 1,2, 3,2,1, 0}},
{{2, 2,3, 3,2,3, 1}},
{{1, 0,4, 3,0,1, 0}},
{{0, 1,2, 6,2,1, 0}},
{{2, 2,3, 6,2,3, 1}},
{{1, 0,4, 6,0,1, 0}},
{{0, 1,2, 9,2,1, 0}},
{{2, 2,3, 9,2,3, 1}},
{{1, 0,4, 9,0,1, 0}},
{{0, 1,2,12,2,1, 0}},
{{2, 2,3,12,2,3, 1}},
{{1, 0,4,12,0,1, 0}},
{{0, 1,2,15,2,1, 0}},
{{2, 2,3,15,2,3, 1}}
}};
const int ID;
std::vector<Point> coords;
std::set<Tile*> neighbours;
State state;
State nextState;
Color color;
Tile() : ID(-1), state(DEAD), nextState(DEAD), color(1, 1, 1) {
const float ln = 0.6f;
const float h = ln * sqrt(3) / 2.f;
coords = {
Point(0.f, 0.f),
Point(ln, 0.f),
Point(ln*3/2.f,h),
Point(ln, h*4/3.f),
Point(ln/2.f, h)
};
for(auto &c : coords) {
c *= SCALE;
}
}
Tile(const int id, const std::vector<Point> coords_) :
ID(id), coords(coords_), state(DEAD), nextState(DEAD), color(1, 1, 1) {}
bool operator== (const Tile &other) const {
return ID == other.ID;
}
const Point & operator[] (const int i) const {
return coords[i];
}
void updateState() {
state = nextState;
}
/// returns "old" state
bool isDead() const {
return state == DEAD || state == DIED;
}
/// returns "old" state
bool isAlive() const {
return state == ALIVE || state == BORN;
}
void translate(const Point &p) {
for(auto &c : coords) {
c += p;
}
}
void rotate(const Point &p, const float angle) {
const float si = sin(angle);
const float co = cos(angle);
for(auto &c : coords) {
Point tmp = c - p;
c.x = tmp.x * co - tmp.y * si + p.x;
c.y = tmp.y * co + tmp.x * si + p.y;
}
}
void mirror(const float y2) {
for(auto &c : coords) {
c.y = y2 - (c.y - y2);
}
}
};
std::array<std::array<int, 7>, 18> constexpr Tile::DESC;
constexpr float Tile::SCALE;
class Game {
static const int CHANCE_TO_LIVE = 30; //% of cells initially alive
static const int dim = 4; //evil grid param
FTGLPixmapFont &font;
std::vector<Tile> tiles;
bool animate; //animate death/birth
bool debug; //show cell numbers (very slow)
bool exportGif; //save gif
bool run;
public:
Game(FTGLPixmapFont& font) : font(font), animate(false), debug(false), exportGif(false), run(false) {
//create the initial pattern
std::vector<Tile> init(18);
for(int i = 0; i < Tile::DESC.size(); ++i) {
auto &desc = Tile::DESC[i];
Tile &tile = init[i];
switch(desc[0]) { //just to check the grid
case 0: tile.color = Color(1, 1, 1);break;
case 1: tile.color = Color(1, 0.7, 0.7);break;
case 2: tile.color = Color(0.7, 0.7, 1);break;
}
if(desc[3] != i) {
const Tile &tile2 = init[desc[3]];
tile.translate(tile2[desc[4]] - tile[desc[1]]);
if(desc[6] != 0) {
float angleRad = getAngle(tile[desc[1]], tile[desc[2]]);
tile.rotate(tile[desc[1]], -angleRad);
tile.mirror(tile[desc[1]].y);
angleRad = getAngle(tile[desc[1]], tile2[desc[5]]);
tile.rotate(tile[desc[1]], angleRad);
}
else {
float angleRad = getAngle(tile[desc[1]], tile[desc[2]], tile2[desc[5]]);
tile.rotate(tile[desc[1]], angleRad);
}
}
}
const float offsets[4] {
init[2][8].x - init[8][9].x,
init[2][10].y - init[8][11].y,
init[8][12].x - init[14][13].x,
init[8][14].y - init[14][15].y
};
// create all the tiles
for(int dx = -dim; dx <= dim; ++dx) { //fuck bounding box, let's hardcode it
for(int dy = -dim; dy <= dim; ++dy) {
for(auto &tile : init) {
std::vector<Point> vert;
for(auto &p : tile.coords) {
float ax = dx * offsets[0] + dy * offsets[2];
float ay = dx * offsets[1] + dy * offsets[3];
vert.push_back(Point(p.x + ax, p.y + ay));
}
tiles.push_back(Tile(tiles.size(), vert));
tiles.back().color = tile.color;
tiles.back().state = tile.state;
}
}
}
//stupid bruteforce solution, but who's got time to think..
for(Tile &tile : tiles) { //find neighbours for each cell
for(Tile &t : tiles) {
if(tile == t) continue;
for(Point &p : t.coords) {
for(Point &pt : tile.coords) {
if(glm::distance(p, pt) < 0.01 ) {
tile.neighbours.insert(&t);
break;
}
}
}
}
assert(tile.neighbours.size() <= 9);
}
}
void init() {
for(auto &t : tiles) {
if(rand() % 100 < CHANCE_TO_LIVE) {
t.state = Tile::BORN;
}
else {
t.state = Tile::DEAD;
}
}
}
void update() {
for(auto &tile: tiles) {
//check colors
switch(tile.state) {
case Tile::BORN: //animate birth
tile.color.g -= 0.05;
tile.color.b += 0.05;
if(tile.color.b > 0.9) {
tile.state = Tile::ALIVE;
}
break;
case Tile::DIED: //animate death
tile.color += 0.05;
if(tile.color.g > 0.9) {
tile.state = Tile::DEAD;
}
break;
}
//fix colors after animation
switch(tile.state) {
case Tile::ALIVE:
tile.color = Color(0, 0, 1);
break;
case Tile::DEAD:
tile.color = Color(1, 1, 1);
break;
}
//draw polygons
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_POLYGON);
glColor3f(tile.color.r, tile.color.g, tile.color.b);
for(auto &pt : tile.coords) {
glVertex2f(pt.x, pt.y); //haha so oldschool!
}
glEnd();
}
//draw grid
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glColor3f(0, 0, 0);
for(auto &tile : tiles) {
glBegin(GL_POLYGON);
Point c; //centroid of tile
for(auto &pt : tile.coords) {
glVertex2f(pt.x, pt.y);
c += pt;
}
glEnd();
if(debug) {
c /= (float) Tile::VERTICES;
glRasterPos2f(c.x - 0.025, c.y - 0.01);
font.Render(std::to_string(tile.ID).c_str()); //
}
}
if(!run) {
return;
}
//compute new generation
for(Tile &tile: tiles) {
tile.nextState = tile.state; //initialize next state
int c = 0;
for(auto *n : tile.neighbours) {
if(n->isAlive()) c++;
}
switch(c) {
case 2:
break;
case 3:
if(tile.isDead()) {
tile.nextState = animate ? Tile::BORN : Tile::ALIVE;
tile.color = Color(0, 1, 0);
}
break;
default:
if(tile.isAlive()) {
tile.nextState = animate ? Tile::DIED : Tile::DEAD;
tile.color = Color(1, 0, 0);
}
break;
}
}
//switch state to new
for(Tile &tile: tiles) {
tile.updateState();
}
}
void stop() {run = false;}
void switchRun() {run = !run;}
bool isRun() {return run;}
void switchAnim() {animate = !animate;}
bool isAnim() {return animate;}
void switchExportGif() {exportGif = !exportGif;}
bool isExportGif() {return exportGif;}
void switchDebug() {debug = !debug;}
bool isDebug() const {return debug;}
private:
static float getAngle(const Point &p0, const Point &p1, Point const &p2) {
return atan2(p2.y - p0.y, p2.x - p0.x) - atan2(p1.y - p0.y, p1.x - p0.x);
}
static float getAngle(const Point &p0, const Point &p1) {
return atan2(p1.y - p0.y, p1.x - p0.x);
}
};
class Controlls {
Game *game;
std::vector<Magick::Image> *gif;
Controlls() : game(nullptr), gif(nullptr) {}
public:
static Controlls& getInstance() {
static Controlls instance;
return instance;
}
static void keyboardAction(GLFWwindow* window, int key, int scancode, int action, int mods) {
getInstance().keyboardActionImpl(key, action);
}
void setGame(Game *game) {
this->game = game;
}
void setGif(std::vector<Magick::Image> *gif) {
this->gif = gif;
}
private:
void keyboardActionImpl(int key, int action) {
if(!game || action == GLFW_RELEASE) {
return;
}
switch (key) {
case 'R':
game->stop();
game->init();
if(gif) gif->clear();
break;
case GLFW_KEY_SPACE:
game->switchRun();
break;
case 'A':
game->switchAnim();
break;
case 'D':
game->switchDebug();
break;
break;
case 'G':
game->switchExportGif();
break;
};
}
};
int main(int argc, char** argv) {
const int width = 620; //window size
const int height = 620;
const std::string window_title ("Game of life!");
const std::string font_file ("/usr/share/fonts/truetype/arial.ttf");
const std::string gif_file ("./gol.gif");
if(!glfwInit()) return 1;
GLFWwindow* window = glfwCreateWindow(width, height, window_title.c_str(), NULL, NULL);
glfwSetWindowPos(window, 100, 100);
glfwMakeContextCurrent(window);
GLuint err = glewInit();
if (err != GLEW_OK) return 2;
FTGLPixmapFont font(font_file.c_str());
if(font.Error()) return 3;
font.FaceSize(8);
std::vector<Magick::Image> gif; //gif export
std::vector<GLfloat> pixels(3 * width * height);
Game gol(font);
gol.init();
Controlls &controlls = Controlls::getInstance();
controlls.setGame(&gol);
controlls.setGif(&gif);
glfwSetKeyCallback(window, Controlls::keyboardAction);
glClearColor(1.f, 1.f, 1.f, 0);
while(!glfwWindowShouldClose(window) && !glfwGetKey(window, GLFW_KEY_ESCAPE)) {
glClear(GL_COLOR_BUFFER_BIT);
gol.update();
//add layer to gif
if(gol.isExportGif()) {
glReadPixels(0, 0, width, height, GL_RGB, GL_FLOAT, &pixels[0]);
Magick::Image image(width, height, "RGB", Magick::FloatPixel, &pixels[0]);
image.animationDelay(50);
gif.push_back(image);
}
std::string info = "ANIMATE (A): ";
info += gol.isAnim() ? "ON " : "OFF";
info += " | DEBUG (D): ";
info += gol.isDebug() ? "ON " : "OFF";
info += " | EXPORT GIF (G): ";
info += gol.isExportGif() ? "ON " : "OFF";
info += gol.isRun() ? " | STOP (SPACE)" : " | START (SPACE)";
font.FaceSize(10);
glRasterPos2f(-.95f, -.99f);
font.Render(info.c_str());
if(gol.isDebug()) font.FaceSize(8);
if(!gol.isDebug()) usleep(50000); //not so fast please!
glfwSwapBuffers(window);
glfwPollEvents();
}
//save gif to file
if(gol.isExportGif()) {
std::cout << "saving " << gif.size() << " frames to gol.gif\n";
gif.back().write("./last.png");
Magick::writeImages(gif.begin(), gif.end(), gif_file);
}
glfwTerminate();
return 0;
}