C ++ с OpenGL (+17)
Поэтому я попробовал 3-изоэдральную выпуклую сетку пятиугольника. Работает для меня;) Применяются стандартные правила жизни, за исключением того, что сетка не бесконечна - за пределами изображения есть граничные ячейки. 30% клеток изначально живы.
Вот как выглядит сетка:
Живая версия:
Синие клетки живы, белые мертвы. Красные клетки только что умерли, зеленые только что родились. Обратите внимание, что артефакты на изображении являются результатом сжатия GIF, поэтому они не любят 10 МБ GIF :(.
Натюрморт: (+2)
Генераторы T = 2, T = 3, T = 12: (+9)
Генераторы T = 6, T = 7: (+6)
Есть еще много разных осцилляторов ... Но кажется, что сетка не достаточно регулярна для корабля ...
Это ничего (без баллов), но мне это нравится
Код беспорядок :) Использует некоторые древние исправленные OpenGL. В противном случае используются GLEW, GLFW, GLM и ImageMagick для экспорта GIF.
/**
* 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;
}