Commit d914fd78 authored by Yann Boucher's avatar Yann Boucher
Browse files

Ajout de la règle de transition non isotropique, et support de Langston Loops

parent 47b53c18
Pipeline #79125 passed with stages
in 47 seconds
......@@ -89,8 +89,8 @@ private:
#define REGISTER_FACTORY_ENTRY(base, child, name) \
namespace detail { \
/* nécéssaire pour que la transition soit enregistrée parmis la liste de classes de transitions*/ \
/* variable globale static permettant seulement de forcer l'enregistrement d'une telle classe dans la factory, 'static' permettant l'instantiation de cette variable dans des fichiers .cpp différents lors de l'inclusion*/ \
static bool const fact_##base##_##child##_registered = register_to_factory<base, child>(name); \
/* variable globale static permettant seulement de forcer l'enregistrement d'une telle classe dans la factory */ \
bool const fact_##base##_##child##_registered = register_to_factory<base, child>(name); \
}
//! \internal namespace contenant les détails d'implémentation de Factory
......
......@@ -64,6 +64,13 @@ public:
throw NeighborhoodException("La coordonnée existe déjà. \n");
}
//! \brief Retourne une copie du voisinage, miroir selon l'axe vertical.
Neighborhood flip_vertically() const;
//! \brief Retourne une copie du voisinage, miroir selon l'axe horizontal.
Neighborhood flip_horizontally() const;
//! \brief Retourne une copie du voisinage, tournée de 90° dans le sens des aiguilles d'une montre.
Neighborhood rotate90() const;
//! \brief Retourne le nombre de voisins dans le Neighborhood.
unsigned size() const
{ return neighborPositions.size(); }
......
......@@ -25,6 +25,5 @@ public:
private:
DEFINE_CONFIGURABLE_DYNLIST (CoordinateProperty, neighbors, "Neighbors", ());
};
REGISTER_FACTORY_ENTRY(NeighborhoodRule, ArbitraryNeighborhoodRule, "Arbitrary");
#endif // ARBITRARYNEIGHBORHOODRULE_HPP
......@@ -33,6 +33,5 @@ private:
//! \brief Rayon du voisinage (en nombre de cellule)
DEFINE_CONFIGURABLE_PROPERTY(IntegerProperty, radius, "Radius", 1);
};
REGISTER_FACTORY_ENTRY(NeighborhoodRule, mooreNeighborhoodRule, "Moore");
#endif // MOORENEIGHBOURHOODRULE_HPP
......@@ -33,6 +33,5 @@ private:
//! \brief Rayon du voisinage (en nombre de cellule)
DEFINE_CONFIGURABLE_PROPERTY(IntegerProperty, radius, "Radius", 1);
};
REGISTER_FACTORY_ENTRY(NeighborhoodRule, vonNeumannNeighborhoodRule, "Von Neumann");
#endif // VONNEUMANNNEIGHBORHOODRULE_HPP
......@@ -17,6 +17,5 @@ private:
DEFINE_CONFIGURABLE_PROPERTY(IntegerProperty, states, "State count", 1);
DEFINE_CONFIGURABLE_PROPERTY(IntegerProperty, threshold, "Threshold");
};
REGISTER_FACTORY_ENTRY(TransitionRule, CircularTransition, "Circular");
#endif // CIRCULARTRANSITION_HPP
......@@ -11,6 +11,5 @@ public:
bool acceptFormat(const std::vector<NeighborhoodFormat>&) const;
unsigned int getState(unsigned int, const Neighborhood &) const;
};
REGISTER_FACTORY_ENTRY(TransitionRule, LifeGameTransition, "Game of Life");
#endif // LIFEGAMETRANSITION_H
#ifndef NONISOTROPICTRANSITIONRULE_HPP
#define NONISOTROPICTRANSITIONRULE_HPP
#include <exception>
#include "transitionrule.hpp"
//! \brief Exception lancée lors de l'évaluation d'une règle totalistique.
class NonIsotropicRuleException : public std::exception
{
public:
NonIsotropicRuleException(const std::string& str)
: m_what(str)
{}
const char * what() const noexcept override
{ return m_what.c_str(); }
private:
std::string m_what;
};
//! \brief Représente une entrée de règle de transition non isotropique configurable.
//! C'est à dire une entrée dans la table de transition permettant de déterminer si une transition s'effectue selon un état de départ et un voisinage donnés.
class NonIsotropicRuleEntry
{
public:
//! Construit la règle à partir d'une chaîne contenant la définition de la règle.
NonIsotropicRuleEntry(std::string rule_string, bool vertical_symmetry, bool horizontal_symmetry, bool rotate4);
//! Détermine si l'entrée accepte l'état et le voisinage donnés.
//! \param initial_state L'état de la cellule concernée.
//! \param neighborhood Le voisinage de la cellule concernée.
//! \param next Référence vers la valeur du prochain état de la cellule si l'entrée est acceptée.
//! \returns true si accepté, false sinon.
bool accept(unsigned initial_state, const Neighborhood& neighborhood, unsigned& next) const;
private:
bool m_vertical_sym, m_horizontal_sym, m_rotate4;
bool m_initial_state_is_variable;
// could use a union here, but it's a bit tricky as we'd have to call the correct destructor
std::string m_initial_variable;
unsigned m_initial_state;
std::vector<unsigned> m_constraints;
std::string m_result_state;
};
//! \brief Représente une règle de transition totalistique configurable textuellement.
//! Le format d'une règle est tel que suit:
/**
## format d'une règle de transition générale (non-isotropique)
On associe un index (commençant) à 0 à chaque cellule du voisinnage, dans un ordre de gauche à droite et de haut en bas.
Le format est alors presque identique, à la seule différence que au lieu d'intervalles associés à des états, on a juste un état attendu à chaque position du voisinage:
etat_cellule, état_voisin_de_position_1, ..., état_voisin_de_position_1-> nouvel_etat
par ex le voisinage :
**
*c*
* *
est énuméré ainsi:
01
2c3
4 5
et on peut créer par exemple cette règle:
0, 0, 1, 0, 0, 1, 1 -> 1
Les règles sont traitées dans l'ordre de leur écriture, si deux règles pourraient s'appliquer à une cellule et à un voisinage, on applique la première dans l'ordre des lignes, du haut vers le bas.
*/
class NonIsotropicTransition : public TransitionRule
{
public:
bool acceptFormat(const std::vector<NeighborhoodFormat>&) const override
{ return true; }
unsigned int getState(unsigned int initial, const Neighborhood &neighborhood) const override;
private:
void generate_entries() const;
private:
mutable std::vector<NonIsotropicRuleEntry> m_entries;
DEFINE_CONFIGURABLE_PROPERTY(StringProperty, rule_string, "Rule String");
};
#endif // NONISOTROPICTRANSITIONRULE_HPP
{
"alphabet": [
{
"color": [
255,
255,
255
],
"name": "Dead"
},
{
"color": [
0,
0,
255
],
"name": "Alive"
}
],
"author": "JM",
"date": "jeu. juin 3 2021",
"desc": "Game of Life définit par une Totalistic rulestring",
"neighborhood_data": {
"radius": 1
},
"neighborhood_name": "Von Neumann",
"title": "gameoflife_rulestring",
"transition_data": {
"rule_string": "0,0:* ,1:[3]->1\n1,0:[0..1],1:* ->0\n1,0:[4..*],1:* ->0"
},
"transition_name": "Totalistic rulestring"
}
{
"alphabet": [
{
"color": [
255,
255,
255
],
"name": "Dead"
},
{
"color": [
0,
0,
255
],
"name": "Alive"
}
],
"author": "JM",
"date": "jeu. juin 3 2021",
"desc": "Langston's Loops.\n!!CORRIGER L'ALPHABET!!",
"neighborhood_data": {
"radius": 1
},
"neighborhood_name": "Von Neumann",
"title": "langston_loops",
"transition_data": {
"rule_string": "rotate4\n0,0,0,0,0->0\n0,0,0,0,1->2\n0,0,0,0,2->0\n0,0,0,0,3->0\n0,0,0,0,5->0\n0,0,0,0,6->3\n0,0,0,0,7->1\n0,0,0,1,1->2\n0,0,0,1,2->2\n0,0,0,1,3->2\n0,0,0,2,1->2\n0,0,0,2,2->0\n0,0,0,2,3->0\n0,0,0,2,6->2\n0,0,0,2,7->2\n0,0,0,3,2->0\n0,0,0,5,2->5\n0,0,0,6,2->2\n0,0,0,7,2->2\n0,0,1,0,2->2\n0,0,1,1,2->0\n0,0,2,0,2->0\n0,0,2,0,3->0\n0,0,2,0,5->0\n0,0,2,1,2->5\n0,0,2,2,2->0\n0,0,2,3,2->2\n0,0,5,2,2->2\n0,1,2,3,2->1\n0,1,2,4,2->1\n0,1,2,5,2->5\n0,1,2,6,2->1\n0,1,2,7,2->1\n0,1,2,7,5->1\n0,1,4,2,2->1\n0,1,4,3,2->1\n0,1,4,4,2->1\n0,1,4,7,2->1\n0,1,6,2,5->1\n0,1,7,2,2->1\n0,1,7,2,5->5\n0,1,7,5,2->1\n0,1,7,6,2->1\n0,1,7,7,2->1\n0,2,5,2,7->1\n1,0,0,0,1->1\n1,0,0,0,6->1\n1,0,0,0,7->7\n1,0,0,1,1->1\n1,0,0,1,2->1\n1,0,0,2,1->1\n1,0,0,2,4->4\n1,0,0,2,7->7\n1,0,0,5,1->1\n1,0,1,0,1->1\n1,0,1,1,1->1\n1,0,1,2,4->4\n1,0,1,2,7->7\n1,0,2,0,2->6\n1,0,2,1,2->1\n1,0,2,2,1->1\n1,0,2,2,4->4\n1,0,2,2,6->3\n1,0,2,2,7->7\n1,0,2,3,2->7\n1,0,2,4,2->4\n1,0,2,6,2->6\n1,0,2,6,4->4\n1,0,2,6,7->7\n1,0,2,7,1->0\n1,0,2,7,2->7\n1,0,5,4,2->7\n1,1,1,1,2->1\n1,1,1,2,2->1\n1,1,1,2,4->4\n1,1,1,2,5->1\n1,1,1,2,6->1\n1,1,1,2,7->7\n1,1,1,5,2->2\n1,1,2,1,2->1\n1,1,2,2,2->1\n1,1,2,2,4->4\n1,1,2,2,5->1\n1,1,2,2,7->7\n1,1,2,3,2->1\n1,1,2,4,2->4\n1,1,2,6,2->1\n1,1,2,7,2->7\n1,1,3,2,2->1\n1,2,2,2,4->4\n1,2,2,2,7->7\n1,2,2,4,3->4\n1,2,2,5,4->7\n1,2,3,2,4->4\n1,2,3,2,7->7\n1,2,4,2,5->5\n1,2,4,2,6->7\n1,2,5,2,7->5\n2,0,0,0,1->2\n2,0,0,0,2->2\n2,0,0,0,4->2\n2,0,0,0,7->1\n2,0,0,1,2->2\n2,0,0,1,5->2\n2,0,0,2,1->2\n2,0,0,2,2->2\n2,0,0,2,3->2\n2,0,0,2,4->2\n2,0,0,2,5->0\n2,0,0,2,6->2\n2,0,0,2,7->2\n2,0,0,3,2->6\n2,0,0,4,2->3\n2,0,0,5,1->7\n2,0,0,5,2->2\n2,0,0,5,7->5\n2,0,0,7,2->2\n2,0,1,0,2->2\n2,0,1,1,2->2\n2,0,1,2,2->2\n2,0,1,4,2->2\n2,0,1,7,2->2\n2,0,2,0,2->2\n2,0,2,0,3->2\n2,0,2,0,5->2\n2,0,2,0,7->3\n2,0,2,1,2->2\n2,0,2,1,5->2\n2,0,2,2,1->2\n2,0,2,2,2->2\n2,0,2,2,7->2\n2,0,2,3,2->1\n2,0,2,4,2->2\n2,0,2,4,5->2\n2,0,2,5,2->0\n2,0,2,5,5->2\n2,0,2,6,2->2\n2,0,2,7,2->2\n2,0,3,1,2->2\n2,0,3,2,1->6\n2,0,3,2,2->6\n2,0,3,4,2->2\n2,0,4,2,2->2\n2,0,5,1,2->2\n2,0,5,2,1->2\n2,0,5,2,2->2\n2,0,5,5,2->1\n2,0,5,7,2->5\n2,0,6,2,2->2\n2,0,6,7,2->2\n2,0,7,1,2->2\n2,0,7,2,2->2\n2,0,7,4,2->2\n2,0,7,7,2->2\n2,1,1,2,2->2\n2,1,1,2,6->1\n2,1,2,2,2->2\n2,1,2,2,4->2\n2,1,2,2,6->2\n2,1,2,2,7->2\n2,1,4,2,2->2\n2,1,5,2,2->2\n2,1,6,2,2->2\n2,1,7,2,2->2\n2,2,2,2,7->2\n2,2,2,4,4->2\n2,2,2,4,6->2\n2,2,2,7,6->2\n2,2,2,7,7->2\n3,0,0,0,1->3\n3,0,0,0,2->2\n3,0,0,0,4->1\n3,0,0,0,7->6\n3,0,0,1,2->3\n3,0,0,4,2->1\n3,0,0,6,2->2\n3,0,1,0,2->1\n3,0,1,2,2->0\n3,0,2,5,1->1\n4,0,1,1,2->0\n4,0,1,2,2->0\n4,0,1,2,5->0\n4,0,2,1,2->0\n4,0,2,2,2->1\n4,0,2,3,2->6\n4,0,2,5,2->0\n4,0,3,2,2->1\n5,0,0,0,2->2\n5,0,0,2,1->5\n5,0,0,2,2->5\n5,0,0,2,3->2\n5,0,0,2,7->2\n5,0,0,5,2->0\n5,0,2,0,2->2\n5,0,2,1,2->2\n5,0,2,1,5->2\n5,0,2,2,2->0\n5,0,2,2,4->4\n5,0,2,7,2->2\n5,1,2,1,2->2\n5,1,2,2,2->0\n5,1,2,4,2->2\n5,1,2,7,2->2\n6,0,0,0,1->1\n6,0,0,0,2->1\n6,0,2,1,2->0\n6,1,2,1,2->5\n6,1,2,1,3->1\n6,1,2,2,2->5\n7,0,0,0,7->7\n7,0,1,1,2->0\n7,0,1,2,2->0\n7,0,1,2,5->0\n7,0,2,1,2->0\n7,0,2,2,2->1\n7,0,2,2,5->1\n7,0,2,3,2->1\n7,0,2,5,2->5\n7,0,2,7,2->0"
},
"transition_name": "Non isotropic rulestring"
}
......@@ -21,6 +21,36 @@ unsigned int Neighborhood::getAt(Coord c) const
return neighborPositions.at(c);
}
Neighborhood Neighborhood::flip_vertically() const
{
Neighborhood n;
for (const auto& val : neighborPositions)
{
n.addNeighbor(Coord{val.first.x, -val.first.y}, val.second);
}
return n;
}
Neighborhood Neighborhood::flip_horizontally() const
{
Neighborhood n;
for (const auto& val : neighborPositions)
{
n.addNeighbor(Coord{-val.first.x, val.first.y}, val.second);
}
return n;
}
Neighborhood Neighborhood::rotate90() const
{
Neighborhood n;
for (const auto& val : neighborPositions)
{
n.addNeighbor(Coord{-val.first.y, val.first.x}, val.second);
}
return n;
}
bool Neighborhood::isUnique(Coord c) const
{
return neighborPositions.count(c) == 0;
......
......@@ -13,6 +13,8 @@ Représente un voisinage arbitraire, défini par l'utilisateur.
#include "neighborhood.hpp"
#include "grid.h"
REGISTER_FACTORY_ENTRY(NeighborhoodRule, ArbitraryNeighborhoodRule, "Arbitrary");
Neighborhood ArbitraryNeighborhoodRule::getNeighborhood(const Grid &grid, Coord pos) const
{
Neighborhood n;
......
#include "mooreNeighborhoodRule.hpp"
REGISTER_FACTORY_ENTRY(NeighborhoodRule, mooreNeighborhoodRule, "Moore");
mooreNeighborhoodRule::mooreNeighborhoodRule(int _radius)
{
radius.val = _radius;
......
#include "vonNeumannNeighborhoodRule.hpp"
REGISTER_FACTORY_ENTRY(NeighborhoodRule, vonNeumannNeighborhoodRule, "Von Neumann");
vonNeumannNeighborhoodRule::vonNeumannNeighborhoodRule(int _radius)
{
radius.val = _radius;
......
......@@ -37,6 +37,7 @@ SOURCES += \
modelloadingdialog.cpp \
configurationloadingdialog.cpp \
transition_rules/totalistictransition.cpp \
transition_rules/nonisotropictransition.cpp \
uibuildervisitor.cpp
......@@ -71,7 +72,8 @@ HEADERS += \
../include/transition_rules/totalistictransition.hpp \
../include/modelloadingdialog.hpp \
../include/neighborhoodWidget.hpp \
../include/configurationloadingdialog.hpp
../include/configurationloadingdialog.hpp \
../include/transition_rules/nonisotropictransition.hpp
FORMS += \
../forms/colorlabel.ui \
......
#include "circulartransition.hpp"
REGISTER_FACTORY_ENTRY(TransitionRule, CircularTransition, "Circular");
unsigned CircularTransition::getState(unsigned cell, const Neighborhood &neighborhood) const
{
unsigned next_state = (cell+1) % states.val;
......
#include "lifegametransition.h"
#include "neighborhoodrule.hpp"
REGISTER_FACTORY_ENTRY(TransitionRule, LifeGameTransition, "Game of Life");
LifeGameTransition::LifeGameTransition()
{
......
#include "nonisotropictransition.hpp"
#include <sstream>
#include "mathexpr.hpp"
REGISTER_FACTORY_ENTRY(TransitionRule, NonIsotropicTransition, "Non isotropic rulestring");
static std::vector<std::string> split(std::string str, std::string token){
std::vector<std::string>result;
while(str.size()){
size_t index = str.find(token);
if(index != std::string::npos){
result.push_back(str.substr(0,index));
str = str.substr(index+token.size());
if(str.size()==0)result.push_back(str);
}else{
result.push_back(str);
str = "";
}
}
return result;
}
NonIsotropicRuleEntry::NonIsotropicRuleEntry(string rule_string, bool vertical_symmetry, bool horizontal_symmetry, bool rotate4)
: m_vertical_sym(vertical_symmetry), m_horizontal_sym(horizontal_symmetry), m_rotate4(rotate4)
{
rule_string.erase(std::remove_if(rule_string.begin(), rule_string.end(), ::isspace), rule_string.end());
auto arrow_tokens = split(rule_string, "->");
if (arrow_tokens.size() != 2)
throw NonIsotropicRuleException("Invalid rule entry format");
auto comma_tokens = split(arrow_tokens[0], ",");
if (comma_tokens.empty())
throw NonIsotropicRuleException("Invalid rule entry format");
if (comma_tokens[0].empty())
throw NonIsotropicRuleException("Invalid rule entry format");
if (isdigit(comma_tokens[0][0]))
{
m_initial_state_is_variable = false;
m_initial_state = std::stol(comma_tokens[0]);
}
else
{
m_initial_state_is_variable = true;
m_initial_variable = comma_tokens[0];
}
for (size_t i = 1; i < comma_tokens.size(); ++i)
{
m_constraints.push_back(std::stol(comma_tokens[i]));
}
m_result_state = arrow_tokens[1];
}
bool NonIsotropicRuleEntry::accept(unsigned initial_state, const Neighborhood &neighborhood, unsigned &next) const
{
if (!m_initial_state_is_variable && initial_state != m_initial_state)
return false;
std::vector<Neighborhood> neighborhoods_to_test;
neighborhoods_to_test.push_back(neighborhood);
if (m_vertical_sym)
neighborhoods_to_test.push_back(neighborhood.flip_vertically());
if (m_horizontal_sym)
neighborhoods_to_test.push_back(neighborhood.flip_horizontally());
if (m_vertical_sym && m_horizontal_sym)
neighborhoods_to_test.push_back(neighborhood.flip_vertically().flip_horizontally());
if (m_rotate4)
{
Neighborhood to_rotate = neighborhood;
for (unsigned i = 0; i < 3; ++i)
{
to_rotate = to_rotate.rotate90();
neighborhoods_to_test.push_back(to_rotate);
}
}
bool entry_accepted = false;
for (const auto& n : neighborhoods_to_test)
{
bool constraint_accepted = true;
for (unsigned i = 0; i < m_constraints.size(); ++i)
{
unsigned neigh_index = i;
unsigned expected_state = m_constraints[i];
if (neigh_index >= n.size() ||
n.neighbor_at_index(neigh_index).second != expected_state)
{
constraint_accepted = false;
break;
}
}
if (constraint_accepted)
{
entry_accepted = true;
break;
}
}
if (entry_accepted)
{
next = eval_math(m_result_state, {{m_initial_variable, initial_state}});
return true;
}
else
return false;
}
unsigned int NonIsotropicTransition::getState(unsigned int initial, const Neighborhood &neighborhood) const
{
// Si la rule string n'a pas encore été lue, la lire et générer les TotalistricRuleEntry correspondantes
if (m_entries.empty())
generate_entries();
// test all possibles entries related to the current cell's state
for (const NonIsotropicRuleEntry& entry : m_entries)
{
unsigned next;
bool entry_accepted = entry.accept(initial, neighborhood, next);
if (entry_accepted)
return next;
}
// no match, don't change the state
return initial;
}
void NonIsotropicTransition::generate_entries() const
{
bool vertical_sym = false;
bool horizontal_sym = false;
bool rotate4 = false;
std::istringstream iss(rule_string.str);
// pour chaque ligne = entrée de la chaîne de la règle :
for (std::string line; std::getline(iss, line); )
{
// on enlève les espaces inutiles
line.erase(std::remove_if(line.begin(), line.end(), ::isspace), line.end());
if (line == "nosym")
{
vertical_sym = false;
horizontal_sym = false;
}
else if (line == "norot")
{
rotate4 = false;
}
else if (line == "rotate4")
rotate4 = true;
else if (line == "vsym")
vertical_sym = true;
else if (line == "hsym")
horizontal_sym = true;
else if (line == "4sym")
{
vertical_sym = true;
horizontal_sym = true;
}
else if (!line.empty())
m_entries.emplace_back(line, vertical_sym, horizontal_sym, rotate4);
}
}
......@@ -14,6 +14,8 @@ Cette classe représente une règle de transition totalistique configurable.
#include <algorithm>
#include <sstream>
REGISTER_FACTORY_ENTRY(TransitionRule, TotalisticTransition, "Totalistic rulestring");
static std::vector<std::string> split(std::string str, std::string token){
std::vector<std::string>result;
while(str.size()){
......@@ -95,7 +97,7 @@ TotalisticRuleEntry::TotalisticRuleEntry(std::string rule_string)
for (size_t i = 1; i < comma_tokens.size(); ++i)
{
auto colon_tokens = split(comma_tokens[1], ":");
auto colon_tokens = split(comma_tokens[i], ":");
if (colon_tokens.size() != 2)
throw TotalisticRuleException("Invalid rule entry format");
m_constraints[colon_tokens[0]] = read_interval(colon_tokens[1]);
......
......@@ -6,6 +6,7 @@
#include <QPushButton>
#include <QDebug>
#include <QScrollArea>
#include <QPlainTextEdit>
UIBuilderVisitor::UIBuilderVisitor(QWidget *base_widget, bool destructive)
{
......@@ -87,11 +88,11 @@ QWidget* UIBuilderVisitor::pop_widget()
void UIBuilderVisitor::visit(StringProperty &str)
{
QLineEdit* line = new QLineEdit(QString::fromStdString(str.str), current_widget());
add_widget(str.display_name(), line);
QPlainTextEdit* edit = new QPlainTextEdit(QString::fromStdString(str.str), current_widget());
add_widget(str.display_name(), edit);
QObject::connect(line, &QLineEdit::textEdited,
[&str](const QString& qstr) { str.str = qstr.toStdString(); });
QObject::connect(edit, &QPlainTextEdit::textChanged,
[&str, edit]() { str.str = edit->toPlainText().toStdString(); });
}
void UIBuilderVisitor::visit(IntegerProperty &prop)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment